Skip to content

Answer the browser's private-network preflight on the login listener - #160

Closed
aterga wants to merge 1 commit into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i
Closed

aterga wants to merge 1 commit into
mainfrom
claude/secondary-mcp-local-deploy-jpof9i

Conversation

@aterga

@aterga aterga commented Aug 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The login listener answers a browser's CORS/Private-Network-Access preflight with 405, because its three routes are registered for GET/POST only. When a public https origin fetches into loopback, Chrome preflights with Access-Control-Request-Private-Network: true first and blocks the fetch unless the response grants it, so the #4091 allow-list is unreadable from a browser while curl and the e2e harness — neither of which preflights — see a healthy 200.

Scope correction (important). I originally opened this believing it explained why imcp2-local's sign-in fails against production Internet Identity with "Invalid request — this connection link is missing information or has been changed". It does not. Instrumenting II's own deployed bundle shows our link parses successfully (its registration_key/callback/state/ttl schema accepts it, loopback http:// callback included), and in both a real browser and a headless replay no request to 127.0.0.1 is ever attempted — so the preflight is never reached. That failure is still open and is II-side; notes below.

This PR is therefore a latent-correctness fix, not a user-visible one: it is necessary for the allow-list fetch to work in any environment where it is attempted (a dev-CSP II deployment, a local replica), and it is not sufficient for production sign-in.

Related issues

Found while testing the local binary end to end on macOS after #152.

Changes

  • All three loopback routes answer OPTIONS with Access-Control-Allow-Private-Network: true plus the accompanying CORS headers. Chrome enforces PNA for the tab navigation into /callback as well as the allow-list fetch, so the preflight is registered on every route.
  • A raw-socket regression test asserts it, since an HTTP-client test structurally cannot reproduce a preflight.

Testing

  • cargo build --locked --all-targets
  • cargo test --locked --all-targets — 240 tests green (21 in imcp2-local, including the new preflight test)
  • cargo fmt --all / cargo clippy --all-targets — no warnings in the changed files (remaining map_or warnings are pre-existing in imcp2-core)
  • Live PocketIC e2e (--features e2e): local_login_end_to_end passes
  • Protocol check against the running release binary:
OPTIONS /.well-known/ii-auth-callbacks  (Origin: https://id.ai,
                                         Access-Control-Request-Private-Network: true)
before → HTTP/1.1 405 Method Not Allowed
after  → HTTP/1.1 204 No Content + access-control-allow-private-network: true

Notes for the still-open production failure

Evidence gathered against the live id.ai bundle, recorded here so it isn't lost:

  • Production CSP is connect-src 'self' https: — a fetch() to http://127.0.0.1:<port> is not permitted at all, which is consistent with no loopback request being issued. The same policy carries form-action 'self' http://127.0.0.1:*, so loopback form submission is explicitly allowed while loopback fetch is not.
  • II's frontend gates http-loopback callbacks behind a dev_csp build flag (its secure-context predicate accepts http: on loopback only when that flag is set), suggesting loopback callbacks are enabled for development deployments rather than production.
  • Both are II-side; nothing in this repository can satisfy an allow-list fetch that the browser refuses to make.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

II's frontend is a public https origin that fetches the #4091 allow-list
from http://127.0.0.1:<port>. Chrome classifies that as a private-network
request and preflights it with Access-Control-Request-Private-Network,
blocking the fetch unless the response grants it. The listener registered
GET/POST only, so preflights got 405 and II could never read the
allow-list: every real browser sign-in failed with "This connection link
is missing information or has been changed", while curl and the e2e
harness — neither of which preflights — saw a healthy 200.

All three routes now answer OPTIONS with the private-network grant (tab
navigation into /callback is preflighted too, not just the allow-list
fetch), and a raw-socket test asserts it, since only a preflight
reproduces the failure.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqNkpMQiQHzYxC2djTfvBK

Copilot AI 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.

Pull request overview

Adds Private Network Access preflight support to the transient loopback login listener, enabling production Internet Identity browser sign-in.

Changes:

  • Handles OPTIONS across all login routes with required CORS/PNA headers.
  • Adds a raw-socket regression test for allow-list and callback preflights.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
crates/imcp2-local/src/login/routes.rs Implements PNA preflight responses.
crates/imcp2-local/src/login/tests.rs Tests browser-style private-network preflights.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@aterga
aterga marked this pull request as ready for review August 27, 2026 20:32
@aterga
aterga requested a review from a team August 27, 2026 20:32
@sea-snake

Copy link
Copy Markdown
Collaborator

The PNA (Private Network Access) standard was abandoned and superseded by LNA (Local Network Access) standard, this is only relevant for non top-level requests (e.g. fetch call), given that we do top-level requests only from II to the MCP server, this is irrelevant. Closing this PR.

@sea-snake sea-snake closed this Aug 28, 2026
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.

4 participants