fix(mcp): send gate bearer token on wake_remote and local daemon calls - #52
Conversation
wake_remote POSTed to ${gateUrl}/wake with no Authorization header, so
any daemon with auth_token set answered 401 unauthorized (repro: Jul 26
wake toward the Mini gate at :8788). The local-daemon forwarding calls
(/intents probe, /intent create, /intents decision poll) had the same
omission and would break the moment the local daemon sets auth_token.
Resolve the fleet token per request - ~/.config/iak-gate.token first
(IAK_GATE_TOKEN_FILE overrides the path), then IAK_GATE_TOKEN env - and
attach Authorization: Bearer when present. No token resolves to no
header, so open daemons keep working unauthenticated. Per-request
resolution means a token rotation never leaves a long-lived MCP session
sending a stale credential. Local daemon calls prefer the config's own
mcp.confirmations.auth_token.
Tests: unit coverage for resolveGateToken/gateAuthHeaders plus stdio
end-to-end regressions against a real HTTP gate asserting the bearer is
sent when a token file exists and absent when none does.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3fc99097b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const res = await fetch(`${args.gateUrl}/wake`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| headers: { 'Content-Type': 'application/json', ...gateAuthHeaders(resolveGateToken()) }, |
There was a problem hiding this comment.
Do not send the fleet token to arbitrary gate URLs
Because gateUrl is an unrestricted tool argument, any MCP caller can point wake_remote at a server it controls; this change then reads the machine-wide gate secret and sends it in the Authorization header, allowing the caller to exfiltrate a credential that protects every daemon endpoint. Only attach credentials selected for a configured/trusted peer or otherwise bind the token to an allowlisted origin rather than sending the fleet token to every supplied URL.
Useful? React with 👍 / 👎.
…ions claudemm's PR #52 review found the blocker: gateUrl is a caller-supplied free-form string, and tool args are reachable from untrusted input, so attaching the fleet bearer to an arbitrary destination is a token- exfiltration path. The bearer now only accompanies requests whose host a fleet daemon can plausibly live on - loopback, RFC1918, the tailnet CGNAT range, or .local names; anything else still gets the wake, just unauthenticated (the same graceful degradation open daemons use). Tests assert no Authorization header travels to off-allowlist or unparseable destinations, and that the Mini/loopback/LAN cases still carry it. 204 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
wake_remotePOSTs to${gateUrl}/wakewith noAuthorizationheader. SincestartConfirmationsServerenforces itsauth_tokenon EVERY endpoint (/wakeincluded), waking any token-protected daemon always fails:Repro (Jul 26):
wake_remotetoward the Mini daemon on :8788 returned 401; the identical POST succeeded (202) onceAuthorization: Bearer <token>from~/.config/iak-gate.tokenwas added by hand.The sibling local-daemon calls in
mcp-server.mjshad the same omission and would break the moment the local daemon setsauth_token:GET /intents(silently falls back to in-process mode, then fights the daemon for the port)request_confirmationforwarding:POST /intentand theGET /intentsdecision pollwake_ide/wake_allare tmux-based (no HTTP), and the/sessions/sendforward adapter insession-send.mjsalready sends its configured token, so those are untouched.Fix
resolveGateToken(): reads~/.config/iak-gate.token(path overridable viaIAK_GATE_TOKEN_FILE), falling back to theIAK_GATE_TOKENenv var; returns empty when neither exists.wake_remotenow sendsAuthorization: Bearer <token>when a token resolves; with no token the request is sent unauthenticated, so open/no-auth daemons keep working for new users out of the box.mcp.confirmations.auth_token, with the fleet token as fallback.Tests
test/mcp-server.test.mjs:resolveGateTokenfile read, file-wins-over-env, env fallback, empty fallback;gateAuthHeadersshape/wakewhen a token file exists, and that NO header is sent when no token existsFull suite: 202 pass / 0 fail.
🤖 Generated with Claude Code