> ## Documentation Index
> Fetch the complete documentation index at: https://reversi.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Threat model

> What Reversi defends against, what it assumes, and what it does not cover.

## Assets

The ledger (what happened and who decided), approvals, run state, API keys and the signing key, and the downstream systems
the tools act on.

## Actors

| Actor | Trust |
| - | - |
| The agent (and the model behind it) | **Untrusted.** It may be confused, manipulated by injected content, or malicious |
| Tool results and upstream MCP servers | Untrusted input. A result can carry injection text |
| Approvers, operators, admins | Trusted to decide, accountable through the ledger |
| Whoever controls the host, database or key files | Fully trusted. Reversi cannot defend against them |

## Threats and mitigations

| Threat | Mitigation | Residual risk |
| - | - | - |
| Agent calls a tool it should not | Manifest required, scope check, policy, allow/deny lists; enforce mode blocks | An unmanifested tool in observe mode runs. A wrong manifest grants too much |
| Agent reports false outcomes over the HTTP API (a made-up `result` becomes a compensation argument; a false `failed` hides a step from rollback) | Use the [gateway](/gateway): there the results come from the upstream, not the agent. Over HTTP, operators should review the plan before rolling back | **Accepted risk on the HTTP plane**: it trusts the agent's own reports |
| Agent chooses `observe` mode to dodge enforcement | Agent keys open `enforce` runs only unless `allow_observe` is set | A lab key with `allow_observe` |
| Agent approves its own call or triggers a rollback | Role separation; operator tools on a separate endpoint with separate credentials | Compromised operator credentials |
| Approved call swapped for another | Approvals bound to the argument hash (calls whose arguments cannot be canonicalized are blocked), single use, claimed atomically in the store, expiring; every decision is written to the ledger with the approver's name | Approvers who approve without reading |
| Privilege escalation through scope | Scope sealed with a digest; key `max_permissions` caps | Over-broad caps |
| Prompt injection or exfiltration through arguments | Content checks (secrets, encoded payloads, injection, cumulative intent) with flag, approval or deny | Heuristics can be bypassed by creative encoding or paraphrase |
| Credentials leak into the audit trail | Arguments and results are hashed, not stored; free text redacted | Unusual credential formats not matched by the patterns |
| Ledger edited or deleted | Hash chain; append-only triggers in SQLite/PostgreSQL; signed bundles | Tail truncation and whole-store rewrite by a privileged actor: anchor the head externally |
| Crash between intent and outcome | Write-ahead intent; unresolved steps reported, never guessed | Needs a human to reconcile |
| Failed or partial compensation | Reported honestly, retried with the same idempotency key, optional verification probe | Some effects cannot be undone |
| Stolen API key | Hashed at rest, rotation windows, expiry, disable, constant-time compare | A key is a bearer credential: use TLS and short lives |
| Forged or confused OIDC token | Signature against JWKS, RS256/ES256 only, `iss`, `aud`, `exp`, `nbf`; unmapped roles rejected | Trust in the identity provider |
| Web page driving a local gateway or console | Host and Origin validation; SameSite=Strict cookies; CSRF tokens; strict CSP. The open console (no login) answers only loopback hosts from the same origin, refuses to start on a non-loopback address, and is never admin | Misconfigured `allowed_hosts`/`allowed_origins`. Any other process or user on the same machine can reach an open console |
| Console session theft or forgery | Signed HttpOnly cookies, 8 h lifetime, re-validated against the key ring every request, revoked on logout, per-session CSRF | XSS in a future page: output is escaped everywhere today |
| An approval is stuck during an incident | Break-glass: admin only, a reason of at least 10 characters and an evidence reference, marked in the ledger and the console (decision note and a break-glass badge) | An admin can still abuse it; it is visible afterwards, not prevented |
| An approver is unavailable | Delegation for a limited time, recorded; a delegate cannot add a second vote | A delegate with the wrong judgement |
| An MCP server's annotations lie (a mutating tool marked read-only) | Annotations only seed draft manifests and the banner. Tools without a manifest are blocked in enforce mode. With `unclassified_reads: observe` a claimed read-only tool runs in a shadow run labelled "unverified" | With that option on, a lying server can run a mutating tool unblocked |
| An upstream changes a tool's schema after review | `schema_hash` in the manifest: a changed input schema is a violation until the manifest is reviewed again. Arguments and structured results are checked against declared schemas; a result that breaks its schema is recorded and not used to build compensation arguments | Manifests without a `schema_hash` are not drift-checked. The schema checker is a subset of JSON Schema |
| A manifest or policy is changed without review | Registry versions with author, approver, checksum and optional Ed25519 signature; drafts are not enforceable; four-eyes approval; `manifest_public_key` makes loading fail unless signed; `reversi apply` and the console record who published | Whoever holds the signing key or an admin credential. Policy versions are recorded but not signed |
| Reversi is pointed at internal addresses (webhooks, anchors, trace export, upstream URLs) | `egress:` host allowlist; cloud metadata addresses always refused; `block_private` refuses loopback and private ranges; upstream URLs are checked at gateway start | `block_private` is off by default so local development works |
| Evidence is deleted in the name of retention | Retention deletes run state, approvals and captures only, never ledger records; legal holds block it; an optional signed bundle is archived first | An administrator with direct store access |
| Runaway agent | Kill switch (API, console, CLI, file), global or scoped by tool, actor, tenant or environment; rate and budget limits | Latency until an operator notices |
| Two writers fork the ledger | `seq` primary key; losers retry or fail closed | None known |
| Upstream MCP server compromised | Nothing: it is a trusted tool provider. Its results are content-checked for injection and schema-checked | A malicious upstream can lie about results |
| Supply chain | Lockfile, dependency audit and SBOM in CI, provenance on release | Not independently reviewed |

## Out of scope

Defending the host, the database or the signing key from their administrators. Discovering what a tool really does. Preventing
an agent from using a path to the tools that does not go through Reversi. Denial of service by authenticated principals beyond
the body-size limit and configured rate limits.

Report vulnerabilities as described in `SECURITY.md`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.