Attestation Bundle

August 23, 2026 · View on GitHub

An attestation bundle is one signed JSON file that captures this machine's governance posture at a moment in time. It bundles what Prismor already tracks:

  • Agent inventory: every agent Prismor governs on this host (name, framework, enforce/observe mode, last seen)
  • Host discovery: agents found on the machine and whether each one runs under Prismor hooks (see Host discovery)
  • Posture findings: the full prismor audit sweep across hooks, policy, cloaking, permissions, feed signature, egress, network, and sandbox
  • Framework coverage: which compliance-framework controls the active policy covers (see Framework coverage)
  • Audit-trail anchor: the signed head of the signed audit trail, tying the bundle to the tamper-evident action log

The whole thing gets a SHA-256 content hash and an Ed25519 signature. Hand the file to an auditor and they re-verify it with one command, on their own machine, without touching yours.

Signing needs the optional cryptography extra:

pip install "prismor[signing]"

Without it the bundle is still assembled and hashed, just unsigned.

What's in a bundle

FieldMeaning
schemaprismor.attestation.v1, the version an auditor verifies against
generated_atISO-8601 UTC timestamp
device_id, prismor_versionwhich machine and which Prismor built it
agentsthe governed-agent inventory
discoveryhost sweep: agents present and whether each is governed
audit_findingsposture findings from prismor audit
framework_coveragewhich compliance-framework controls the active policy covers
trail_checkpointsigned audit-trail head (seq, hash)
content_hashSHA-256 over the JCS-canonical bundle body
signature, signing_pubkey, signing_key_idthe Ed25519 signature

The body is canonicalized with RFC 8785 (JCS) before signing. That matters for auditors: a verifier written in any language can reproduce the exact bytes Prismor signed, so re-verification doesn't depend on Python.

Commands

prismor attest                        # print a fresh bundle to stdout
prismor attest --out evidence.json    # write it to a file
prismor attest verify evidence.json   # re-check hash + signature
prismor attest verify evidence.json --pubkey B64   # pin an out-of-band signer key
prismor attest verify evidence.json --json         # machine-readable report
prismor attest coverage               # framework-control coverage of active policy

Building a bundle is read-only. It runs the audit, reads the inventory, grabs the current trail head, and signs the result. Nothing on disk changes except the file you asked for with --out.

Host discovery

prismor discover sweeps this machine for AI running outside Prismor's reach — shadow AI. It covers three surfaces, each diffed against the governed set that actually applies to it:

SurfaceGoverned when
Coding agentsPrismor's hook dispatcher is wired into the agent's config
MCP serversthe server is routed through prismor mcp-gateway
Provider keysthe credential is registered with Cloak
  PRISMOR  discover   ~/src/acme

  AGENTS
    governed               Claude Code  (enforce)
    SHADOW                 Gemini CLI (Google)
    no coverage            Warp (Agent Mode)  (Prismor has no hook for this agent)

  MCP SERVERS
    SHADOW                 context7  [cursor]  medium
      https://mcp.context7.com/mcp
      · MCP server 'context7' has a hardcoded secret in headers ('CONTEXT7_API_KEY')

  PROVIDER KEYS
    SHADOW                 anthropic  ANTHROPIC_API_KEY

  ──────────────────────────────────────────────────────────
  Coverage:  42%  of discovered AI surface is governed
  Shadow:    1 agent(s), 1 MCP server(s), 1 key(s)

Pass a section (agents, mcp, keys) to limit the report, --json for the machine-readable form, and --fail-on-shadow to exit non-zero in CI.

Governing what it finds

prismor discover --fix acts on the report: it installs the global hook for every unmanaged agent, moves the workspace's MCP servers behind the gateway, and imports dotenv provider keys into Cloak. It shows the plan, states what it cannot fix and why, and asks before writing to your config files — --yes skips the prompt, --fix-mode enforce installs in enforce rather than observe.

Every finding an enrolled device reports also carries whether it is fixable, the command that fixes it, and — when it is not — the reason. The console uses that to separate "a developer clears this in one command" from "this needs a decision", which is the difference between a list that gets triaged and one that gets ignored. The verdicts come from the same planner --fix runs, so the console can never offer a remediation the CLI would refuse.

There is a hard limit worth understanding. Prismor cannot constrain an agent it has not hooked: egress screening, the sandbox, tool denies and kill switches all act on a hook payload, and an unhooked agent never produces one. So --fix governs by eliminating the shadow rather than policing it, and an agent with no hook surface at all — Warp, Trae, Antigravity — is reported as unfixable instead of quietly skipped.

On an enrolled device this inventory also reaches your organization console, where it becomes a fleet-wide Shadow AI view. prismor enroll seeds it, and the runtime refreshes it once a day from a detached background scan — set PRISMOR_DISCOVER_INTERVAL (seconds) to change the cadence, or run prismor discover --report to push one immediately. Reporting is gated on the managed workspace, like telemetry: a personal repo never reports what is installed on your machine, and an unenrolled device reports nothing at all.

An agent counts as present when its config or install directory exists, and agents Prismor has no hook for are shown as no coverage and left out of the shadow count — nothing was skipped, so counting them would inflate the number. The agent half of this report lands in every bundle under discovery, so an auditor sees not just what Prismor governs but what it doesn't.

Credential-shaped values are redacted before they reach output. An MCP endpoint often carries the caller's key in a path segment or query parameter, so the raw URL is itself a secret; those are masked at the point the record is built, which means the JSON output is as safe as the terminal one. Findings under keys record the provider and the location only — never a value.

This is host-local and read-only. It doesn't scan the network or probe other machines. Finding AI across a fleet is a bigger job for a separate tool; here the question is narrower and answerable from local files: on this box, is anything running outside Prismor's reach?

Framework coverage

prismor attest coverage shows which compliance-framework controls the active policy covers, and the same data rides inside every bundle under framework_coverage:

  PRISMOR  framework coverage  (19/19 controls across 4 frameworks)

  OWASP Top 10 for LLM Applications  6/6
    ✓ LLM01          Prompt Injection  (prompt-injection, prompt-injection-hidden)
    ✓ LLM02          Sensitive Information Disclosure  (secret-exfiltration, ...)
    ...

A control counts as covered when at least one policy rule mapped to it is active. Disable the last rule behind a control and it flips to uncovered, so the report tracks your real posture rather than a static claim. The mapping lives in plain YAML under prismor/runtime/checklists/: one pack per framework (control IDs and titles) plus crosswalk.v1.yaml tying Prismor rule IDs to control IDs. Fork a pack, add a rule to the crosswalk, and it flows into the next bundle.

Four frameworks ship today: OWASP Top 10 for LLM Applications, OWASP Agentic AI Threats, NIST AI RMF, and the EU AI Act high-risk obligations. Coverage is a statement about what Prismor enforces at the tool boundary. It is not a legal compliance opinion, and Prismor is one control among the many a full program needs.

Handing a bundle to an auditor

Say you're closing out a compliance review. You run:

prismor attest --out q3-evidence.json

and send q3-evidence.json to the auditor. On their machine, they run:

prismor attest verify q3-evidence.json

A clean bundle prints:

✓ attestation verified — schema prismor.attestation.v1, generated 2026-07-11T18:24:31
  signed by key id 18ea124a3b10e500

Edit a single field of the file and re-verify, and the content hash no longer matches:

✗ attestation FAILED — schema prismor.attestation.v1, generated 2026-07-11T18:24:31
  ✗ content_hash mismatch — the bundle body was altered

Verify exits non-zero on any failure, so it drops straight into CI or a compliance script.

Pinning the signer

By default verify trusts the public key embedded in the bundle. That catches tampering, but not a bundle forged wholesale by someone with a different key. An auditor who has your device's public key out of band (from enrollment, or a key you published) pins it:

prismor attest verify q3-evidence.json --pubkey <your-device-pubkey>

Now a bundle signed by any other key is rejected, even if its own hash and signature are internally consistent.

What this is and isn't

The bundle is signed, re-verifiable evidence of what Prismor was enforcing, which agents it saw, and which framework controls that enforcement covers. An auditor can trust the file came from your device and hasn't been touched.

Read the coverage as a map of Prismor's runtime controls onto framework language, not as a certification. A full NIST AI RMF or EU AI Act program has obligations Prismor never touches: data governance, model documentation, human oversight processes, legal review. Prismor attests to the slice it enforces at the tool boundary. Wider framework packs (ISO/IEC 42001, HIPAA-for-AI) and per-control evidence links are the next additions to the crosswalk.