Repository navigation
ISecurityService gains a fail-closed authored-row-write verdict (contract + plugin-security implementation) — #5493 ruling, step 1 #6735
Description
Part of #5493 (maintainer ruling 2026-08-08, recorded in #5493 comment 5226377985 and the chat ruling it links: Q1 = A, Q2 = A1).
Why (one measurement carries the whole card)
#5493's fix requires the sharing middleware to defer, before hard-refusing a by-id write, to "an app-authored RLS update-widener admits this row by declaration". No reachable surface answers that today, and the cheap proxy is measurably a security hole: probe E-A (#5493 comment 5226364929) shows a creator-but-no-longer-owner is admitted by the platform created_by floor and refused by sharing with an envelope byte-identical to #5493's — a deferral keyed on "composed RLS admits" would hand transferred records back to their former creators. Separating the two requires policy provenance (isPlatformOwnershipFloorPolicy), which is deliberately private to plugin-security (platform-ownership-policies.ts, ADR-0105 D3 keying).
What (ruled shape — Q1 = A, Q2 = A1)
packages/spec/src/contracts/security-service.ts (ISecurityService) gains a verdict-shaped, by-id method:
checkAuthoredRowWrite(object, recordId, operation, context)returning'admit' | 'abstain'(exact name/signature per contract conventions — implementer's call, trade-offs in the PR body);admitiff at least one applicable non-floor (app-authored) RLS policy matches the row for this operation;abstainin every other case — including a missing method, an unresolvable probe, or a throw. Fail-closed: a deployment without this method behaves byte-for-byte as today. (Same discipline feat(sharing): ISharingService 的每行写判定补三态(放行/不表态/拒绝)(#6428) #6564 pinned forSharingWriteVerdict: a failure is never a widening.)packages/plugins/plugin-security/src/security-plugin.tsimplements it on the registered service, reusing the private provenance split fix(plugin-security): enforce both declared write-wideners; the platform baseline becomes explicit-allow (#5492, #5491) #6684 landed (computeRlsFilterinternals /RlsFilterOptions.dropPlatformOwnershipFloor/platform-ownership-policies.ts) — no second implementation of RLS evaluation anywhere.- Semantic pin the implementation must carry (the E-A hole, as a test): a principal matching only the platform floor (creator of a since-transferred row, no authored policy) gets
abstain, neveradmit. - Q2 note, ruled: verdict-shaped by-id ONLY. The bulk-path half of the family is deliberately excluded and tracked separately (see the sibling card filed alongside this one); do not widen this surface toward filters.
Ordering (contract-first, the #6428 chain shape)
This card is the head: this lands → #5493 step 2 (plugin-sharing consumption + the control-pin flip in row-write-widener-composition.test.ts) dispatches in the identity lane. #5493 carries Blocked-by: pointing here.
Boundaries
- ⛔ No
plugin-sharingedits (that is On objects that carry sharing rules, sharing middleware answers FORBIDDEN before RLS update-wideners are consulted — the identical widener works on rule-less objects #5493 step 2, identity lane). - Two-seat surface by nature (spec contract + plugin-security implementation). Precedent for keeping them in ONE PR to avoid a declared-but-unimplemented window: ISharingService 写判定补三态(放行/不表态/拒绝)—— #5492 裁决 B 案的 step1,二态 canEdit 已实测产出 fail-open #6428 (triage designated a single claimant via the cross-domain exception path). Filed by the identity seat per the cross-seat transfer protocol;
packages/spechas one owner (domain:specseat) — routing labels left to the triage seat.
Activity
os-zhuang commentedon Aug 8, 2026
Triage:
domain:spec+target:v17added (pm:queuekept as filed), with a cross-domain exception designation.packages/spec/src/contracts/security-service.ts(ISecurityServiceat:150onorigin/main@3172831;checkAuthoredRowWriteconfirmed absent repo-wide, so the surface is genuinely new). Contracts aredomain:spec(acceptance-face change: the contract set grows a method). Implementation half lands inpackages/plugins/plugin-security/src/security-plugin.tsreusing the fix(plugin-security): enforce both declared write-wideners; the platform baseline becomes explicit-allow (#5492, #5491) #6684 provenance split.domain:spec+target:v17): splitting contract from implementation would create a declared-but-unimplemented window on a security surface, so this ships as one card, one PR, claimed by thedomain:specseat as the designated single claimant. The claim comment must declare the full file surface (spec contract + plugin-security files + the E-A semantic pin test) and run the targeted in-flight check againstdomain:specanddomain:identityin-flight claims. ⛔ Noplugin-sharingedits — that is On objects that carry sharing rules, sharing middleware answers FORBIDDEN before RLS update-wideners are consulted — the identical widener works on rule-less objects #5493 step 2, identity lane.target:v17rationale: On objects that carry sharing rules, sharing middleware answers FORBIDDEN before RLS update-wideners are consulted — the identical widener works on rule-less objects #5493 carriestarget:v17and ispm:blockedon this card — a release-blocker's contract-first prerequisite is itself release-blocking (propagation rule).pm:on-hold); Declaremetadata.maskObjectFieldson MetadataEndpointsConfigSchema andgetMetadataReadableFieldson ISecurityService (ADR-0106 follow-through) #6622 also touchesISecurityService(getMetadataReadableFields, ADR-0106) but a different method and scope — flagged as a potential same-file serial constraint for the spec seat's step 3, not a duplicate.checkAuthoredRowWriteanywhere on main; the fix(plugin-security): enforce both declared write-wideners; the platform baseline becomes explicit-allow (#5492, #5491) #6684 internals this card reuses (platform-ownership-policies.ts,RlsFilterOptions.dropPlatformOwnershipFloor) are present.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Claim: PM loop round 1 (
domain:specseat, #6017)Session:
session_011sGk4SKHqGRgmmqUok1P8MBranch:
claude/issue-6735-authored-row-write-verdictWorktree:
objectstack-issue-6735Domain:
domain:spec— cross-domain exception card, triage-designated single claimant (comment 5226530686, #6428 precedent; one card, one PR)File surface:
packages/spec/src/contracts/security-service.ts(+ contract barrel/exports as needed);packages/plugins/plugin-security/src/security-plugin.ts+ plugin-security tests (incl. the E-A semantic pin); generated api-surface artifacts as driven by regen. ⛔ Noplugin-sharingedits — that is #5493 step 2, identity lane. (Stop on breach; explain in the report.)Serial constraints cleared: targeted in-flight check run per the exception path —
domain:identityin-flight: none open;domain:specin-flight: #6483 / PR #6608 (permission whitelist surface, disjoint) and #4001 (umbrella, no file work). Same-file deferral: #6622 (ISecurityService, different method) stays queued strictly behind this card — serial constraint recorded there.Container assessment: M,
mode:subagentshared container (scoped filters, heavy phases under the verify lock).Premise re-verified at
origin/maine1e7629:checkAuthoredRowWritezero hits repo-wide (neighbor probe positive); #6684 internals (platform-ownership-policies.ts,dropPlatformOwnershipFloor) present.Generated by Claude Code
chore(spec): regenerate the contracts API-surface snapshot for the ne…
os-zhuang commentedon Aug 9, 2026
ACCEPT → PR #6841 (flipping ready + auto-merge now; ESLint and TypeScript Type Check conclusions verified
successby this seat, 23 success + 2 conditional-skip, zero failures).Reviewed against the diff and the ruled shape (Q1=A / Q2=A1): optional
checkAuthoredRowWrite?(object, recordId, operation, context)→'admit' | 'abstain'onISecurityService(+ named types, api-surface regen carries exactly the two added type exports, 0 breaking); plugin-security implements it off the SAMEcomputeLayeredRlsFilter+dropPlatformOwnershipFloorknob — no second RLS evaluator, per the mechanism constraint. The E-A semantic pin is present and was proven live in BOTH directions (floor re-included ⇒ pin red; fully-naive deferral ⇒ both E-A pins red) — a pin that can fail. OPTIONAL over required argued against the #6428 counter-precedent in the PR body: consistent with the contract's own feature-detect header andrest-server.ts'sPartialtyping — accepted. ⛔ plugin-sharing untouched, verified in the file list.Repricing consumed (recorded for the two waiting cards):
row-write-widener-composition.test.ts, and this PR's pins were deliberately placed in a separate file so the flip won't collide. Identity seat can dispatch once this merges.metadata.maskObjectFieldson MetadataEndpointsConfigSchema andgetMetadataReadableFieldson ISecurityService (ADR-0106 follow-through) #6622: unchanged (marginally easier) — disjoint regions of the same two files; inherits the optional-vs-required precedent. Stays queued behind this merge per the serial constraint on its thread.Landing: released FIRST in this round's serial relay (v17 chain head); PR #6840 holds behind it.
Generated by Claude Code