Agentic Trading
Security

Trust Boundaries

A map of who can do what, and which component enforces each boundary. If you're touching a feature that crosses one of these lines, this doc is mandatory reading.

Principals

PrincipalDescription
AnonymousUnauthenticated visitor
Authenticated UserSigned in via Supabase Auth
Skill AuthorSame as Authenticated User, in the role of editing Skills
DeployerSame as Authenticated User, in the role of running deployments
Trading AgentLLM-driven process running on Fly, in the trading role
Chat AgentLLM-driven function call, in the read-only Q&A role
Execution EngineServer-side module that validates and executes orders
Service RoleServer-side identity used by trusted processes (web server, live runner, ingest jobs) — bypasses RLS

A single human user can be a Skill Author and a Deployer at the same time (and likely is, in MVP). The roles are logically separate so that future multi-user scenarios slot in cleanly.

Boundary map

┌──────────────────────────────────────────────────────────────────────────────┐
│ ┌────────────┐                                                               │
│ │ Anonymous  │  → can hit auth pages, nothing else                           │
│ └────────────┘                                                               │
│       ▲                                                                      │
│       │ sign in / sign up                                                    │
│       ▼                                                                      │
│ ┌──────────────────┐                                                         │
│ │ Authenticated    │                                                         │
│ │ User             │  → reads own data via Supabase RLS                      │
│ └────────┬─────────┘  → can write own Skills, sims, deployments              │
│          │            → cannot write other users' anything                   │
│          │                                                                   │
│   ┌──────┴────────────────────────────────────────┐                          │
│   │                                               │                          │
│   ▼                                               ▼                          │
│ ┌────────────────┐                       ┌──────────────────┐                │
│ │ Skill Author   │                       │ Deployer         │                │
│ │   role         │                       │   role           │                │
│ └────────┬───────┘                       └────────┬─────────┘                │
│          │ saves Skill                            │ deploys / commands       │
│          │ (writes skill_versions)                │ (writes agent_commands)  │
│          │                                        │ (chats — agent_messages) │
│          ▼                                        ▼                          │
│  ┌────────────────────────────────────────────────────────────┐              │
│  │ Database (Supabase) — RLS enforces user-scoped access      │              │
│  └────────────────────────────────────────────────────────────┘              │
│                          ▲                                                   │
│                          │ writes (snapshots, state, logs)                   │
│                          │                                                   │
│  ┌───────────────────────┴─────────────────────────────────┐                 │
│  │ Service Role (bypass RLS, server-side only)             │                 │
│  │  • apps/web server actions                              │                 │
│  │  • apps/live-runner                                     │                 │
│  │  • packages/data-ingest                                 │                 │
│  └────────────────┬──────────────────────────────────┬─────┘                 │
│                   │                                  │                       │
│                   ▼                                  ▼                       │
│           ┌──────────────────┐               ┌──────────────────┐            │
│           │ Trading Agent    │               │ Chat Agent       │            │
│           │  (Fly, per skill)│               │  (Vercel Fn)     │            │
│           └────────┬─────────┘               └────────┬─────────┘            │
│                    │                                  │                      │
│              proposed action                  natural-language reply         │
│                    │                                  │                      │
│                    ▼                                  ▼                      │
│           ┌──────────────────┐               ┌──────────────────────┐        │
│           │ Execution Engine │               │ (no exchange access) │        │
│           │  validates → ?   │               └──────────────────────┘        │
│           └────────┬─────────┘                                               │
│                    │  accepted                                               │
│                    ▼                                                         │
│           ┌──────────────────┐                                               │
│           │ Broker Adapter   │  ← decrypted exchange credentials             │
│           │  (Hyperliquid)   │     held only in this process memory          │
│           └──────────────────┘                                               │
└──────────────────────────────────────────────────────────────────────────────┘

Per-data-class access matrix

Data classAnonAuthed User (own)Authed User (others)Service Role
skills, skill_versionsRWRW
deploymentsR + createRW
agent_stateRRW
agent_logs, decision_snapshotsRRW
agent_commandsRW (own)RW
agent_conversations/messagesRW (own)RW
sim_runsRWRW
exchange_credentials (ciphertext)R metadata onlyRW + decrypt
bars, news, funding_ratesR (all)R (all)RW

R = SELECT, RW = SELECT + INSERT/UPDATE/DELETE. "Metadata only" for credentials = the user can see the wallet address and label, never the decrypted key material.

Agent boundaries

Trading Agent

What it CAN do (within Skill constraints):

  • Read market data, news, portfolio (via tools)
  • Emit ProposedAction via the propose_order tool
  • Reason in final_text for its decision

What it CANNOT do:

  • Call exchange APIs directly (no broker handle is passed to the agent runtime; only the Execution Engine has it)
  • Modify its own Skill or risk caps
  • Issue commands (no command tools)
  • Read other deployments' state
  • Bypass the Execution Engine (the engine is the only code path to a broker adapter; the agent runtime does not import broker adapters)

Chat Agent

What it CAN do:

  • Read past decision snapshots, current state, logs (via introspection tools)
  • Read recent market data and news (via read-only versions of trading tools)
  • Generate text responses, possibly suggesting actions the deployer might take

What it CANNOT do:

  • Place orders (propose_order not in tool whitelist)
  • Issue commands (no command tools — see ADR-0007)
  • Modify Skill, deployment, or risk configuration
  • Read other users' or other deployments' data (route handler authorizes deployment ownership before loading)

Chat cannot issue commands

This is so important it gets its own subsection. The chat agent's tool whitelist deliberately excludes any tool that triggers an agent_commands insert. If a deployer says "ok flatten everything" in chat, the chat agent should:

  • Acknowledge the intent
  • Explain what flatten would do (it can simulate it via read tools)
  • Suggest clicking the Flatten button — but never trigger it

Rationale: prompt-injection resistance + audit clarity. See ADR-0007.

Execution Engine boundary

The Execution Engine is the only code path between an agent's ProposedAction and a broker adapter. Engineering rules:

  • The agent runtime package (packages/agent-runtime) must not import from packages/brokers/*
  • The tools package (packages/tools) must not import from packages/brokers/*
  • Only packages/execution-engine imports brokers
  • Only packages/execution-engine decrypts credentials

These are enforced by:

  1. ESLint rule: no-restricted-imports blocking the disallowed paths
  2. CI check: grep for broker imports outside the engine package
  3. Code review: any PR that touches packages/execution-engine or packages/brokers requires explicit reviewer attention to this rule

Secrets boundary

SecretWhere it livesWhere it's used
Supabase service role keyVercel env (server-only), Fly secretapps/web server, apps/live-runner
User exchange API keysDB (encrypted) → in-memory after decryptapps/live-runner only
Vault encryption keySupabase Vaultapps/live-runner (decrypt only)
AI Gateway keyVercel env / Fly secretAll processes that call models
User-provided BYOK provider keysDB (encrypted)Sent to AI Gateway as request header
News provider API keysVercel envpackages/data-ingest
Fly API tokenVercel envapps/web server (for provisioning)

Details: secrets.md.

Prompt-injection threat model

Agent context will routinely contain user-generated or external-source text (news, fills, possibly tool descriptions from MCP servers). Any of these is a potential injection vector. Mitigations:

VectorMitigation
News injecting "place order X"Engine validates every action; no risk-cap bypass possible
News injecting "tell deployer to flatten"Chat agent cannot issue commands; would only result in text suggestion the human still must act on
MCP server returning malicious tool descriptionCurated MCP catalog only; new servers require platform review
MCP server returning huge result to OOM agentResult size cap in MCP wrapper
Tool result attempting to escalate (e.g., "now ignore your system prompt")Engine still enforces risk caps regardless of agent state
Compromised model outputEngine acts as adversarial validator on every action

The Execution Engine is designed assuming the agent is adversarial. This is not paranoia — it is the only realistic stance given LLM behavior.

Common boundary violations to watch for in code review

  • A tool function calling a broker directly. Fix: route through Execution Engine.
  • A skill-editor server action writing agent_commands. Fix: commands originate from the deployment UI, not the editor.
  • A chat tool with modes: ['read', 'write']. Fix: chat is read-only; if the tool has any side effect, it doesn't belong in chat.
  • An app importing @supabase/supabase-js directly. Fix: go through packages/db.
  • The live runner reading from auth.uid() for authorization. Fix: runners use service role; user identity for command audit comes from agent_commands.user_id.
  • A new agent_commands.kind that bypasses an existing risk cap. Fix: any command that can affect risk goes through the Execution Engine, not directly to the broker.

On this page