test(oauth): pin the https redirect port #9

Merged
jlxq0 merged 1 commit from oauth-loopback-port into main 2026-08-25 02:23:51 +00:00
Owner

The RFC 8252 §7.3 port carve-out shipped in c9f5ae1 (2026-08-19), before this
issue was filed, and is live in v0.2.14. What was missing was the test holding
the other half of the rule: the port relaxation is loopback-only.

Mutation testing showed the gap. Rewriting loopback_redirect_matches to compare
only scheme + host + path — relaxing the port on every scheme, including
https://claude.ai/… — left allowlist_matches_exact_redirect_uri_only green.
The only test that noticed was the private-use-scheme one, and only incidentally.

This PR adds the fourth assertion from the issue: an allowlisted
https://claude.ai/api/mcp/auth_callback still rejects
https://claude.ai:8443/api/mcp/auth_callback. Under that mutation it is now the
test that goes red.

Verification

Three mutations, each watched red before trusting the suite:

Mutation Red test
exact string equality (the original bug) loopback_allowlist_varies_only_the_port
drop scheme/host guards, keep path+query allowlist_accepts_private_use_schemes, loopback_allowlist_varies_only_the_port
relax port on every scheme allowlist_matches_exact_redirect_uri_only (new assertion)

Live probe against jmap-mcp.kampong.social running v0.2.14:

  • http://localhost:3118/callback (the issue's exact failing case) → 201
  • http://evil.example/callback400 unregistered redirect_uri
  • https://claude.ai:8443/api/mcp/auth_callback400 unregistered redirect_uri

URL-normalisation probe: 127.1, 0177.0.0.1 and /x/../cb are accepted because
the parser canonicalises them to the same effective loopback host and path;
127.0.0.2, [::ffff:127.0.0.1] and /%63b are rejected.

Gates: cargo fmt --all --check clean, cargo clippy --all-targets --all-features --locked -- -D warnings clean, cargo test --all-features --locked 164 passed.

Cross-engine review by Codex (read-only) on whether the relaxed branch can admit a
non-loopback host or a different path: no. Non-http URIs never reach it; they
take the raw-string equality path.

No release tag follows — this is test-only and the behaviour is already in production.

Closes #8

The RFC 8252 §7.3 port carve-out shipped in `c9f5ae1` (2026-08-19), before this issue was filed, and is live in `v0.2.14`. What was missing was the test holding the other half of the rule: the port relaxation is loopback-only. Mutation testing showed the gap. Rewriting `loopback_redirect_matches` to compare only scheme + host + path — relaxing the port on **every** scheme, including `https://claude.ai/…` — left `allowlist_matches_exact_redirect_uri_only` green. The only test that noticed was the private-use-scheme one, and only incidentally. This PR adds the fourth assertion from the issue: an allowlisted `https://claude.ai/api/mcp/auth_callback` still rejects `https://claude.ai:8443/api/mcp/auth_callback`. Under that mutation it is now the test that goes red. ### Verification Three mutations, each watched red before trusting the suite: | Mutation | Red test | |---|---| | exact string equality (the original bug) | `loopback_allowlist_varies_only_the_port` | | drop scheme/host guards, keep path+query | `allowlist_accepts_private_use_schemes`, `loopback_allowlist_varies_only_the_port` | | relax port on every scheme | `allowlist_matches_exact_redirect_uri_only` (**new assertion**) | Live probe against `jmap-mcp.kampong.social` running `v0.2.14`: - `http://localhost:3118/callback` (the issue's exact failing case) → `201` - `http://evil.example/callback` → `400 unregistered redirect_uri` - `https://claude.ai:8443/api/mcp/auth_callback` → `400 unregistered redirect_uri` URL-normalisation probe: `127.1`, `0177.0.0.1` and `/x/../cb` are accepted because the parser canonicalises them to the same effective loopback host and path; `127.0.0.2`, `[::ffff:127.0.0.1]` and `/%63b` are rejected. Gates: `cargo fmt --all --check` clean, `cargo clippy --all-targets --all-features --locked -- -D warnings` clean, `cargo test --all-features --locked` 164 passed. Cross-engine review by Codex (read-only) on whether the relaxed branch can admit a non-loopback host or a different path: no. Non-`http` URIs never reach it; they take the raw-string equality path. No release tag follows — this is test-only and the behaviour is already in production. Closes #8
test(oauth): pin the https redirect port
All checks were successful
CI / cargo (pull_request) Successful in 2m33s
CI / docker (pull_request) Successful in 2m10s
7c7af7d7fa
The RFC 8252 §7.3 port carve-out is loopback-only, but no test held that
line: relaxing the port for every scheme left the whole suite green except
for the private-use-scheme case. Assert that an allowlisted
`https://claude.ai/api/mcp/auth_callback` still rejects the same URI on
port 8443.

Refs #8

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011yNTmXYzauoyQXxpfhyPxo
jlxq0 merged commit e9dad05478 into main 2026-08-25 02:23:51 +00:00
jlxq0 deleted branch oauth-loopback-port 2026-08-25 02:23:55 +00:00
Sign in to join this conversation.
No reviewers
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/jmap-mcp!9
No description provided.