Uh oh!
There was an error while loading. Please reload this page.
docs(objectql): disclose the security middleware's total isSystem bypass on repo.execute() - #15131
Conversation
…ass on repo.execute() PR #15051's "what widens" statement named only the static `readonly` strip that isSystem: true skips on ctx.api.object(x).update(y). That is true but materially incomplete: ObjectQL's registered security middleware (security-plugin.ts:1614-1616) reads isSystem as a TOTAL, unconditional bypass, so every find/insert/update/delete through this ctx.api also skips RLS scoping, field-level security, the CRUD/export checks, the ADR-0103 engine-owned write guard, the package-managed/system-row/curated-capability/ audience-anchor write gates, the referential-integrity check and the tenant-audit mute -- matching REST /actions and MCP run_action exactly, and documented in full at content/docs/permissions/system-context.mdx. Bounded on two sides: metadata-plane schema masking (object-schema-fls.ts) is a separate REST/GraphQL dispatch path this ctx.api surface never reaches, and plugin-sharing's insert/update materialisation skip was already retired by the 2026-08-31 ruling on #13533. Rewrites the changeset paragraph and the execute() docstring to agree; the PR body itself is corrected via the GitHub API in the same round (a merged PR's body can still be edited for the historical record). No implementation, census, ablation, gate, or changeset-level change -- prose only, per the review finding on #15051. Part of #13866 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
The disclosure-completeness docstring insertion in engine.ts shifted every line after it by 26; check-system-context-census.mjs --fix re-anchored the one row that rotted off its line (the ScopedContext.isSystem getter, row 63). Pure line rot, no population or classification change (still 106 elevation read sites in 20 packages across 45 files, all anchored). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 491493b45069b9ae845b2634d29a8e876933a643 && git checkout 491493b45069b9ae845b2634d29a8e876933a643
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2cc4610304c58c3de05a36efbb7cd1710b9d440e ba771e706b8ae3a001a7b4b6f56db7fc747fefed && git checkout -B drift-repro 2cc4610304c58c3de05a36efbb7cd1710b9d440e && git merge --no-ff ba771e706b8ae3a001a7b4b6f56db7fc747fefed
node scripts/docs-audit/affected-docs.mjs --json 2cc4610304c58c3de05a36efbb7cd1710b9d440e |
os-musk
commented
Sep 4, 2026
PM: |
os-musk
commented
Sep 4, 2026
Superseded: the handoff above is no longer neededThe previous comment ended by asking a human to mark this PR ready for review, because the GraphQL bucket was exhausted and Sequence, so the record is not just an assertion:
One caveat stated rather than dressed up as certainty. The auto-merge receipt came back in the empty form, That matters because of how the last one failed: #15008 sat armed for seven hours without landing, because it had gone Generated by Claude Code |
Part of #13866 — disclosure-completeness follow-up to #15051 (merged as a548550)
Why this exists
#15051 elevated
ObjectRepository.execute()'sctx.apito{ ...callerContext, isSystem: true }, closing the thirdexecuteActioncaller per #13866. Independent contract review returned exactly one finding on that PR, after it had already been merged by the maintainer: its "what widens" statement named only the staticreadonlystrip, and that is true but materially incomplete.isSystem: trueis a flag, not a behaviour — it is read by ObjectQL's registered security middleware as a total bypass, not a readonly-only one.⛔ No implementation, census, ablation, gate, or changeset-level change. The elevation, the pins, the 5-case ablation and the
patchlevel all stood on review — this PR only corrects the disclosure text in the three places that repeated the incomplete framing:.changeset/repo-execute-elevated-context.mdexecute()docstring inpackages/objectql/src/engine.tsA fourth file changed for mechanical reasons only:
content/docs/permissions/system-context.mdx— the docstring insertion shifted every later line inengine.tsby 26, rotting one anchor (check-system-context-census.mjs's own--fix, pure line rot, no population/classification change: still 106 elevation-read sites in 20 packages across 45 files, all anchored).The corrected "what widens" statement
ctx.apiinside arepo.execute()-dispatched action handler carriesisSystem: true, and ObjectQL's registered security middleware reads that as a total, unconditional bypass —plugin-security/src/security-plugin.ts:1614-1616, "System operations bypass security" /return next()ahead of every other gate in that middleware. Everyfind/insert/update/deletethe handler drives through thisctx.apitherefore also skips, in the same stroke as the readonly strip:security-plugin.ts:1614-1616getReadFilter)security-plugin.ts:4344security-plugin.ts:4495security-plugin.ts:1616canExport)security-plugin.ts:4573system-write-guard.ts:96,120, called atsecurity-plugin.ts:1736security-plugin.ts:1690-1724readonlystrip — UPDATE, single rowengine.ts:11290readonlystrip — UPDATE, bulkengine.ts:11473readonly/runtime-owned strip — INSERT (stripRuntimeOwnedFields, covers autonumber AND readonly together)engine.ts:10025assertReferencesResolve)engine.ts:5892engine.ts:3773isSystempropagation (carries the flag onward, not a gate)engine.ts:3543(buildSession)'system'vs'anonymous', not a gate)engine.ts:12014Every row re-derived independently by reading the cited line on this branch (not copied from the review) — see the terminal report for the full method.
Bounded on two sides, also re-verified independently:
metadata-core/object-schema-fls.ts:228) is a separate REST/GraphQL schema-serving dispatch path (GET /meta/object/:nameand friends) that thisctx.apisurface (find/insert/update/delete/count/aggregate/execute) never calls into — confirmed by reading the file's own header (@objectstack/metadata-coredepends on spec+zod only; its two consumers are@objectstack/restand@objectstack/runtime's schema-serving exits, not the engine's registered middleware chain).isSystemdoes bypass this elsewhere in the platform (content/docs/permissions/system-context.mdxrow 7) — just not reachable from this PR's widening.plugin-sharing/rule-hooks.ts's insert/update materialisation skip was already retired by the maintainer's 2026-08-31 ruling on 审批回写(系统身份)不触发共享规则物化,「批准后团队看不见」——平台只记一条日志、无补偿、无声明式手段 #13533 — confirmed by reading the file: the skip and its announcing INFO notice are both deleted, and the survivingisSystemcheck atrule-hooks.ts:292is a different skip (delete-side revocation timing, delegated torecord-share-cascade.ts, not a silencer) per the file's own comment. System and user writes materialise sharing grants identically today..object(x).execute() acrosspackages/,examples/,apps/— zero hits outside this PR's own pin test (engine-repo-execute-elevation.test.ts), against a firing positive control (.object(x).update(→ 195 hits,.insert(→ 49 hits) confirming the grep methodology isn't blind. So the widening is total in kind, empty in practice, today.This matches the platform's own documented posture —
content/docs/permissions/system-context.mdx: "Elevation is total, and it is not granular." — and REST/actions/ MCPrun_actionalready carry the identical exposure, so #15051 extends an existing bypass to a third dispatch path rather than introducing a new one.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths — merge-base derivation againstorigin/main), re-derived after the anchor-fix commit: 73 commands on the final 3-file diff. All passed except two, both pre-existing "build the whole monorepo first" prerequisites unrelated to this diff (same shape #15051 itself reported):check:dual-build-cjs-loads→ exit 3PREREQUISITE NOT MET(needs every workspace package'sdist/)@objectstack/spec check:skill-examples→ needs@objectstack/client-reactbuilt (unrelated topackages/objectql/docs)node scripts/pm/check-governed-merges.mjs --test .changeset/repo-execute-elevated-context.md content/docs/permissions/system-context.mdx packages/objectql/src/engine.ts→ NOT governed (0 of 3 paths hitGOVERNED_SURFACES).The PR's own 5 pins (
packages/objectql/src/engine-repo-execute-elevation.test.ts) and the packagetypecheckwere re-run on this branch to confirm the text-only edit disturbed neither: 5 passed (5), typecheck clean, both on final headba771e706.Scope note
No code behaviour changes here — only prose (a changeset paragraph, a docstring, one doc anchor). Left as draft; not enqueued, not auto-merge-armed.
needs:contract-reviewwas already cleared on #15051 when it merged — flagging in the report that this seat could not mechanically re-apply a label from here (no label-write tool available) and leaving that call to the PM/reviewing seat.Generated by Claude Code