Repository navigation
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
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsMaintainer queue-jump, relayed: this card goes first in its lanes. It is not claimable by
domain:cli, so the seat routes itdomain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-10-01T04:31Z · ⛔ not a claim · ⛔ no labels written ·⚠️ classes and positions only- Provenance:
- who: the maintainer;
- what, verbatim: 「21063 插队」;
- where: the
domain:cliseat's session chat, the message right before this comment.
- Why this seat does not claim it:
- The card is not yet graded: it has no
domain:*and nopm:*state. - By its own text its first step is a ruling on a published contract answer (
getReadableFields/getMetadataReadableFieldsinpackages/spec/src/contracts/security-service.ts), which isdomain:spec's. - The implementation lands in
plugin-security, which isdomain:services. - An execution seat claims only in its own lane.
- The card is not yet graded: it has no
- For triage, one adjacency to weigh in the grade: security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061's emergency grade
5924543711split its direction 2 to adomain:servicescard: for a non-system caller with a principal, "no set resolved" becomes the deny baseline at object admission and the row scope. That card also names theISecurityServicezero-set docblocks asdomain:spec's.- If that half lands, this caller class is no longer admitted to the object.
- The field-level answer here may then be subsumed, or reduce to the contract-docblock correction.
- Triage should say whether this card stands alone, folds into that split, or orders after it.
- security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061's door half (
domain:cli,priority:p0) was claimed by this seat (5924762402) and is in development.
Generated by Claude Code
- Provenance:
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·security·priority:p0·domain:spec·area:access·pm:queue. The maintainer's queue-jump (「21063 插队」). Direction: the contract's zero-set answer narrows, and the field's describe is deliveredTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T04:53Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only (the family's disclosure discipline).Why p0. The maintainer ordered this card to jump the queue (relayed in
5924780893, thedomain:cliseat's note, verbatim 「21063 插队」), andpriority:p0is how the dispatcher orders it first. On the merits it is #20995's class, served a capability-gated field stored while explain reports it hidden.Routing.
domain:specis a seam, dispatched vertically, as #20935 was. The first act changes a published contract answer:getReadableFields/getMetadataReadableFieldsinpackages/spec/src/contracts/security-service.ts. The same claim declaresplugin-securityas its cross-lane surface.The ruling, triage's. The safe side follows the protocol: the field's describe ("mask on read, deny on write") is the protocol for that field.
- The contract's zero-set answer narrows: for a caller who resolves no permission set, a capability-gated field is not readable.
plugin-securityfolds the gate into the zero-set stand-in, as it already does for masked fields since PR fix(plugin-security)!: a caller who resolves no permission set is served a masked field masked and may not query on it #21051.- Explain and the record doors then agree.
- ⛔ No second derivation, and ⛔ the full-set answer is never widened for any other class.
- It is a narrowing of a published answer, so
Clause-②: yes, with an ADR-0087 disposition if the contract is versioned.
Pins (private evidence stays private): for the zero-set class, a capability-gated field is hidden on the record doors and in explain. A holder of the capability sees it (the control).
Generated by Claude Code
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingpriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVPand removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (maintainer queue-jump 「21063 插队」, relayed at
5924780893) · 2026-10-01T05:06Z
Session:session_017VaLJnYwhPsanVCe9dMCJU
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21063-zero-set-capability-fold
Worktree:objectstack-issue-21063
Domain:domain:spec(vertical seam, as triage5925000390routes it)
Seat:domain:spec#2(seat post #18549)
File surface (⚠️ classes and positions only, per the card's disclosure discipline):packages/spec/src/contracts/security-service.ts: the zero-set answer in thegetReadableFields/getMetadataReadableFieldsdocblocks narrows. A capability-gated field is not readable for a caller who resolves no permission set. The stale "the middleware skips its whole field gate" sentence is corrected.packages/plugins/plugin-security/src/**(domain:services, a cross-lane surface): the zero-set stand-in folds the capability gate, as it folds masking since PR fix(plugin-security)!: a caller who resolves no permission set is served a masked field masked and may not query on it #21051. Plus its tests.- An ADR-0087 entry and the regenerated
migrations/registry.ts, only if the registration gate asks. - Changesets for
@objectstack/specand@objectstack/plugin-security.
⛔ Not object admission or row scope for this class (#21061's direction 2, the
domain:servicessplit). ⛔ Not the REST or analytics door (#21061,domain:cli). ⛔ Not the public-form doors (#21062). Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates--tier: no path-derived mandate; a published contract answer narrows, so the at-tier contract review is owed before enqueue).
Clause-②: yes (narrowing: a published contract answer narrows for one caller class; no other class's answer moves)
Thread-read: 5925000390
Serial constraints cleared: read at this stamp againstorigin/main. One hold: PR #21051 (#20995,domain:services, ACCEPTed, in the merge queue since 03:59Z) editssecurity-plugin.ts's zero-set stand-in, the region this card must edit. The dev is dispatched the moment it lands, and builds on it. Nothing else in flight touches this surface:- Claim security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061 names
security-plugin.tsonly as outside its surface. - Claim plugin-audit: sys_activity has no parent-record read gate, so any object-level read opens every activity row in the environment. Add the same read filter sys_comment has, keeping rows whose parent record the caller can read #20833 bars
plugin-security. - No other open PR touches
security-service.ts. - The
domain:servicessplit of security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061 direction 2 (not yet filed) shares the zero-set stand-in, so it serialises after this card or folds with it, per its own claim's fold-or-serial answer.
The
domain:servicesseat is notified on its seat post.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21063,
"status": "done",
"branch": "claude/issue-21063-zero-set-capability-fold",
"pr": "#21134",
"session": "session_017VaLJnYwhPsanVCe9dMCJU (subagent run; the parent's harness-stamped id)",
"premise_still_valid": true,
"summary": "Implemented triage's ruling 5925000390 as written. The zero-set stand-in (resolveCallerPosture, from PR #21051) now carries the posture's per-field capability contract (fieldRequiredPermissions). Every reader already folds it with foldFieldRequiredPermissions, the ADR-0066 D3 helper the full-set path uses, so there is no second derivation. For a non-system caller who resolves no permission set, a field that declares requiredPermissions and no masking rule is now not served on the record doors (step 4), matching explain, which already reported it hidden. Queries naming the field are refused 403 (2.9, 2.5b), and getReadableFields, getMetadataReadableFields (when the fallback resolves nothing) and getQueryableFields leave it out. H3 measured that the stand-in also feeds getWritableFields, whose contract is the exact complement of the step 2.5 write gate. Step 2.5 was guarded by a resolved set, so two load-bearing edits in the same file keep the projections and enforcement one answer. First, step 2.5 is ungated, as 2.5a and 2.5b have been since PR #21051: for this class it refuses only capability-gated fields, which is 'deny on write'. Its verdict is one helper, computeForbiddenFieldWrites, replacing the middleware's and canWriteObject's two spellings. Second, canWriteObject (the write-preview probe) gains a field arm for this class when a payload is supplied, failing closed on an unreadable posture. The object-level capability contract is NOT carried, so object admission for this class is unchanged. The spec contract docblocks narrow: getReadableFields states the zero-set answer, getMetadataReadableFields' 'skips its whole field gate' sentence now says it skips the permission-set grant gates, and getWritableFields names requiredPermissions as part of its answer. PR #21101 landed during the work, so main was merged (f692a17) and the pins, suite, generated check and gate union were re-run there. My edits touch neither the publicFormGrant region nor step 4's body; the public-form read-back now reads the same stand-in, which is noted in the PR. Two changesets (plugin-security, spec), each minor with a bang, BREAKING, Clause-②: yes (narrowing), ADR-0087 not-required (no-migration-prescription).",
"tests": "Pins: packages/plugins/plugin-security/src/zero-set-capability-fold.test.ts (new; the 3 zero-set ways, each asserting the zero-set premise first; per case, record door vs explain, read projections, query projection vs both query guards in 4 positions, and write gate vs getWritableFields vs canWriteObject field for field; controls: a capability holder, a set without the capability, and the principal-less boundary). RED on the pins commit d49cc26: 'Tests 12 failed | 8 passed (20)'. The 12 are exactly the class cases. H4 before the fix: door gatedServed true while explainHides true. GREEN after the fix, 20/20. get-writable-fields.test.ts: the one zero-set case updated to expect the capability-gated field excluded, with middleware agreement. ABLATION at 65d1da2 via scripts/ablation-replace.mjs, wrapped. On disk: anchor 1->0, injected marker 0->1, blob changed. Restored by git checkout HEAD -- on the absolute path inside an EXIT/INT/TERM trap, proven by blob 4d142d0254b7 == HEAD and an empty git diff HEAD. The pins import plugin source relatively, so there is no dist on the resolution path and no dist preflight applies. A1 (stand-in field contract emptied): 12 failed | 8 passed, the same 12 as the red run. A2 (step 2.5 re-gated on a resolved set): 3 failed | 17 passed ('insert naming gated: expected { admitted: true } to match { admitted: false … }'). A3 (canWriteObject zero-set field arm off): 3 failed | 17 passed ('canWriteObject: insert naming gated: expected true to be false'); two earlier A3 attempts returned 99 queue-timeout, read as NOT MEASURED, then measured. FULL @objectstack/plugin-security at f692a17: 'Test Files 154 passed (154) / Tests 3321 passed | 23 skipped (3344)', VERDICT command-exit 0 (pre-merge at 8767201: 153 files, 3306 passed, 23 skipped). Typecheck at f692a17: exit 0, 'check:test-typecheck: OK'. Spec: build + check:generated '✓ All 15 generated artifacts are up to date' (at 6c8171f and again at f692a17). src/contracts: 45 files, 434 passed. CONSUMERS at 65d1da2, after turbo builds of the upstream closures (--filter='@objectstack/rest^...' and the same suffix form for each; upstream direction): @objectstack/rest whole suite in 2 shards, 130+129 files, 2603+2427 passed, 67+76 skipped; @objectstack/service-analytics 154 files, 3498 passed, 10 skipped; @objectstack/plugin-approvals 52 files, 804 passed; @objectstack/metadata-core 16 files, 298 passed; @objectstack/objectql's 4 suites that boot plugin-security, 34 passed (objectql reads no projection, only canWriteObject via the write-gate probe, whose cross-package seam is write-preview-field-gate-parity.test.ts, green in the full plugin-security run). LINT at f692a17: eslint --no-inline-config --format json on the 4 touched TS files gives 4 linted, 0 errors, 0 warnings. Population: eslint.config.mjs '/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'. Invariance: the config enables no type-aware linting (its own text), so the verdict on untouched files cannot move. NOT MEASURED: a real-boot reading of the record and explain doors (reason: it would rebuild every package downstream of the spec docblock change; the doors read the step 4 the pins drive).",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — each one repository_dispatch through the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, draft (PR #21134; read-back 11140 bytes sent, 11140 stored, identical; run 36838514956); (2) label-write --assign os-bill, POST /repos//issues/21134/assignees (read-back MATCHES; run 36838583890); (3) this os-dev-report comment, POST /repos//issues/21063/comments. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the domain:services split of #21061 direction 2 · noted, not filed · Asked with NO payload, canWriteObject still admits a caller who resolves no set on an unreadable object posture, while the middleware refuses it (since PR #21051). That is object admission for this class, outside this card's surface. No reach: the write-preview probe always hands canWriteObject an array of submitted rows (objectql engine.ts, submittedRows), and with a payload this PR's field arm now fails closed there. Also in the PR's Acceptance notes.",
"carrier: the domain:services seat (owner of the public-form doors, PR #21101) · noted, not filed · The public-form read-back reads resolveCallerPosture, so a capability-gated field is now also left out of the record echoed to a submitter who resolves no set. This follows from the one call; it is not pinned at that door. Also in the PR's Acceptance notes."
],
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: 2 declared-breaking changeset(s), each carrying an ADR-0087 disposition. (both [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription))",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ This diff introduces nomajorbump.",
"pnpm --filter @objectstack/plugin-security test :: exit 0 @ f692a17 :: Test Files 154 passed (154) / Tests 3321 passed | 23 skipped (3344)",
"pnpm --filter @objectstack/plugin-security typecheck :: exit 0 @ f692a17 :: check:test-typecheck: OK",
"pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated :: exit 0 :: ✓ All 15 generated artifacts are up to date (re-run check:generated @ f692a17 exit 0)",
"node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack @ f692a17 :: 87 commands; each run with its exit captured before any pipe: 84 exit 0, 3 exit 3 PREREQUISITE NOT MET (pnpm check:dual-build-cjs-loads, pnpm check:i18n, pnpm check:type-check-debt — each needs every package's dist/)",
"node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack :: exit 0 :: ✓ dispatch-gates --ran: 87 derived famil(ies) accounted for — 84 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3).",
"pnpm exec eslint --no-inline-config --format json (4 touched TS files) :: exit 0 @ f692a17 :: 4 linted, 0 errors, 0 warnings"
],
"line_budget": "none owed: no skills/ in the diff",
"deviations": [
"Surface beyond H1–H4's read focus, inside the claimed file: (a) the step 2.5 field write gate is ungated for this class; (b) canWriteObject gains a zero-set field arm; (c) the two spellings of the 2.5 verdict (the middleware and canWriteObject arm 9) are folded into one helper, computeForbiddenFieldWrites. H3 measured that the stand-in feeds getWritableFields, whose contract is the exact complement of 2.5. Without (a) the published write answer would narrow while the write path admits, and without (b) the preview would admit what the write refuses. All three are declared in the PR body, and ablations A2 and A3 prove (a) and (b) load-bearing.",
"The spec docblock for getWritableFields also gains one sentence (H2 named only getReadableFields and getMetadataReadableFields), because its answer for this class narrows too.",
"The consumer suites were measured at 65d1da2, before the main merge that brought PR #21101. After the merge, only the plugin-security suite and typecheck, spec check:generated and the gate union were re-run, at f692a17. The merge touched none of this PR's lines.",
"A post-merge closure build was cut by my own outertimeout 590(exit 124) before its last task. It was re-run without the outer timeout: 18/18 tasks, VERDICT command-exit 0. That run is the reading.",
"No real-boot (dogfood) reading of the record and explain doors; declared NOT MEASURED with its reason in the PR's Acceptance notes."
],
"files_changed": [
".changeset/21063-security-contract-zero-set-fields.md",
".changeset/21063-zero-set-capability-fold.md",
"packages/plugins/plugin-security/src/get-writable-fields.test.ts",
"packages/plugins/plugin-security/src/security-plugin.ts",
"packages/plugins/plugin-security/src/zero-set-capability-fold.test.ts",
"packages/spec/src/contracts/security-service.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21134 at head
f692a171c0·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T09:13Z ·⚠️ Classes and positions onlyReviewed against GitHub (the PR object, its file list, its diff and the head's check-runs), not against the dev's report on this card.
-
Shape: draft, base
main, first lineFixes #21063and no other closing keyword,Clause-②: yes (narrowing)at line start. That matches claim5925129243. The PR assignee isos-bill. -
Scope: 6 files, inside the claim:
packages/spec/src/contracts/security-service.ts(docblocks);packages/plugins/plugin-security/src/security-plugin.ts,zero-set-capability-fold.test.ts(new) andget-writable-fields.test.ts;- two changesets.
check-governed-merges.mjs --pr 21134says NOT governed. No other open PR touches these files. PR fix(plugin-security)!: the record an anonymous public-form submit echoes back passes the result masker #21101 (domain:services) landed during the build, and the dev mergedmainand re-ran the suite there. -
What changed (classes and positions): for a non-system caller who resolves no permission set, a field that declares
requiredPermissionsand no masking rule now behaves as follows.- It is not served on the record doors, which now agree with explain.
- A query naming it is refused.
- It is left out of
getReadableFields, ofgetMetadataReadableFieldswhen the fallback resolves nothing, and ofgetQueryableFields. - The zero-set stand-in carries the posture's per-field capability contract, folded by the same ADR-0066 D3 helper the full-set path uses. ⛔ No second derivation.
-
The write side, the seat's own question to the review: the dev narrowed it too. Step 2.5 is ungated for this class,
canWriteObjectgains a field arm, and one helpercomputeForbiddenFieldWritesreplaces two spellings. ThegetWritableFieldsdocblock follows. The at-tier record judges this inside triage's ruling5925000390and right:- The ruling takes the field's describe ("mask on read, deny on write") as the protocol.
- The fold marks a gated field both unreadable and uneditable.
getWritableFields' published contract is the exact complement of step 2.5, so a read-only narrowing would need a second derivation.- Ablations A2 and A3 show both write edits are load-bearing.
-
No other class moves: a caller with sets reads the real posture as before. A principal-less caller and the system path are unchanged, nothing widens, and object admission for this class is unchanged (the object capability contract is not carried).
-
Contract review: the at-tier record
5928372729on the PR is a PASS atf692a171c06e, the head this lands. It also judged the PR's public text to keep the disclosure discipline. Both changesets are right:minor, a bang, BREAKING,Clause-②: yes (narrowing),adr-0087: not-required (no-migration-prescription). -
Evidence read:
- The pins went red on the pins commit (12 of 20, exactly the class cases) and green after the fix.
- Ablations A1, A2 and A3 were each red where expected, each restored to blob equality.
- Suites: plugin-security, 154 files and 3321 tests at the merged head.
- Consumers: rest (2 shards), service-analytics, plugin-approvals, metadata-core, and objectql's plugin-security suites.
- Gates: 87 derived, 84 exit 0, and 3 NOT MEASURED that read whole-workspace dist (CI's).
- A real-boot door reading is declared NOT MEASURED with its reason.
-
CI at
f692a171c0, read at this stamp: 32 success, 3 roster skips, 0 failed, 0 in progress. -
Findings: both go to Acceptance notes, not filed.
canWriteObjectasked with no payload still admits this class on an unreadable object posture. That is object admission. Carrier: thedomain:servicessplit of security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061's direction 2. No reach today: the write-preview probe always passes rows.- The public-form read-back now also drops a gated field for a zero-set submitter; it is not pinned at that door. Carrier: the
domain:servicesseat (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 / PR fix(plugin-security)!: the record an anonymous public-form submit echoes back passes the result masker #21101); a pointer is left on 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.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded · PR #21134 MERGED through the merge queue as
665cab338f(single parentc6954d6d0) ·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T09:40Z ·⚠️ Classes and positions only- Verified by content, not by the merged flag. All 6 files have the same blob in the merge commit as at the ACCEPTed head
f692a171c0, the head the at-tier PASS5928372729covers. The merge commit is an ancestor oforigin/main. - Closing keywords: the body carried
Fixes #21063only. This card closedcompleted, and no other card was named. - What is now true on
main: for a non-system caller who resolves no permission set, a field that declaresrequiredPermissionsand no masking rule is hidden on the record doors and refused in queries and writes. The published contract answers (getReadableFields,getMetadataReadableFields,getWritableFields) say so, and explain agrees. - Carried forward:
- object admission for this class is the
domain:servicessplit of security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061's direction 2; - the public-form echo's pin gap is noted at
5928414754on 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.
- object admission for this class is the
- State:
pm:dispatchedcomes off in this act.
Generated by Claude Code
- Verified by content, not by the merged flag. All 6 files have the same blob in the merge commit as at the ACCEPTed head
- added 3 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a product defect with a measured⚠️ Disclosure discipline: classes and positions only.
reach:. Finding class (b): the field'srequiredPermissionsdescribe ("mask on read, deny on write") is not delivered for one caller class.reach:the same public doors as #20995, on a real boot, measured by #20995's dev (os-dev-report5924254306,out_of_scope_findingsF3). The readings are private; this seat has read them. Filed by thedomain:servicesexecution seat (#6021,session_01XY5uCwTjZj7884yYtyur4H). ⛔ Not a claim.What happens (by class)
maskingRule's describe and the result masker mask it — which one a public door serves is not measured #20995's class).requiredPermissions(an ADR-0066 D3 capability gate) and no masking rule.maskingRule's describe and the result masker mask it — which one a public door serves is not measured #20995) masked fields are masked for this class, but the capability fold is deliberately NOT carried into the zero-set stand-in.5924418816explains why: carrying the fold would narrowgetReadableFieldsfor this class. Its zero-set answer ("the full set … for a caller with no permission sets") is stated in the published contract docblock (packages/spec/src/contracts/security-service.ts, andgetMetadataReadableFieldsthere).Why it is a decision for the contract lane
Delivering the field's describe for this class changes a published contract answer. That makes it
domain:spec's to rule: either the contract's zero-set answer narrows to exclude capability-gated fields (thenplugin-securityfolds the capability for the class), or the describe is corrected to state the exception. ⛔ Not a silent change of either side.getMetadataReadableFields' docblock says the middleware skips its whole field gate for a zero-set caller. After PR fix(plugin-security)!: a caller who resolves no permission set is served a masked field masked and may not query on it #21051 that holds for the grant-based gates and not for masking.Reader who acts
Triage, which routes to
domain:specfor the contract question. The implementation then lands inplugin-security(domain:services).Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:requiredPermissions在 record 块上到底是对象动作还是 ADR-0066 能力?—— 已发布的record:quick_actions那一半今天是 fail-open #19186, spec(ui):record:details,record:highlightsandrecord:related_listrefuserequiredPermissions/enforceFieldSecurity/redactFieldsby name while objectui's renderers read and honour all three —requiredPermissionsis declared on the siblingrecord:quick_actionsand nowhere else (spec half of objectui#8649) #18159, finding(plugin-hono-server): /auth/me/permissions never seeds an unrestricted object for a wildcard-only principal, so the Console renders Export where the server answers 403 EXPORT_NOT_PERMITTED #18931, A permission set accepts a hierarchyreadScopebesideviewAllRecords: true, never reads it, and emits no diagnostic — the declaration materialises and a capability census counts it as coverage #16870 and others arerequiredPermissionson UI blocks or permission sets, all closed. None is this caller class's capability fold.maskingRule's describe and the result masker mask it — which one a public door serves is not measured #20995 (masking for this class, PR fix(plugin-security)!: a caller who resolves no permission set is served a masked field masked and may not query on it #21051) and security (P0 suspect): a non-system caller who resolves no permission set — an unauthenticated one included — is admitted to aggregate any object at an analytics door, object admission and row scope skipped #21061 (object admission for this class).Dedupe words:
zero permission sets requiredPermissions fold·capability-gated field zero-set·explain fls hidden servedGenerated by Claude Code