Repository navigation
fix(plugin-auth): a public plain-HTTP boot says its AS discovery is unencrypted, in English - #19689
Conversation
Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF Co-authored-by: Claude <noreply@anthropic.com>
…nencrypted Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 71d043664b366c353db5c71551fa2b0bacb5a9be && git checkout 71d043664b366c353db5c71551fa2b0bacb5a9be
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1f53b0b685bc5477f2eb702c7938e94b7009e59d 629178648adebd181ee031ae4b0412839231583c && git checkout -B drift-repro 1f53b0b685bc5477f2eb702c7938e94b7009e59d && git merge --no-ff 629178648adebd181ee031ae4b0412839231583c
node scripts/docs-audit/affected-docs.mjs --json 1f53b0b685bc5477f2eb702c7938e94b7009e59d
|
Gate on the record:
|
Fixes #19571
Implements ruling batch #210 item 5 (letter B/B, maintainer 「210 同意」), which governs over the issue body. Upstream ruling: #19489 item ④. PR #19534 is landed and is not reverted here — this is its follow-up.
Clause-②: no
D1 — eligibility decides WHICH sentence, never WHETHER one is emitted
packages/plugins/plugin-auth/src/auth-plugin.ts#registerOidcDiscoveryRoutes.The structural defect, verified on
origin/mainbefore writing code: the three AS discovery routes (/.well-known/oauth-authorization-server,/.well-known/openid-configurationand the RFC 8414 §3.1 path-insertion variant) are mounted unconditionally, while the only line that mentioned a refused transport —MCP server is enabled but the OAuth track is NOT live— sits insideif (readMcpServerEnabledEnv() && ...). A public plain-HTTP boot withOS_MCP_SERVER_ENABLED=falsetherefore emitted no warning at all, while publishing its authorization-server metadata in the clear. The loudest-needed configuration was the quietest.The plain-HTTP branch now has a sibling
else if, beside it and not inside the MCP block:OAuth is served UNENCRYPTED: ...OAuth discovery is served over PUBLIC plain HTTP: ..., naming (a) the discovery documents and the issuer, (b) that the MCP OAuth track is DISABLED, (c) that TLS is the remedy.Both fire once at mount, neither under TLS, and no configuration key or environment variable gates either.
⛔ No admission decision changes.
isOAuthEligibleBaseUrland every transport-rule predicate are byte-unchanged; the discovery routes are mounted exactly where and when they were. ⛔ No new exported symbol (servedOverPlainHttpis a local const) — theClause-②: nodeclaration stands, and the mechanical floor atreferences/contract-review.md:12is not tripped.D2 — the emitted string is English; the ruled sentence moves to the comment
The
'OAuth 未加密:仅限可信内网 — 'prefix is out of the executable string. The maintainer's sentence is kept verbatim in the code comment beside the call, cited to the #19489 ruling and to batch #210 item 5, and read as the line's meaning rather than its literal encoding. ⛔ No CJK executable string remains in either file'ssrctree; ⛔ no bilingual line.AGENTS.mdis untouched.The comment block above the branch previously argued the opposite — that the Chinese belongs in the emitted string "not only in this comment". That paragraph is falsified by this ruling and has been rewritten to state the current rule with its citation.
Carriers moved with it
packages/plugins/plugin-auth/src/auth-plugin.ts— the two branches and the comment.packages/plugins/plugin-auth/src/mcp-oauth-plaintext-notice.test.ts—NOTICE_MARKERre-anchored to the English line;PUBLIC_NOTICE_MARKERadded; the "does NOT fire on a PUBLIC plain-HTTP deployment" leg rewritten to assert the sentence swaps rather than vanishes; new coverage for the MCP-surface-OFF boot, MCP-independence, once-per-mount, warn level, mutual exclusivity and the no-env-var floor. 15 tests, all green.docs/qa/platform-checklist/areas/ai.json—ai.mcp-oauth-private-host-transportrevision 1 → 2 with its history entry. The grep anchors move to the two English lines; case (b) moves from asserting 0 occurrences to asserting exactly 1 of the new line; step text,clause,verify, thenegativeconflation entry, thesourceline and the title moved together..changeset/19489-oauth-private-host-transport-rule.md— still unreleased onorigin/main(the file is present in.changeset/, sochangeset versionhas not consumed it). Its last bullet asserted the startup line carries the Chinese and that a public plain-HTTP boot gets no such line; both become false when this lands, so the sentence is corrected. The Chinese is kept only as a quoted ruling citation.Plus a new changeset,
.changeset/19571-plaintext-oauth-public-host-notice.md(@objectstack/plugin-auth: patch).node scripts/check-empty-changeset.mjs --base origin/main— exit 1, on carrier 4:This is the gate's DELIBERATE CORRECTION class, not the COLLISION class. Its own text says the remedy is not to restore the file — restoring it from the base republishes a sentence this PR makes false — and that correcting a pending release note "is a decision about a release rather than a refactor — say so on the PR, naming the note and what changed under it, and get it confirmed". Naming it here: the note is
19489-oauth-private-host-transport-rule.md, and what changed under it is the D1 branch above. The gate stays red until a person confirms.Verification
pnpm --filter @objectstack/plugin-auth testpnpm --filter @objectstack/plugin-auth run typechecktsconfig.examples.jsonresolves throughdist)pnpm lint(repo-wideeslint . --no-inline-config)pnpm --filter '@objectstack/plugin-auth^...' buildscripts/pm/dispatch-gates.mjs --commands), 66 commandsPREREQUISITE NOT METpnpm check:platform-checklist,check:nul-bytes,check:doc-authoring,check:test-source-alias,check:cross-package-test-inputs,check:published-files,check:type-check-coverage,check:adr-0087-registration,check:changeset-no-majorall exit 0.NOT MEASURED (each prints
PREREQUISITE NOT MET— explicitly "not a pass and not a finding" — and needs a full workspace build, which is CI's):pnpm check:dual-build-cjs-loads(exit 3),pnpm check:type-check-debt(exit 3). This diff changes noexports, nopackage.jsonand no build shape.Reverse verification (both legs from the committed state; restored byte-identically)
Run through
scripts/ablation-replace.mjs, which proves the mutation landed on disk by blob hash and proves the restore bygit diff HEADbeing empty. The subject resolves fromsrcthrough a relative import, so nodistleg applies.} else if (servedOverPlainHttp) {→} else if (servedOverPlainHttp && false) {. Blob17bd405104b2→eb7d11ae9d01. Result: 9 failed | 6 passed (15) — every public-face assertion red. Restored: blob back to17bd405104b2,git diff HEAD0 bytes.17bd405104b2→c7aba397fff2. Result: 1 failed | 14 passed (15) —fires on a private-address deploymentred on the no-CJK assertion. Restored:ok restored: blob == HEAD (17bd405104b2) and git diff HEAD is empty.Predicted direction was RED for both, and RED is what was observed.
Acceptance notes
.well-knowndiscovery routes should be mounted at all on a refused origin is pre-existingmainbehaviour neither ruling touched, and is not decided at that call site — per the ruling, that would be a card on its own evidence, ⛔ not folded here.Generated by Claude Code