Skip to content

fix(security): refuse cross-site calls, gate debug routes, drop the netlog recorder - #81

Merged
crisng95 merged 1 commit into
mainfrom
fix/debug-endpoints-cors
Nov 4, 2026
Merged

crisng95 merged 1 commit into
mainfrom
fix/debug-endpoints-cors

Conversation

@crisng95

Copy link
Copy Markdown
Owner

The agent API has no auth: it trusted any caller that could reach 127.0.0.1:8100, and every web page open in the browser can. With CORS *, an unauthenticated /api/ext/eval-tab (arbitrary JS in the signed-in Flow tab) and a netlog recorder that stored every Flow request body — including the session's at token — behind an open GET /api/ext/netlog, any site could act in or read from the user's Flow session.

Changes

  • Cross-site guard. Refuse requests whose Origin is not localhost / 127.0.0.1 / [::1] (any port) or chrome-extension://<id>, Origin-less requests marked Sec-Fetch-Site: cross-site, and any Host that is not a local name (DNS rebinding; FLOWKIT_ALLOWED_HOSTS adds names, e.g. a tunnel). CORS limited to the same origins.
  • Debug routes gated behind FLOWKIT_DEBUG_ENDPOINTS=1 (404 otherwise): eval-tab, test-*, stream-chat, submit-ui-prompt, find-actions, media-url, reload*, debug-tab, /api/flow/debug-*, /api/test-direct.
  • Netlog recorder removed from the extension (now 0.5.3) and the agent. docs/CAPTURE.md keeps the throwaway recipe with a warning never to ship it.
  • Callback secret enforced on /api/ext/callback (the extension already sent it; the server never checked). The extension falls back to the WebSocket if the callback is rejected.
  • WebSocket origins: the extension bridge (:9222) accepts only chrome-extension:// or no Origin; /ws/dashboard uses the exact local-origin match (it accepted http://localhost.evil.example).
  • StreamChat (CHAT_GENERATION) now goes through the generation throttle and unusual-activity hold from fix(flow): keep the unusual-activity hold across restarts, bound hijack retries #75.
  • Operation caches evict oldest-first instead of being cleared wholesale at 512; the media/poll caches were unbounded.

Upgrade: reload the extension in chrome://extensions after pulling. Extension 0.5.2 keeps working against this agent (it already sends the secret).

Test plan:

  • pytest -q tests: 507 passed (new tests/unit/test_api_security.py: cross-site/Host refusals, local callers pass, CORS, debug gate on/off, netlog gone, callback secret, dashboard WS lookalike origin; throttle tests for StreamChat hold and cache eviction)
  • node tests/extension_mv3_bootstrap.test.cjs passes (it failed before: its chrome stub had no webRequest.onBeforeRequest)
  • Live with the running extension: /api/ext/tabs round-trips through the secret-checked callback (200); Origin: https://evil.example → 403; Host: evil.example → 403; debug routes → 404
  • Independent security review; its findings (lookalike dashboard origin, missed debug routes, Host check) are fixed here
  • Dashboard through the Vite dev proxy not run live (covered by the localhost:5173 origin test)

🤖 Generated with Claude Code

…etlog recorder

The agent API has no auth and trusted any caller that could reach
127.0.0.1, which includes every web page open in the browser.

- Requests are refused when their Origin is not localhost / 127.0.0.1 /
  [::1] or the extension, when an Origin-less request says
  Sec-Fetch-Site: cross-site, or when Host is not a local name (DNS
  rebinding; FLOWKIT_ALLOWED_HOSTS adds more). CORS is limited to the same
  origins instead of "*".
- Debug routes (eval-tab, test-*, stream-chat, submit-ui-prompt,
  find-actions, media-url, reload*, debug-tab, /api/flow/debug-*,
  /api/test-direct) answer 404 unless FLOWKIT_DEBUG_ENDPOINTS=1.
- The "temporary" webRequest netlog recorder is removed from the extension
  (0.5.3) along with /api/ext/netlog. It logged every flow.google.com
  request body, including the session's `at` token, to a file any web page
  could read back.
- /api/ext/callback checks the X-Callback-Secret the extension already
  sends; the extension falls back to the WebSocket if it is rejected.
- The extension WebSocket server and /ws/dashboard accept only the
  extension / exact local origins (the dashboard check matched
  http://localhost.evil.example).
- StreamChat (CHAT_GENERATION) now shares the generation throttle and
  unusual-activity hold.
- Operation poll caches evict their oldest entries instead of being
  cleared wholesale at 512; the media and poll caches were unbounded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@crisng95
crisng95 merged commit cdd2dee into main Nov 4, 2026
2 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