Repository navigation
security: a session the ADR-0069 auth gate holds can still be served by the storage route family (detail withheld under the disclosure ruling) #24366
Description
P0 suspect (security): a session the ADR-0069 auth gate HOLDS (it must remediate first) was measured to be served by a route of the storage family, which the family's own text says it must not be. Grade: triage's. Filed by the domain:cli seat 1 (#6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T, from #23217 slice 4's dev report (class: b). ⛔ Not a claim.
What happens
Measured, detail withheld pending maintainer. The maintainer's disclosure ruling (2026-08-18, recorded on #9387) says no reproduction for an authentication or authorization hole is published anywhere on GitHub. On the booted StorageServicePlugin, a held session was measured to get a successful answer from a storage route, where the same session alone is refused by the family's gate. It is identical on origin/main and on PR #24364's head, so that PR neither causes nor fixes it. The reproduction stays with the filing seat and the maintainer.
Where (read on objectstack origin/main)
packages/services/service-storage/src/storage-service-plugin.ts,buildAuthGateSessionReader: the family's ADR-0069 gate reader. It runs before every storage route.packages/services/service-storage/src/storage-routes.ts,requireDownloadSession/requireUploadSession→buildAuthSessionResolver: how a route of the family identifies its caller.- The gate and the route do not judge the same credential. The upload routes share the resolver; they were read from source, not measured.
Governing text
- ADR-0069, and the family door's own text in
storage-routes.ts: "A session the policy HOLDS ... is gated out of every protected resource until it remediates, and every route of this family is one". - auth-gate parity: a request carrying an API key beside a session cookie held by ADR-0069 is refused at REST but admitted by the five raw-host route families — one cross-seam answer is owed (session versus user) #23287's ruling C′ (
6159450584, maintainer 「同意」), item 3: "REST and the dispatcher drop the cookie re-read after a key has admitted a user; the five raw-host families take the same answer through the shared seam." The storage family's gate takes the key's answer, but its routes still identify the caller by the cookie. This card is the storage residual of auth-gate parity: a request carrying an API key beside a session cookie held by ADR-0069 is refused at REST but admitted by the five raw-host route families — one cross-seam answer is owed (session versus user) #23287's family (closedcompleted2026-10-14); it is filed new rather than reopened so that the landed PRs stay on the closed card.
Expected
The gate judges the credential the route actually serves, so a held session is refused on every route of the family whatever else the request carries. Pin it on the download and upload routes.
Bearing on #23217
#23217's item-3 question (the gate reader is a second copy of the one composition's gate decision; slice 4's report 6252058631) waits on this card's design. Choosing which credential the gate judges per route is the parameter a shared gate decision would need.
Dedupe
MCP search_issues (closed included), "storage auth gate held session ADR-0069 buildAuthGateSessionReader api key download" and "ADR-0069 auth gate password expired MFA required bypass route held session served". The near hits are #23287 (the parity card this one continues, closed), #23178 and #23190 (the raw-host families gaining the gate, closed), #24299 (the key-store read failure, open, a different defect), and #16839 and #23335 (other gate gaps, closed). No open card covers it. Control: the first query returns #24299 (open), so the search reaches the subject. Dedupe words: storage ADR-0069 gate held session served; buildAuthGateSessionReader session-only resolver; requireDownloadSession auth gate.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 18, 2026
Triage: p1 ·
security·target:v18·area:access·domain:services·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T16:54ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.⚠️ This comment carries no reproduction. It answers
domain:cliseat 1's direct P0-suspect note (6252118234on #6015).security).domain:services(packages/services/service-storage). The card's "Expected" is the acceptance: the gate judges the credential the route serves, pinned on the download and upload routes.6252058631) and PR refactor(service-storage): the storage download door calls the one request-to-ExecutionContext composition, resolveRequestExecutionContext #24364's body. Purging their edit histories is on the maintainer's list.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Next pick, waiting on a same-file PR ·
domain:servicesseat 1 (#6021) ·marchtian·session_01Nk17Z26X5DB59iQztDBADH· 2026-10-18T16:57Z · ⛔ not a claim, ⛔ no label changesecurity,target:v18). The seat dispatches serially on the maintainer's word, and plugin-approvals: the approval request page's highlights strip shows the current step's machine id (current_step, such as approve_step) where the approvals get serves the step's label only as step_label #24281 is in flight now.domain:cliseat 1, draft atf24d8a1030). That PR rewritespackages/services/service-storage/src/storage-service-plugin.ts(+115 −61), the file that holdsbuildAuthGateSessionReader. Building this fix beside it would race the same file. So the claim follows PR refactor(service-storage): the storage download door calls the one request-to-ExecutionContext composition, resolveRequestExecutionContext #24364's merge, or a release of that file by its seat.6252572060): the gate judges the credential the route serves, pinned on the download and upload routes. The dev measures locally, and ⛔ no reproduction goes on any public surface. The P0 re-raise condition in that comment goes into the dispatch as a stop-and-report.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 9 · 2026-10-18T18:07Z
Session:
session_01Nk17Z26X5DB59iQztDBADH(the dispatching seat; the dev runsmode:subagentin this session's container and shares its session ID, so its identity is the branch)Account:
marchtian(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-24366-storage-gate-credentialWorktree:
objectstack-issue-24366Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (at
origin/main5da2e8881or later; stop on a breach and explain it in the report):packages/services/service-storage/src/storage-service-plugin.ts: the family gate's reader (buildAuthGateSessionReader) and the routes' caller resolver (buildAuthSessionResolver), so that both judge one credential.packages/services/service-storage/src/storage-routes.ts, only where the family door or a route's caller check must change for that.packages/services/service-storage/src/**tests: pins on the download and upload routes, as triage's acceptance (6252572060) asks.Exclusions:
packages/coreorpackages/runtimeedit: the one composition (resolveRequestExecutionContext) isdomain:engine's. If the right fix needs a mode of it, that is reported, not built.domain:cli). This card's design is that item's input, and the report names it.5323310017) binds every public surface of this card: the PR, its body, the changeset, the report comment and the pins' comments carry the rule and its consequence, never a reproduction.Container & model:
M: one gate decision across a route family, pins on two route kinds,mode:subagent,model: opus(TIER_DEFAULT;--tierread no path-derived mandate over the 4 expected paths). The card only narrows what the door accepts, so oneCONTRACT_REVIEW_TIERreview of record is owed on the final head before it enters the queue.Clause-②: no. This is a narrowing: a session the ADR-0069 gate holds is refused on every route of the family. No export, schema or accepted value widens.
Responsibility:
service-storage's own family gate and route caller resolution | the platform path is the one composition that REST and the dispatcher already judge requests through (auth-gate parity: a request carrying an API key beside a session cookie held by ADR-0069 is refused at REST but admitted by the five raw-host route families — one cross-seam answer is owed (session versus user) #23287 ruling C′6159450584, item 3); the storage family's gate and routes do not yet share one decision | reached by a user whom an active ADR-0069 policy holds (an expired password, or enforced MFA not yet enrolled). Nobody has reported it from a deployment.Thread-read: 6252599020
Serial constraints cleared: PR refactor(service-storage): the storage download door calls the one request-to-ExecutionContext composition, resolveRequestExecutionContext #24364 (runtime: one internal request-to-ExecutionContext resolver for the platform's five hand-rolled copies (rest, runtime, service-settings, service-storage, plugin-hono-server) — not a published export (ruling C′ on #23128, p3) #23217 slice 4, the same file) merged as
8d3dd6f808, which ends the wait recorded at6252599020. runtime: one internal request-to-ExecutionContext resolver for the platform's five hand-rolled copies (rest, runtime, service-settings, service-storage, plugin-hono-server) — not a published export (ruling C′ on #23128, p3) #23217 is back indomain:cli's queue for slice 5 (plugin-hono-server), a different package. 6 open PRs were read at this write, and none touchesservice-storage. Serial: nothing else from this seat is in flight (plugin-approvals: the approval request page's highlights strip shows the current step's machine id (current_step, such as approve_step) where the approvals get serves the step's label only as step_label #24281 closed at6253280120).Generated by Claude Code
test(service-storage): the #24366 pin fixture's WHERE matcher refuses…
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 24366,
"status": "done",
"branch": "claude/issue-24366-storage-gate-credential",
"pr": "#24388",
"session": "session_01Nk17Z26X5DB59iQztDBADH",
"premise_still_valid": true,
"summary": "The storage family's ADR-0069 gate now judges every credential a route of the family can serve, so a session the policy holds is refused 403 with its posture code on every route of the family whatever else the request carries, before any of the family's own logic runs; a held key's owner is still refused through the shared key-admission seam. One narrowing in buildAuthGateSessionReader (service-storage): the gate the door asks always covers the credential the route will serve. No request a storage route refuses today becomes accepted; no export, schema or accepted value changes (Clause-②: no).",
"tests": "pnpm --filter @objectstack/service-storage test :: 61 files, 1051 passed. pnpm --filter @objectstack/service-storage typecheck :: exit 0 (incl. check:test-typecheck). New pin storage-routes.family-gate-credential.pin.test.ts (10 pass): held session refused 403 on the download routes (unclaimed + parent-governed) and the upload start route whatever else the request carries, storage never touched; controls: un-held session served, held owner's key alone refused as today. Ablation: restoring the prior exclusive reader turned three of the new pin's cases red, then restored byte-clean against HEAD (git diff HEAD empty).",
"gates": "Local subset run (exit captured before any pipe), all exit 0: check-nul-bytes; check-issue-citations; check-changeset-no-major --base origin/main; check-empty-changeset --base origin/main; check-adr-0087-registration --base origin/main; check:changeset-gate-self-tests; check-comment-mask-adoption; check-comment-mask-corpus; check:cross-package-test-inputs; check:test-source-alias; check:type-check-coverage; check:type-check-debt; check-plugin-teardown-shape; check-system-context-census; check-tenant-audit-census; check-platform-object-tenancy-census; check-registry-log-declared; check-rest-log-spy-declared; check-undeclared-dep-imports; check-keyed-text-bounds; check:slot-lookup; check:where-matcher (was red on the new pin's silently-excluding fixture matcher; fixed to throw on unknown combinator/operator, now exit 0). dispatch-gates derived 68 families for this diff; reconciled with --ran (11 recorded, 0 NOT-MEASURED). The remaining families (self-tests, repo-wide censuses, build-dependent gates) run in CI on PR #24388; read them from the PR's check-runs.",
"mcp_calls": "0 (no MCP GitHub tools used)",
"api_writes": "3 REST proxy writes via the fleet relay: POST /repos/objectstack-ai/objectstack/pulls (pr_create), POST /repos//issues/24388/assignees (marchtian), POST /repos//issues/24366/comments (this report). git push is not a REST write (3 pushes to the branch).",
"open_questions": [],
"out_of_scope_findings": []
}
test(service-storage): pin the #24366 fake engine to the engine contr…
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 24366,
"round": "patch round 1 (rework 6253965841)",
"status": "done",
"branch": "claude/issue-24366-storage-gate-credential",
"pr": "#24388",
"head": "82adc50a4",
"session": "session_01Nk17Z26X5DB59iQztDBADH",
"premise_still_valid": true,
"summary": "Item 1: check:engine-double-contract reproduced locally (exit 1, two problems, both the new pin's fake engine). The fake's findOne now opens with assertEngineFindOnePredicate and its update with assertEngineUpdateDispatch (from @objectstack/objectql, already a devDependency), and scripts/engine-double-contract.pinned.json records the two new pinned rows (--write: 0 lost); the gate now exits 0. No gate, test or baseline was skipped or loosened. Item 2: content/docs/permissions/authentication.mdx now states the default rule and that the storage routes refuse a gated session whatever else the request carries. Item 3: the whole derived list ran on the final head through os-verify-lock.sh.",
"tests": "pnpm --filter @objectstack/service-storage test :: exit 0 (61 files, 1051 passed); pnpm --filter @objectstack/service-storage typecheck :: exit 0.",
"gates": "dispatch-gates --commands (no paths) on 82adc50 derived 107 families; all 107 run through scripts/pm/os-verify-lock.sh after a workspace build, every one exit 0; --ran: 107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN. CI on 82adc50: read the PR's check-runs (in_progress at this write).",
"mcp_calls": "0",
"api_writes": "1 this round: POST /repos//issues/24366/comments (this report), via the fleet relay. git push is not a REST write (1 push).",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Oct 18, 2026
Re-grade: p1 → P0 ·
security·target:v18· the re-raise condition is met · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T19:54ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.⚠️ This comment carries no reproduction.
6252572060is met. The dev measured it onorigin/main5da2e8881(domain:servicesseat 1,6253926144on [PM seat] triage (objectstack-wide) — 🟢 Routine · session_01AavokzJ5DndAwitDXvKy4U · trig_01NcnCtMS2tH46nUg1a5TaMJ (hourly) · state = body + the round records newer than it #6015): a credential the held user does not own makes the held session usable on the storage family. What is served is still the held user's principal, but the ADR-0069 hold is defeated with someone else's credential.Fixes #24366,packages/services/service-storageonly, a narrowing). It lands after itsCONTRACT_REVIEW_TIERrecord and green CI, as the seat says.741aa85da6).6253873673) are on the maintainer's purge list.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 24366,
"round": "patch round 2 (rework 6254490409, answering contract review 6254478003 item 2)",
"status": "done",
"branch": "claude/issue-24366-storage-gate-credential",
"pr": "#24388",
"head": "8ab3f986c",
"session": "session_01Nk17Z26X5DB59iQztDBADH",
"premise_still_valid": true,
"summary": "Semver faces corrected in the form of this package's precedent (08b38cb): the changeset grades @objectstack/service-storage minor, its summary carries the bang, Clause-② reads no (narrowing) with a one-line reason, it carries the BREAKING banner and an ADR-0087 not-required (no-migration-prescription) disposition that closes the other categories on facts. PR title takes the bang and the body's Clause-② line reads no (narrowing); the body was built from the live body with only that line changed and read back identical. The lifecycle-arm comment in the gate reader now states what is true on each route. No code change; rule and consequence only.",
"tests": "pnpm --filter @objectstack/service-storage test :: exit 0 (61 files, 1051 passed); pnpm --filter @objectstack/service-storage typecheck :: exit 0.",
"gates": "check-changeset-no-major --base origin/main :: exit 0, and with an offline pull_request event carrying the new title and body :: exit 0 (level axis read no (narrowing), no package graded patch); check-adr-0087-registration --base origin/main :: exit 0 (BREAKING+bang+clause-② narrowing, not-required); check-empty-changeset --base origin/main :: exit 0; check:changeset-gate-self-tests :: exit 0. Whole derived list on 8ab3f98: 107 families, all run through os-verify-lock.sh, all exit 0; --ran: 107 derived, 107 run, 0 NOT-MEASURED, 0 UNRUN. CI on 8ab3f98: read the PR's check-runs.",
"mcp_calls": "0",
"api_writes": "2 this round via the fleet relay: PATCH /repos//issues/24388 (title and body, issue_patch), POST /repos//issues/24366/comments (this report). git push is not a REST write (1 push).",
"open_questions": [],
"out_of_scope_findings": []
}
objectstack-fleet commentedon Oct 18, 2026
ACCEPT → PR #24388 comment⚠️ No reproduction here.
6255048096·domain:servicesseat 1 (#6021) ·session_01Nk17Z26X5DB59iQztDBADH· 2026-10-18T20:56Z. Head8ab3f986c3; contract review PASS6254884596atCONTRACT_REVIEW_TIER(the narrowing face, after FAIL6254478003on82adc50a4). It lands through the merge queue, and every check on its head is green.Fixes #24366closes this card at the merge.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Landed ·⚠️ This note carries no reproduction.
domain:servicesseat 1 (#6021) ·marchtian·session_01Nk17Z26X5DB59iQztDBADH· 2026-10-18T21:23Z.Landed: PR #24388 (
Fixes #24366) merged through the merge queue asd8e11b58e7at 2026-10-18T21:22Z. That commit is onorigin/main(git merge-base --is-ancestorexit 0; control5da2e88818exit 0, negative control PR head8ab3f986c3exit 1, as a squash). Six of its seven files are byte-identical to the PR head. The seventh,scripts/engine-double-contract.pinned.json, is a shared ledger. The merge added exactly this PR's two rows; the blob differs only by ten linesmainadded there since the branch base. Contract review PASS6254884596, ACCEPT6255048096.What a user now sees: a user whom the ADR-0069 policy holds (an expired password, a required reset, or enforced MFA not yet enrolled) is refused
403with the gate's own code on every storage route, whatever else the request carries, until they remediate. Nothing that was refused before is served now. It ships in@objectstack/service-storageas aminorwith the BREAKING banner (Clause-②: no (narrowing)).Still open, with carriers:
domain:cli): its input is now onmain. A note is left there in this act.6254427208). The release is not this seat's.6253873673: on the maintainer's list.The card closed
completedat the merge.pm:dispatchedis cleared and the assignee released in this act.Generated by Claude Code