Offline · no upload · CI-ready

Verify Content Credentials, offline.

attest reads the C2PA manifest embedded in a signed image, validates the claim signature and the hard binding, and reports the signer chain, the recorded actions, and every issue it finds. It runs entirely on your machine: no upload, no account, no network call.

bunx github:srivtx/attest#main inspect photo.jpg

attest is not on npm; bunx github:srivtx/attest#main runs the latest main with Bun. Install the attest binary with the one-line script, or add it as a dev dependency.

attest inspect
$ attest inspect fixtures/C2PA-signed.jpgVALID  jpeg  132518 bytessha256  a2d14755db55de67a47c04090340d8266e892367be4104a45626d7a6fa6e9ffdmanifest  contentauth:urn:uuid:b2b1f7fa-b119-4de1-9c0d-c97fbea3f2c3  title      C.jpg  generator  make_test_images  format     image/jpeg  instance   xmp:iid:22704d84-c37f-4733-a207-56c4c2e67b1a  signature  valid (PS256)  trust      unknown — no trust list was supplied  chain    . C=US, ST=CA, L=Somewhere, O=C2PA Test Signing Cert, CN=C2PA Signer    . C=US, ST=CA, L=Somewhere, O=C2PA Test Intermediate Root CA, CN=Intermediate CA  [CA]  actions    c2pa.created  by Make Test Images 0.33.1  hard binding  matched (sha256)  assertions    40 error(s), 0 warning(s)

Valid vs tampered

One changed byte separates a signed file from a forged one. The signature still validates, because the manifest is untouched — but the hard binding no longer matches the pixels.

attest verify
$ attest verify fixtures/C2PA-signed.jpgvalid: C.jpg — signature valid, hard binding matchedexit 0$ attest verify tampered.jpginvalid: C.jpg — signature valid, hard binding mismatchexit 1

The exit code is the gate: 0 means the file verified, 1 means it did not. In CI, that is the whole check.

Try it in your browser

Choose a signed image and attest verifies it right here. The file is read with File.arrayBuffer() and never leaves this machine: there is no upload and no network request. You can watch the network tab.

Runs entirely in your browser. Nothing is uploaded, and no request is made.

No file chosen.

An unsigned file is not a failure of the tool: it means the file carries no Content Credentials at all, so its origin is unknown.

What C2PA is

C2PA — the Coalition for Content Provenance and Authenticity — is an open standard for attaching cryptographically signed provenance to media: who made it, with what tool, and what happened to it since. A signed file carries a manifest holding a signed claim plus assertions, such as the creating action, a thumbnail, and a hash of the asset itself. Content Credentials is the user-facing name for that provenance.

The manifest is written as JUMBF boxes, embedded in the file's own bytes: an APP11 segment in a JPEG, a caBX chunk in a PNG, or a C2PA chunk in a WebP. It can also stand alone as a raw JUMBF file.

What it checks

attest re-derives each check from the bytes in front of it. Nothing is taken on trust from the file's own metadata.

The claim signature
The COSE Sign1 signature over the claim, verified against the signing certificate. PS256, ES256, RS256 family algorithms and the other COSE algorithms are handled.
The signer chain
Every certificate in the x5chain: its subject and issuer, its validity window, and the issuer signature of each link.
Trust
Whether any certificate in the chain is on the trust list you supply, as a SHA-256 fingerprint or an exact subject name.
Assertion hashes
Every assertion the claim declares must be present, and its SHA-256 must match the hash recorded in the claim.
The hard binding
The asset bytes are re-hashed with the manifest region excluded and compared to the c2pa.hash.data assertion. This is what detects editing after signing.
Actions and ingredients
The recorded actions, their software agents and timestamps, the digital source type, and any declared ingredients.

Not checked. This build does not evaluate c2pa.hash.boxes (the BMFF fragment bindings) and reports them as unsupported. It does not perform OCSP or CRL revocation checks, and it does not fetch a remote manifest — a remote_manifest URL is reported, never requested. It is not a conformance implementation, and it makes no claim about whether the picture is truthful.

Why offline matters

Verifying provenance is sensitive. The file you are checking is often the very thing you are not ready to publish, and sending it to a third-party service tells that service what you are looking at. attest reads the file and does the cryptography on the machine you are already on.

Offline also means it works where a network does not: an air-gapped newsroom machine, a locked-down build runner, a laptop on a plane. There is no API key to rotate, no rate limit, and no service that can disappear or change its answer underneath you.

The tool is deterministic: the same bytes produce the same report and the same exit code, today or in a year. That is what makes it usable as a gate. The only network call anywhere in this project is the one you type to install it.

In CI

Point attest at your image directory and let the exit code fail the build when a file is invalid or signed by a signer you do not trust.

attest verify · CI
$ attest verify public/hero.jpg --require-trusted --trust ci/trust-list.json# exit 0 when the signature, the hard binding, and the trust list all agree# exit 1 when the file changed, the chain is broken, or the signer is not trusted

There is a complete GitHub Actions workflow, including anchoring a card in a registry commit, on the CI page.

Commands

One binary, six commands. Everything the browser bundle does, the CLI does with the same engine.

inspect
Verify and print the full report: chain, actions, ingredients, assertions, and every issue.
verify
The same check with a one-line summary, for scripts and CI. The exit code is the answer.
card
Print a portable provenance card — the asset hash, state, signer, and actions — as JSON.
anchor
Append a card to a JSON Lines registry, so the state of a file is recorded in a commit.
resolve
Look up a card by SHA-256, or re-verify a file against the card already in the registry.
mcp
Run the Model Context Protocol server over stdio, exposing three provenance tools to an agent.

Every command, flag, exit code, and the browser API are in Usage.

Documentation

The landing page is the pitch. The details live on four short pages.

Rules
Every issue code the engine emits, the four validation states, and what the hard binding is.
Usage
The CLI commands and flags, exit codes, the MCP server, the JavaScript API, trust lists, and the registry.
CI
A copy-paste GitHub Actions workflow, anchoring cards, and the exit codes as a build gate.
FAQ
Scope, honest limits, the file types it reads, and what provenance can and cannot prove.