Documentation

Reference for the open-source Attestral CLI, the security scanner for AI agents and MCP servers. Everything here is licensed Apache 2.0 and complete: nothing below requires an account, a key, or a server.

# install and run the whole loop, offline, in under a minute
pip install attestral
attestral scan ./my-project               # review the design (16 findings on the demo)
attestral validate ./my-project           # show the attack path is reachable
attestral verify review.json              # chain VALID, offline
attestral compile ./my-project -o policy.yaml     # turn the review into a default-deny policy
attestral drift policy.yaml events.jsonl  # catch when runtime diverges from the design

Prefer clicking to installing? Run the whole loop live in the Playground, real CLI output streamed in your browser. Or jump to the loop, adversarial validation, or the rule pack.

See it run

Every panel below is real output from the CLI against the fixtures that ship in the repo, not a mockup. Switch tabs to walk the whole loop: review a deliberately vulnerable agent, inspect a rule, compile the attested design into a policy, catch runtime drift (the running system diverging from the reviewed design) against it, show a whole attack path is reachable, and verify the evidence chain offline.

$ attestral scan examples/vulnerable-agent
attestral · examples/vulnerable-agent 6 components · 16 findings · 4 critical · 8 high · 4 medium Attack paths (1) internal chain: entry: untrusted input ingested by a tool [jira, web] pivot: code execution [deploy, shell] impact: exfiltration [web] CRITICAL (4) ATL-103 Shell-capable MCP server configured (mcp_server.shell) fix: Replace generic shell access with narrowly scoped, allowlisted tools; gate with human approval. run: attestral explain ATL-103 ATL-103 Shell-capable MCP server configured (mcp_server.deploy) fix: Replace generic shell access with narrowly scoped, allowlisted tools; gate with human approval. run: attestral explain ATL-103 ATL-108 Agent tool calls auto-approved without a human checkpoint (mcp_server.shell) fix: Remove blanket auto-approval; allowlist only low-risk read-only tools and require confirmation for... run: attestral explain ATL-108 ATL-202 Tool fleet forms an exfiltration chain (lethal trifecta) (model) fix: Split the workflow so no single agent session combines private-data access with unrestricted egress... run: attestral explain ATL-202 HIGH (8) ATL-101 MCP server uses non-TLS transport (mcp_server.jira) fix: Serve the MCP endpoint over HTTPS/WSS only. run: attestral explain ATL-101 ATL-102 Filesystem MCP server rooted at a broad path (mcp_server.filesystem) fix: Scope the server to the narrowest project directory that supports the workflow. run: attestral explain ATL-102 ATL-105 MCP server auto-installs packages at launch (mcp_server.filesystem) fix: Pin the package to an exact version and integrity hash; vendor or mirror it; drop the auto-confirm... run: attestral explain ATL-105 ATL-105 MCP server auto-installs packages at launch (mcp_server.web) fix: Pin the package to an exact version and integrity hash; vendor or mirror it; drop the auto-confirm... run: attestral explain ATL-105 ATL-115 Remote MCP server holds a downstream credential (confused-deputy risk) (mcp_server.jira) fix: Do not co-locate delegated credentials with a remote endpoint. Exchange the caller's own identity f... run: attestral explain ATL-115 ATL-203 Tool fleet combines shell execution with outbound network reach (model) fix: Remove one side of the pair per session, or force shell use through an allowlisted, non-networked s... run: attestral explain ATL-203 ATL-207 Unsafe data flow - untrusted input can reach a sensitive action (model:taint_flow) fix: Break the path - keep untrusted-input tools and execution tools in separate agent sessions, or inte... run: attestral explain ATL-207 ATL-ML-001 Prompt-injection text detected in tool 'fetch_page' description (mcp_server.web) run: attestral explain ATL-ML-001 MEDIUM (4) ATL-104 Secrets passed to MCP server via environment (mcp_server.jira) fix: Use a secret manager or OS keychain; never place raw credentials where tool output can echo them. run: attestral explain ATL-104 ATL-106 MCP server pinned to a mutable tag (mcp_server.deploy) fix: Pin to an immutable version or digest so the reviewed tool is the tool that runs. run: attestral explain ATL-106 ATL-107 MCP server grants outbound network or browser access (mcp_server.web) fix: Constrain the tool to an allowlist of destinations; deny access to internal metadata endpoints and... run: attestral explain ATL-107 ATL-ML-001 Prompt-injection text detected in system_prompt 'system-prompt' (system_prompt.system-prompt) run: attestral explain ATL-ML-001 (no files written - add -o to save a report)

The scan is examples/vulnerable-agent: two small config files, sixteen design findings, three of them fleet-level and two caught by the default prompt-injection classifier. Compile and drift run against examples/demo-project. In that output, (model:taint_flow) on ATL-207 means untrusted input reaching a sensitive action along a tracked path. To see how a scan flows through the package itself, the architecture page draws the pipeline from Attestral's own code graph.

Pipeline

A scan flows through three stages: ingest your agent (MCP configs, system prompts, tool descriptions) and the cloud it runs on 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
    MCP["MCP configs (mcp.json)"] --> M["SystemModel
components · edges · trust boundaries"] SP["System prompts + tool descriptions"] --> M TF["Terraform (.tf)"] --> M K8S["Kubernetes manifests (.yaml)"] --> M M --> L1["L1 Deterministic rules
typed matchers · fail-closed"] L1 --> L2["L2 ML classifier (optional)
prompt-injection scoring, three tiers"] 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 · AI-BOM"]

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 extras: [llm] adds the anthropic package for elicitation and the judge; [onnx] adds a light, model-grade prompt-injection classifier without torch; [ml] adds the full local DeBERTa tier (transformers, torch). the heuristic prompt-injection tier runs by default with no extra installed; --ml / --ml-engine upgrades to a model-grade tier.

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 [--local] [-o STEM] [--format md|json|both|sarif|aibom]
               [--ml] [--llm] [--judge] [--waivers FILE] [--fail-on SEVERITY] [-q]
FlagBehavior
--localScan the MCP configs already installed on this machine (Claude Desktop, Cursor, VS Code, Windsurf) instead of a PATH.
-o, --outputWrite report files to this stem. Without -o or --format, results only print; nothing is written to disk.
--formatmd for the human report, json for the chained evidence, both, sarif for GitHub Code Scanning, or aibom for a CycloneDX 1.6 AI-BOM (AI Bill of Materials) of the agent stack.
--ml / --no-mlThe zero-dependency heuristic prompt-injection tier runs by default. --no-ml turns it off; --ml / --ml-engine upgrades to the ONNX or DeBERTa tier. Findings tagged origin: ml. See the ML layer page.
--aivssRank agentic findings by an OWASP AIVSS (AI Vulnerability Scoring System) Agentic AI Risk Score (AARS), a 0–10 score, each mapped to an OWASP Agentic (ASI, the OWASP Agentic Security Initiative) / LLM Top-10 category. AARS measures agentic amplification, a different axis from CVSS severity.
--llmAdds LLM threat elicitation on top of the deterministic layer. Requires ANTHROPIC_API_KEY. Findings are tagged origin: llm and never mixed silently with rule findings.
--judgeCross-examine findings with an LLM judge; see LLM-as-judge.
--waiversYAML of documented waivers; attestral-waivers.yaml at the scan root is picked up automatically.
--fail-onExit non-zero if findings at or above this severity exist. Works as a fail-closed CI gate.
-q, --quietSuppress per-finding detail; print only the summary and gate.

Ingestion is automatic: *.tf feeds the cloud model; Kubernetes manifests feed the cluster model; mcp.json, *.mcp.json, and claude_desktop_config.json feed the agent runtime, along with system prompts, skill and instruction files, subagent definitions, hooks, and A2A agent cards.

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. The evidence chain section below runs the same check live on a real chain.

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.
DRF-005criticalA server's tool manifest changed since attestation (a rug-pull, a tool silently changed after you approved it).
DRF-006highA runaway tool-call loop suggests resource drain.
DRF-007mediumA server's call volume exceeds its attested budget.

--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.

attestral validate

The other commands stop at findings. attestral validate takes the attack path the model already assembled and walks it over the model's own edges to show it is reachable, entry to pivot to impact, then commits the walked path to the evidence chain. A finding is a claim about one config block; a walked, chained record of the exact path is not.

attestral validate PATH [-o STEM] [--fail-on-reachable] [--remediate] [--action-space] [--generate] [--execute]

Each reachable path becomes a redteam-origin finding: ATL-RT-EXTERNAL when an external caller can reach a sink, or ATL-RT-INTERNAL when a prompt injection can. A design with no complete path yields no reachable path, which is itself an attestable result. Pass -o to write the reachability report plus an evidence chain attestral verify confirms offline; --fail-on-reachable exits non-zero when any path is reachable, so it gates CI.

TierWhat it doesStatus
SymbolicWalks each path over the model's edges. Deterministic, zero-dependency, no execution, no network.shipping now
GenerativeWith --generate, an LLM drafts the predicted payload and transcript for a path. No live target is touched, labeled predicted.shipping (opt-in)
ExecutedWith --execute, replays the path through Attestral's sandbox harness with a planted canary, capturing the transcript. No real system, secret, or network is touched; execution against your own fingerprinted live target stays gated.shipping (harness)

Verify the fix, not just the flaw. --remediate gives the minimal fix for each reachable path and verifies it by re-synthesis: it strips the rung, rebuilds the whole model, and re-runs the walk to confirm the path is gone from the graph. It also reports the OWASP AIVSS agentic risk score (AARS) before vs after each fix, and ranks fixes by how far they lower it, so the highest-leverage change comes first. --action-space enumerates the tool-call sequences the fleet can be induced into, not just the one collapsed chain.

Scope, by design. The symbolic tier never touches a live system, and the executed tier will run only against a target whose fingerprint matches the attested model, with explicit authorization, in a sandbox, moving planted canaries rather than real secrets. Attestral surfaces the reachable paths in a design you own; it is not a point-it-at-any-endpoint attack tool, and it never will be.

Watch it render live: the interactive proof walker lets you toggle a fleet, animate the attack path across the trust boundary, and break a chain in your browser.

Code scanning (SARIF)

Scan with --format sarif and the review becomes SARIF 2.1.0 (the static-analysis result format GitHub's Security tab reads). 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 (--judge-panel N) is a real cross-examination, not the same prompt polled N times: each panelist reviews through a different adversarial lens, one for exploitability, one for a compensating-control false positive, one for blast radius, and the majority verdict wins, which blunts any single angle's error. The verdict itself is schema-constrained, so a well-formed result is guaranteed rather than parsed hopefully, and if a call fails the scan says so instead of quietly returning nothing. Tune rigor per run with --judge-effort (low through max).

The judge layer needs an API key and the anthropic package (pip install "attestral[llm]"). It defaults to claude-opus-4-8; override with --judge-model. Like the elicitation layer, its output is clearly separated from the deterministic core.

The rule pack

Every deterministic check ships in the open, in plain YAML, and each cites the control it enforces. This is the complete built-in pack, searchable. Click a rule for the same detail attestral explain prints. For the holistic picture, what each of the five packs covers, how they compose, and what the ID bands mean, see the rule packs guide.

    The index is generated from the packs in attestral/rules/ at build time, so it matches the release exactly. Agentic and cross-boundary rules cite OWASP Agentic Security and MITRE ATLAS; cloud rules cite CIS and NIST.

    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.

    Try to tamper with one. Below are the first four entries of a real chain from attestral scan examples/demo-project. Downgrade an entry's severity and your browser recomputes the chain with the exact algorithm attestral verify uses. Watch the break propagate.

    chain VALID · 4 entries recomputed in this browser

    Each card shows the entry's recorded hash and the hash of the entry before it. Tampering rewrites one finding's canonical JSON, so its recomputed hash no longer matches the recorded one, and every later entry points at a hash that no longer exists. The only way to hide an edit is to rewrite every subsequent entry, and the chain head an auditor recorded elsewhere still gives it away.

    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 that resolves variables, locals, and modules. 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 ships today: the deterministic rule pack you can search on this page, SARIF, waivers, the ML layer (a zero-dependency heuristic tier that runs by default, plus optional ONNX and DeBERTa tiers; prompt-injection detection on agentic surfaces, tagged origin: ml, with a fine-tuning recipe in training/), and the LLM-as-judge verifier. Next up, roughly in order: more cloud and Kubernetes rules, PR design-diffing, and richer drift telemetry adapters. Issues and contributions are welcome on GitHub.