Skip to content

fix(mcp): send gate bearer token on wake_remote and local daemon calls - #52

Merged
ThinkOffApp merged 2 commits into
mainfrom
fix/wake-remote-gate-auth
Jul 28, 2026
Merged

fix(mcp): send gate bearer token on wake_remote and local daemon calls#52
ThinkOffApp merged 2 commits into
mainfrom
fix/wake-remote-gate-auth

Conversation

@ThinkOffApp

Copy link
Copy Markdown
Owner

Problem

wake_remote POSTs to ${gateUrl}/wake with no Authorization header. Since startConfirmationsServer enforces its auth_token on EVERY endpoint (/wake included), waking any token-protected daemon always fails:

HTTP 401 {"ok":false,"error":"unauthorized"}

Repro (Jul 26): wake_remote toward the Mini daemon on :8788 returned 401; the identical POST succeeded (202) once Authorization: Bearer <token> from ~/.config/iak-gate.token was added by hand.

The sibling local-daemon calls in mcp-server.mjs had the same omission and would break the moment the local daemon sets auth_token:

  • the boot probe GET /intents (silently falls back to in-process mode, then fights the daemon for the port)
  • request_confirmation forwarding: POST /intent and the GET /intents decision poll

wake_ide / wake_all are tmux-based (no HTTP), and the /sessions/send forward adapter in session-send.mjs already sends its configured token, so those are untouched.

Fix

  • New resolveGateToken(): reads ~/.config/iak-gate.token (path overridable via IAK_GATE_TOKEN_FILE), falling back to the IAK_GATE_TOKEN env var; returns empty when neither exists.
  • Resolved per request, not cached at boot, so a token rotation never leaves a long-lived MCP session sending a stale credential (the poller stale-key incident shape).
  • wake_remote now sends Authorization: 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.
  • Local daemon calls prefer the config's own mcp.confirmations.auth_token, with the fleet token as fallback.

Tests

test/mcp-server.test.mjs:

  • unit: resolveGateToken file read, file-wins-over-env, env fallback, empty fallback; gateAuthHeaders shape
  • stdio end-to-end against a real HTTP gate that mirrors the daemon's 401 behavior: asserts the bearer header arrives at /wake when a token file exists, and that NO header is sent when no token exists

Full suite: 202 pass / 0 fail.

🤖 Generated with Claude Code

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>
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/mcp-server.mjs Outdated
Comment on lines +723 to +725
const res = await fetch(`${args.gateUrl}/wake`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', ...gateAuthHeaders(resolveGateToken()) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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>
@ThinkOffApp
ThinkOffApp merged commit 1ed97ba into main Jul 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant