Repository navigation
objectql: a detail write reads its master-detail header elevated (tenant kept), so a parent-scoped requiredWhen may disclose one bit of a header the caller cannot read — measure whether any real configuration reaches it #22519
Description
Activity
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsTriage: first grade,
security·bug·priority:p2·target:v18·domain:engine·area:access·pm:queue(findingremoved). Measurement first, as the card sets itTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T17:52Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes, positions and functions only.Triage: the read site is
packages/objectql's master-detail header resolution. That puts it indomain:engine. Step 1 composesplugin-security's real rules, so the claimant runs them, and adomain:servicesreviewer reads the configurations tried.- Why p2, measurement-first: a possible one-bit disclosure, seen only under a synthetic read scope. The resolver's docblock declares the elevation deliberate, on the premise that write access to a detail implies read access to its header. If no real configuration breaks that premise, there is nothing to fix. If one does, it is a disclosure in the security family. So the card stays in that family until Step 1 answers.
- Step 1 is the whole first dispatch. Measure only, no fix:
- Measure every configuration class the card lists (object permissions, sharing rules,
controlled_by_parent, an RLS policy on the master), insert and update, and the update-pathreadonlyWhen. - Cover details that are not
controlled_by_parentexplicitly. Forcontrolled_by_parent,assertControlledByParentWriteanswers a metadata defect and a missing row with the same403 PERMISSION_DENIED"requires edit access to its master record" #7474 andcontrolled_by_parentderivation ignores the master's ownership and share grants — children are readable (and writable) regardless of parent access #5386 already tie the write to the master. - Report each configuration with its answer.
- ⛔ The report and any record of it stay at the level of classes, positions and functions. No request recipes.
- Measure every configuration class the card lists (object permissions, sharing rules,
- Then:
- None reachable: close
not_planned, citing the measurement. - Some reachable: the fix only tightens; it never loosens. Read the header through the caller's read door at write, as PR fix(objectql): a validate() preview binds the master-detail header the write binds (#22474) #22518 makes the preview do, or refuse the write. Choosing between them is the lane's call.
- If either changes what ADR-0055 says a detail write implies, it comes back to triage for the decision box before building.
- None reachable: close
- Serial: PR fix(objectql): a validate() preview binds the master-detail header the write binds (#22474) #22518 (objectql: a
validate()preview binds no master-detailparentheader in either mode, so an import dry run refuses a detail row whose rule readsparentwhile the write admits it #22474) edits the preview's header read in the same file. Step 1 writes no code, so it does not wait. A fix round rebases on whichever lands first.
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardspriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 2 · 2026-10-09T18:11Z
Session:session_01Bw3y2DWhT9RPnrmDsNqEVG
Account:os-tesla(the seat's linked user, asget_meanswers it; the card's assignee)
Branch:claude/issue-22519-header-read-reach(Step 1 is local only: nothing is pushed and no PR is opened)
Worktree:objectstack-issue-22519
Domain:domain:engine
Seat:domain:engine#2(seat post #20966)
File surface (read onorigin/maince3d0ad419): Step 1 is measurement only, as triage 6086312029 sets it.- A throwaway measurement harness in the worktree that composes
ObjectQLwithplugin-security's real rules on a real SQL driver. ⛔ It is not committed for review and not pushed. - Read-only: the write's header read
resolveMasterDetailParent(s)(packages/objectql/src/engine.ts:8637/:8683, underreferenceCheckContext), andplugin-security'sassertControlledByParentWrite(security-plugin.ts:9263). - ⛔ No product code changes in this dispatch. A fix round, if Step 1 finds a reachable configuration, re-claims with its own surface.
- Stop on a breach and explain in the report.
Container & model:M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)
Clause-②: no - Measurement only: this dispatch opens no PR and changes no accept set. Per triage, any later fix only tightens, never loosens.
Responsibility:objectql's write reads the master-detail header elevated, with the tenant kept |plugin-security's write gates may already tie a detail write to header access:assertControlledByParentWriteanswers a metadata defect and a missing row with the same403 PERMISSION_DENIED"requires edit access to its master record" #7474 andcontrolled_by_parentderivation ignores the master's ownership and share grants — children are readable (and writable) regardless of parent access #5386 forcontrolled_by_parent, unmeasured for the other classes | a caller who may write a detail but not read its header, under a parent-scoped rule; whether any real configuration produces that caller is Step 1's question
Thread-read: 6086312029
Serial constraints cleared: the serial triage named, PR fix(objectql): a validate() preview binds the master-detail header the write binds (#22474) #22518 (objectql: avalidate()preview binds no master-detailparentheader in either mode, so an import dry run refuses a detail row whose rule readsparentwhile the write admits it #22474), landed asda989bbb24. Step 1 writes no code, so no open PR's files bear on it. This seat's in-flight feat(metadata-core,metadata-protocol,objectql,plugin-security): thesys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206 S4 (metadata-protocol,runtime,spec) touches other packages.
- A throwaway measurement harness in the worktree that composes
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsStatus: Step 1 NOT measured ·
domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG(os-tesla) · 2026-10-09T18:19Z.- The run dispatched under claim 6086615875 stopped before any harness was written. One of its responses hit an automated safety stop, and it did not go on alone after that.
- It read the header resolution and the
controlled_by_parentwrite gate, read-only. - It built
plugin-security's dependency closure. - It measured no configuration, edited and committed nothing, and pushed nothing.
- It read the header resolution and the
- No verdict is asserted.
reachable_configurationsis NOT MEASURED. The card's premise is neither confirmed nor falsified. - The card stays claimed by this seat. ⛔ It is not re-dispatched as is. The seat has put to the maintainer how Step 1 should run. Two options:
- a human-driven measurement;
- a re-dispatch with triage's
domain:servicesreviewer taking part from the start.
- Nothing about the possible disclosure is changed by this note. Triage's grade and its ⛔ (classes, positions and functions only) stand.
- The run dispatched under claim 6086615875 stopped before any harness was written. One of its responses hit an automated safety stop, and it did not go on alone after that.
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsRelease of claim 6086615875 ·
domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG(os-tesla) · 2026-10-10T16:00Z. The seat stands down by the maintainer's order (6098072552 on #20966).- State at release. Nothing was measured, branched or pushed under this claim. Step 1 stopped before measuring (status 6086746973).
- The open question, put to the maintainer in chat and still unanswered: how should Step 1 run?
- (A) human-driven;
- (B) with the
domain:servicesreviewer from the start; - (C) re-dispatched as it was.
- ⛔ The next seat does not re-dispatch Step 1 as it was until the maintainer answers. That run was stopped by a safety classifier before any measurement.
pm:dispatchedand the assignee are removed in this act. The card is not put back intopm:queue. Triage holds it, with the question above.
Filing gate: ① under the possible-data-disclosure exception. The first step is to measure reachability. Measured by #22474's dev (os-dev-report 6085563448,
out_of_scope_findings[0]) with a synthetic read-scope middleware, not withplugin-security's real rules. Filed bydomain:engineseat 2 (seat post #20966),session_01Bw3y2DWhT9RPnrmDsNqEVG. ⛔ Not a claim; triage grades and routes.reach: exception — possible data disclosure, unmeasured at a real door. Step 1 is the measurement below. If it finds no reachable caller, this card closes as not planned with that evidence.
What was seen
resolveMasterDetailParent/resolveMasterDetailParents(packages/objectql/src/engine.ts) read underreferenceCheckContext(the caller's context plusisSystem). That is [finding] master-detail parent binding reads the header with no tenant —parent.*predicates leak another org's header fields; the dangling-reference audit is blind to cross-org references #19837's fix, which closed the cross-organization version of this oracle.requiredWhen: "parent.status == 'sent'"is evaluated against that header. With a read-scope middleware that hides headerhxfrom the caller while the caller may write a detail line under it, the insert admits whilehx.statusis draft. It refusesdescriptionasrequiredoncehx.statusis sent. So each write discloses one bit of a header the caller cannot read, and a caller who can author the rule chooses the comparison. The reference check also admitted the unreadable header id.controlled_by_parent(ADR-0055). The disclosure exists only if a real deployment lets a caller write a detail whose header that caller cannot read.validate()preview binds no master-detailparentheader in either mode, so an import dry run refuses a detail row whose rule readsparentwhile the write admits it #22474) makes the preview read the header through the caller's read door, keeping only values served in full. So for such a caller, the preview and the write can disagree, and PR fix(objectql): a validate() preview binds the master-detail header the write binds (#22474) #22518's changeset says so.Step 1 — measure reachability (owed before any fix)
On a real
ObjectQL+SecurityPlugin+SqlDriverstack, in each posture, find whether any configuration grants a caller write on a master-detail detail object while denying read on the header row. The configurations to try are object permissions, sharing rules,controlled_by_parent, and an RLS policy on the master. Try both insert and update. Also try the update-pathreadonlyWhenthat readsparent.Who acts on it
Triage settles the lane. The read site is
packages/objectql(domain:engine); the sharing and permission semantics areplugin-security(domain:services).Dedupe: MCP
search_issues, repo-scoped, open and closed:assertControlledByParentWriteanswers a metadata defect and a missing row with the same403 PERMISSION_DENIED"requires edit access to its master record" #7474 (assertControlledByParentWriterequires edit access on the master record) andcontrolled_by_parentderivation ignores the master's ownership and share grants — children are readable (and writable) regardless of parent access #5386 (controlled_by_parentderivation follows the master's ownership and shares). For acontrolled_by_parentdetail, the write may therefore already imply header access. Step 1 must still cover details that are notcontrolled_by_parent.parent.*predicates leak another org's header fields; the dangling-reference audit is blind to cross-org references #19837 (closed), the cross-organization version whose fix this card's within-scope question follows.Dedupe words:
write elevated master-detail header read·parent-scoped requiredWhen one-bit header disclosure·referenceCheckContext header unreadable caller