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.

Loop: compile → drift → contain → attest Checks: DRF-001..011 Needs: no API key, offline

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.

attested designthe review default-deny policymcp-guard / cedar / gateway runtimethe guard reads it driftDRF-001..011 containnarrows only, journaled incidentsigned attestation compile events judged against the ORIGINAL policy the loop closes
The dashed edge is the load-bearing one. Enforcement acts on the runtime, but detection keeps reading the original attested policy, so a compromised runtime can trip its own containment and can never drive its own policy.

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:

CheckSeverityWhat it catches
DRF-001criticala server observed at runtime that the attested design does not contain
DRF-002criticala server the policy denies, invoked anyway
DRF-005criticalthe served tool manifest no longer matches the attested pin (a rug-pull)
DRF-006 / 007high / mediuma runaway identical-call loop; a server crossing its attested call-volume budget
DRF-011criticala 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

in-toto attestation framework and DSSE (the envelope the incident statement signs into)
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