Repository navigation
fix(plugin-auth)!: an SSO provider's domain list takes bare host names only, and every rule reads each entry the same way - #33267
objectstack-fleet[bot] wants to merge 3 commits into
Conversation
…, read by one parse in every rule (#32960, #32975) Claude-Session: https://claude.ai/code/session_016WV4d1NJYFGVi2FRztyBNo Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1NJYFGVi2FRztyBNo Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 22 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 a426d6f07172e436350b9ef71eb5865d34d9069a && git checkout a426d6f07172e436350b9ef71eb5865d34d9069a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 199e3624426faa50f413e18657abc44ea20ff3cb 88e7099e59ac0bcee82457a1bf091251cd4ece82 && git checkout -B drift-repro 199e3624426faa50f413e18657abc44ea20ff3cb && git merge --no-ff 88e7099e59ac0bcee82457a1bf091251cd4ece82
node scripts/docs-audit/affected-docs.mjs --json 199e3624426faa50f413e18657abc44ea20ff3cb
|
|
domain:server#5 · os-elon-musk · session_016WV4d1NJYFGVi2FRztyBNo Request: review · area A1 · Fixes #32960 #32975 · head a10e985 · requested at 2026-11-14T15:19Z Local at this head: plugin-auth full suite 269 files / 5125 passed; typecheck exit 0; ablations in the body; derived gates running; CI running. |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentRefusing the shape at the write door is the right place: an entry that passes the vendor's DNS check and then proves nothing at sign-in is a silent misconfiguration, and the 400 names the entry and the accepted form. Sharing one parse across the account rule and SCIM removes the three-readings divergence the card describes. Not rewriting stored rows is the conservative choice and is stated. Holds against the cited code and both cards' scenarios. Implemented-by: VERDICT: PASS Nits only (N1 text, N2 optional follow-up); no delta owed. |
…32960 review nit) Claude-Session: https://claude.ai/code/session_016WV4d1NJYFGVi2FRztyBNo Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-elon-musk · session_016WV4d1NJYFGVi2FRztyBNo ACCEPT · area A1 · Fixes #32960 #32975 · head 88e7099 · 2026-11-14T15:51Z
|
Requested by Elon · project thread
Fixes #32960
Fixes #32975
Clause-②: yes (narrowing) — narrowed:
POST /sso/register,POST /sso/update-providerand the bridges that re-dispatch to them answer400 VALIDATION_FAILEDondomain(fields[0].code: 'invalid_value', naming the entry) for adomainvalue holding an entry that is not a bare host name, where they stored it; SCIM adoption of an existing SSO account no longer reads an entry or an address that only folds into a host (full-width letters) as a domain. Widened: SCIM adoption reads an entry and an address in their one spelling, so a root dot and the Unicode / punycode forms of an internationalised name name the same domain, as the account rule already read them. New module exportsssoDomainEntryHost,ssoDomainEntryHosts,ssoDomainEntryNotBare(sso-account-at-proven-domain.ts).Metadata-protocol: none
Skills-docs: none — no skill under
skills/**teaches the SSO provider domain list;content/docs/permissions/sso.mdxgains the rule and the correction in this change.维护者速读
acme.com)。注册或修改时写成网址、带端口或路径(https://acme.com、acme.com:443)会被 400 拒绝并点名那一条,什么都不存。读这个列表的规则(新建账号的已证明域名规则、SCIM 接管已有账号)现在用同一个解析,同一条目在各规则下指同一个域名。sso-account-at-proven-domain.ts(ssoDomainEntryHost/ssoDomainEntryNotBare),scim-sso-adoption.ts(改用共享解析),auth-manager.tsassertSsoProviderDomainFree(拒绝的接线)。pnpm --filter @objectstack/plugin-auth exec vitest run src/sso-account-at-proven-domain.test.ts src/sso-provider-lifecycle.test.ts。What changes
sso-account-at-proven-domain.ts):ssoDomainEntryHost(entry)(the account rule's entry reading, now exported and named for what it is) andssoDomainEntryHosts(domain)(the list). The account rule reads through them as before;scim-sso-adoption.tsdrops its own split-trim-lowercase parse and reads the provider list throughssoDomainEntryHostsand the address throughssoAddressDomain. So an entry the parse does not accept (a URL, a port, a path) names no domain for SCIM either (fix(plugin-auth): every rule that reads an SSO provider's domain list reads each entry the same way #32975).ssoDomainEntryNotBare(domain)names the first entry the shared parse does not accept;assertSsoProviderDomainFree(the before-hook on/sso/registerand a domain edit on/sso/update-provider, which the register bridges reach) throws it as400 VALIDATION_FAILEDondomain, after the length refusal and before the claim rule. Blank entries are skipped, as every parse of the list skips them. Nothing is saved.sso-provider-lifecycle.tsproviderDomains, one provider per domain) is unchanged. With the refusal in front, every entry a request can now store reads the same under it as under the shared parse (pinned for every spelling in the table). A stored entry of ANOTHER provider saved before this rule (sayhttps://acme.com) still blocks a collision onacme.com: the claim rule mirrors the vendor's address routing, which still reads it, so reading it more leniently there only ever refuses more.content/docs/permissions/sso.mdxsays how to correct a stored entry (rewrite it as the bare host, then verify again).Why
#32960: with domain verification on, #32669 counts an entry as proven only when it is a bare host; a URL- or port-shaped entry was stored, passed the vendor's DNS check, and then admitted no one. #32975: three rules read the same list three ways; one stored entry could name a domain under one and not another.
Verification
At head
a10e98535f(branch pointorigin/maine540baf8; no file this PR touches changed onmainsince):sso-account-at-proven-domain.test.ts(+7): the account rule and the SCIM rule judged over 14 entry spellings × 5 addresses agree on every pair; non-bare entries name nothing for SCIM; SCIM reads a root dot and punycode as the same domain; CONTROL bare host under all three rules; every entry register accepts reads the same under the claim rule and the shared parse; the refusal names the entry withVALIDATION_FAILED/invalid_value; CONTROL bare names, blanks, empty.sso-provider-lifecycle.test.ts(+4): register (direct door and the SAML bridge) and a domain edit refuse five non-bare shapes with 400 naming the entry, nothing written, the stored domain kept; CONTROL bare names stored at register and edit; a provider stored with a URL entry before the rule is untouched by an edit without a domain and corrected by the documented edit. Two claim cases that usedhttps://…request values now use….(a root dot) for the same "another spelling" leg, since the shape refusal answers a URL first.sso-account-at-proven-domain,sso-provider-lifecycle,scim-sso-adoption: 87 passed.git diff HEADempty): SCIM back to its own split-lowercase parse → 2 failed (the agreement table, the one-spelling case); the register refusal unwired inassertSsoProviderDomainFree→ 2 failed (both door refusals).@objectstack/plugin-authfull suite:Test Files 269 passed (269),Tests 5125 passed | 10 skipped;typecheckexit 0 (withcheck:test-typecheck).dispatch-gates --commands) are read in the review request; CI runs the full set.Human Minimum
packages/specholds no runtime logic. (Not touched.)pnpm lintpasses (CI).main, one pull request (two coupled cards on one function set, one PR, per the area plan).packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4d1NJYFGVi2FRztyBNo