Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SEO.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ that page's frontmatter, never here.
| `docs/production/recovery` | agent crashed mid action | A restarted process repairs nothing and cannot know the holder is dead. |
| `docs/production/receipt-integrity` | verify receipt chain | Run ctrlrun receipts --verify-chain and read the six names it can report. |
| `docs/production/anchoring` | anchor receipt chain outside database | Anchor the chain's head where your database's writer cannot reach it, and what that does not prove. |
| `docs/production/retention` | delete old receipts hash chain | Prune a prefix of the receipt chain and still verify across the gap, or be refused. |
| `docs/production/soak` | ctrlrun soak test results | One published run, its measured duration, and the exit criterion it does not meet. |
| `docs/production/operations` | ctrlrun monitoring | Watch how many effects are sitting in an unknown outcome that nobody has answered. |
| `docs/mcp/overview` | MCP gateway human approval | CTRLRun works with MCP in four ways. |
Expand Down
6 changes: 6 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"docs/production/recovery",
"docs/production/receipt-integrity",
"docs/production/anchoring",
"docs/production/retention",
"docs/production/soak",
"docs/production/operations"
]
Expand Down Expand Up @@ -491,6 +492,11 @@
"destination": "/docs/production/anchoring",
"permanent": true
},
{
"source": "/production/retention",
"destination": "/docs/production/retention",
"permanent": true
},
{
"source": "/production/soak",
"destination": "/docs/production/soak",
Expand Down
4 changes: 2 additions & 2 deletions docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ the framework's own interrupt, and a framework with no such primitive does not n

{/* generated from the suite, pyproject and the soak (mdx) — run the generator */}
- **Version 0.10.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14.
- **6,158 tests**, every version specified before it was written and every requirement mutation-tested.
- **29 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates.
- **6,201 tests**, every version specified before it was written and every requirement mutation-tested.
- **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates.
- **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite.
- **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak).
- **Each receipt carries the hash of the one before it**, so an alteration is detected and named.
Expand Down
48 changes: 24 additions & 24 deletions docs/CLAIMS.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/OWASP-AGENTIC-TOP10.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ mechanism, not the entry.
| **G26** a hop is named on both sides | The receipt of an action taken under a hop names the hop, and so does the record of the hop's creation, so an action and the delegation that authorised it are joined from either end without inference. | `ASI07:2026` (partly), `ASI10:2026` (partly), `ASI03:2026` (partly) | Attribution across a hop was reconstruction before this: a reader had to match timestamps and principals and hope. It is evidence, not prevention, and it is on the receipt rather than in a log that can be rotated away. A hop created *inside* an action is not named on its creator's receipt; the `DELEGATION_CREATED` event carries the `action_id` and holds the join instead. |
| **G27** a swapped upstream is denied | An action entry may pin the upstream it authorises, by the SHA-256 of the server's leaf certificate or by the hash of a tool's advertised schema. A server that is not the pinned one, or a tool whose schema moved under an approved action name, is refused `upstream_mismatch`; an upstream nothing observed is refused `upstream_unverified` and never admitted. | `ASI02:2026` (partly), `ASI07:2026` (partly) | An approved action name is a name, and until v0.10 nothing checked that the thing answering to it was the thing that was approved. Enforced by `ctrlrun gateway`, the surface that holds the connection: at startup, at the decision, and at the TLS handshake. In-process there is no upstream to observe, so a pinned action refuses on every call, which is fail-closed and is why `ctrlrun verify` skips a pinned action unless a scenario asks for it by name. This is one slice of a supply chain and not the category: nothing here inspects a package, a model, a build or a signature chain. |
| **G28** truncation past an anchor fails | The chain's head is a row in the same database, so erasing the end of the log and updating that row is two statements and the chain reports itself intact. An anchor records the pair the head holds (`seq` and the hash at it) through a provider **you** supply, outside the store, and anything at or below an anchored `seq` can then no longer be removed or altered without the anchored pair failing to reproduce. Reported as `anchor_broken`, `anchor_missing` or `anchor_repudiated`, in the anchor's own report. | `ASI09:2026` (partly) | **An anchor freezes a prefix, and the limits are the point.** An **append is not detected**: a forged receipt lands above every anchored `seq`, so nothing stops reproducing and the next anchor freezes it like any other. Receipts written and erased entirely between two anchors are not detected either. It is **not a signature** and says nothing about who wrote the log, and an administrator who rewrites everything before the next anchor is still out of scope. The window you are exposed to is `(last anchored seq, current head]`, and its size is your choice of interval: that is the number to tune, and the number to quote instead of any sentence about tamper-evidence. CTRLRun ships **no** anchor provider, because an RFC 3161 client is a network client; the anchor is worth exactly what the record you point it at is worth, and one in the same directory as the database is worth nothing. |
| **G29** a prune adds no new chain break | Receipts accumulate, and deleting them breaks the chain by design. `ctrlrun prune` removes a **prefix** and leaves a checkpoint the reader seeds from, so the chain verifies **across** the gap. It refuses rather than warns: a prune that would leave a `(kind, seq)` pair the store did not already report, one through the head, one moving the checkpoint backwards, or one deleting a budget ledger row whose charge is still held. There is no `--force`. | `ASI09:2026` (partly) | Retention and evidence pull against each other, and the honest answer is that a prune is the only operation here that **destroys** evidence: what it deletes is gone. What this makes true is that the deletion is bounded and visible rather than silent, and that a deletion nobody can verify around is **refused** rather than completed with a warning. The rule is a **delta** and not a promise that the chain verifies afterwards: a store carrying `unchained` rows from before v0.6 can still be pruned, because the alternative is retention being impossible on exactly the oldest stores. It does not prune events, approvals, delegations or continuations, and nothing runs on a schedule. |
| **G30** a held range refuses to prune | `ctrlrun hold place` names a range of receipts and a reason, and any prune overlapping it is refused with the hold named. | `ASI09:2026` (partly) | The case this is for is a legal hold arriving in the middle of a retention schedule, and the failure it prevents is a scheduled job quietly deleting what somebody has just been told to keep. **A hold has no expiry**: one that lapsed on a timer would release evidence on a schedule nobody reviewed, so a person places it and a person ends it. What it does not do is stop anyone with database access from deleting rows directly; it binds `ctrlrun prune`, not `DELETE`. |
| **G32** an honest prune keeps anchors | A prune anchors its checkpoint **before** it deletes anything, and an anchor at or below an anchored checkpoint is then **superseded** rather than broken. | `ASI09:2026` (partly) | Without this the two features cancel: anchoring hourly and pruning at ninety days, every anchor older than the retention window would be permanently `anchor_broken`, and a deployment would have to choose between pruning and a permanent tamper signal. The half that stops *superseded* becoming a hole is that the checkpoint must itself be anchored, through the provider, which is outside the store: an attacker who erases a prefix and writes a checkpoint to explain it has to leave a record of the prune in the operator's own anchor history. **A prune stays visible even though the receipts are gone**, which is the whole of what retention owes evidence. |
| **G31** five receipt schemas verify | One receipt chain can hold every schema version a store has been written under, and `ctrlrun verify` walks it end to end, hash by hash, **each row hashed by the rule its own version wrote**. A store kept since v0.6 holds five: `v3`, `v4`, `v5`, `v6`, `v7`. A receipt whose schema label this binary does not know is **named** and is not reported as a break. | `ASI09:2026` (partly) | The receipt chain has been checkable since v0.6, but nothing proved it stayed checkable **across an upgrade**, which is the only interesting case: a chain that verifies on the day it is written and stops verifying two releases later is evidence with a shelf life nobody stated. The proof is built from the released wheels rather than from fixtures, because a fixture is this build's opinion of what 0.6 wrote. What it does not close is everything `G11` does not close: this is about whether the record can still be **read and recomputed** years later, not about who wrote it, and an administrator who rewrites every row including the head is still out of scope. Nor does it make a future version's fields intelligible: an unknown label is named so an operator knows which row this binary could not fully interpret, which is a different sentence from *this row was tampered with*. |

---
Expand Down
4 changes: 2 additions & 2 deletions docs/OWASP-SOLUTIONS-LANDSCAPE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ stages from that one point rather than covering each in its own right.
| Stage | Status | What CTRLRun has there | Since |
|---|---|---|---|
| Scope & Plan | Partly | A published threat model of the execution boundary ([THREAT_MODEL](/docs/THREAT_MODEL)), and a policy document that *is* the plan for what an agent may do. Nothing that models *your* agent for you. | v0.1 |
| Develop & Experiment | Yes | `@protect` on any function in the process; `ctrlrun scan` reports the consequential call sites a policy does not cover. | v0.1, scan since v0.6 |
| Develop & Experiment | Yes | `@protect` on any function in the process; `ctrlrun scan` reports the consequential call sites a policy does not cover, and `ctrlrun scan --coverage` reports what a store shows was declared and never exercised. Both are lists with reasons, not scores. | v0.1, scan since v0.6, coverage since v0.11 |
| Augment & Fine Tune Data | No | CTRLRun never touches training data, models or memory. | none |
| Test & Evaluate | Yes | `ctrlrun verify` runs the kernel's own failure scenarios against your configuration and reports pass, fail or **not applicable** per guarantee ([verify](/docs/verify)). | v0.4 |
| Release | Yes | The `ctrlrun verify` GitHub Action and badge as a release gate; the badge means *declared guarantees pass*, never "this agent is secure by inspection". | v0.4 |
Expand Down Expand Up @@ -88,7 +88,7 @@ example is theirs and is not a claim that CTRLRun uses it.

| Checkbox | Status | What it means here | Since |
|---|---|---|---|
| Perform SAST/DAST on agent planning code, tool wrappers, & plugin interfaces | Partly | `ctrlrun scan` reads a Python tree and reports the consequential call sites and policy entries CTRLRun is *not* covering. It is a coverage scanner, not a vulnerability scanner, and its report says what it misses by construction on every run. | v0.6 |
| Perform SAST/DAST on agent planning code, tool wrappers, & plugin interfaces | Partly | `ctrlrun scan` reads a Python tree and reports the consequential call sites and policy entries CTRLRun is *not* covering, and since v0.11 `--coverage` adds the runtime half: what the store shows was declared and never exercised. It is a coverage scanner, not a vulnerability scanner, and its report says what it misses by construction on every run. **Neither half produces a number**: a policy entry nothing exercised may be correctly unused, and saying otherwise would be grading the operator's document. | v0.6, v0.11 |
| Harden agent loop logic against infinite loops, unsafe function routing, & unauthorized self-modification | Partly | A retry loop cannot turn one intended effect into several (`G3`, `G5`), an unknown action is refused (`G6`), and renewal after `FAILED` has an operator-set ceiling (`G15`). Nothing here inspects loop logic or prevents self-modification. | v0.1, v0.7 |
| Validate connector (e.g., MCP) contracts (input/output schemas & permissions) | Partly | The gateway maps every MCP tool call onto a policy decision, and a policy entry may pin the hash of an upstream's advertised tool schema so a schema that moved under an approved name is a `deny`. It does not validate schemas in general. | v0.2, v0.10 |
| Implement policy enforcement hooks in Frameworks (e.g. LangGraph, CrewAI, Others) | Yes | `@protect` for anything in-process; the adapter contract with OpenAI Agents SDK and LangGraph reference adapters, each routing an `approve` through the framework's own interrupt; the OWASP Agent Control Standard adapter ([ACS](/docs/ACS)). | v0.1, v0.2, v0.5 |
Expand Down
Loading