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.
| Code | Severity | What it means | What to do |
|---|---|---|---|
claimSignature.validated | info | The 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.mismatch | error | The 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.missing | error | The 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.invalid | error | A 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.untrusted | warning | No 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.trusted | info | A certificate in the chain matched the trust list; the anchor is named. | Nothing. The signer is one you vouched for. |
signingCredential.expired | warning | A 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.intermediate | info | The 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. |
| Code | Severity | What it means | What to do |
|---|---|---|---|
assertion.missing | error | The claim declares an assertion that is not present in the manifest. | Treat the manifest as damaged or stripped; reject. |
assertion.hashedURI.mismatch | error | An 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.undeclared | warning | An 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.matched | info | The asset bytes re-hash to the c2pa.hash.data value. | Nothing. The signed claim describes these exact bytes. |
hardBinding.mismatch | error | The asset bytes do not match the hard binding. Something changed after signing. | Reject the file as edited. This is the tampering signal. |
hardBinding.invalid | error | The c2pa.hash.data assertion did not decode, or carries no hash. | Reject; the binding cannot be evaluated. |
hardBinding.unsupported | warning | The 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.missing | error | The manifest has no hard binding assertion at all. | Reject; nothing ties the claim to the asset bytes. |
manifest.missing | warning | No 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.
valid0. If --require-trusted is set and a trust list names the signer, the state stays valid.invalid1.untrustedassertTrusted 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.unsigned1. 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.