Repository navigation
docs(permissions): the "How enforcement works" callout names every transport seam that consults the ADR-0069 gate - #23333
Merged
objectstack-fleet[bot] merged 1 commit intoOct 13, 2026
Conversation
…ansport seam that consults the ADR-0069 gate The callout said only the REST server and the runtime dispatcher block a gated session. Every transport seam that resolves a caller from the session now consults the gate through core's shared decision: REST, the dispatcher (MCP's HTTP endpoint included), and the route families mounted directly on the host server. Described at function level, with no route list. Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 13, 2026
objectstack-fleet
Bot
deleted the
claude/issue-23288-auth-gate-seams-callout
branch
October 13, 2026 21:17
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23288
Clause-②: no (docs)
What changed
One callout on one page: the "How enforcement works" box in
content/docs/permissions/authentication.mdx(Enterprise Authentication Hardening, after the Network table). It said that only "the REST server + runtime dispatcher block gated sessions". That stopped being true when PR #23200 (08b38cb9d8), PR #23242 (d9d7eddd01) and PR #23285 (bae5841bce) landed. The new text says:@objectstack/core(evaluateAuthGateand its helpers);403with the gate's own code (PASSWORD_EXPIREDorMFA_REQUIRED) at whichever of them serves the request;The page names functions and seams only. It lists no route and no family, as the card's acceptance asks. No other page is touched.
The disclosure rule this follows
The card asks for the text "at function level as the lane's disclosure rule asks". The card family it comes from (#23178, #23190) cites the
domain:serviceslane rule. Verbatim, from.claude/skills/pm-dispatch/references/lanes/services.md(lines 19 and 20 at4d4040ccb6):In English: on a security-family card, a reproduction recipe never goes on a public surface (card, PR or comment), and evidence is carried by an abstract description or a private channel. The record that sets the same level for this gate's door classes is ADR-0138 (
docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md, its Card paragraph): "Classes, positions and functions only: door-level readings that would work as an exploit recipe stay private". So the page names functions, the call-site list below is at file:line level only, and nothing here is a request.Measured first (the PM's mechanism assumptions)
All readings are on
origin/mainat4d4040ccb6, the base of this branch.1. Where the gate is consulted (file:line; this list goes in the PR and the report, never on the page)
packages/rest/src/rest-server.ts:2343enforceAuthreadscontext.authGate(:2348), andcomputeExecCtxlifts it withnormalizeAuthGate(:3272)403 { error: { code, message } }(:2369)packages/runtime/src/http-dispatcher.ts:1362enforceAuthGatecallsevaluateAuthGate(:1396), anddispatch()runs it before any domain (:2746)403with the gate's codedispatcher.dispatch(packages/runtime/src/dispatcher-plugin.ts:1272), so behind the dispatcher's gate abovepackages/runtime/src/endpoint-policy.ts:421evaluateAuthGate(PR #23242)authGateDenial(:334),403packages/rest/src/package-routes.ts:83(refusePackageRequest) andpackages/rest/src/external-datasource-routes.ts:333(refuseFederationRequest), PR #23242403with the gate's codepackages/cloud-connection/src/cloud-connection-plugin.ts:234(sessionFromAuthService), refused at seven doors (:332through:688);packages/cloud-connection/src/marketplace-install-local-plugin.ts:3378(resolveInstallPrincipal), refused at:566. PR #23242403with the gate's codepackages/services/service-datasource/src/admin-routes.ts:533(requireDatasourceAdmin), PR #23200403with the gate's codepackages/plugins/plugin-sharing/src/share-link-routes.ts:123and:282(the family door); posture lifted atpackages/plugins/plugin-sharing/src/sharing-plugin.ts:970. PR #23200403with the gate's codepackages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts:543(serveRedeliver), PR #23200403with the gate's codepackages/services/service-storage/src/storage-routes.ts:687and:695(the family door), PR #23200403with the gate's codepackages/services/service-settings/src/settings-routes.ts:176and:223(the family door); posture lifted atpackages/services/service-settings/src/settings-service-plugin.ts:334. PR #23285403with the gate's codeEach landing's pins were re-run on this tree, and all pass:
@objectstack/restrest-auth-gate.test.ts,direct-mount-auth-gate.pin.test.ts@objectstack/runtimeendpoint-policy.auth-gate.pin.test.ts,http-dispatcher.root-auth-gate.test.ts@objectstack/cloud-connectioncloud-connection-auth-gate.pin.test.ts@objectstack/service-datasourceadmin-routes-auth-gate.pin.test.ts@objectstack/plugin-sharingshare-link-routes.auth-gate.pin.test.ts@objectstack/plugin-webhookswebhook-redeliver-auth-gate.pin.test.ts@objectstack/service-storagestorage-routes.auth-gate.pin.test.ts@objectstack/service-settingssettings-routes.auth-gate.pin.test.tsEach ran as
pnpm --filter PKG exec vitest run --maxWorkers=2 FILE…underscripts/pm/os-verify-lock.sh(VERDICT command-exit 0).Where the card's list and the measurement differ.
authGate: undefinedon the record (packages/mcp/src/plugin.ts:206). The MCP HTTP face is gated by the dispatcher, as in the table.plugin-hono-server's current-user endpoints: they withholdauthGateon the record (packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:564). Their routes are the allow-listed bootstrap reads.plugin-auth's admin endpoints: their judge,gateAdmin(packages/plugins/plugin-auth/src/auth-plugin.ts:2706), reads no posture. They sit under the auth namespace, which the allow-list's auth arm exempts as a whole (packages/core/src/security/auth-gate.ts:296).d9d7eddd01, closing cli: REST's direct-mount registrars, the runtime dispatcher plugin's declared-endpoint fallback and cloud-connection's raw-app plugins never consult the ADR-0069 authentication-policy gate #23190) gated the declared-endpoint fallback, REST's two direct mounts and cloud-connection's session doors. They are in the table.2. The callout's other sentences, checked against the source
AuthManager.computeAuthGate(packages/plugins/plugin-auth/src/auth-manager.ts:8061) returnsPASSWORD_EXPIREDfor an expired password and for an administrator-forced change (must_change_password,:8101), andMFA_REQUIREDonce the grace window has elapsed (:8135). The page now names the forced change.customSessionstamps the session with anauthGateposture." True, with one correction: the posture goes on the session user. The return isuser: { ...user, …, authGate }(auth-manager.ts:4119). The page now says "the session user".isAuthGateAllowlistedexempts the auth namespace plusALLOW_ROUTES(health, ready, discovery, the two current-user reads, the approval action page) and the POST-only inbound hook row (auth-gate.ts:126and following). The raw-host doors pass an empty path, so they grant no exemption at all. The page keeps "a short allow-list" and gives "health and discovery among them" as examples, not as a full list.PUTandPOSTdoors).3. Left out on purpose
6149872257. "Resolves a caller from the session" is true under either ruling.HttpDispatcher.enforceAuthGatefails open when an active ADR-0069 gate cannot read the session, where REST answers 503 under #13906's decision 2 #23245, which is not addressed here. The sentence makes no claim about that window.Verification (tree
14f73ed586)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 43 commands from the one changed path. All 43 ran, each with its exit code recorded before any pipe. Reconcile:--ranprinted43 derived, 43 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Every command exits 0. The named ones:check:doc-anchors: "499 internal #fragment link(s) across 420 source file(s) all resolve to a real heading".check:doc-authoring: "53 published skill files clean — no internal issue-id references".check:corpus-claim-drift: "OK, no new claim sites beside a pinned spelling".check:nul-bytes: "OK (scanned 11270 text file(s) … no raw ASCII control bytes)".@objectstack/spec check:docs: "226 generated files in sync with packages/spec".@objectstack/lint check:doc-security-posture: "28 ObjectSchema.create example(s) … carry an os validate-clean security posture".@objectstack/spec check:skill-examplesfirst answered PREREQUISITE NOT MET (exit 3:client-reactwas not built). That is not a measurement. Afterpnpm --filter '@objectstack/client-react...' --filter '@objectstack/client...' buildit exits 0: "263 prose examples type-check across 3 surface(s)".check-links.ymlargv. lycheev0.24.2(the workflow's pin) with--offline --root-dir content --fallback-extensions mdx,md --config lychee.toml 'content/**/*.md' 'content/**/*.mdx' README.md ARCHITECTURE.mdreports "2460 Total … 2162 OK 🚫 0 Errors 👻 298 Excluded", exit 0. The diff adds no link.@mdx-js/mdxasfumadocs-mdx15.2.3 resolves it, withremark-gfm. Both the base and the edited page compile (exit 0). Negative control: an unclosed expression fails the same harness (exit 1).dispatch-gatesnames (theBuild Docsjob and the type-check lanes) are not run locally. A docs-only diff moves no TypeScript.Changeset: none (
skip-changeset)The diff touches only
content/docs/**. Measured: across the 71 non-private workspace packages, 4134 files under theirfiles[], plus eachpackage.jsonandREADME.md, were scanned. Nofiles[]entry points outside its package directory.ExecutionContextSchema: 48 hits, among thempackages/lint/dist/index.cjs.No package build reads
content/docsinto its output;git grepover package manifests, tsup configs and package scripts finds only@objectstack/lint's doc checkers. The only packages built here are the closures above, and the scan says so.Acceptance notes
plugin-auth's platform-admin mounts. Those mounts sit under the auth namespace thatisAuthGateAllowlistedexempts as a whole, and their judge (gateAdmin→judgePlatformAdmin) reads no posture. ADR-0069 D3 holds a session "out of all protected resources until enrollment + verification". The gate module's header lists the exemptions it means as "the auth endpoints (change-password, two-factor enrollment, sign-out)". Whether user administration under that namespace belongs inside that exemption is a security-boundary question. This was read from source at function level only. It is not measured, and nothing about it changes here.Generated by Claude Code