- 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>
Both read a caller-chosen server path with extract.Text, so any file the
pod could read (the ServiceAccount token, /etc/passwd) could be ingested
into the brain and read back with brain_query.
ResolveIngestPath resolves relative paths against brain/raw/ (the inbox
the watcher already owns), evaluates symlinks, and refuses anything
that lands outside. A directory ingest re-resolves every walked file,
so a symlinked file inside raw/ cannot lead out. Existing ingest-path
tests moved their fixtures under brain/raw/.
Refs #149
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>