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
security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079
Was blocked by #20995. That block is spent: #20995 closed when PR #21051 merged, as triage recorded in 5925669762. Under ruling E this card has no blocker; its one serial constraint is #21180, on a shared dogfood file.
Filing gate: ① a product defect with a measured reach:, under the possible-data-disclosure exception. This is the plugin-security half that #21061's emergency triage (5924543711) split off and asked this seat to file. ⚠️ Disclosure discipline, the same as #21061's: doors, caller classes, files, functions, codes and statuses only. Every reading is private.
reach:measured by #20995's dev during that card's reach step (os-dev-report5924254306 on #20995, out_of_scope_findings F1). The readings are in the dev's private scratch space, and this seat has read them. Filed by the domain:services execution seat (#6021, session_01XY5uCwTjZj7884yYtyur4H). ⛔ Not a claim.
What was measured (by class)
The caller class: a non-system caller that carries a principal (the guest envelope, a picker context, or a user id) and whose permission-set resolution returns no set. Measured members:
an unauthenticated request carried as the guest envelope;
a picker context on a deployment with no guest set registered;
a signed-in user on an embedder that disables the baseline (a constructor option; objectstack serve never passes it).
What it gets: on the measured boots, object-level admission to objects no set grants. The row-scope skip is read from source (below), ⛔ not measured.
The positions (source read at origin/main, cited by #21061's grade)
In packages/plugins/plugin-security/src/security-plugin.ts:
Object admission:
the engine middleware's step 2 CRUD gate runs only under a non-empty set list;
canReadObject returns true for an empty set list;
canWriteObject (the organization wall alone) and canExport carry a zero-set arm too.
Row scope:
for an empty set list, computeLayeredRlsFilter collects no policy, so its first layer compiles to no filter;
getReadFilter then returns the sharing predicate alone, which constrains only objects whose sharing model is private;
step 2.6's depth stash and checkAuthoredRowWrite ('abstain') also stand down for an empty set list.
ADR-0056 D2 says an unauthenticated principal gets the deny baseline, not "no checks". ADR-0090 D9 says a guest holds the guest position and nothing else.
Measure first, privately: which doors and callers this class reaches on a stock boot and on a baseline-disabled embedder, and what each known consequence below costs.
One answer per layer: for a non-system caller that carries a principal, an empty set list is the deny baseline.
At object admission, step 2's guard and the zero-set arms of canReadObject, canWriteObject and canExport read that one answer.
At the row scope, getReadFilter's zero-set path answers the deny sentinel, as its failure paths already do.
⛔ No door-specific copy of the rule. ⛔ The principal-less context (no positions, no sets, no user id) is out of scope; ADR-0096 stages it separately.
Known consequences, to measure and state in the PR:
Pins: a zero-set caller of each measured member is refused object admission and gets the deny scope, with a signed-in member resolving a set as the control. No pin title states a request.
Why Blocked-by: #20995: PR #21051 (#20995) holds security-plugin.ts and is in the merge queue. This card also changes every door's zero-set answer, so it measures on top of #20995's merge.
Reader who acts
Triage (grade and route), then the domain:services seat. plugin-security is domain:services.
Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:
Ruled: 5933054144 · letter E · 2026-10-01T14:02Z
Was blocked by #20995. That block is spent: #20995 closed when PR #21051 merged, as triage recorded in 5925669762. Under ruling E this card has no blocker; its one serial constraint is #21180, on a shared dogfood file.
Filing gate: ① a product defect with a measured⚠️ Disclosure discipline, the same as #21061's: doors, caller classes, files, functions, codes and statuses only. Every reading is private.
reach:, under the possible-data-disclosure exception. This is theplugin-securityhalf that #21061's emergency triage (5924543711) split off and asked this seat to file.reach:measured by #20995's dev during that card's reach step (os-dev-report5924254306on #20995,out_of_scope_findingsF1). The readings are in the dev's private scratch space, and this seat has read them. Filed by thedomain:servicesexecution seat (#6021,session_01XY5uCwTjZj7884yYtyur4H). ⛔ Not a claim.What was measured (by class)
objectstack servenever passes it).200for objects no set names, the record and list doors included.The positions (source read at
origin/main, cited by #21061's grade)In
packages/plugins/plugin-security/src/security-plugin.ts:canReadObjectreturnstruefor an empty set list;canWriteObject(the organization wall alone) andcanExportcarry a zero-set arm too.computeLayeredRlsFiltercollects no policy, so its first layer compiles to no filter;getReadFilterthen returns the sharing predicate alone, which constrains only objects whose sharing model is private;checkAuthoredRowWrite('abstain') also stand down for an empty set list.ADR-0056 D2 says an unauthenticated principal gets the deny baseline, not "no checks". ADR-0090 D9 says a guest holds the
guestposition and nothing else.Direction (from #21061's grade; ⛔ not a ruling)
canReadObject,canWriteObjectandcanExportread that one answer.getReadFilter's zero-set path answers the deny sentinel, as its failure paths already do.ISecurityServicedocblocks inpackages/spec/src/contracts/security-service.tsthat state the zero-set answers belong todomain:spec. A change to them is a contract-lane edit, and the field-level zero-set answer is security(spec): a capability-gated field (requiredPermissions, no maskingRule) is served stored to a caller who resolves no permission set, while explain reports it hidden; narrowing it moves getReadableFields' contract answer #21063's.Why
Blocked-by: #20995: PR #21051 (#20995) holdssecurity-plugin.tsand is in the merge queue. This card also changes every door's zero-set answer, so it measures on top of #20995's merge.Reader who acts
Triage (grade and route), then the
domain:servicesseat.plugin-securityisdomain:services.Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:access: privatecredential-bearing identity objects that the sets themselves declare deny-by-design #20027, member_default gives every authenticated member read on sys_scim_user / sys_scim_group with no row policy and no tenant column: one organization's IdP-provisioned users (emails, names) are readable from any other #20001 and/auth/me/permissionsand/me/appsstill apply the baseline only when the caller resolves to ZERO sets — the ADR-0090 D5 fallback cliff, one plane over from where it was abolished #7608 (closed) are other mechanisms: a shipped set's blanket on identity objects, a baseline set's grant, and the plane where the baseline applies.readScopebesideviewAllRecords: true, never reads it, and emits no diagnostic — the declaration materialises and a capability census counts it as coverage #16870, analytics: read-scope-sql's ruled $not-over-$in-empty residue has no open card — and #13640 turned it into an echo-vs-execution disagreement on the ObjectQL strategy #13926, [permissions] 行级读可见范围无法按业务字段收窄:viewAllRecords 全有/全无两档之间缺共享规则 #4376, finding: after ADR-0106, a restricted caller's GET → edit → PUT of an object schema DELETES the fields that were masked out of their read #6603 and permission-set backfill (ADR-0094 D4) 现在 100% 失败:行里的active存储列喂进了 #4001 之后严格化的 permission spec #4669 (closed) are unrelated permission-set or read-scope defects.plugin-security.maskingRule's describe and the result masker mask it — which one a public door serves is not measured #20995 (this class's field masking, PR fix(plugin-security)!: a caller who resolves no permission set is served a masked field masked and may not query on it #21051), security(spec): a capability-gated field (requiredPermissions, no maskingRule) is served stored to a caller who resolves no permission set, while explain reports it hidden; narrowing it moves getReadableFields' contract answer #21063 (this class's capability-gated fields) and security(forms): two public-form doors meet a field masking rule wrongly — the submit read-back serves masked fields stored to an anonymous submitter, and a picker whose first display field is masked answers 403 to every caller it applies to #21062 (the picker door).Dedupe words:
zero permission sets object admission·empty set list deny baseline·no-set caller row scope·canReadObject zero-set armGenerated by Claude Code