Engineering guide · public fixture proof · updated 24 July 2026
Test whether C2PA Content Credentials survive your CDN
Compare repository source bytes with the final delivered URL, retain inspectable evidence, and detect whether the declared source manifest remains reachable through delivered ingredient ancestry.
Synthetic public fixtures · released v0.3.0 · local-first · configured checks only
The delivery gap
A signed source file is not evidence of the bytes users receive.
Content Credentials are bound to asset bytes. A resize, re-encode, metadata policy, or generated rendition can change what reaches the public URL. The source may still inspect cleanly while the delivered variant has no embedded manifest or no longer links back to the selected source.
Manifest disappears
The final bytes contain no manifest even though the repository source does.
Ancestry changes
A delivered manifest exists, but its reachable ingredient ancestry does not contain the selected source label.
Validation fails
Local C2PA inspection reports an invalid state or a failed validation status for the inspected bytes.
The practical test target is therefore the exact delivery URL, not a screenshot, source-file assumption, or CMS setting. Repeat it for every format, crop, breakpoint, or cache variant that matters to the product.
A narrow technical boundary
Test the declaration without turning provenance into a truth verdict.
| Configured expectation | Observed by art50-ci | Narrow PASS meaning |
|---|---|---|
| Manifest required | Manifest presence in each inspected byte stream | A manifest was observed in those exact bytes |
| Embedded required | Whether the observed manifest is embedded | The configured embedded condition was observed |
| Fail on invalid | C2PA reader validation state and statuses | No invalid state or failed status was reported |
| Source chain required | Reachable ingredient ancestry from the delivered active manifest | The selected source label was reachable |
This is a byte-level regression check. It is not a signer trust determination, authenticity guarantee, truth assessment, ownership finding, or legal-compliance conclusion. A local C2PA state of Valid is not a Trusted signer result.
Article 50 does not require C2PA by name. If a team has chosen C2PA as one delivery control, this test can help verify the configured mechanics; it does not provide complete Article 50(2) marking or detection coverage. For disclosure testing, use the Article 50 CI guide.
Public released-version proof
Inspect the evidence before copying the configuration.
The controlled fixture uses a public test key and synthetic source-type data. The source and delivered images render the same test card. The delivered file adds an update manifest whose reachable ancestry contains the source label. The negative control removes the delivered PNG's C2PA caBX chunk and verifies that the remaining bytes equal the unsigned fixture.

PASS
24 Jul 2026, 14:21 UTC
Source ancestry observed in delivered bytes
- Tool
- art50-ci 0.3.0
- Source
- Embedded · Valid
- Delivered
- Embedded · Valid
- Source label
urn:c2pa:f039c0ce-…d02e7237- Delivered ancestry
- Contains source label
- Source SHA-256
3d21ffe863fa…84242c6a- Delivered SHA-256
f5794782cd47…249f07cf
EXPECTED FAIL
24 Jul 2026, 14:21 UTC
Manifest-free delivery control rejected
- Tool
- art50-ci 0.3.0
- Source
- Embedded · Valid
- Delivered manifest
- Not observed
- Exit
- 1 · completed audit
- Failure
MANIFEST_MISSING- Failure
SOURCE_MANIFEST_NOT_IN_DELIVERED_CHAIN- Delivered SHA-256
dadb42a86c03…cd4ffcb5
Exact claim: for these exact bytes, released v0.3.0 observed embedded locally valid manifests and found the controlled source label in reachable delivered ancestry. The manifest-free control produced the two recorded failures. This public fixture simulates destructive delivery; it is not a claim about every CDN.
Two assets, one declaration
Compare the source with the final public URL.
Keep the known source file in the repository and point delivered at the exact URL people receive. Both sides are required for the chain comparison.
# .art50-ci.yml
version: 1
project:
name: generated-media-delivery
output:
directory: artifacts/art50
screenshots: false
provenance:
- id: delivery-image
source: ./assets/source.png
delivered: https://cdn.example.com/media/image.png
requireManifest: true
requireEmbedded: true
requireSourceManifestInDeliveredChain: true
failOnInvalid: trueDo not add expectedDigitalSourceType to this shared pair unless the exact URI must appear directly in both active manifests. v0.3.0 checks that field independently against every configured asset; it does not inherit a source value from delivered ancestry.
Manual baseline with c2patool
Before automating, download the exact delivered bytes and inspect them with the official Content Authenticity Initiative CLI. This establishes what the URL serves at that moment.
curl --fail --location --output delivered.png https://cdn.example.com/media/image.png
c2patool delivered.pngManual inspection is useful for diagnosis. The CI check adds a declared source comparison, scheduled recurrence, structured failure codes, and retained JSON/HTML evidence.
Make delivery observable
Run on changes and against production every week.
# .github/workflows/c2pa-delivery.yml
name: C2PA delivery regression
on:
pull_request:
workflow_dispatch:
schedule:
- cron: "17 8 * * 1"
permissions:
contents: read
jobs:
provenance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: Rubiss/art50-ci@v0.4.0
with:
config: .art50-ci.yml
output: artifacts/art50
artifact-name: c2pa-delivery-evidence
retention-days: "30"
install-browser: "false"The tagged composite Action supports GitHub-hosted Linux runners. A completed audit uploads the validated report directory before returning 0 for a pass or 1 for failed assertions. Configuration or execution errors return 2; an early error before report creation may produce no artifact.
Pull requests catch configuration and source changes. The scheduled run checks the production delivery path even when no repository change occurs. Each report remains a time-bound observation, so choose a cadence that matches delivery risk and evidence-retention policy.
Inspect the exact v0.4.0 releaseEvidence, not a badge
Read the observations that produced PASS or FAIL.
- Target, resolved target, MIME type, byte count, observation time, and SHA-256 for each inspected asset
- Manifest presence, embedded state, active label, manifest labels, and reachable ancestry labels
- Local validation state, individual validation statuses, and directly observed action digital-source types
- Whether the source active label was observed in delivered ancestry and the exact structured failure codes
- Matching aggregate JSON, rendered HTML, and one privacy-minimised provenance summary per asset
Diagnose the failure code before changing the policy.
| Failure | What was observed | First engineering check |
|---|---|---|
MANIFEST_MISSING | The inspected delivery bytes contained no manifest even though the configuration required one. | Download the exact URL, record its hash and content type, then inspect resize, format-conversion, metadata, and cache settings. |
SOURCE_MANIFEST_NOT_IN_DELIVERED_CHAIN | The configured source active label was not reachable through delivered ingredient ancestry. | Confirm the pipeline used the intended source revision and that its update or transformation manifest retains the source as a reachable ingredient. |
MANIFEST_INVALID | Local inspection reported an invalid state or at least one failed validation status. | Read the retained status codes, compare the inspected hash with the expected rendition, and reproduce with c2patool before re-signing or redeploying. |
PROVENANCE_INSPECTION_FAILED | The reader could not complete inspection, so manifest expectations were not evaluated. | Check format support, response completeness, maximum byte limits, and the privacy-minimised inspection error before treating it as a provenance verdict. |
A failure is evidence about the inspected target, not a remediation instruction. Preserve the failing bundle first. Then reproduce against the downloaded bytes, change one delivery setting at a time, and rerun both the manual inspection and CI assertion. If a team deliberately changes source identity or provenance policy, review and update the declared expectation rather than weakening it merely to make the job green.
art50-ci does not copy inspected media or full manifests into the evidence bundle. It does retain public URLs, hashes, timestamps, labels, statuses, project names, and optional commit SHAs. Those values can correlate assets or reveal internal naming. Review reports before publishing them and keep customer evidence access-controlled.
v0.3.0 verifies after reading the asset locally. Signer-chain trust, timestamp trust, OCSP fetching, and remote-manifest fetching are disabled. The runner inspects and reports; it does not sign, repair, or rewrite content.
Production checklist
Test every delivery variant that can change the bytes.
- Use the final public URL, not the CMS preview or origin URL
- Cover important resize, crop, quality, format, and device variants separately
- Verify both warm and cold cache paths when the platform can produce different renditions
- Rerun after media-library, image-optimization, or CDN-policy changes
- Schedule production checks so delivery drift is detected without waiting for a code change
- Retain the report with the release and investigate the exact failure code before changing policy
Preservation is not universal. Cloudflare documents an explicit Content Credentials preservation setting; Cloudinary documents delivery-time provenance features; and image libraries such as sharp expose metadata-retention choices. Treat those settings as implementation inputs, then verify the resulting bytes.
Choose the path
Copy the proof for free or choose a monitored launch for delivery and accountability.
Self-serve
Free source-to-delivery check
- Public test fixtures and exact pass/fail configs
- Open-source v0.4.0 GitHub Action
- Published v0.4.0 npm CLI package
- Your team selects assets, policy, and run cadence
- Your team reviews and retains the evidence
Founding offer · 30-day monitored launch
€500 one time
The open-source software remains free to self-serve. €500 buys delivery and accountability—not access to the code.
- Working release gate for one product and up to five declared checks
- Configuration and one GitHub Actions workflow integrated
- Reproducible baseline run and evidence delivered: a confirmed pass or product failure
- Up to three distinct failed-run causes triaged during the 30-day window
- Agreed configuration, workflow, evidence index, and runbook handoff
Each browser disclosure assertion, optional first-interaction control, or C2PA provenance item counts as one check.
Check monitored launch fit Read the exact delivery scopePublic 60-second fit check. No product URLs, media, repository access, private details, or payment. Preliminary fit or no-fit comes first; private intake requires explicit opt-in. Daily or weekly monitoring is asynchronous, with no on-call or response-time SLA. Engineering delivery only: no legal advice or certification, C2PA creation or embedding, trust determination, or product fixes. Evidence is handed off; hosted custody is not included.
Result boundaries
Frequently asked questions
Does a Valid result mean the signer is trusted?
No. In v0.3.0, art50-ci verifies locally after reading the bytes, while certificate-chain trust, timestamp trust, OCSP fetching, and remote-manifest fetching are disabled. Valid must not be presented as a Trusted signer result.
Does C2PA prove that content is authentic or true?
No. Content Credentials can bind signed assertions to asset bytes and record provenance. They do not prove that an assertion is truthful, that a person owns the content, or that a product complies with law.
Does every CDN remove Content Credentials?
No. Delivery behavior depends on the service, transformation, format, and configuration. Test the exact public URL and each important rendition instead of assuming preservation or removal.
Why configure both source and delivered assets?
The chain assertion compares the source active-manifest label with the ingredient ancestry reachable from the delivered active manifest. Without both sides, there is no meaningful source-to-delivery comparison.
Should expectedDigitalSourceType go in the shared pair?
Only if that exact URI must appear directly in both active manifests. v0.3.0 evaluates it independently for every configured source and delivered asset; it does not inherit a value from ancestry. Use a separate source-only item when that is the intended policy.
Is C2PA required by Article 50 of the EU AI Act?
No. Article 50 does not require C2PA by name. It may be one technical implementation selected by a team, but this check is not complete marking, detection, or legal-compliance coverage.
Primary technical sources
Start with the specifications and service documentation.
These sources describe C2PA binding and ingredient structures, validation concepts, manual tooling, and current delivery settings. They—not this guide—are authoritative for their respective systems.
Make delivery observable
Start with one source asset and one real URL.
Reproduce the public proof and keep self-serving for free. If you want us accountable for delivery and the first 30 days, the founding monitored launch is €500.