Repository navigation
Conversation
|
Recommendation: ready. A valid OIDC access token for a subject the admission file does not name used to get HTTP 401 with The server now returns one of three results. A token that fails its own checks still gets 401
The sign-in loop stops. The operator admits the identity, or publishes a fresh snapshot. A caller with no valid signature learns nothing about the admission file. The response names no subject. A client that decodes the shipped 0.14 No blocking findings. Optional:
Inline on Inline on Tradeoff and liability. The check uses the snapshot keys after
Protected routes and Validation. Head Merge base, predict 401 for an unadmitted subject:
Result: fail. Head, same test, separate target directory: Result: pass. 1 passed and 0 failed. Head unit tests Head Head CI on this head is green, including Test Workspace, Lint (clippy), Format, and Fix Regression Gate. Local clippy and the CLI test binary were not rerun. Limits. The HTTP 503 answer was not driven through the integration suite. Snapshot parse refuses an already-expired file, and the server clock is not injectable. The unit test covers the |
Fixes #1172. The issue is still
needs-triage; this PR should merge only after it is accepted.A server with
--oidc-identity-trustanswered 401error="invalid_token"in three cases: to a forged or expired token, to a valid token whose subject the admission file does not name, and to a valid token while the admission snapshot was expired. For the last two, signing in again issues a token for the same subject, so an OAuth or MCP client that follows RFC 6750 §3.1 (Claude, for example) loops on sign-in or reports a failed sign-in.What changes
invalid_tokeninvalid_token(unchanged)invalid_tokennot_admitted, noWWW-Authenticate, dispositionfixinvalid_tokenservice_unavailable, no challenge,Retry-AfterOidcIdentityTrust::check_atreturns a typed refusal (Invalid,NotAdmitted,Unavailable). The token's own checks run first, against the snapshot's published keys and binding. So a caller without a validly signed token still gets 401 and learns nothing about the admission file. A stale snapshot's keys only classify a refusal; they never admit. One refinement to the issue's sketch: a token that fails its own checks stays 401 even while the snapshot is expired, so a mistyped static token doesn't turn into a misleading 503.not_admittedis a new entry in theErrorCoderegistry (403, actionfix). It counts as refused before start in the server's disposition, and the CLI's admission-refusal table accepts it besideforbidden. The 403 body names no subject.verify_atremains as a test-only wrapper, so the existing OIDC unit tests are unchanged.client_idthat server: an OIDC actor drops the token's client_id, so every client acting for one person is the same actor #1173 asks to keep is not touched here.Compatibility
ErrorCodeis a closed enum and contract 0.14 has shipped. A client that decodescodewith the 0.14.0 enum will not parse anot_admittedbody. Only an OIDC server that refuses an unadmitted subject sends it, and the status (403, with no challenge) is what stops the OAuth loop. MCP clients that read only the status and headers are unaffected.openapi.jsonis regenerated.Tests
auth_policy::issue_1172_an_unadmitted_subject_is_refused_without_a_sign_in_challenge(the regression test the Fix Regression Gate keys on), onGET /graphs/discoveryandPOST /mcp:not_admittedwith noWWW-Authenticate, disposition{effects: none, action: fix}, and the subject not echoed;error="invalid_token"andresource_metadata.oidc_identity::tests::refusals_say_whether_a_new_token_can_help_issue_1172covers the three refusal kinds, including garbage while the snapshot is expired, which staysInvalid.invalid_tokencases now expects 403 with no challenge for it.not_admitted.The failing behaviour was reproduced on
mainin the issue's comments: the same request shape got 401invalid_tokenfor an unadmitted subject.Local runs, with
RUST_MIN_STACK=16777216andCARGO_INCREMENTAL=0:cargo test -p omnigraph-server -p omnigraph-api-types --locked: server lib 219 passed, 2 ignored;integration287 passed;boot_settings43 passed, 1 ignored;multi_graph8 passed;omnigraph-api-types16 passed. Run on Rust 1.98.0.cargo test -p omnigraph-cli --locked --lib: 367 passed, 9 ignored. Run on Rust 1.98.0.cargo +1.97.1):cargo clippy -p omnigraph-server -p omnigraph-api-types -p omnigraph-cli --all-targets --locked -- -D warnings -W clippy::dbg_macrois clean,oidc_identityunit tests 8 passed, andintegrationauth_policy::/mcp::/openapi::163 passed. Clippy 0.1.98, the local default, also flags existing code inomnigraph-catalog(chunks_exactwith a constant size); that is outside this change and the pinned toolchain does not flag it.cargo fmt --all --check,python3 scripts/check-docs.py,bash scripts/check-agents-md.sh,typos: clean.Not run locally: the workspace-wide test and clippy graphs, the CLI integration suites, GQT and DST.
Docs
The MCP guide's sign-in section, the
server.mdauth paragraph (kept at the 350-line cap), the troubleshooting status table (403 and 503 rows), RFC 0003's challenge section and its decision log, and achangelog.dfixed note.