Skip to content

Security: AI ToolExecutionContext contract documents system-level as the missing-actor default — a contract-level fall-open across all data tools #2991

Description

@os-zhuang

Surfaced by the ADR-0096 E4 verification. The framework owns the AI tool-execution contract but not its executor (which lives in cloud). The contract's documented default for a missing actor is system-level (RLS-bypassing) execution, spanning all built-in data tools — a fall-open baked into the type's documentation.

The contract

packages/spec/src/contracts/ai-service.ts:

  • ToolExecutionContext.actor is optional (:393). Docstring (:388-397): "Built-in tools promote this into the ObjectQL ExecutionContext so RLS engages. Omit for internal/system invocations."
  • ChatWithToolsOptions.toolExecutionContext (:367-378): the route "should populate this from req.user … so RLS automatically scopes what the LLM can see or change. Optional for backward compatibility — when omitted, tools fall back to system-level behaviour." (:376-377)

So a missing actor ⇒ system-level, for "built-in data tools" generically (query/get/create/update/delete/run_action, not just knowledge). Any cloud or custom route that forgets to populate actor from req.user runs the LLM's tools with full authority.

Why framework-side, even though the executor is cloud

Fix direction

  • Change the documented default from "system-level behaviour" to anonymous/deny for data-touching tools (ai-service.ts:376-377, :388-397): a missing actor must mean an unauthenticated (RLS-on, sees nothing) principal, never isSystem. This turns the fall-open into a fail-closed.
  • Optionally make actor non-optional for data tools, or add an explicit system: true opt-in (mirroring IKnowledgeService's isSystem convention) so "run as system" is a deliberate, greppable choice — not the consequence of a forgotten field.
  • Cloud side (cross-repo): the tool loop must (a) populate actor from req.user on every AI route, and (b) default a missing actor to anonymous/deny ExecutionContext, never isSystem.

Relationship to ADR-0096

E4 "unknown seam" resolved to a contract-level fall-open. This is the D1 invariant ("no identity is never a grant of authority") applied at the contract layer, and the mirror of the #2981 knowledge/RAG fix (which closed the same shape framework-side where an executor existed). Refs: ADR-0096 (#2975 / e07645c), #2981, #2849.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p2Medium: important, M3

    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