Repository navigation
Description
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Ruled: 6663849489 · letter A (re-affirmed) + delegate read · 2026-11-05T16:47Z (supersedes 6573561091)
What happens
A write that names a record in a lookup field is checked for existence, never for the writer's access to it. A caller who reads a record as 404 can still store a reference to it.
Measured on @objectstack/objectql 18.2.0, live (objectstack-ai/duly main 98c188a, pnpm demo). The caller is a member whose permission set grants readScope: 'own' on the target object duly_duty (sharingModel: 'private'):
| request (as the member) | answer |
|---|---|
GET /api/v1/data/duly_duty/<id> |
404 RECORD_NOT_FOUND |
POST /api/v1/data/duly_task with { duty: <id> } (duty is Field.lookup('duly_duty')) |
201 |
PATCH /api/v1/data/duly_task/<own task> with { duty: <id> } |
200 |
MCP create_record / update_record with the same payload |
200 |
| the same POST with an id that exists nowhere | 400 reference_not_found |
Where
packages/objectql/src/engine.ts (objectstack main cae71fe: assertReferencesResolve → referenceExists, around line 10521). For a plain lookup the probe runs under referenceCheckContext ({ ...context, isSystem: true }, tenant kept). The docblock states the choice: existence "is a fact about the database, not about the caller's row-level visibility", and refusing an unreadable target "would make the platform's own admin flows fail".
#22519 (ruling A) made the exception for master_detail. masterDetailHeaderExists asks the caller's read door first, and an unreadable header answers reference_not_found. The docblock then says "⛔ A plain lookup keeps the elevated probe above, unchanged" and "the plain lookup probe is not this rule's". The one-hop read for validation predicates (resolveOneHopRelated, authority: 'predicate') is also elevated for a tenant-walled target, so a validations[] script rule cannot express this either. lookupFilters is picker-only.
Why it matters
- Integrity, not disclosure. The write leaks nothing new. The existence oracle is already closed per tenant ([finding] a lookup's existence check reads under a bare system context with no tenant — an org-bound caller can store a reference to another organization's row, and tell "exists elsewhere" from "missing" #19808), and the probe answers only exists/not. But the stored reference attaches the writer's row to a record they have no business touching. In the measured app, a task's
dutyis part of a unique dispatch identity(duty, owner, period_key). A member who writes ahead of the scheduler on someone else's duty takes that person's period from them. - An app cannot declare the rule. No field key exists for "the writer must be able to read the target". The app's only recourse is a hand-written
beforeInsert/beforeUpdatehook that re-reads the target withrunAs: 'inherit'and refuses. That hook must also mimic the engine'sreference_not_foundenvelope by hand, in one language, because hook text has no translator.
Asked
A declarable opt-in on a lookup field (for example referenceAccess: 'caller', name illustrative) that runs the #22519 caller-read-door check for that field. The elevated default stays for the admin-flow case the docblock names. An unreadable target then answers exactly reference_not_found, localized like the engine's own. System writes (isSystem) stay exempt, as for master_detail.
Workaround in place
objectstack-ai/duly#174: hook duly_task_duty_readable on duly_task (runAs: 'inherit', compares against the stored value so a re-save is not re-judged). It is meant to be deleted when the platform can declare this.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:server·area:access·security· 2026-11-02T07:02Z⛔ Not a claim.
security, p2,domain:server. Handling goes through the PM session under the maintainer's standing rule for security findings; ⛔ add no further mechanism here. Triage has put the card to the maintainer.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Maintainer ruling on this security card · 2026-11-02T07:51Z
The maintainer answered triage's options in the triage session, verbatim: 「A+C 同意」.
domain:serverfixes it from this card, as queued. The fix PR, its changeset and its records state the rule only: what a door must check, not how it can be got around. ⛔ No reproduction, probe or payload goes into the PR, its comments or this thread. Anything more that the fix needs goes through the PM session.pm-dispatchto a dev, with its mechanism kept in the session, and is never filed as an issue. This card stays open because it is already public; the rule applies from now on.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
决策请求 · #29029 修法形状 · 2026-11-02T08:26Z
⛔ Not a claim. 维护者已裁「A+C 同意」(triage 记录 6571949926 之前的 6571949926):本卡由
domain:server修,只写规则不写复现。本席在派发前读代码发现,卡面所求的修法需要一个新的 lookup 字段键,那是 spec 契约面和新公开键名,按 SKILL.md 必须由维护者定,所以先停在这里问修法形状。Governing text:
packages/objectql/src/engine-referential-integrity.ts(origin/maina4015ab47b)引用检查:master_detail走masterDetailHeaderExists(调用者读门,#22519 裁决 A),普通lookup走referenceExists(提权探测,只判存在);engine 的 docblock 写明「普通 lookup 保持提权探测」,理由是拒绝不可读目标会让平台自己的管理流程失败。lookup/user引用都问调用者读门,读不到即reference_not_found,与 master_detail 同一规则;系统上下文(isSystem)照旧豁免。不加新键packages/objectql;带前提:开发先实测仓内以用户身份写入、引用其读不到的记录的合法路径,有则停下回报,不硬做referenceAccess: 'caller',名字待定),只对声明的字段走调用者读门,默认仍提权os-decision-facets
① 项目长远合理性:A 让引用规则只剩一种(调用者能读才可引用,系统豁免),与 master_detail 对齐,缩小特例;B 留两种语义,靠作者记得开。
② 实际业务拉动:duly 今天在撞(任务引用决定排班唯一性,成员可占别人的期次),现靠应用手写 hook;A、B 都满足,B 只救声明了的字段。
③ 防 AI 犯错:A 是结构性的,写元数据的 AI 不需要知道任何开关;B 要求每个敏感 lookup 记得声明,漏写即静默不安全。
④ 创业阶段不扩散:A 不加公开面;B 新增一个永久契约键。
Prior rulings read: "reference_not_found" lookup caller read → 35 hits; #22519 ruling A (master_detail reads the caller's door); thread: 2
推荐 A,前提:仓内不存在需要以用户身份引用不可读记录的合法流程(开发先测,命中即回报,不改走 B)。
zhuangjianguo commentedon Nov 2, 2026
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #453 · 2026-11-02T08:57Z
Director seat (第 56 场). The fix-shape question (6572536863) is presented to the maintainer as batch #453 item 2 in the 总监决裁 thread; the ruling comment follows the maintainer's answer. Per the maintainer's ruling 6571949926 (A+C), the presentation states the rule only and carries no reproduction. ⛔ Not a claim.
Generated by Claude Code
zhuangjianguo commentedon Nov 2, 2026
director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw
Ruling: batch #453 item 2 · letter A · maintainer 「同意」 2026-11-02T09:22Z
Director seat (第 56 场; brief-claimed above in 6573082000 and presented in the 总监决裁 thread as batch #453 item 2, the fix-shape question of 6572536863, recommendation A). The maintainer's word is 「同意」 for the whole batch, so the fix shape is ruled A: the default tightens, no new key. Per the maintainer's earlier ruling 6571949926 (A+C), this comment states the rule only.
What the ruling binds (A):
isSystem, every reference the caller supplies in alookup(anduser) field must name a record the caller's own read door returns, exactly asmaster_detailalready requires under objectql: a detail write reads its master-detail header elevated (tenant kept), so a parent-scopedrequiredWhenmay disclose one bit of a header the caller cannot read — measure whether any real configuration reaches it #22519 ruling A; an unreadable target answersreference_not_found, localized like the engine's own. System writes stay exempt, as today.Workload of the letter:
packages/objectqlonly, red-first tests, full dogfood; a narrowing of the write accept set, sofix(objectql)!,Clause-②: yes (narrowing),CONTRACT_REVIEW_TIER(this seat).Execution:
domain:serverseat 5 dispatches as queued (6572536863); the lane seat moves the label. TheRuled:body line is written by this act.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1
Claim: PM loop round 7 · card thread · 2026-11-02T12:19Z
Session:
session_01ADHkmQKwKFCp1m6Pb6Q8c1(the seat); the card thread is named in a follow-up claim once it reports its branchAccount:
os-steve(the seat's linked user; the card's assignee)Branch: pending: the card thread's harness-pinned branch, written into a follow-up claim before the dev starts
Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#5Ruling-ref: 6573561091 (batch #453 item 2 · letter A · the default tightens, no new key)
File surface:
packages/objectql/src/(the write's reference check inengine.ts/engine-referential-integrity.ts:lookupanduserjoinmaster_detailon the caller's read door when the write carries noisSystem), the localizedreference_not_foundtext if a new site needs it, their tests, changeset. ⛔ Nopackages/spec/**, no new field key.Container & model:
M,mode:thread, model: the thread's default judgement tier,effort: high(p2, security)Clause-②: yes (narrowing):
fix(objectql)!; contract review atCONTRACT_REVIEW_TIERby this seat on the delivered headMetadata-protocol: none
Responsibility: the code is objectql's write-time reference check · a non-system write can point a
lookup/userfield at a record its caller cannot read · reached by every app whose users write lookupsThread-read: 6573561091
Premise: holds (read at
origin/main5780fa49d: themaster_detailbranch asks the caller's read door;lookupkeeps the elevated probe)Serial constraints cleared: this seat's #29253 (
claude/project-thread-u66mdq) edits the same two files; the dev writes now and opens its PR only after #29253's PR merges, mergingorigin/mainthen. #29245 has merged. Precondition from the ruling: full dogfood and the example apps on the tightened engine before the PR opens; a legitimate non-system flow that breaks stops the work and is reported here.Resume-from: none: fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.17 remaining items
fix(verify): the parent-write-reach personas read the records a creat…
objectstack-fleet commentedon Nov 5, 2026
domain:server#2 · os-bill · session_01Utp6Cbvcmv8YWFhFJwzjKL
Resume-from:
claude/project-thread-m0wypr@4ba91079fb(supersedes the head in Release 6670899065; still released topm:queue)Since the release, the dev thread fixed two CI reds that the new rule caused, without loosening anything:
mainwrites users that have no directory record. They are now seeded the way sibling suites seed them.@objectstack/verify's parent-write-reach, the create probe filled a requiredlookupwith a record the writer cannot read. The verifier persona now reads the probe's off-chain required reference objects.The second fix is product code, so the changeset adds
'@objectstack/verify': patch. Owed by the next seat: before ACCEPT, add@objectstack/verifyand this deviation to the PR body (the dev cannot edit it). security#1's hold on the PR is recorded; it stays draft and is not queued until that hold clears.objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim: PM loop round 1 · 2026-11-05T23:19Z
Session:
session_016WV4czvZykUqhYxYXSSDt4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-m0wypr(continued, PR #30672); the card thread's session is named in a claim amendment once the thread reports itWorktree:
objectstack-issue-29029Domain:
domain:serverSeat:
domain:server#4File surface: PR #30672's file list as it stands at
4ba91079fb(packages/objectql/**reference check and engine docblock,packages/plugins/plugin-security/**delegate business-unit read,packages/plugins/plugin-audit/**,@objectstack/verifyparent-write-reach probe persona,docs/qa/platform-checklist/areas/records-forms.json, the fourcontent/docs/**pages, one root.changeset/file); no new surface. ⛔packages/spec/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable; the narrowing face owes aCONTRACT_REVIEW_TIERreview, run by the seat through a fresh reviewer),effort: xhigh(security)Clause-②: no
Metadata-protocol: narrowed
Responsibility: the engine's reference check in
packages/objectql(probes alookup/usertarget elevated, so a write may name a record its caller cannot read) |master_detailalready asks the caller's read door; nothing coverslookup/user| any non-system writer over REST, MCP or a flow's user-context write; measured on the cardThread-read: 6671674952
Premise: holds per os-dev-report 6670586454 and the security read 6671171164; the PR's two CI reds since then are fixed at
4ba91079fb(6671674952)Serial constraints cleared: re-derived by the card thread before its next push: merge
mainagain before queueing (Release 6670899065 item 4)Resume-from:
claude/project-thread-m0wypr@4ba91079fb(Release 6670899065, superseded head in 6671674952)Narrowing note: narrowing (BREAKING): a non-system write may name, in a
lookup/user/master_detailfield, only a record its caller's read door returns. Owed before the queue, in order (Release 6670899065): the contract-review-tier review on the narrowing face; the seat ACCEPT, with@objectstack/verifyand the verify-persona deviation recorded in it (the relay cannot edit the PR body); the maintainer's per-PR word, because the PR is BREAKING; security#1's hold cleared;mainmerged again.objectstack-fleet commentedon Nov 5, 2026
domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW
Claim: PM loop round 2 · 2026-11-05T23:19Z · resumes release 6670899065 + 6671674952 (domain:server#2)
Session:
session_016WV4cytLmJxfAmgPVjUbbWAccount:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch: PR #30672 at
claude/project-thread-m0wypr@4ba91079fb(the released head). No dev is ordered at claim time; if the review returns a must-fix, ormainmust be merged again, a card thread is ordered and named in an amendmentWorktree:
objectstack-issue-29029Domain:
domain:serverSeat:
domain:server#1File surface: PR #30672's existing 39 files, unchanged at claim. The seat edits only the PR body (adds
@objectstack/verifyand the parent-write-reach probe deviation, per 6671674952)Container & model:
security, seat-run; contract review atCONTRACT_REVIEW_TIER(fable) by a fresh reviewer,effort: xhighClause-②: yes (narrowing)
Metadata-protocol: narrowed
Responsibility: the reference check on plain
lookupwrites inpackages/objectql| the elevated reference probe, which lets a non-system write name a target the writer cannot read | any tenant user who can write a record with alookupto an object they cannot readThread-read: 6671674952
Premise: holds per ruling 6663849489 (A re-affirmed + delegate read) and os-dev-report 6670586454
Serial constraints cleared: owed in order, per the release: ① contract review on the narrowing face (
record-recognisers --face narrowing, fresh reviewer); ② the security#1 hold recorded on the PR clears; ③ seat ACCEPT; ④ the maintainer's per-PR word (BREAKING), asked through 总监决裁; ⑤ mergemainagain before queueing.objectstack-fleet commentedon Nov 5, 2026
domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW
Yield: already claimed.
domain:server#4's Claim 6672100060 is stamped before this seat's Claim 6672101946, so seat 4 holds #29029 and Claim 6672101946 is void. Labels and assignee stay as they are (same account).objectstack-fleet commentedon Nov 5, 2026
domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4
Claim amendment · claim 6672100060 · 2026-11-05T23:24Z
Branch:
claude/project-thread-2l4nno(started atclaude/project-thread-m0wypr@4ba91079fb,origin/main06cccd32merged in)Dev session:
session_016WV4d1ZVVUQY6crnfvCXhg(the card thread this seat opened)Clause-②: yes (narrowing)
Metadata-protocol: none
Correction: the claim's
Clause-②: no/Metadata-protocol: narrowedpair was wrong. The change narrows runtime data writes, not what a metadata author can write, so the lines follow ruling 6573561091 and PR #30672's body and changeset (patch). The contract-review-tier review on the narrowing face is still owed before the queue.Serial: seat 1's competing claim 6672101946 yielded (6672130517).
Everything else in claim 6672100060 stands. PR #30672 is superseded by the PR this branch opens; the seat closes #30672 with a pointer then.
objectstack-fleet commentedon Nov 6, 2026
domain:server#4 · os-elon-musk · session_016WV4d1ZVVUQY6crnfvCXhg
os-dev-report
{ "issue": 29029, "status": "done", "branch": "claude/project-thread-2l4nno", "pr": "https://github.com/objectstack-ai/objectstack/pull/30747", "session": "session_016WV4d1ZVVUQY6crnfvCXhg", "premise_still_valid": true, "summary": "Continuation of the released PR #30672: the branch starts at its released head 4ba91079fb and merges origin/main 06cccd32e1 (merge commit 0e54448624, no conflict, no file of the diff touched by the nine incoming commits); the delta against main is unchanged at 39 files, +1317 / -246. No code was changed. The behaviour of record is os-dev-report 6670586454: a non-system write may name, in a lookup, user or master_detail field, only a record its caller's own read door returns, answered reference_not_found before the record's validation rules; system writes keep their exemption and order; the delegate reads the business units its admin scope covers. New draft PR #30747 carries #30672's body updated to this head, plus the verify-persona fix (Test Core 4/8) and the agent-ceiling seeding fix (Test Core 8/8) in Deviations, @objectstack/verify in the changeset list, and states it supersedes #30672.", "tests": "All on 0e54448624 after a whole-workspace build of the merged tree (turbo run build --concurrency=2, 118/118 tasks, lock VERDICT command-exit 0). @objectstack/objectql: Test Files 557 passed (557), Tests 11168 passed (11168). @objectstack/plugin-security: Test Files 246 passed (246), Tests 5426 passed | 45 skipped (5471). @objectstack/plugin-audit: Test Files 73 passed (73), Tests 1145 passed (1145). @objectstack/verify: Test Files 48 passed (48), Tests 408 passed (408). Merge-touched joint behaviour (the banned user's work hand-over writes owner_id as the caller): core src/security/user-assigned-work.test.ts 12 passed, rest src/user-work-routes.test.ts 7 passed. typecheck (with test programs) objectql / plugin-security / plugin-audit / verify: each lock VERDICT command-exit 0. No ablation this round: no tested behaviour changed; the seven ablations recorded at 75a7c44d96 stand, the rule's source files having changed since only by main's unrelated navigation-contribution hunk in engine.ts.", "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 0e54448624: 97 derived families, each run with its exit code captured before any pipe: 96 exit 0 on the first pass, pnpm check:type-check-debt exit 3 (its closure build queued behind the dogfood battery on the verify lock, 540 s timeout), re-run under the lock after the battery: exit 0 (re-measure OK, 0 ledger entries above their number). --ran: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. Record: /mnt/project-files/server-lane-seat4/29029/gates-ran-0e54448624.txt. CI check-runs on #30747: in_progress at report time (PR opened just before this report).", "dogfood_battery": "0e54448624 · os-verify-lock: VERDICT command-exit 0 (held 27m38s) · Test Files 421 passed | 3 skipped (424) · Tests 3513 passed | 36 skipped (3549) · no failure", "mcp_calls": "5 - reads only: issue_read get x1, issue_read get_comments x1, pull_request_read get x2 (#30672, #30747), pull_request_read get_comments x1; no write tool", "api_writes": "2 - POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create, PR #30747, body read back byte-identical 14260/14260); POST /repos/objectstack-ai/objectstack/issues/29029/comments (post-stamped, this report)", "deviations": [ "No assignee and no review request on #30747: os-dev rule 2 leaves both to the seat at hand-over, and the dispatch allows no GitHub MCP write.", "main moved 18 commits (to 82142ed65c) while the build, suites, gates and dogfood ran; none touches a file of this diff. Not merged again before opening (AGENTS.md multi-agent section 10); the merge before queueing stays owed to the seat's sequence.", "pnpm lint (repo-wide scan) not run locally: CI-owned per os-dev; read from the PR's Lint and Repo Gates check-run.", "The first push answered 'remote rejected (fatal error in commit_refs_rules)'; a plain retry seconds later succeeded. GitHub-side transient, not a 403." ], "open_questions": [], "security_findings": "none", "out_of_scope_findings": [] }Report taken 2026-11-06T00:29Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 6, 2026
security#1 · os-tesla · session_01L9qibvynRbCtMndW8EHNGc
os-dev-report
{ "issue": 29029, "status": "done", "branch": "claude/sec-hardening-r12", "pr": "https://github.com/objectstack-ai/objectstack/pull/31031", "session": "session_01L9qibvynRbCtMndW8EHNGc", "premise_still_valid": true, "summary": "Continuation of #30747 by the security seat on a new branch from 0e54448624: origin/main merged three times (no conflict in any), and the update's reference forgiveness now compares only stored values the caller may read (master_detail, lookup, user; by id and in bulk). A column the caller may edit but not read forgives nothing; a caller who reads the column keeps the forgiveness; system writes unchanged. Two families red after the merges fixed: the system-context census row and the test-source-alias source-reach half on the verify test #30747 edited. Draft PR #31031, Supersedes #30747.", "tests": "head 2ae4d980c2. New: plugin-security reference-forgiveness-readable-column.test.ts 26/26 (real engine + SecurityPlugin), objectql engine-unreadable-reference-target.test.ts 113/113 (new top-level describe). Suites: objectql 560 files / 11314 tests passed; plugin-security 247 files / 5731 passed, 45 skipped; verify 48 / 408 passed. Ablation via scripts/ablation-replace.mjs (comparison handed the rows as stored): plugin-security 6 failed | 20 passed, objectql 6 failed | 107 passed, exactly the new legs; restore blob 6fc9b11c36b0 == HEAD, git diff HEAD empty, both legs; source-aliased, no dist leg.", "gates": "dispatch-gates --commands at 2ae4d980c2: 98 families, stdin /dev/null, exit captured before any pipe: 97 exit 0, check:type-check-debt exit 3 (queued behind this branch's own locked run) then exit 0 re-run alone; --ran with exit codes: 98 derived, 98 run, 0 NOT-MEASURED, 0 UNRUN. check-governed-merges --branch origin/claude/sec-hardening-r12: 0 of 40 governed, 1987 changed lines. Typecheck: turbo typecheck over objectql, plugin-security, verify, plugin-audit and their 37 direct consumers (downstream), 112/112 tasks. PR check-runs at open: Governed Surface Queue Guard success; the rest in_progress.", "dogfood_battery": "2ae4d980c2 · VERDICT command-exit 0 · Test Files 428 passed, 3 skipped (431) · Tests 3560 passed, 41 skipped (3601) · no failure", "mcp_calls": "2 — mcp__github__pull_request_read (get #30747, get_check_runs #31031); reads only", "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft); this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/29029/comments)", "open_questions": [], "security_findings": "withheld", "out_of_scope_findings": ["carrier: security#1 seat · noted in the private report, not filed"] }