ADR-0096 (execution-surface identity admission) + fixes for the 3 confirmed-exploitable holes it surfaced (#2980, #2981, #2982)#2975
Merged
Conversation
Generalizes #2849 from an action-shaped bug to its class: metadata-driven execution surfaces that reach the data engine without an ExecutionContext silently inherit full authority via the security plugin's empty-principal fall-open (#2308 and #2849 are two instances found only by adversarial review). Decides one invariant and four mechanisms: every engine call carries a real principal or an explicit audited SystemGrant (D1); a single elevation constructor systemContext(reason) (D2); identity-required engine signatures so forgetting context is a compile error, not a fall-open (D3); an execution-surface admission test wired into the authz conformance matrix with a CI meta-test (D4); and a staged fail-closed strict mode (D5). Re-bases #2849 Phase 2/3 as D6: action-level runAs adopts ADR-0073's three-posture vocabulary and lands as this mechanism's first application. Staged per ADR-0049: v1 is the decision record + systemContext + honest matrix rows + the ai.exposed/trusted-body lint; signature migration and the strict-mode default flip are evidence-gated M2+. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155bjzJZxZaZTKSMSvg6Aum
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Jul 15, 2026
A four-axis sweep (context-less calls, elevation literals, fall-open seams, execution-surface identity) confirms #2849 is a populated class, not a lone bug. Records the findings as the ADR's evidence base: - E1: the empty-principal fall-open predicate replicated across getReadFilter, the tenant-scope path, guest routes, custom API endpoints, the authored-body sandbox, MCP resource reads, and messaging. - E2: three confirmed-exploitable trusted-implicit surfaces, spun out as #2980 (reports IDOR + scheduled-report RLS bypass), #2981 (knowledge/RAG retrieval fall-open), #2982 (bulk-write OWD gap) — fixed independently. - E3: structural middleware-bypass paths (executeAction, raw execute, driver getters). - E4: unknown seams (GraphQL, realtime, blob RLS, chatWithTools contract). - E5: the D2 migration sizing (~300 sites / ~50 constructors / ~25 files) and its existing prototype (resolveRunDataContext). Wires the seed rows into D4, the exploitable instances into 'what lands now', and the References/Trigger notes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155bjzJZxZaZTKSMSvg6Aum
…write OWD gap (#2980, #2981, #2982) Three confirmed-exploitable execution-surface authz holes from the ADR-0096 class sweep, each fixed at its enforcement point; no change for correctly- scoped callers. - #2980 reports: getReport/deleteReport/saveReport/listReports were caller- identity-blind (read/wrote sys_saved_report with a system context) → any authenticated user could read/delete/overwrite/enumerate any report. Now owner-scoped (system read of the protection-locked metadata object, authz by owner match); create/overwrite can't spoof ownership. Scheduled dispatch no longer runs isSystem (which emailed the target object's whole table past the owner's RLS): resolves the owner via a new resolveOwnerContext seam and fails closed when unresolved (ADR-0073 user-less identity consumer). - #2981 knowledge/RAG: applyPermissionFilter returned all hits on missing OR system context. Missing identity now fails closed (drops object-backed hits, keeps ACL-less file/http); only an explicit system context passes through. - #2982 sharing: bulk update({multi:true})/deleteMany had no single id to canEdit-gate, skipping owner scoping on private/public_read objects. New SharingService.buildWriteFilter (edit-set analogue of buildReadFilter) is AND-ed into the multi-write AST, incl. the on-behalf-of delegator intersection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155bjzJZxZaZTKSMSvg6Aum
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 15, 2026 16:31
This was referenced Jul 16, 2026
os-zhuang
pushed a commit
that referenced
this pull request
Jul 16, 2026
…d statuses, mark PRIORITIZATION stale Follow-up to the ADR-0086 re-evaluation (#2557): an audit of docs/adr found three hygiene defects; this fixes the mechanical ones. 1. Resolve the ADR-0096 numbering collision. Two unrelated ADRs landed as 0096 on the same day; the first-merged one (execution-surface identity admission, #2975) keeps 0096, and the connector ADR (#2977) is renumbered to ADR-0097. All connector-semantic "ADR-0096" references in code comments, test titles, and zod describe/message strings are updated to ADR-0097; execution-surface references (http-dispatcher, authz-conformance, graphql/realtime contracts, service-realtime README) are untouched. CHANGELOGs and commit history are left as historical record. 2. Flip Status lines of implemented ADRs that were still Proposed, with evidence: 0093 (tenancy service + membership reconciler + dogfood test) and 0095 (tenant-layer.ts + posture-ladder.ts + matrix-gate tests). Verified code+tests on main before flipping; other stale-looking ADRs (0049/0056/0057/0066/...) are left for per-ADR verification. 3. Mark docs/adr/PRIORITIZATION.md as a historical snapshot (it predates ADR-0050+) and record the status-hygiene rule: the PR that lands an ADR's implementation flips its Status line with one line of evidence.
os-zhuang
pushed a commit
that referenced
this pull request
Jul 16, 2026
…d statuses, mark PRIORITIZATION stale Follow-up to the ADR-0086 re-evaluation (#2557): an audit of docs/adr found three hygiene defects; this fixes the mechanical ones. 1. Resolve the ADR-0096 numbering collision. Two unrelated ADRs landed as 0096 on the same day; the first-merged one (execution-surface identity admission, #2975) keeps 0096, and the connector ADR (#2977) is renumbered to ADR-0097. All connector-semantic "ADR-0096" references in code comments, test titles, and zod describe/message strings are updated to ADR-0097; execution-surface references (http-dispatcher, authz-conformance, graphql/realtime contracts, service-realtime README) are untouched. CHANGELOGs and commit history are left as historical record. 2. Flip Status lines of implemented ADRs that were still Proposed, with evidence: 0093 (tenancy service + membership reconciler + dogfood test) and 0095 (tenant-layer.ts + posture-ladder.ts + matrix-gate tests). Verified code+tests on main before flipping; other stale-looking ADRs (0049/0056/0057/0066/...) are left for per-ADR verification. 3. Mark docs/adr/PRIORITIZATION.md as a historical snapshot (it predates ADR-0050+) and record the status-hygiene rule: the PR that lands an ADR's implementation flips its Status line with one line of evidence.
os-zhuang
added a commit
that referenced
this pull request
Jul 16, 2026
…d statuses, mark PRIORITIZATION stale (#3032) * docs(adr): status-hygiene sweep — fix 0096 collision, flip implemented statuses, mark PRIORITIZATION stale Follow-up to the ADR-0086 re-evaluation (#2557): an audit of docs/adr found three hygiene defects; this fixes the mechanical ones. 1. Resolve the ADR-0096 numbering collision. Two unrelated ADRs landed as 0096 on the same day; the first-merged one (execution-surface identity admission, #2975) keeps 0096, and the connector ADR (#2977) is renumbered to ADR-0097. All connector-semantic "ADR-0096" references in code comments, test titles, and zod describe/message strings are updated to ADR-0097; execution-surface references (http-dispatcher, authz-conformance, graphql/realtime contracts, service-realtime README) are untouched. CHANGELOGs and commit history are left as historical record. 2. Flip Status lines of implemented ADRs that were still Proposed, with evidence: 0093 (tenancy service + membership reconciler + dogfood test) and 0095 (tenant-layer.ts + posture-ladder.ts + matrix-gate tests). Verified code+tests on main before flipping; other stale-looking ADRs (0049/0056/0057/0066/...) are left for per-ADR verification. 3. Mark docs/adr/PRIORITIZATION.md as a historical snapshot (it predates ADR-0050+) and record the status-hygiene rule: the PR that lands an ADR's implementation flips its Status line with one line of evidence. * chore: retrigger CI * docs: rename remaining connector-semantic ADR-0096 refs introduced by #3016/#3029 --------- Co-authored-by: Claude <noreply@anthropic.com>
68 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #2964 / #2849. Two parts: the decision record (ADR-0096) and the three confirmed-exploitable fixes its evidence sweep surfaced. See the branch/PR note at the bottom — these can be split if a separate fast-merge PR is preferred.
Part 1 — ADR-0096: Execution-Surface Identity Admission (docs)
Names the failure class behind #2849 and #2308: a metadata-driven execution surface reaches the data engine without an
ExecutionContext, and the SecurityPlugin's empty-principal fall-open rewards the omission with full authority — silently. Decisions:SystemGrant. "No context" is a defect, never an authorization.systemContext(reason)(grep-able, audited); D3 compiler-enforced requiredCallIdentity(the Security: MCP action surface must gate onai.exposed— action bodies run trusted (unbounded RLS/FLS), so invoke-time is the only agent boundary (#2849) #2849 facade bug becomes a compile error); D4 execution-surface admission test wired into the ADR-0056 D10 conformance matrix + CI meta-test; D5 staged fail-closed strict mode; D6 re-bases Security: MCP action surface must gate onai.exposed— action bodies run trusted (unbounded RLS/FLS), so invoke-time is the only agent boundary (#2849) #2849 Phase 2/3 (actionrunAsin ADR-0073'suser/automation/systemvocabulary).Part 2 — the three confirmed-exploitable fixes
Each fixed at its own enforcement point; no behaviour change for correctly-scoped callers. Tests added;
plugin-reports38,service-knowledge22,plugin-sharing85 green; eslint + doc-authoring guard clean; changeset added.getReport/deleteReport/saveReport/listReportswere caller-identity-blind (read/wrotesys_saved_reportwith a system context) → any authenticated user could read/delete/overwrite/enumerate any report (cross-owner, cross-tenant). Now owner-scoped (system read of the protection-locked metadata object, authorization by owner match; create/overwrite can't spoof ownership). Scheduled dispatch no longer runsisSystem(which emailed the target object's whole table past the owner's RLS): it resolves the owner via a newresolveOwnerContextseam and fails closed when unresolved (the reports-surface consumer of ADR-0073's user-less identity resolution — wiring that resolver re-enables scheduled runs under owner RLS).applyPermissionFilterreturned all hits on missing or system context. Missing identity now fails closed (drops object-backed hits, keeps ACL-less file/http); only an explicit system context passes through. Closes the agent path where an omittedToolExecutionContext.actoryielded unfiltered semantic search over the whole corpus.update({multi:true})/deleteManyhad no single id tocanEdit-gate, skipping owner scoping on private (and public_read) objects. NewSharingService.buildWriteFilter(edit-set analogue ofbuildReadFilter) is AND-ed into the multi-write AST, incl. the on-behalf-of delegator intersection.🤖 Generated with Claude Code
https://claude.ai/code/session_0155bjzJZxZaZTKSMSvg6Aum