Threat model¶
What an agent built on the adapter has to protect, where the trust boundaries are, what the adapter enforces on its own, and what stays your job. The short version of the controls is in Security; this page is the map for a security review.
Assets¶
| Asset | Why it matters |
|---|---|
| Side effects of tools | refunds, emails, deletes, payments: they cannot be taken back |
| Approval decisions | a forged or replayed "yes" runs a risky call |
| Money spent on models and paid APIs | a loop or an abusive user burns the budget |
| Personal data and secrets | in prompts, tool arguments, results, memory, logs and traces |
| Per-user memory and retrieval scopes | one user must never read another's notes or documents |
| Durable run state | a forged checkpoint could replay or skip a step |
| Run records and the audit trail | who did what, who approved it, what it cost |
Trust boundaries¶
Everything the model writes is untrusted, and so is everything a tool reads from outside.
flowchart LR
subgraph U["Untrusted"]
M["model output<br/>tool calls, text"]
X["tool results, web pages,<br/>files, recalled memory"]
C["remote callers<br/>HTTP, MCP, A2A"]
end
subgraph H["Your host process (trusted)"]
E["Executor<br/>validate · guards · budgets ·<br/>one-use approval · ledger"]
T["your tool code"]
S[("stores: approvals, budgets,<br/>memory, checkpoints, runs")]
end
R["reviewer"] -- "decision" --> S
C -- "auth" --> E
M --> E
X --> E
E --> T
E <--> S
T -- "code_tool" --> B["sandbox<br/>(Docker or hosted)"]
What the adapter protects¶
| Control | Guarantee | Details |
|---|---|---|
| Validation | every call is checked against the tool's schema before any code runs; bad values are not echoed | Security |
| One-use approvals | a confirm call runs only after the executor's port approves; a decision is bound to the run, call id, tool and argument digest and is used once; no port, a stop, a timeout or an error denies |
Approval |
| Framework approval never counts alone | a framework's "approved" only adds a gate; bridges pass a one-use token the adapter issued after its own port approved | Framework approvals |
| HTTP server auth | no authenticator is a startup error unless anonymous mode is explicit; ids are scoped to the caller; one owner per run; hooks fail closed | Security model |
| Viewer and chat page | 127.0.0.1 only, a new token per start, Host, Origin and CSRF checks on POSTs, a strict CSP, data shown as text; chat is off unless you pass an agent |
See your runs, Chat |
| Guardrails | PIIGuard and your guards block, redact or ask for approval on arguments, results, requests and replies; a failing guard blocks |
Guardrails |
| Budgets | BudgetHook refuses a model call before it is made, on an estimate; shared stores count every worker; a store error fails the call |
Team budgets |
| Sandbox | code_tool needs approval for every run; inputs, file names, output size and time are checked even if a backend ignores them |
Sandboxed code |
| Memory isolation | the host sets the scope from ctx.user_id, never the model; every backend's output is re-checked against the scope; recalled notes are fenced as untrusted |
Memory, Chat memory |
| Policy file | one file sets access, approvals, guards, budgets and limits per tool, team and environment; it only adds approvals, fails closed at load, and stays on the executor through replace(...) |
Policy file |
| Retrieval scope | filters come from the host; results that do not match are dropped | Retrieval |
| Checkpoint signing | every checkpoint and every logged call is signed (HMAC) and numbered; a changed or forged record, or a gap in the numbers, is CheckpointStoreCorrupt; deleting the newest records or checkpoint is not detected, so protect the store; calls in doubt need a decision |
Crash-safe runs |
| Audit trail | approval records keep the tool, the argument digest, the decision and decided_by; approval.*, budget.*, memory.* and checkpoint.saved events go to your sinks and the recorder; the audit log keeps a signed, hash-chained record you can verify and export |
Observability, Audit log |
| Log hygiene | logs carry exception types, not their text; LoggingHook never logs confirm arguments and redacts secret-looking keys; content stays out of sinks unless capture_content=True |
Observability |
What it does not protect¶
| Not covered | Why | Mitigate with |
|---|---|---|
| Model behaviour | the adapter checks calls, not judgement: a valid call can still be the wrong one | confirm= on risky tools, evals in CI, small tool sets per surface |
| Prompt injection in general | text from tools, files, pages or memory can steer the model; guards catch patterns, not intent | treat results as data, approvals on side effects, GuardrailHook with your classifier |
| Bugs in your tool code | a tool runs with your process's rights | least-privilege credentials, input checks in the tool, tests |
| A compromised host | stores, keys and the process itself are inside the boundary | OS hardening, a vault for keys, file permissions on stores |
| Shared caches | the LLM response cache keys on the request, not the caller; tool and node caches with scope="global" share results with every caller |
run or thread scopes for per-user data; no response cache where answers depend on who asks (Caching) |
| Side effects already in flight | a call cut off mid-way may or may not have happened; no adapter can fence the outside world | idempotent tools, idempotency keys at the remote system, RecoveryNeedsDecision checks |
| Local code on the same machine | anyone who can read the runs or chat folders, or the viewer link, sees that data | keep links private, folder permissions, capture_content=False |
| Network edge | no TLS, CORS or general rate limiting in the HTTP server | a reverse proxy |
What you must do¶
- Mark every tool with a side effect
confirm=True, and give the executor a real approval port with a durable store (Approval stores). - Authenticate callers and set
ctx.user_idfrom the authenticated user, never from the model or a request field the user controls. - Keep reviewers real: map users to reviewer roles; four-eyes where it matters.
- Set budgets per user or team with a shared store, and prices for your models.
- Run guardrails on arguments and outputs; add your own classifier for content rules.
- Run model-written code only through
code_toolon Docker (gVisor or a VM for hostile code) or a hosted sandbox. - Keep
checkpoint_key, bearer tokens and API keys in a vault. Use the same checkpoint key on every worker; checkpoints signed with another key read asCheckpointStoreCorrupt. - Protect the stores (approvals, budgets, memory, checkpoints, runs) like audit tables: permissions, backups, retention.
- Gate releases on evals, and review the Production checklist.
The golden path wires most of these into one small service.