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
Found by the ADR-0090 D10 adversarial security review.
What
Business action bodies (type: script/body) are dispatched via invokeBusinessAction with an engine facade — buildActionEngineFacade(ql) (packages/runtime/src/http-dispatcher.ts:897-920) — whose insert/update/delete/find call the engine with no ExecutionContext:
asyncinsert(object,data){constres=awaitql.insert(object,data);…}// no contextasyncupdate(object,id,data){awaitql.update(object,data,{where: { id }});}// no context
A context-less engine call hits the SecurityPlugin's empty-principal fall-open (security-plugin.ts ~520) → all RLS / FLS / CRUD / tenant scoping and the D10 agent intersection are skipped. So an action body reads/writes with ambient unrestricted authority (the "SECURITY DEFINER / trusted code" model). The same context-less facade exists on the REST action route (http-dispatcher.ts:~3495).
Why it matters (agent angle)
With the D10 agent feature (#2843/#2845), an MCP agent can invoke actions (run_action, gated by actionPermissionError + actions:execute). Because the body runs context-less, the agent's data ceiling does NOT bound what an invoked action reads or writes — contradicting the safety framing in #2845 ("a data:read agent invoking a writing action is blocked at the write"). The real boundary for agents is invoke-time only (ai.exposed + the capability gate), not the data ceiling.
Thread the caller's ExecutionContext into the action facade so bodies run bounded by the caller (and, for agents, the ceiling∩user intersection). Correct, but a larger change that may break actions relying on trusted-mode cross-cutting writes.
A hybrid: default bodies to the caller context, with an explicit opt-in runAs: 'system' for trusted actions.
This is also a general (non-agent) consideration: any action a normal user invokes runs unbounded internally — defensible as trusted code, but worth making explicit.
Found by the ADR-0090 D10 adversarial security review.
What
Business action bodies (
type: script/body) are dispatched viainvokeBusinessActionwith an engine facade —buildActionEngineFacade(ql)(packages/runtime/src/http-dispatcher.ts:897-920) — whoseinsert/update/delete/findcall the engine with noExecutionContext:A context-less engine call hits the SecurityPlugin's empty-principal fall-open (
security-plugin.ts~520) → all RLS / FLS / CRUD / tenant scoping and the D10 agent intersection are skipped. So an action body reads/writes with ambient unrestricted authority (the "SECURITY DEFINER / trusted code" model). The same context-less facade exists on the REST action route (http-dispatcher.ts:~3495).Why it matters (agent angle)
With the D10 agent feature (#2843/#2845), an MCP agent can invoke actions (
run_action, gated byactionPermissionError+actions:execute). Because the body runs context-less, the agent's data ceiling does NOT bound what an invoked action reads or writes — contradicting the safety framing in #2845 ("adata:readagent invoking a writing action is blocked at the write"). The real boundary for agents is invoke-time only (ai.exposed+ the capability gate), not the data ceiling.Decision needed
ai.exposed, not data-bounded); optionally revert feat(security): ADR-0090 D10 — delegate action capabilities to MCP agents #2845 to strict (deny cap-gated actions to agents).ExecutionContextinto the action facade so bodies run bounded by the caller (and, for agents, the ceiling∩user intersection). Correct, but a larger change that may break actions relying on trusted-mode cross-cutting writes.runAs: 'system'for trusted actions.This is also a general (non-agent) consideration: any action a normal user invokes runs unbounded internally — defensible as trusted code, but worth making explicit.
Refs
packages/runtime/src/http-dispatcher.ts:897-920(facade),:1010-1015(actionContext),:985-1034(invokeBusinessAction)