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.

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:

attestral scan

attestral scan PATH [-o STEM] [--format md|json|both|sarif] [--llm] [--fail-on SEVERITY]
FlagBehavior
-o, --outputOutput file stem (default attestral-report).
--formatmd for the human report, json for the chained evidence, both (default), or sarif for GitHub Code Scanning.
--llmAdds 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-onExit 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:

Do not hand-edit a compiled policy. Change the design, re-run the review, re-compile. If policy and review can diverge silently, the evidence is worthless. That is the point of the binding.

attestral drift

attestral drift POLICY_FILE EVENTS_FILE [--fail-on-drift]
RuleSeverityFires when
DRF-001criticalA server appears in telemetry that is not in the attested design.
DRF-002criticalA server the review denied is invoked at runtime.
DRF-003highA filesystem path outside the attested roots is accessed.
DRF-004highA 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.

A waiver suppresses a finding from the --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.

The judge layer needs an API key and the 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"]
MatcherSemantics
attr_equalsAttribute equals value exactly.
attr_inAttribute is one of the listed values.
attr_missingAttribute is absent from the ingested design.
attr_starts_with / attr_containsString prefix / substring on the attribute.
attr_list_contains / attr_list_any_ofMembership tests over list attributes.
attr_any_containsAny of several needles across one or more attributes.
model_has_bothModel-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"]}
FieldRequiredMeaning
serveryesMCP server name as configured.
toolyesTool invoked.
argsnoArguments; paths are checked against attested roots.
urlnoTransport endpoint; checked against TLS constraints.
tsnoISO-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

Attestral is an open-source project. Everything documented above is the complete, current surface. Next up, roughly in order: a full HCL expression evaluator, more rule packs (Kubernetes, GCP, Azure), PR design-diffing, and import from legacy threat modeling exports. Issues and contributions are welcome on GitHub.