Field notes / The runtime loop
The review is not a report. It is the policy.
A design review that ends at a PDF stops mattering the day the deployment changes. Attestral compiles the attested design into the runtime's own policy, judges every runtime event against it, contains divergence live, and signs the incident record. This page walks the loop end to end.
Every stage of the loop is built on the same three invariants, and they are worth stating before any command. Detection never moves: every runtime event is judged against the original attested policy, never against whatever the runtime has become. Enforcement only ever narrows: every automatic action carries a machine-checked proof that it removes capability and grants none, and an action that would widen is refused. Everything lands on a hash chain: pushes, refusals, and the final incident record are tamper-evident, the same discipline as the scan's evidence chain.
The review is the source of truth the runtime is held to. Not a snapshot of what was once true; the policy that decides what is allowed to be true.
Stage one
compile: the review becomes the policy
attestral compile turns the attested design into a default-deny runtime policy: every MCP server the review allowed becomes an explicit allow with its attested capability envelope, filesystem roots, and manifest pin; everything else is denied. A finding raised to critical by a reachable attack chain denies its server here too, so the policy inherits the review's judgment, not just its inventory.
Three targets share one policy model: mcp-guard (the default, and the format the drift stage reads back), cedar (an AWS Cedar authorization policy; lossy and one-way, it cannot serve as a prior), and agentgateway (a CB4A credential-broker config, so a server that held a standing secret is re-issued short-lived, per-call, provider-scoped tokens instead).
# attestral compile examples/attested-agent -o mcp-guard-policy.yaml wrote mcp-guard-policy.yaml · target mcp-guard · default deny · 2 allowed, 0 denied [ALLOW] internal-tools [ALLOW] web-fetch
The gate that makes re-attestation safe is --against. Point a new compile at the prior policy and Attestral classifies the change: NARROWING, UNCHANGED, or EXPANSION. An expansion, any new server, capability, or dropped constraint, exits non-zero, because a design that grants more than the reviewed one must go back through a human, never silently through CI.
# attestral compile . -o policy.yaml --against mcp-guard-policy.yaml re-attestation vs mcp-guard-policy.yaml: EXPANSION this design grants more ambient capability than the reviewed one; a human must approve it before it runs: + internal-tools: gained capability ['shell'] # exit 1 - the widened design does not ship
For the properties that must hold regardless of what the design says, --verify (or --fail-on-violation as a CI gate) proves them over the compiled policy directly, no secret exfiltration, no code-exec egress, default-deny, TLS-only, and prints a counterexample for any that fail.
Stage two
drift: every event, judged against the original
attestral drift POLICY EVENTS diffs runtime telemetry against the compiled policy. Batch mode reads a recorded JSONL file; --stdin runs as a continuous sidecar on a live mcp-guard telemetry pipe; --watch tails the events file and streams drift as lines append. In every mode the reference never moves: each event is evaluated against the original attested policy, so detection is always a comparison to the reviewed design, not to the runtime's own recent behavior.
Eleven checks make up the DRF family. The ones that carry the story:
| Check | Severity | What it catches |
|---|---|---|
| DRF-001 | critical | a server observed at runtime that the attested design does not contain |
| DRF-002 | critical | a server the policy denies, invoked anyway |
| DRF-005 | critical | the served tool manifest no longer matches the attested pin (a rug-pull) |
| DRF-006 / 007 | high / medium | a runaway identical-call loop; a server crossing its attested call-volume budget |
| DRF-011 | critical | a credential-brokered server reached without the broker (bypass) |
# attestral drift mcp-guard-policy.yaml runtime-events.jsonl --fail-on-drift [CRITICAL] DRF-001 Unattested server observed at runtime (mcp_server.helper-x) [CRITICAL] DRF-005 Tool manifest changed since attestation (rug-pull) (mcp_server.web-fetch) [HIGH ] DRF-006 Runaway tool-call loop (resource drain) (mcp_server.web-fetch) 42 events · 3 drift findings DRIFT: deployment no longer matches the attested design # exit 1 - the CI / cron gate
Stage three
drift --remediate: propose the narrowing, never the widening
A drift finding means the runtime diverged from the reviewed design. --remediate answers with the minimal tightening that would have prevented each finding: quarantine the offending server toward denial, re-emitted for both compiled targets. It is a proposal, a human reviews and re-compiles. And it is one-directional by construction: the proposal is re-classified through the same narrowing check as --against, and a result that would widen is refused outright. The tool will never "fix" drift by amending the design to match what the runtime started doing.
# attestral drift mcp-guard-policy.yaml runtime-events.jsonl --remediate [CRITICAL] DRF-008 Unauthorized runtime capability / process spawn (mcp_server.internal-tools) 5 events · 1 drift findings PROPOSED tightening - a human reviews and re-compiles. PROPOSE DRF-008 internal-tools: allow True->False (quarantine: exercised capability 'shell' outside its attested envelope ['filesystem']) narrowing check: narrowing
Stage four
drift --lockdown --enforce: containment, live
When drift is an incident in progress, a proposal is too slow. --lockdown builds the containment action, the enforcement policy with every drifted server quarantined, and --enforce pushes it atomically (temp file plus rename, so the guard never reads a torn write) to the live policy file a running mcp-guard reads. --reload-cmd can nudge the guard to re-read it. The exit code is part of the interface: a pushed lockdown exits 2, the responder signal, distinct from the generic drift gate's 1.
What licenses automation here, and what a bare kill-the-server script cannot claim, is the narrowing proof. The locked-down policy is verified to only remove capability from the reviewed design, so applying it can at worst deny a server, never escalate one. That is why lockdown is the one drift response safe to run without a human in the loop, and it is enforced, not assumed:
Narrowing verified
The lockdown is pushed to the enforcement point and appended to the hash-chained journal. A compromised runtime that triggers it can, at worst, deny itself.
Would widen
REFUSED. Nothing is pushed, the refusal itself is journaled, and the monitor keeps running. The enforcement point never receives a policy that could widen the attested design.
# mcp-guard telemetry | attestral drift mcp-guard-policy.yaml --stdin \ # --lockdown --enforce live-policy.yaml [CRITICAL] DRF-008 Unauthorized runtime capability / process spawn (mcp_server.internal-tools) LOCKDOWN pushed: quarantined internal-tools -> live-policy.yaml 5 events · 1 drift findings (stream ended) # exit 2 - the responder signal; trail in live-policy.yaml.journal.jsonl
Every push and every refusal is appended to <ENFORCE>.journal.jsonl, a hash-chained, append-only journal in which each entry commits to the whole chain behind it. Altering, removing, inserting, or reordering any line breaks verification. The journal is not logging; it is the containment trail the next two stages are built on.
Stage five
drift --replay: when did it diverge, and what contained it
After the incident, the questions change tense. --replay re-runs a recorded stream through a fresh monitor so every finding lands at the exact event ordinal and timestamp where it crossed, and --journal merges the containment trail into the same chronological timeline, chain verified first. The summary answers what a responder actually asks: when was first drift, how long until containment, does the journal hold, and did the run end CONFORM, DRIFTED, or CONTAINED.
# attestral drift mcp-guard-policy.yaml events.jsonl --replay \ # --journal live-policy.yaml.journal.jsonl REPLAY - incident reconstruction: 5 event(s) against the attested design #4 09:00:12 DRF-008 internal-tools: exercised capability 'shell' outside its attested envelope ['filesystem'] #5 09:00:14 LOCKDOWN pushed quarantined: internal-tools incident summary events 5 drift findings 1 (DRF-008 x1) first drift #4 at 09:00:12 (DRF-008) containment pushed at 09:00:14 - 2s after first drift journal chain intact (1 entry) final state CONTAINED
The fail-closed rule carries through: a journal whose hash chain does not verify is a headline of the reconstruction, not a footnote. It earns no containment credit, the final state stays DRIFTED, the gap stats are withheld, and its entries render as claims, reported but never trusted. A rewritten trail cannot launder an incident into a contained one.
Stage six
incident: the reconstruction, signed
A replay on a terminal convinces the person who ran it. attestral incident makes it convince everyone else. It performs the same reconstruction as --replay and binds it into a DSSE-signable in-toto Statement (predicate attestation/incident/v1): the digest of the compiled policy the stream was judged against, the event stream's digest, the containment journal's verdict and chain head, and a digest of the full replay timeline, so an edit to any moment, not just the headline verdict, breaks the binding.
# attestral incident mcp-guard-policy.yaml events.jsonl \ # --journal live-policy.yaml.journal.jsonl --key signer.key -o incident.json wrote incident.json Incident attestation CONTAINED first drift: DRF-008 at event #4 at 2026-07-18T09:00:12Z findings 1 · pushes 1 · refusals 0 · journal intact signed · binds policy digest, event stream, journal chain head, and the full reconstruction # attestral incident ... --verify --public-key signer.pub -o incident.json incident attestation VERIFIED - the supplied policy, events, and journal reproduce exactly this reconstruction
--verify re-derives everything offline from the supplied policy, events, and journal: a doctored event line, a rewritten journal, or an edited verdict fails by recomputation, never by trust. A tampered journal is attested as tampered. Optional --rekor anchors the signed attestation in Sigstore Rekor, a public transparency log outside your control, so a later rewrite has an external witness to contradict it. An unsigned bundle still binds every digest; the signature adds authorship, not integrity.
The loop closes: attested design, compiled policy, live containment, attested incident. The record of the incident carries the same tamper-evidence as the design review it descends from.
Honest limits
What the loop does and does not claim
What it holds
Any event that reaches the telemetry stream is judged against the reviewed design, contained by a provable narrowing, and reconstructable offline to the byte. Conformance is checkable by a third party.
What it cannot see
An event that never reaches the stream. The loop polices the MCP surface the guard mediates, not arbitrary processes beside it, and conformance to a design is not proof the design itself is safe. That is the scan's job.
One more limit is deliberate. Because remediation and lockdown only ever narrow, a legitimate capability expansion cannot enter through the runtime loop at all; it must go back through review and re-attestation, where --against will classify it and a human will sign off. That friction is the point: the runtime can lose capability automatically and can gain it only on purpose.
Keep reading
Where the attested design comes from
Everything this loop enforces is produced upstream by the review: the deterministic layer that builds the system model and the findings, the reachability walker that proves which attack paths are traversable, and the optional ML and judge layers. The runtime loop is what makes that review permanent.
References
AWS Cedar policy language (the second compile target)
Sigstore Rekor transparency log (the optional public witness)
The code:
attestral/compile.py, attestral/drift.py, attestral/lockdown.py, attestral/incident.py, attestral/narrowing.py