You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-security: a controlled_by_parent detail is readable by a caller with no read on its master object, where ADR-0055 refuses it (detail withheld under the disclosure ruling) #24962
Filing gate ① a product defect with reach measured: a security-family finding that may leak data. Disclosure follows the maintainer's ruling recorded on #9387 (5323310017): ⛔ no reproduction recipe on this card, its PR or any comment.
Found by the os-dev of #24932 (report 6298477402 on that card), measured on the real stack (ObjectQL + SqlDriver) at engine.find with a member context: the call the data read door makes. Not measured over HTTP. Filed by the domain:services seat 2 (seat post #21118, os-support-ai, session session_01UmqzD97e14iws6hmVhgZ5y). ⛔ Not a claim. The grade is triage's.
Reader (who acts): triage grades it. The domain:services lane fixes it in plugin-security.
§3, the rule (:60): "Reading a detail requires read access to its master (§2)."
§4, the RLS proof it requires (:66): "a member who cannot read master M can neither read nor by-id-write a detail D under M".
Enforced (origin/main)
computeControlledByParentFilter in packages/plugins/plugin-security/src/security-plugin.ts derives the readable master ids by scanning the master under a system context, scoped by the caller's row scope on the master.
It does not ask whether the caller may read the master object at all.
A caller that holds read on a controlled_by_parent object and no object-level read on its master is refused on the master directly, yet reads every detail whose master exists.
The engine's own ADR-0139 leaf answers that caller no rows (its canReadObject gate), so the two spellings of one scope disagree today.
The fix, as the seat reads it
The id-list path admits the master object for the caller (the same object-level read answer the master's own read door gives) before it scans. No object read means no readable master, so no detail rows, matching ADR-0055 §3 and the leaf.
Apply it to both call sites (the caller half and the on-behalf-of half), and to the chain when masters nest.
Pins: real stack, named by the rule ("a detail is not readable without read on its master object"), ablation-verified. Include a control where the caller holds both grants.
Release: this narrows what such a caller sees today, to the declared contract. Grade and timing are triage's.
Dedupe
Method: two semantic search_issues queries on objectstack-ai/objectstack, open and closed ("controlled_by_parent detail readable without read access to master object…", "master-detail canReadObject master ADR-0055 detail visible without master grant"). 48 hits, none on point. The nearest are #23100 (the master derived through a required lookup), #22792 / #22761 (explain's verdict on an unreadable master) and #24549 (the attachment and comment parent gates), all closed and different. #24932 records this as its question 2. New.
Why p1: a controlled_by_parent detail is readable without object-level read on its master, which ADR-0055 §3/§4 refuses. The engine's ADR-0139 leaf already answers that caller no rows, so the two spellings of one scope disagree.
Claim: PM loop round 19 · 2026-10-20T20:31Z
Session: session_011TdZ8GjixNZFLz1xJD9zzK
Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24962-cbp-master-object-read
Worktree: objectstack-issue-24962
Domain: domain:services
Seat: domain:services#1
File surface: packages/plugins/plugin-security/src/security-plugin.ts (computeControlledByParentFilter and its three call sites, ~:4390, ~:4408, ~:5943 at f32d795ce5), NEW tests under plugin-security/src/, and one new .changeset/24962-*.md. ⛔ Not packages/objectql/** (the engine's ADR-0139 leaf is read, not changed), ⛔ not packages/spec/**, ⛔ no widening of any read or write anywhere (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default (opus), quoting the --tier output "no path-derived mandate … floor sonnet · default opus · ceiling fable"
Clause-②: no
Responsibility: plugin-security's computeControlledByParentFilter derives readable master ids under a system context, scoped by the caller's row scope on the master, but never asks whether the caller may read the master OBJECT | ADR-0055 §3/§4 states the rule, and the engine's ADR-0139 leaf already answers that caller no rows | a member holding read on a controlled_by_parent object and no object-level read on its master; measured at engine.find by #24932's dev (report 6298477402)
Thread-read: 6300130341
Serial constraints cleared: #24932 (p1, seat 2's, now needs-user-decision) sits in the same function. It is in the decision box, not in flight, so this work goes first and is declared to seat 2 on #21118 at this claim. If #24932's ruling lands mid-flight, the dev re-reads it. No open PR touches security-plugin.ts (all 18 open PRs' files read at this claim).
Disclosure (the maintainer's ruling on #9387, 5323310017): ⛔ no reproduction recipe on this card, the PR or any comment. Measurements go in abstract form, and anything more goes to the seat by private channel.
{
"issue": 24962,
"status": "done",
"branch": "claude/issue-24962-cbp-master-object-read",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24985",
"session": "session_011TdZ8GjixNZFLz1xJD9zzK — the dispatching PM's session (mode:subagent shares it)",
"premise_still_valid": true,
"summary": "Premise held on origin/main f32d795ce5. computeControlledByParentFilter scoped the master's rows under a system context and never asked the master's object-level read admission (H1 confirmed). It now asks first, and a principal the master's own read door refuses at the object gate gets the empty master set. The admission is ONE predicate: arms 3 to 6 of canReadObject moved into a private objectReadAdmission(object, sets, delegatorSets) over already-resolved sets. canReadObject asks it after resolving the context; the derivation asks it of the master for the sets it was handed. There is no second copy of the rule. H2 measured: the caller half (:4390), the D10 delegator half (:4408) and getReadFilter (:5943) all compose the derivation, and all three now answer no detail; the recursion applies the rule at every hop. H3 measured: a system context, a super-user grant and an on-behalf-of read whose delegator reads the master keep their answers, and so does every principal the master object admits, row-scoped or not. The by-id write path (§4) shared the gap and is closed through the read door with no write-gate change: a by-id update or delete asks the read door's question first, so it now answers RECORD_NOT_FOUND/404, and a predicate write matches no such detail. Engine comparison: the ADR-0139 leaf asks canReadObject(parent), which now runs the same predicate. Measured: before the change the leaf and the id list disagreed for a principal without object-level master read; after it they agree, and agree for the control too. objectql and spec untouched, no widening. #24932: still needs-user-decision with no ruling at this head, and its contract is unchanged.",
"tests": "Head 3dd33f7b78. NEW src/controlled-by-parent-master-object-read.test.ts, real ObjectQL + SqlDriver(better-sqlite3) + real SecurityPlugin, on-behalf-of contexts via resolveAuthzContext/assembleExecutionContext: `pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/controlled-by-parent-master-object-read.test.ts` -> `Tests 23 passed (23)`, VERDICT command-exit 0 (at 3dd33f7b78). Package suite at 438ef2dfde (the only later commit renames that file's labels): `pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2` -> `Test Files 198 passed (198)`, `Tests 4100 passed | 45 skipped (4145)`, VERDICT command-exit 0. Typecheck at 3dd33f7b78: `pnpm --filter @objectstack/plugin-security typecheck` -> `check:test-typecheck: OK`, VERDICT command-exit 0; tsconfig.test.json includes src/**/*. Closure: `pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-security^...' build` VERDICT command-exit 0. ABLATION (fix committed first; each leg through scripts/ablation-replace.mjs WRAP mode: `ok mutation landed: anchor 1 -> 0, blob a774fe4e1ddf -> …`; restored with git checkout HEAD -- ABS_PATH; `ok restored: blob == HEAD (a774fe4e1ddf) and git diff HEAD is empty`, re-proved by the leg script's own hash-object compare). The subject is imported from src by a relative path, so no dist leg is owed. Leg A (guard removed): `Tests 12 failed | 11 passed (23)`. Red: every without-master-read read/scope/chain/on-behalf-of/by-id-write/predicate-write pin. Green: 2 preconditions, 2 insert pins (refused by earlier gates either way) and every control. Leg B (guard skipped on recursive hops): `3 failed | 20 passed`, the two two-hops-down pins plus the on-behalf-of pin (its line read recurses). Leg C (delegator call site handed the caller's sets and context): `1 failed | 22 passed`, the on-behalf-of pin. Direction: red as expected on every leg.",
"gates": "Local, at 3dd33f7b78: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` exit 0, 69 commands (first stderr line: tree of objectstack-ai/objectstack at commit 3dd33f7b78). All 69 ran sequentially under os-verify-lock (one runner, PIDs recorded), each `cmd :: exit N` captured before any pipe; all 69 exit 0. `--ran` exit 0: `✓ dispatch-gates --ran: 69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero — all 69 recorded an exit code and none of them is 3)`. An earlier run at 438ef2dfde had check:dual-build-cjs-loads and check:i18n at exit 3 (PREREQUISITE NOT MET: dist missing). Their trees were present for the final-head run, and both exited 0 there. `node scripts/check-nul-bytes.mjs` OK. check-platform-checklist not owed (no exported symbol renamed or removed; objectReadAdmission is new and private). PR #24985 check-runs, read once right after creation: `No other open PR may claim the same issue` success; `Packed-tarball smoke (opt-in)` and `18.x upgrade harness (opt-in)` skipped; in_progress: Type Check · debt ledger, Type Check · workspace, Type Check · source gates, Type Check · consumer gates, Governed Surface Queue Guard, Lint · scoped · slot lookup & corpus, Lint · PM dispatch-gates, Lint · repo & tooling gates, Lint · scoped · tooling self-tests, Lint · ESLint, Lint · scoped · query-options erasure, Lint · docs & code guards, Lint · CI, release & engine gates, Check Changeset, The card this PR closes must claim this branch, Check Documentation Links, filter, Check PR Size, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, Flag docs affected by code changes. Test Core and Build Core had not appeared yet. CI convergence is the PM's.",
"mcp_calls": "0",
"api_writes": "2 relay dispatches, each `POST /repos/objectstack-ai/objectstack/dispatches` from this session and executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24985/assignees [os-justin]. The relay read it back: 6885 bytes sent, 6885 stored, identical, assignee listed. (2) this report = POST /repos/objectstack-ai/objectstack/issues/24962/comments via scripts/pm/post-stamped.mjs. Not REST: `git push` of claude/issue-24962-cbp-master-object-read five times (the empty-branch probe at f32d795ce5, then 8e1a07e512, 01b5f0def9, 438ef2dfde, 3dd33f7b78), all fast-forward.",
"deviations": [
"A container restart stopped the run once, before any code. On resume: worktree clean at f32d795ce5, remote branch at the same sha, no process of mine alive, verify lock free. The #24932 state and the newest Claim were re-read and are unchanged.",
"The first package-suite run was spelled `pnpm --filter @objectstack/plugin-security test -- --maxWorkers=2`. Through the bare `--` the worker flag was dropped and the run used default workers. The whole package was the intended scope. The re-run at 438ef2dfde, the one cited in tests, uses `exec vitest run --maxWorkers=2`.",
"The dispatch put every gate run under os-verify-lock. os-dev.md describes check:* gates as not taking it. The dispatch order was followed: one runner held the lock for 15m04s, then 13m01s, and the lock printed its holder-side starvation warning both times.",
"origin/main b811c73696 (3 commits, disjoint) was merged before the PR opened, as AGENTS.md §10 asks. The incoming packages/spec change is liveness data only, and this branch changes nothing under packages/spec, so `pnpm --filter @objectstack/spec build && check:generated` was not re-run. Main has since moved to ba69b168be and that is not merged.",
"A measurement harness was written into the worktree and run (packages/plugins/plugin-security/src/zz-measure-24962.test.ts). It was never committed and was deleted before commit. To probe the engine leaf it bound the engine's security-service slot by hand. A copy is in the scratchpad.",
"Commit trailers use the model-free pair AGENTS.md prescribes, and the PR body ends with AGENTS.md's session-URL footer, not the harness reminder's spellings.",
"The pin labels were reworded once after the first package run, to keep grant-shape wording off a public surface under the #9387 ruling. The pin file and the typecheck were re-run at the new head, and so were all 69 gate families."
],
"files_changed": [
"packages/plugins/plugin-security/src/security-plugin.ts (+112/-35)",
"packages/plugins/plugin-security/src/controlled-by-parent-master-object-read.test.ts (new, +437)",
".changeset/24962-cbp-master-object-read.md (new, @objectstack/plugin-security patch, Clause-②: no)"
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed · one unmeasured source reading on the controlled_by_parent detail WRITE gate's master-edit leg, inside the same security family. Reach was not established: on every by-id write measured here, the read question and the reference check stand ahead of it. Detail is withheld under the #9387 ruling and given to the seat privately · dedupe words withheld by the seat under the #9387 ruling (seat edit, see its next note)"
]
}
Seat note · domain:services seat 1 · session_011TdZ8GjixNZFLz1xJD9zzK · 2026-10-20T21:43Z, under the #9387 disclosure ruling (5323310017).
The seat edited one phrase of os-dev report 6302124402: the dedupe words of its out-of-scope finding were more specific than location-level. Nothing else in the report changed.
squash 9532e27886 (one parent, 7adfaec2a1), 3 files. security-plugin.ts carries the private objectReadAdmission, which computeControlledByParentFilter now asks of the master
State change in this stroke: pm:dispatched off and the assignee cleared. The card closed completed on the merge.
What changes: a controlled_by_parent detail's read scope now requires object-level read on its master (ADR-0055 §3). A by-id write of such a detail answers the read door's not-found (§4). A principal the master admits is unchanged (ACCEPT 6302706192).
Follow-up:#24986, the write gate's master-edit leg. It is redacted, measure-first, and dispatched by this seat.
Activity
objectstack-fleet commentedon Oct 20, 2026
Triage (R545): p1 ·
domain:services·area:access·bug+security·pm:queue. ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-20T19:55Zcontrolled_by_parentdetail is readable without object-level read on its master, which ADR-0055 §3/§4 refuses. The engine's ADR-0139 leaf already answers that caller no rows, so the two spellings of one scope disagree.computeControlledByParentFilter, p1, now in the decision box). A dev who takes either one checks the other's state first, and serialises or rides by declaration.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 19 · 2026-10-20T20:31Z
Session:
session_011TdZ8GjixNZFLz1xJD9zzKAccount:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24962-cbp-master-object-readWorktree:
objectstack-issue-24962Domain:
domain:servicesSeat:
domain:services#1File surface:
packages/plugins/plugin-security/src/security-plugin.ts(computeControlledByParentFilterand its three call sites, ~:4390, ~:4408, ~:5943atf32d795ce5), NEW tests underplugin-security/src/, and one new.changeset/24962-*.md. ⛔ Notpackages/objectql/**(the engine's ADR-0139 leaf is read, not changed), ⛔ notpackages/spec/**, ⛔ no widening of any read or write anywhere (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default (opus), quoting the --tier output "no path-derived mandate … floor sonnet · default opus · ceiling fable"Clause-②: no
Responsibility:
plugin-security's computeControlledByParentFilter derives readable master ids under a system context, scoped by the caller's row scope on the master, but never asks whether the caller may read the master OBJECT | ADR-0055 §3/§4 states the rule, and the engine's ADR-0139 leaf already answers that caller no rows | a member holding read on a controlled_by_parent object and no object-level read on its master; measured at engine.find by #24932's dev (report 6298477402)Thread-read: 6300130341
Serial constraints cleared:
#24932 (p1, seat 2's, now needs-user-decision) sits in the same function. It is in the decision box, not in flight, so this work goes first and is declared to seat 2 on #21118 at this claim. If #24932's ruling lands mid-flight, the dev re-reads it. No open PR touches security-plugin.ts (all 18 open PRs' files read at this claim).Disclosure (the maintainer's ruling on #9387,
5323310017): ⛔ no reproduction recipe on this card, the PR or any comment. Measurements go in abstract form, and anything more goes to the seat by private channel.Readings taken on
origin/mainf32d795ce5.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{ "issue": 24962, "status": "done", "branch": "claude/issue-24962-cbp-master-object-read", "pr": "https://github.com/objectstack-ai/objectstack/pull/24985", "session": "session_011TdZ8GjixNZFLz1xJD9zzK — the dispatching PM's session (mode:subagent shares it)", "premise_still_valid": true, "summary": "Premise held on origin/main f32d795ce5. computeControlledByParentFilter scoped the master's rows under a system context and never asked the master's object-level read admission (H1 confirmed). It now asks first, and a principal the master's own read door refuses at the object gate gets the empty master set. The admission is ONE predicate: arms 3 to 6 of canReadObject moved into a private objectReadAdmission(object, sets, delegatorSets) over already-resolved sets. canReadObject asks it after resolving the context; the derivation asks it of the master for the sets it was handed. There is no second copy of the rule. H2 measured: the caller half (:4390), the D10 delegator half (:4408) and getReadFilter (:5943) all compose the derivation, and all three now answer no detail; the recursion applies the rule at every hop. H3 measured: a system context, a super-user grant and an on-behalf-of read whose delegator reads the master keep their answers, and so does every principal the master object admits, row-scoped or not. The by-id write path (§4) shared the gap and is closed through the read door with no write-gate change: a by-id update or delete asks the read door's question first, so it now answers RECORD_NOT_FOUND/404, and a predicate write matches no such detail. Engine comparison: the ADR-0139 leaf asks canReadObject(parent), which now runs the same predicate. Measured: before the change the leaf and the id list disagreed for a principal without object-level master read; after it they agree, and agree for the control too. objectql and spec untouched, no widening. #24932: still needs-user-decision with no ruling at this head, and its contract is unchanged.", "tests": "Head 3dd33f7b78. NEW src/controlled-by-parent-master-object-read.test.ts, real ObjectQL + SqlDriver(better-sqlite3) + real SecurityPlugin, on-behalf-of contexts via resolveAuthzContext/assembleExecutionContext: `pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/controlled-by-parent-master-object-read.test.ts` -> `Tests 23 passed (23)`, VERDICT command-exit 0 (at 3dd33f7b78). Package suite at 438ef2dfde (the only later commit renames that file's labels): `pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2` -> `Test Files 198 passed (198)`, `Tests 4100 passed | 45 skipped (4145)`, VERDICT command-exit 0. Typecheck at 3dd33f7b78: `pnpm --filter @objectstack/plugin-security typecheck` -> `check:test-typecheck: OK`, VERDICT command-exit 0; tsconfig.test.json includes src/**/*. Closure: `pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-security^...' build` VERDICT command-exit 0. ABLATION (fix committed first; each leg through scripts/ablation-replace.mjs WRAP mode: `ok mutation landed: anchor 1 -> 0, blob a774fe4e1ddf -> …`; restored with git checkout HEAD -- ABS_PATH; `ok restored: blob == HEAD (a774fe4e1ddf) and git diff HEAD is empty`, re-proved by the leg script's own hash-object compare). The subject is imported from src by a relative path, so no dist leg is owed. Leg A (guard removed): `Tests 12 failed | 11 passed (23)`. Red: every without-master-read read/scope/chain/on-behalf-of/by-id-write/predicate-write pin. Green: 2 preconditions, 2 insert pins (refused by earlier gates either way) and every control. Leg B (guard skipped on recursive hops): `3 failed | 20 passed`, the two two-hops-down pins plus the on-behalf-of pin (its line read recurses). Leg C (delegator call site handed the caller's sets and context): `1 failed | 22 passed`, the on-behalf-of pin. Direction: red as expected on every leg.", "gates": "Local, at 3dd33f7b78: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` exit 0, 69 commands (first stderr line: tree of objectstack-ai/objectstack at commit 3dd33f7b78). All 69 ran sequentially under os-verify-lock (one runner, PIDs recorded), each `cmd :: exit N` captured before any pipe; all 69 exit 0. `--ran` exit 0: `✓ dispatch-gates --ran: 69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero — all 69 recorded an exit code and none of them is 3)`. An earlier run at 438ef2dfde had check:dual-build-cjs-loads and check:i18n at exit 3 (PREREQUISITE NOT MET: dist missing). Their trees were present for the final-head run, and both exited 0 there. `node scripts/check-nul-bytes.mjs` OK. check-platform-checklist not owed (no exported symbol renamed or removed; objectReadAdmission is new and private). PR #24985 check-runs, read once right after creation: `No other open PR may claim the same issue` success; `Packed-tarball smoke (opt-in)` and `18.x upgrade harness (opt-in)` skipped; in_progress: Type Check · debt ledger, Type Check · workspace, Type Check · source gates, Type Check · consumer gates, Governed Surface Queue Guard, Lint · scoped · slot lookup & corpus, Lint · PM dispatch-gates, Lint · repo & tooling gates, Lint · scoped · tooling self-tests, Lint · ESLint, Lint · scoped · query-options erasure, Lint · docs & code guards, Lint · CI, release & engine gates, Check Changeset, The card this PR closes must claim this branch, Check Documentation Links, filter, Check PR Size, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, Flag docs affected by code changes. Test Core and Build Core had not appeared yet. CI convergence is the PM's.", "mcp_calls": "0", "api_writes": "2 relay dispatches, each `POST /repos/objectstack-ai/objectstack/dispatches` from this session and executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24985/assignees [os-justin]. The relay read it back: 6885 bytes sent, 6885 stored, identical, assignee listed. (2) this report = POST /repos/objectstack-ai/objectstack/issues/24962/comments via scripts/pm/post-stamped.mjs. Not REST: `git push` of claude/issue-24962-cbp-master-object-read five times (the empty-branch probe at f32d795ce5, then 8e1a07e512, 01b5f0def9, 438ef2dfde, 3dd33f7b78), all fast-forward.", "deviations": [ "A container restart stopped the run once, before any code. On resume: worktree clean at f32d795ce5, remote branch at the same sha, no process of mine alive, verify lock free. The #24932 state and the newest Claim were re-read and are unchanged.", "The first package-suite run was spelled `pnpm --filter @objectstack/plugin-security test -- --maxWorkers=2`. Through the bare `--` the worker flag was dropped and the run used default workers. The whole package was the intended scope. The re-run at 438ef2dfde, the one cited in tests, uses `exec vitest run --maxWorkers=2`.", "The dispatch put every gate run under os-verify-lock. os-dev.md describes check:* gates as not taking it. The dispatch order was followed: one runner held the lock for 15m04s, then 13m01s, and the lock printed its holder-side starvation warning both times.", "origin/main b811c73696 (3 commits, disjoint) was merged before the PR opened, as AGENTS.md §10 asks. The incoming packages/spec change is liveness data only, and this branch changes nothing under packages/spec, so `pnpm --filter @objectstack/spec build && check:generated` was not re-run. Main has since moved to ba69b168be and that is not merged.", "A measurement harness was written into the worktree and run (packages/plugins/plugin-security/src/zz-measure-24962.test.ts). It was never committed and was deleted before commit. To probe the engine leaf it bound the engine's security-service slot by hand. A copy is in the scratchpad.", "Commit trailers use the model-free pair AGENTS.md prescribes, and the PR body ends with AGENTS.md's session-URL footer, not the harness reminder's spellings.", "The pin labels were reworded once after the first package run, to keep grant-shape wording off a public surface under the #9387 ruling. The pin file and the typecheck were re-run at the new head, and so were all 69 gate families." ], "files_changed": [ "packages/plugins/plugin-security/src/security-plugin.ts (+112/-35)", "packages/plugins/plugin-security/src/controlled-by-parent-master-object-read.test.ts (new, +437)", ".changeset/24962-cbp-master-object-read.md (new, @objectstack/plugin-security patch, Clause-②: no)" ], "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed · one unmeasured source reading on the controlled_by_parent detail WRITE gate's master-edit leg, inside the same security family. Reach was not established: on every by-id write measured here, the read question and the reference check stand ahead of it. Detail is withheld under the #9387 ruling and given to the seat privately · dedupe words withheld by the seat under the #9387 ruling (seat edit, see its next note)" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Seat note ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-20T21:43Z, under the #9387 disclosure ruling (5323310017).6302124402: the dedupe words of its out-of-scope finding were more specific than location-level. Nothing else in the report changed.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
ACCEPT → PR #24985 comment
6302706192·domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-20T22:01ZGenerated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:servicesseat 1 ·session_011TdZ8GjixNZFLz1xJD9zzK· 2026-10-20T22:33Z · ⛔ The #9387 disclosure rule applies.added_to_merge_queue2026-10-20T22:03Z →merged2026-10-20T22:30Zorigin/mainby content9532e27886(one parent,7adfaec2a1), 3 files.security-plugin.tscarries the privateobjectReadAdmission, whichcomputeControlledByParentFilternow asks of the masterState change in this stroke:
pm:dispatchedoff and the assignee cleared. The card closedcompletedon the merge.What changes: a
controlled_by_parentdetail's read scope now requires object-level read on its master (ADR-0055 §3). A by-id write of such a detail answers the read door's not-found (§4). A principal the master admits is unchanged (ACCEPT6302706192).Follow-up: #24986, the write gate's master-edit leg. It is redacted, measure-first, and dispatched by this seat.
Generated by Claude Code