Reference

The rules

Every check the engine runs produces an issue with a stable code and one of three severities: error, warning, or info. An error makes a manifest invalid; a warning is reported but tolerated unless it is the trust result and you asked for --require-trusted.

Issue codes

These are the codes this build emits. Their scope is a single manifest, and several can appear on one file.

Signature and signer checks.
CodeSeverityWhat it meansWhat to do
claimSignature.validatedinfoThe COSE Sign1 claim signature validated against the signing certificate. The algorithm is named, for example PS256.Nothing. It confirms the claim was signed by the holder of the certificate.
claimSignature.mismatcherrorThe claim signature did not validate. The manifest was altered, or the signature is not from that certificate.Treat the file as untrusted and do not rely on its provenance. The asset may still be otherwise fine.
signingCredential.missingerrorThe COSE signature carries no x5chain, so there is no certificate to tie the claim to.Cannot be trusted without a signer; escalate or reject.
signingCredential.invaliderrorA certificate's issuer signature did not validate, so the chain is broken.Reject the chain. Provide the correct issuer or root via a trust list if you expect it.
signingCredential.untrustedwarningNo certificate in the chain matched the trust list you supplied.Add the signer's certificate or subject to the trust list, or treat the file as untrusted with --require-trusted.
signingCredential.trustedinfoA certificate in the chain matched the trust list; the anchor is named.Nothing. The signer is one you vouched for.
signingCredential.expiredwarningA certificate is outside its validity window — expired or not yet valid.Check the clock and the certificate dates. A signature made inside the window is still cryptographically sound; it just cannot be validated now.
signingCredential.intermediateinfoThe chain does not carry the issuer of the last certificate, so it cannot be anchored to a root on its own.Supply the expected anchor with --trust; trust has to come from a trust list.
Assertion and hard-binding checks.
CodeSeverityWhat it meansWhat to do
assertion.missingerrorThe claim declares an assertion that is not present in the manifest.Treat the manifest as damaged or stripped; reject.
assertion.hashedURI.mismatcherrorAn assertion's bytes hash to something other than the hash the claim records for it.Reject. An assertion was changed after the claim was signed.
assertion.undeclaredwarningAn assertion is present in the manifest but not declared in the claim.Usually harmless, but it is not covered by the signature. Investigate an unexpected assertion.
hardBinding.matchedinfoThe asset bytes re-hash to the c2pa.hash.data value.Nothing. The signed claim describes these exact bytes.
hardBinding.mismatcherrorThe asset bytes do not match the hard binding. Something changed after signing.Reject the file as edited. This is the tampering signal.
hardBinding.invaliderrorThe c2pa.hash.data assertion did not decode, or carries no hash.Reject; the binding cannot be evaluated.
hardBinding.unsupportedwarningThe manifest uses c2pa.hash.boxes, which this build does not evaluate.Do not treat the binding as verified. Use a tool that implements BMFF fragment bindings.
hardBinding.missingerrorThe manifest has no hard binding assertion at all.Reject; nothing ties the claim to the asset bytes.
manifest.missingwarningNo C2PA manifest store was found in the file. The asset state is unsigned.Nothing to verify. If a manifest was expected, the file was stripped or is the wrong format.

Validation states

The report and the exit code reduce to one state per file. The state is the worst result across every manifest in the store.

valid
A manifest store is present and every manifest has no error-severity issue: the signature verified, the assertions hashed correctly, and the hard binding matched. Exit code 0. If --require-trusted is set and a trust list names the signer, the state stays valid.
invalid
At least one manifest produced an error: a bad signature, a broken chain, a failed assertion hash, a hard binding mismatch, or a missing binding. The file may still be authentic media, but its provenance manifest cannot be trusted. Exit code 1.
untrusted
The signature and hard binding are fine and there are no errors, but no certificate in the chain matched the trust list, and assertTrusted was requested — the CLI's --require-trusted. Without that flag the same file reports valid and exits 0. A trust list must be supplied for this state to be reachable: with no list, trust is unknown and never flips the state. Exit code 1 only with --require-trusted.
unsigned
No C2PA manifest store could be located: no JUMBF in a JPEG, PNG, or WebP, and no standalone JUMBF. The report says so plainly, and the exit code is 1. It is not a judgment that the file is fake — only that it carries no provenance to check.

The hard binding

Inside the manifest is a c2pa.hash.data assertion: a hash of the asset's bytes with the manifest region excluded, together with the exact byte ranges to exclude. That is the hard binding — it names the specific pixels the signed claim is about.

When attest verifies a file, it recomputes that hash over the bytes in front of it and compares. If even one byte outside the manifest region has changed, the hashes differ and the binding fails. This is why the tampered fixture reports signature valid, hard binding mismatch: the signature over the unchanged manifest is still genuine, but that manifest no longer describes the file it is attached to.