fix(mcp): CORS-aware scaffold, cors.wildcard rule, server-not-appended FP; v1.1.2

This commit is contained in:
2026-08-08 00:55:39 -06:00
parent 81233311d9
commit 6b4d9be141
9 changed files with 198 additions and 39 deletions
+48
View File
@@ -0,0 +1,48 @@
package rules
import (
"go/ast"
"go/token"
"strings"
)
// The web/mw CORS convention: mw.CORS uses exact-origin matching and rejects "*"
// at construction (panic). Passing "*" is the trap this rule catches before runtime —
// allow-all is done with mw.CORSAllowAll(), gated by env in application code.
func init() {
registered = append(registered,
Rule{
ID: "cors.wildcard-noop",
Severity: SeverityError,
Module: "web",
Check: checkCORSWildcard,
},
)
}
// checkCORSWildcard flags any call to <pkg>.CORS(...) whose arguments contain a
// "*" string literal — which mw.CORS rejects (panics) at boot.
func checkCORSWildcard(c *Context) []Finding {
var hits []Finding
ast.Inspect(c.File, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok || !strings.HasSuffix(exprName(call.Fun), ".CORS") {
return true
}
for _, arg := range call.Args {
ast.Inspect(arg, func(m ast.Node) bool {
lit, ok := m.(*ast.BasicLit)
if ok && lit.Kind == token.STRING && strings.Trim(lit.Value, `"`) == "*" {
hits = append(hits, Finding{
Message: `mw.CORS with "*" panics at construction — "*" matches no real origin (exact-match only)`,
Hint: "Use mw.CORSAllowAll() for local development, or list explicit origins.",
Line: c.Fset.Position(call.Pos()).Line,
})
}
return true
})
}
return true
})
return hits
}
+58
View File
@@ -0,0 +1,58 @@
package rules
import "testing"
const corsWildcardSnippet = `package wire
import "code.nochebuena.dev/einherjar/web/mw"
func f() { _ = mw.CORS([]string{"*"}) }
`
const serverAppendedSnippet = `package wire
import (
"code.nochebuena.dev/einherjar/core/launcher"
"code.nochebuena.dev/einherjar/web/server"
)
func f() {
srv := server.New(logger, cfg)
lc := launcher.New(logger)
lc.Append(srv)
}
`
const serverNotAppendedSnippet = `package wire
import (
"code.nochebuena.dev/einherjar/core/launcher"
"code.nochebuena.dev/einherjar/web/server"
)
func f() {
srv := server.New(logger, cfg)
lc := launcher.New(logger)
_ = srv
_ = lc
}
`
func TestCORSWildcardFires(t *testing.T) {
got := findingsFor(Run(corsWildcardSnippet), "cors.wildcard-noop")
if len(got) == 0 {
t.Fatal("cors.wildcard-noop did not fire on mw.CORS([]string{\"*\"})")
}
}
func TestServerAppendedNoFalsePositive(t *testing.T) {
if got := findingsFor(Run(serverAppendedSnippet), "web.server-not-appended"); len(got) != 0 {
t.Errorf("web.server-not-appended false-positived on an appended server: %+v", got)
}
}
func TestServerNotAppendedFires(t *testing.T) {
if got := findingsFor(Run(serverNotAppendedSnippet), "web.server-not-appended"); len(got) == 0 {
t.Error("web.server-not-appended should fire when server.New is not appended")
}
}
+7 -11
View File
@@ -268,19 +268,15 @@ var registered = []Rule{
if !c.Importing("einherjar/web/server") || !c.Importing("einherjar/core/launcher") {
return nil
}
if !c.Called(".Append") {
// Fire only when a server is constructed but never appended — an appended
// server (lc.Append(srv)) is correctly managed, so stay silent (no false positive).
if !c.Called("server.New") || c.Called(".Append") {
return nil
}
// Heuristic: warn if server.New is constructed but not appended via .Append.
// We can't statically prove the argument was the server, so this is informational.
if c.Called("server.New") {
return []Finding{{
Severity: SeverityInfo,
Message: "web/server is constructed — ensure it is passed to launcher.Append() so its lifecycle is managed",
Hint: "lc.Append(srv) lets the launcher start and gracefully stop the HTTP server",
}}
}
return nil
return []Finding{{
Message: "web/server constructed but never appended to the launcher — its lifecycle won't be managed",
Hint: "lc.Append(srv) so the launcher starts and gracefully stops the HTTP server",
}}
},
},
}