Documentation
Everything on this page covers the open-source CLI, licensed Apache 2.0. It is complete: nothing below requires an account, a key, or a server.
Pipeline
A scan flows through three stages: ingest into one model, review in layers (each finding tagged by origin), and commit to a tamper-evident evidence chain. The deterministic core is never silently mixed with model reasoning.
flowchart TB
TF["Terraform (.tf)"] --> M["SystemModel
components · edges · trust boundaries"]
MCP["MCP configs (mcp.json)"] --> M
M --> L1["L1 Deterministic rules
26 typed matchers · fail-closed"]
L1 --> L2["L2 ML classifiers (planned)
DeBERTa on agentic surfaces"]
L2 --> L3["L3 LLM: elicitation + judge
(optional, tagged origin: llm)"]
L3 --> W["Waivers
documented, expiring"]
W --> EV["Evidence: SHA-256 chain
verify offline"]
EV --> OUT["Markdown · JSON · SARIF"]
And the product loop: attest the design, prove the record, enforce it as a runtime policy, and detect divergence.
flowchart LR
A["scan (attest)"] --> B["verify (prove)"]
A --> C["compile (enforce)"]
C --> D["drift (detect)"]
D -->|"design changed? re-attest"| A
Install
pip install attestral
attestral --version
Python 3.10+. The core has two dependencies (click, pyyaml). Optional LLM elicitation adds the anthropic package: pip install "attestral[llm]".
Try the full loop on the bundled demo:
git clone https://github.com/attestral-labs/attestral && cd attestral
attestral scan examples/demo-project
attestral compile examples/demo-project -o policy.yaml
attestral drift policy.yaml examples/demo-project/runtime-events.jsonl
The loop
Attestral treats the design review, the runtime policy, and the audit evidence as one artifact in three forms:
- Attest.
scanbuilds a system model from Terraform and MCP configs and reviews it against typed rules. Findings are committed to a hash chain. - Enforce.
compileturns the reviewed model into a default-deny mcp-guard policy, stamped with the model hash and the review chain head. Your threat model stops being a document and becomes the configuration itself. - Prove.
driftdiffs runtime telemetry against the compiled policy. Anything the review never saw (a new server, a denied capability, an out-of-scope path) is drift, andverifyproves nobody edited the record afterward.
attestral scan
attestral scan PATH [-o STEM] [--format md|json|both|sarif] [--llm] [--fail-on SEVERITY]
| Flag | Behavior |
|---|---|
-o, --output | Output file stem (default attestral-report). |
--format | md for the human report, json for the chained evidence, both (default), or sarif for GitHub Code Scanning. |
--llm | Adds LLM threat elicitation on top of the deterministic layer. Requires ANTHROPIC_API_KEY. LLM findings are tagged origin: llm and never mixed silently with rule findings. |
--fail-on | Exit non-zero if findings at or above this severity exist. Works as a fail-closed CI gate. |
Ingestion is automatic: *.tf files feed the cloud model; mcp.json, *.mcp.json, and claude_desktop_config.json feed the agent-runtime model.
attestral verify
attestral verify report.json
Recomputes the hash chain offline and exits 0 (VALID) or 1 (INVALID). No network, no server, no account. An auditor can verify a two-year-old report on an air-gapped laptop.
attestral compile
attestral compile PATH [-o mcp-guard-policy.yaml]
Compilation is fail-closed by construction:
default: deny, so a server absent from the reviewed design is never allowed.- A critical finding against a server compiles to
allow: falsewith the rule id as the recorded reason. - Filesystem servers are narrowed to their attested roots; broad roots (
/,~,/home) deny until the design is re-scoped. - TLS-constrained servers observed with
http://URLs deny. - The policy header carries the model hash and review chain head, which binds the policy cryptographically to the review that produced it.
attestral drift
attestral drift POLICY_FILE EVENTS_FILE [--fail-on-drift]
| Rule | Severity | Fires when |
|---|---|---|
DRF-001 | critical | A server appears in telemetry that is not in the attested design. |
DRF-002 | critical | A server the review denied is invoked at runtime. |
DRF-003 | high | A filesystem path outside the attested roots is accessed. |
DRF-004 | high | A TLS-constrained server is observed over plaintext. |
--fail-on-drift exits non-zero on any finding, so drift checks run as a CI step or a cron job against yesterday's telemetry.
Code scanning (SARIF)
Scan with --format sarif and the review becomes SARIF 2.1.0, the format GitHub Code Scanning speaks. Findings show up in the Security tab and inline on the pull request, each mapped to a severity GitHub understands and tagged with its framework references.
attestral scan . --format sarif -o attestral
Wire it into a workflow and every push publishes findings straight to the Security tab:
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: attestral.sarif
A complete workflow ships in examples/github-actions/code-scanning.yml. The job needs security-events: write permission to upload.
Baseline & waivers
Real repositories start with findings. A waiver lets you accept a known risk and keep the gate green, without hiding anything: the finding stays in the evidence chain with its justification, so an auditor sees exactly what was accepted and why. Drop an attestral-waivers.yaml at your scan root and it is picked up automatically.
waivers:
- rule: ATL-005
component: aws_db_instance.app # or "*" for every component
reason: Encryption enforced at the storage layer; tracked in SEC-1234.
expires: 2026-12-31 # optional; waiver lapses after this date
Two rules keep waivers honest. A waiver with no reason is ignored, and an expired waiver stops suppressing, so a finding can only be silenced by a current, justified exception. In SARIF, a waived finding becomes a suppression, which GitHub Code Scanning shows as dismissed rather than an open alert.
--fail-on gate. It does not remove it. The whole point of the evidence chain is that accepted risk stays on the record.LLM-as-judge
Deterministic rules fire on patterns; some are false positives in context. The judge is an optional layer that cross-examines each finding. It sees the finding plus its component and returns a structured verdict, confirmed, false_positive, or needs_review, with a confidence and reasoning. Verdicts are recorded on the finding and carried into the evidence chain, so the judgment itself is auditable.
export ATTESTRAL_JUDGE_API_KEY=... # or reuse ANTHROPIC_API_KEY
attestral scan . --judge --judge-panel 3 # 3 judges vote per finding
attestral scan . --judge --judge-suppress
The judge never deletes a finding. By default it only annotates. With --judge-suppress, a high-confidence false_positive becomes a machine-generated waiver whose reason is the judge's reasoning: suppressed from the gate, but kept on the record like any human waiver. A panel of judges (--judge-panel N) votes, and the majority verdict wins, which blunts any single model's error.
anthropic package (pip install "attestral[llm]"). Like the elicitation layer, its output is clearly separated from the deterministic core.Writing rules
Rules are YAML with structured matchers. There is no eval anywhere, and an unknown matcher fails closed: the rule simply never matches.
rules:
- id: ORG-001
title: Internal ALB missing auth attribute
severity: high
target: aws_lb # component type prefix, or "model"
match: { attr_missing: auth }
description: ...
recommendation: ...
frameworks: ["NIST AC-3", "SOC2 CC6.1"]
| Matcher | Semantics |
|---|---|
attr_equals | Attribute equals value exactly. |
attr_in | Attribute is one of the listed values. |
attr_missing | Attribute is absent from the ingested design. |
attr_starts_with / attr_contains | String prefix / substring on the attribute. |
attr_list_contains / attr_list_any_of | Membership tests over list attributes. |
attr_any_contains | Any of several needles across one or more attributes. |
model_has_both | Model-level: both component type prefixes exist in the design. |
Load custom packs alongside the core pack via RuleEngine(["org_rules.yaml"]).
Evidence chain
Every run commits its findings to a SHA-256 hash chain: entry N hashes the canonical JSON of finding N together with the hash of entry N-1, starting from a zero genesis. The final hash (the chain head) appears in the report header and in every compiled policy.
The consequence: altering, inserting, or deleting any past entry changes every subsequent hash. A reviewer who records the chain head (in a PR comment, a ticket, an email) has permanently committed the whole review.
Telemetry format
drift reads JSONL, one event per line. mcp-guard emits this format natively, and anything else can adapt to it in a few lines:
{"ts": "2026-07-10T14:01:02Z", "server": "docs", "tool": "read_file", "args": ["/srv/docs/design.md"]}
| Field | Required | Meaning |
|---|---|---|
server | yes | MCP server name as configured. |
tool | yes | Tool invoked. |
args | no | Arguments; paths are checked against attested roots. |
url | no | Transport endpoint; checked against TLS constraints. |
ts | no | ISO-8601 timestamp, carried into drift findings. |
GitHub Action
Run drift checks on a schedule so conformance is continuous, not annual:
- uses: attestral-labs/attestral@v1
with:
policy: policy.yaml
events: events.jsonl
A complete nightly workflow (pull telemetry, re-compile the policy from the reviewed main branch, drift-check) ships in examples/github-actions/drift-schedule.yml. The mcp-guard telemetry emitter lives in integrations/mcp-guard/.
Terraform extra
The core ships a dependency-free Terraform scanner. For full-fidelity HCL parsing (nested blocks, correct types), install the extra. Attestral picks it up automatically and falls back gracefully on malformed files:
pip install "attestral[terraform]"
Roadmap
Everything documented above is the complete, current surface (26 deterministic rules, SARIF, waivers, and the LLM-as-judge layer all ship today). Next up, roughly in order: an ML classifier layer (a local DeBERTa model for prompt-injection and capability classification on agentic surfaces, tagged origin: ml), more rule packs (Kubernetes, GCP, Azure), deeper HCL resolution (variables, modules), and PR design-diffing. Issues and contributions are welcome on GitHub.