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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions