Skip to main content
An assurance case is the argument, with its evidence, that a system meets its security requirements. This page is CTRLRun’s. It adds no guarantee; it says why the ones the threat model states can be relied on, and where the evidence for each sits. Read it beside that page and the architecture.

The requirements

The threat model names three assets, and each one is a requirement on the kernel: Everything below argues these three. What CTRLRun does not defend against is listed in the threat model under Out of scope and is not argued here.

The trust boundary

Untrusted: agent reasoning, model outputs, tool outputs, retry logic, other agents, and every token or header a caller presents. Trusted: the CTRLRun process, the policy file, the state store, and the approver’s shell. Data crosses the boundary in one place: the proposed action with its arguments, and whatever identity the caller presents. Nothing the agent asserts is trusted. Everything it proposes is normalized, checked against the policy, and checked against authority before anything is reserved. Inside the boundary the kernel trusts its own store, and the receipt chain (R3) is what lets an operator check that trust afterwards. The boundary has an edge the model states plainly: a compromised host, process or Python environment is inside it, and so is an administrator with write access to the policy or the store. The kernel does not defend against itself.

Design principles, applied

Saltzer and Schroeder’s principles, each with where it shows in the design and where a regression would be caught.

Implementation weaknesses, countered

The common ways a sound design goes wrong in code, what the kernel does about each, and where a regression would be caught.

The evidence

  • The suite. Every specification’s acceptance section has tests; the README’s tests badge carries the live count. CI runs it on four Python versions with a Postgres service, and a separate step proves the Postgres tests will actually run rather than skip.
  • Mutation tables. For every MUST a change touches, the guard is removed, its test is confirmed red, and the guard is restored, with the table in the pull request. CONTRIBUTING.md, Tests first, lists the four shapes of a false green the tables are checked against.
  • Fuzzing. The canonicalizer and the policy parser run under Atheris on every pull request and weekly, asserting the invariants in fuzz/properties.py.
  • Static analysis. CodeQL, ruff with the bugbear rules, and mypy --strict.
  • Coverage. Measured in CI and held at the floors CONTRIBUTING.md states.
  • ctrlrun verify. The eleven guarantees, replayed against the operator’s own policy and grants in a scratch instance of the configured store backend, with fake executors and no network.
  • Review. Every change is reviewed by a person who did not write it. A change to authorization, identity, delegation, the gateway, an adapter or the store gets the deeper review CONTRIBUTING.md describes before its pull request opens.
  • Provenance. Releases carry signed SLSA provenance and PyPI attestations, and CONTRIBUTING.md says how to rebuild a release and compare it.

What this argument does not cover

  • Everything under Out of scope in the threat model: a compromised host, process or environment; an administrator with write access; a compromised remote or identity provider; a wrong approver; an executor that raises NotExecuted when it did execute.
  • No external review yet. Every review so far ran inside the project, by sessions that did not write the code under review. How this is built says so, and will carry the report if an external review happens.
  • The argument is for the kernel. An adapter or a gateway deployment adds a boundary of its own. The adapter conformance kit checks an adapter’s behaviour; ctrlrun verify checks only the kernel guarantees the configured policy, grants and store backend can exercise.

Next