Skip to content

Security: business action bodies run context-less (unbounded RLS/FLS) — agent action effects escape the D10 ceiling #2849

Description

@os-zhuang

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:

async insert(object, data) { const res = await ql.insert(object, data);  }        // no context
async update(object, id, data) { await ql.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.

Decision needed

  1. Accept the trusted-action model and correct the docs/feat(security): ADR-0090 D10 — delegate action capabilities to MCP agents #2845 framing (agent action power = invoke-gate + 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).
  2. 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.
  3. 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.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p1High: required for production / M2

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions