Repository navigation
[mcp-2026] CIMD: deny host mismatch and delete duplicate helpers #26
Description
Context
CIMD in #21 is the strongest part of the merge: HTTPS-only (HTTP on loopback), no userinfo/fragment/./.., DNS pre-resolve, RFC 6890 IP blocks, connect to pinned IP, no redirects, 5 KiB cap, document client_id must equal fetch URL, trusted-proxy rules for inbound X-Forwarded-Host.
Gaps:
nitrostack/auth/cimd.pydefineslooks_like_cimd_urlandresolve_cimd_synctwice (identical copy-paste at end of file).apply_cimd_to_registration_body(nitrostack/auth/oauth_module.py) sets_cimd_host_matches_requestand returns the document when False. Host pin is telemetry, not a deny. Tests intests/test_mcp20_oauth_cimd.pyassert the flag is False and continue.cimd_peer_is_acceptableallows a trusted proxy as TCP peer even when it is not the pinned IP. Outbound CIMD iscreate_connection((pinned_ip, port))— that exception must not apply to outbound fetch sockets.verify_bearer_payload(nitrostack/auth/request.py)except Exceptionaround DI + JWT verify. Missing JWT or programming errors look like “anonymous.”
Keep the SSRF defenses. Do not rewrite OAuth introspection or DCR static client issuance except where CIMD registration must fail closed.
Handoff: #21 (comment)
This issue is largely independent of WP0 (sidecar). Can ship in parallel.
Goal
Host mismatch is an error. One definition of each CIMD helper. Outbound pin is strict. JWT/DI failures are not silently None when they are bugs.
Probe
rg -n "def looks_like_cimd_url|def resolve_cimd_sync|cimd_peer_is_acceptable|_cimd_host_matches_request" nitrostackRead:
nitrostack/auth/cimd.py— especially the last ~40 lines (duplicates),assert_safe_fetch_target,_PinnedHTTPSConnection,cimd_peer_is_acceptablenitrostack/auth/oauth_module.py—apply_cimd_to_registration_bodynitrostack/transports/proxy.py—cimd_url_matches_request_host(keep inbound trusted-proxy behavior)nitrostack/auth/request.py—verify_bearer_payloadtests/test_mcp20_oauth_cimd.py— host match tests ~211–230; they currently allow mismatch
Implementation
- Delete the duplicate
looks_like_cimd_url/resolve_cimd_syncblock. One definition each. - In
apply_cimd_to_registration_body: if request headers/peer are provided andcimd_host_matches_requestis False, raiseCimdValidationError. Do not return_cimdon mismatch. - Outbound
cimd_peer_is_acceptable: peer must equal pinned IP (and not be blocked). Do not treatTRUSTED_PROXIESas a substitute destination for CIMD fetch. Inbound Host vs CIMD URL comparison may still use trusted forwarded host (proxy.py). verify_bearer_payload: catch JWT verification failure only (invalid token →None). MissingJWTService/ DI misconfig should not be a bareexcept Exception. Empty identity on bad token stays correct.
Do not weaken: IP allowlists, redirect block, size cap, client_id equality, HTTPS requirement.
Out of scope: @cache decorator skipping ExecutionContext in cache keys (additional_decorators.py); OAuth OAUTH_REQUIRED fail-open.
Test / verify
pytest tests/test_mcp20_oauth_cimd.py tests/test_oauth.py -qChange/add:
- Host mismatch raises (update tests that currently
assert ... is Falsethen succeed). looks_like_cimd_urldefined once: e.g. countdef looks_like_cimd_urlincimd.py== 1 (simple unit or grep in review).- Existing: blocked RFC 6890 IP, redirects, oversized body,
client_idURL mismatch still fail. - Optional: pin connect still uses pinned IP (
test_fetch_connects_to_pinned_ipor equivalent).
Review
- Deny on mismatch is in
apply_cimd_to_registration_body(or a helper it always calls), not only a test flag. - Trusted proxies still cannot rebind inbound Host via untrusted
X-Forwarded-Host. - No new HTTP client dependency.
Success
- Duplicate functions gone
- CIMD host mismatch is
CimdValidationError(or equivalent), not a successful registration body - Outbound CIMD peer must be the pinned IP
-
tests/test_mcp20_oauth_cimd.pyupdated and green - SSRF tests still fail closed
Related
Independent of sidecar removal. Identity extraction is used by task access (WP3) — do not break “unsigned userId is not identity.”
Activity