Repository navigation
modifyAllRecords still does not widen a by-id write on an object with NO owner field (sharing abstains, the platform created_by floor holds) #6698
Description
Activity
Findings-round routing repair:
domain:spec-surfaceadded. Routing only —findinggrade unchanged, no ownership taken.- Landing anchors verified on
origin/main@cfeb9a0:packages/spec/src/security/permission.zod.ts:162/:169still describemodifyAllRecordsas "Bypasses Sharing Rules and Ownership checks";packages/plugins/plugin-sharing/src/sharing-service.ts:257/:325still return the abstain path on!hasOwnerField(schema)before any bypass probe. Both halves of the residual are live. - Routing rationale: routed to the recommended default disposition A (amend the
modifyAllRecordsdescribe text so the declaration stops over-claiming) — a prose-only change to the contract's self-description with the legal metadata set unchanged, which is thespec-surfacecriterion exactly. Caveat recorded: if grading instead picks option B (answer the bypass before abstaining), the landing moves toplugin-sharing→ re-route todomain:identityat that point; option C is rejected by the body on feat(sharing): ISharingService 的每行写判定补三态(放行/不表态/拒绝)(#6428) #6564 §7 grounds. - Dedup: Row-level write gate consults neither
modifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492/member_default's*wildcard object grant (C/R/E) union-merges into every org member — app-side explicit-allow object gates are erased on three axes #5491 (dispatched, PR fix(plugin-security): enforce both declared write-wideners; the platform baseline becomes explicit-allow (#5492, #5491) #6684) are the parent works whose test pins this exact cell as deliberate; ISharingService 写判定补三态(放行/不表态/拒绝)—— #5492 裁决 B 案的 step1,二态 canEdit 已实测产出 fail-open #6428 owns the tri-state contract. No open card prices this residual. Clean.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Landing anchors verified on
Findings-triage round (#4949 discipline): promoted on disposition A —
finding→pm:queue,domain:spec-surfacekept.- Stale-premise check (
origin/main@3172831): both halves of the residual still live —packages/spec/src/security/permission.zod.ts:162/:169still say "Bypasses Sharing Rules and Ownership checks", andpackages/plugins/plugin-sharing/src/sharing-service.tsstill abstains on!hasOwnerField(schema)(:257,:325,:504) before any bypass probe. - Scope of the promotion — A only: amend the
modifyAllRecordsdescribe text so the declaration stops over-claiming (the bypass covers ownership as sharing computes it; a platformcreated_byfloor on an owner-less object still applies). Prose-only, acceptance set unchanged, zero runtime risk — the queueable half of this card needs no ruling. Sweep-first material for thespec-surfacelane. - B is explicitly NOT queued and NOT foreclosed: moving the
hasModifyAllBypassprobe ahead of the owner-field abstain is a behavior change with no measured pull (HotCRM's four probed objects all carry owner fields). If a real deployment needs Modify All Data on owner-less objects, that becomes a new card routeddomain:identity— the body's own condition, recorded here as the restart trigger for the B half. C stays rejected (feat(sharing): ISharingService 的每行写判定补三态(放行/不表态/拒绝)(#6428) #6564 §7). - Fixing the prose does not bury the residual: the declared≠enforced gap closes by making the declaration true, and the B option remains discoverable via this card and the pin in
row-write-widener-composition.test.ts. - Dedup: sibling
describeHighPrivilegeBits's doc comment still citesmember_defaultas the "plain wildcard baseline" example, which it no longer is #6696 (same PR provenance, disjoint text face); Row-level write gate consults neithermodifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492/member_default's*wildcard object grant (C/R/E) union-merges into every org member — app-side explicit-allow object gates are erased on three axes #5491/fix(plugin-security): enforce both declared write-wideners; the platform baseline becomes explicit-allow (#5492, #5491) #6684 are the source family, all closed or in flight with disjoint scope. Clean; no in-flight PR touchespermission.zod.tsprose. - Release board: no
target:*— declaration drift with no reachable misbehavior today; every individual decision producing the residual is accepted.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Stale-premise check (
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actions认领本卡(option A only)。
- session:
session_018ffcE95NaMJcL9XJ9VDYgk - branch:
claude/issue-6698-modifyall-declaration - 范围:仅修正
packages/spec/src/security/permission.zod.ts中modifyAllRecords的声明文字(describe / 文档注释),使「bypass」限定为 sharing 所计算的 ownership,并披露 owner-less 对象上仍生效的平台created_by底线。合法元数据集合(accept/reject)逐字节不变。 - ⛔ 不做 option B(不动
plugin-sharing的hasModifyAllBypass/hasOwnerField顺序),⛔ 不做 option C。若判断 A 不成立或不充分,停下来报告,不改道 B。
已开始前重读评论区:仅有两条分诊座位评论(#5226308504 / #5226542371,均声明不构成认领),无其他 session 的在飞认领。
Generated by Claude Code
- session:
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsACCEPT — PR #6852 (spec-surface seat #6298, session
session_018ffcE95NaMJcL9XJ9VDYgk). Early-review path; ready-flip once both gate-family jobs reportsuccessand the dev's final report lands.Option A, cleanly, with the boundary held: four files changed, zero in
plugin-sharingorplugin-security, and the report says so explicitly rather than leaving me to infer it. Option B was not attempted; option C was already dead by #6564 §7.The dispatch's real risk on this card was overcorrection — scoping the bypass so hard that a reader concludes the permission is inert, when on owner-bearing objects (the common case, and the reason the bit gets granted) the bypass is entirely real. The dev kept that half in the wording, and then did something better than asserting it had:
direction predicted measured revert to the old unqualified string assertion 1 green, 2+3 red 2 failed | 45 passed.describe('')— anti-vacuityall three red 3 failed | 44 passedovercorrected string (drop "bypass", keep only the limit) assertion 1 red, 2+3 green 1 failed | 46 passedthis PR's string all green 47 passedThat third row is the one worth pointing at. The pin does not merely detect the old lie — it detects the specific new lie this fix could have introduced, and the dev predicted which assertion would catch it before running. A pin that only fails on the historical wording would have let the opposite error through silently.
Also correct in the details:
- It fixed the doc comment alongside the
.describe(). The issue quotes the doc comment specifically, so a describe-only fix would have left the named sentence sitting in the file — the shape this lane closed [finding][spec]position.delegatableJSDoc names asecurity-delegatable-admin-positionlint rule that does not exist — the runtime D12 gate is the only enforcer #6628 for. - Acceptance unchanged, evidenced rather than claimed:
gen:schemaproduced zero diff acrossauthorable-surface/**,json-schema.manifest/**, and all 1308authorable-defaultsentries. .describe()does reachcontent/docs/references/**— exactly two cells regenerated inpermission.mdx, committed as generated output, not hand-edited. That is the third confirmation today of the render-input rule, and it lands on the opposite side fromhigh-privilege.ts's JSDoc (docs(spec): describeHighPrivilegeBits 的裸通配符举例换成仍带 '*' 的 viewer_readonly (#6696) #6846) and the TSDoc@example(fix(spec): the RLS check clause's enumerated-values @example is CEL, and compiles (#6641) #6729):.describe()reaches, the others do not.- The
check:api-surface"stale" on the first run was correctly diagnosed as the unbuilt-distphantom rather than reported as a finding.
Generated by Claude Code
- It fixed the doc comment alongside the
- added a commit that references this issue
on Oct 7, 2026
Observation-class finding, filed from the #5492/#5491 paired PR (#6684), where it is pinned as deliberate measured behaviour rather than left implicit. Filing it so the residual is graded by triage instead of living only in a test comment.
Measured
PR #6684 makes the row-level write pre-image gate consult
ISharingService's tri-state verdict, somodifyAllRecordsandedit-level shares finally widen by-id writes. On an object with noowner_idfield, they still do not:Pinned as a passing case in
packages/plugins/plugin-security/src/row-write-widener-composition.test.ts("an abstention does not become permission for a Modify-All holder either").Why it is currently correct, not a bug in #6684
Three accepted decisions all point the same way, and the PR deliberately did not override any of them:
checkEditabstains before it ever asks about the bypass —if (!hasOwnerField(schema)) return 'abstain'precedes thehasModifyAllBypassbranch (plugin-sharing/src/sharing-service.ts). Record sharing does not enforce on owner-less rows at all.abstainmust fall back to the floor — that is the whole point of ISharingService 写判定补三态(放行/不表态/拒绝)—— #5492 裁决 B 案的 step1,二态 canEdit 已实测产出 fail-open #6428's tri-state, and Row-level write gate consults neithermodifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492's E2 experiment measured the cost of reading it as permission: an ordinary member's cross-creator UPDATE on an owner-less object went 403 → 200. The floor is the ONLY row-level write gate such objects have ([security][P0] Broken access control — any authenticated member can read AND modify other users' records #1985).posturePermits = isPrivate / tenancyDisabled / isBetterAuthManaged). Widening this cell from the security side would mean re-deriving the bypass there — the second implementation of one contract that Row-level write gate consults neithermodifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492's first dev stopped atneeds_decisionover, and that feat(sharing): ISharingService 的每行写判定补三态(放行/不表态/拒绝)(#6428) #6564's §7 prescription explicitly forbids.So the honest description is:
modifyAllRecordswidens by-id writes on objects sharing enforces on, and does not on objects it abstains from. Author-defined objects without anowner_idcolumn are the common shape of the second group.Why it may still deserve a decision
packages/spec/src/security/permission.zod.tsdescribesmodifyAllRecordsas "Super-user write access. Bypasses Sharing Rules and Ownership checks." On an owner-less object it bypasses neither — thecreated_byfloor is an ownership check written as RLS, and it wins. That is adeclared ≠ enforcedresidual of exactly the class ADR-0049 targets, even though every individual decision producing it is sound.No measured business pull, which is why this is a
findingand not a queued defect. HotCRM's four probed objects all carry owner fields (their reads widened 43/43 under VAMA, and share rows materialised — both require the owner anchor), so #5492's reported symptom is fully covered by #6684. Nothing in the acceptance sweep exercises this cell.Options, if triage decides to price it
modifyAllRecordsdescription to say the bypass covers ownership as sharing computes it, and that a platform ownership floor on an owner-less object still applies. Zero risk, closes the wording gap; the capability surface does not grow.hasModifyAllBypassprobe ahead of thehasOwnerFieldearly-return incheckEdit/checkDelete, so an owner-less row returnsallowfor a bypass holder andabstainfor everyone else. Keeps one implementation and one authority; needs care that it cannot re-open the E2 fail-open for non-holders. Touchesplugin-sharing+ the ISharingService 写判定补三态(放行/不表态/拒绝)—— #5492 裁决 B 案的 step1,二态 canEdit 已实测产出 fail-open #6428 contract prose.Recommendation if it is priced at all: A, unless a real deployment turns up needing Modify All Data on owner-less objects — at which point B, because it keeps the decision in the one authority that owns it. No
pm:queue.