feat(auth): scope-split LLM bearer — /llm/* accepts a second token that never opens writes - #63
Conversation
…at never opens writes (t263) New --llm-token-file / MOOS_LLM_TOKEN: a second bearer accepted ONLY on the /llm/* egress routes. Rationale: the pilot (a browser-resident client) needs to reach the Gemini kernel-proxy now that #60 gates egress, but storing the fleet WRITE token in a browser context would widen the exact blast radius the bearer was built to contain. With the split, a pilot-side leak exposes Gemini egress only - never HG writes. llmGate matrix: both-empty = open (backward compatible); write-token-only = current behavior; llm-token-only = /llm/* closed by explicit intent; both = either passes on /llm/*, write routes accept the write token ONLY. CORS wildcard stripped on /llm/* regardless, matching writeGate. 10 new tests incl. the boundary case (llm token 401s on POST /rewrites). authored-by: agent:claude-cowork.hp-z440 / session:sam.z440-cowork-workspace / t263-auth-deploy Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a scope-split bearer token for the /llm/* proxy so browser-resident clients (pilot/gemini lane) can access LLM egress without holding the fleet write token, reducing blast radius while keeping backward-compatible “open when unset” behavior.
Changes:
- Introduces
Server.llmTokenand an/llm/*-specificllmGatethat accepts either the write token or the LLM token (and still strips permissive CORS headers). - Adds
--llm-token-file/MOOS_LLM_TOKENwiring incmd/moosand sets the LLM token on the transport server. - Expands auth tests to cover the scope-split matrix and the “LLM token must not open writes” boundary.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/transport/server.go | Adds llmToken, SetLLMToken, and llmGate; switches /llm/... route from writeGate to llmGate. |
| internal/transport/auth_test.go | Adds tests covering /llm/* token acceptance matrix and rejects LLM token for writeGate. |
| cmd/moos/main.go | Adds --llm-token-file / MOOS_LLM_TOKEN resolution and configures/logs the scope-split LLM bearer. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // SetLLMToken records the scope-split bearer accepted only on /llm/* routes | ||
| // (in addition to the write token, which always works there). An empty string | ||
| // (the default) means /llm/* is gated by the write token alone. Call once | ||
| // before Handler() is attached, like SetAuthToken. | ||
| func (s *Server) SetLLMToken(v string) { s.llmToken = v } |
| if llmToken != "" { | ||
| tSrv.SetLLMToken(llmToken) | ||
| log.Printf("auth: scope-split LLM bearer configured — /llm/* accepts it OR the write token; mutating routes accept the write token only") | ||
| } |
- SetAuthToken/SetLLMToken comments now spell out the llmGate matrix (either-token on /llm/*, both-empty = open, llm token never opens writes) - the scope-split startup log line is now posture-conditional: proxy-off = token inert; no write token = mutating routes remain unauthenticated authored-by: agent:claude-cowork.hp-z440 / session:sam.z440-cowork-workspace / t263-gemini-lane Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both Copilot comments addressed in f9fbe2b — the SetAuthToken/SetLLMToken doc comments now spell out the llmGate matrix exactly (either-token on authored-by: agent:claude-cowork.hp-z440 / session:sam.z440-cowork-workspace / t263-gemini-lane |
Implements Sam''s t263 design call for the gemini lane: the pilot must reach the kernel''s
/llm/*proxy (now bearer-gated by #60) without holding the fleet write token in a browser context.Change: new
--llm-token-file/MOOS_LLM_TOKEN— a second bearer accepted ONLY on/llm/*. The write token still works everywhere it did.llmGatematrix: both-empty = open (backward compatible) · write-token-only = exactly today''s behavior · llm-token-only =/llm/*closed by explicit intent · both = either passes on/llm/*, mutating routes accept the write token ONLY. CORS wildcard stripped regardless, matchingwriteGate.Blast radius: a pilot-side leak exposes Gemini egress, never HG writes — the exact containment #60 was built for.
Verified:
go build/go vet/ fullgo test ./...green (10 new tests incl. the boundary case: llm token → 401 onPOST /rewrites). No new deps; stdlib only.Deploy plan after merge (Zappa runs, Sam-gated restart): rebuild Z440 kernel → mint
secrets/moos-llm-token→ launcher gains--enable-llm-proxy+ conditional--llm-token-file→ restart → pilot PR flipsgeminienabled:true+ sends the LLM bearer fromchrome.storage.Sovereign-runtime change — Sam''s review + merge.
🤖 Generated with Claude Code