- main.go's wiring was untested (the test built its own copy of the
router, so dropping auth in main or re-registering POST /query as
public stayed green). Routing is now one function, buildMux, which
main calls and the tests call: it constructs the bearer middleware
itself, registers public handlers only for patterns on a fixed
allowlist (a public REST pattern would beat the authenticated
catch-all by specificity), refuses an empty token, and returns an
http.Handler so nothing can add a route to the result.
- hyperguild CLI: PassRate sent no token and would 401. Every request
now goes through brainClient.do, which adds it; the test covers
Query, Write and PassRate and is named for that.
- ResolveIngestPath returns one refusal for every bad path; the raw
EvalSymlinks error let a caller probe which server files exist and
leaked the brain directory.
- PROJECT.md (and derived adapters) and the pass-rate runbook state
that the REST API needs BRAIN_MCP_TOKEN and is tailnet/in-cluster.
Refs #149
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only /mcp was wrapped in BearerMiddleware; /query, /write, /ingest*,
/promote, /pending and the rest on the same mux had no auth, and
infra#517 found them on the internet (mitigated there by narrowing the
ingress). They are still open on the tailnet and LAN NodePort 30330.
mountREST puts the REST routes on their own mux, mounted at "/" behind
the same bearer middleware and credential as /mcp. Routes registered on
the outer mux stay explicitly public (/mcp has its own auth, /capture,
the HMAC webhook, OAuth metadata, /metrics); anything else, including a
route added later, is authenticated by default.
The hyperguild CLI (cmd/hyperguild and internal/brain) now sends
BRAIN_MCP_TOKEN, which it never did. brain-ui and brain-ingest-ntfy
already send theirs.
Refs #149
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>