Claude now attaches signed C2PA provenance metadata to the files it generates. OpenAI’s image models do too, and so does Gemini. That means your upload endpoint may receive a real provenance signal for the first time—and your image pipeline may be deleting it before anyone can verify it.
This usually is not intentional. By default, sharp().resize() writes a clean file without metadata unless you explicitly retain it. ImageMagick, Pillow, and many image CDNs behave similarly. A signed manifest goes in, an optimized JPEG comes out, and your logs remain silent.
You can test this failure mode. This guide shows how metadata is lost in a typical image pipeline, how to identify the exact stage responsible, and how to add a CI round-trip check. Use Apidog to orchestrate the API flow and c2patool to verify the file bytes.
What actually gets destroyed
A C2PA manifest is a cryptographically signed block embedded in an asset’s file container. It records information about the signer and the asset’s claimed provenance. Because it is signed, changing the bytes without re-signing causes validation to fail.
The key detail is that C2PA data is container-level metadata. Rewrite the container, and the manifest is typically gone.
| Operation | Manifest survives by default? |
|---|---|
| Byte-for-byte copy or move | Yes |
sharp().resize().toBuffer() |
No |
ImageMagick convert / magick
|
No |
Pillow Image.save()
|
No |
| PNG to WebP, JPEG to AVIF | No |
| Image CDN auto-optimization | Usually no |
| Screenshot | No |
| Re-save from an image editor | No |
| S3 upload with no transformation | Yes |
The “no” column covers standard web-image processing: thumbnails, responsive variants, format negotiation, and EXIF scrubbing. Each operation is reasonable, but each can silently end the provenance chain.
There is also a privacy trade-off. A blanket -strip operation can remove EXIF data such as GPS coordinates and camera serial numbers, but it can also remove the C2PA manifest. If you need privacy controls and provenance, remove only the metadata blocks you do not need instead of stripping everything.
Prove it in two minutes
First, verify that your pipeline has the problem.
Use a file with a valid C2PA manifest. For example, use an image generated by Claude or a signed sample from the Content Authenticity Initiative.
Install the reference CLI:
cargo install c2patool
Verify the fixture before uploading it:
c2patool fixtures/signed-sample.png
You should receive a JSON report with the claim generator and signature status.
Next, send that same file through your real upload and delivery path:
# Upload through your real endpoint
curl -sS -X POST https://api.example.com/v1/assets \
-H "Authorization: Bearer $API_TOKEN" \
-F "file=@fixtures/signed-sample.png" \
-o /tmp/upload.json
# Fetch through the URL used by your frontend
ASSET_URL=$(jq -r '.url' /tmp/upload.json)
curl -sS "$ASSET_URL" -o /tmp/roundtrip.png
# Verify the returned file
c2patool /tmp/roundtrip.png
Interpret the result carefully:
- Valid report: the manifest survived.
- No manifest found: a pipeline stage stripped the manifest.
- Validation error: the manifest is still present, but its signature no longer matches the file bytes.
The last outcome deserves immediate investigation. It commonly means a transformation library retained a metadata block while changing the pixels. Downstream verifiers will treat that as tampering.
Find the step that removes metadata
If the round trip fails, do not guess. Check the manifest after every transformation stage:
- Original upload
- Ingest normalization
- Thumbnail generation
- Format conversion
- Object storage write
- CDN delivery URL
The most common failure points are below.
1. Resize and thumbnail generation
In sharp, metadata is dropped unless you retain it explicitly:
// Drops the C2PA manifest
await sharp(input).resize(1200).toFile(output);
// Retains the metadata block
await sharp(input).resize(1200).keepMetadata().toFile(output);
Keeping metadata is necessary, but it is not enough to preserve a valid provenance chain. The pixels changed, so the original signature no longer validates against the transformed output.
To preserve verifiable provenance after a resize:
- Retain the relevant metadata.
- Re-sign the generated file.
- Add an action assertion describing the transformation, typically
c2pa.resized.
The c2pa libraries for Rust, Python, JavaScript, and C support this workflow.
2. Format conversion
Converting PNG to WebP or JPEG to AVIF creates a new container. Apply the same rule:
- Preserve metadata and re-sign the transformed file, or
- Treat the conversion as the end of the provenance chain and document that behavior.
3. CDN optimization
Many image CDNs transform assets during delivery. Some preserve and re-sign Content Credentials natively, while many historically stripped them.
Always test the public delivery URL your users access. Testing only the origin file can give you a passing result that does not reflect production behavior.
4. Upload normalization
Ingest pipelines often re-encode uploads to standardize dimensions, formats, or quality. These transformations are easy to miss because they may live in a separate infrastructure service.
Add a c2patool check immediately after normalization, not only after delivery.
Make the check permanent
A manual curl test proves the current state. It does not prevent a future resize step from breaking provenance.
Add the check to CI in two layers:
- An API round trip that uploads and fetches the file.
- A byte-level signature check against the downloaded output.
Layer one: run the round trip in Apidog
The API scenario is straightforward:
- Upload a signed fixture.
- Capture the returned delivery URL.
- Fetch the asset through the real delivery path.
- Check status, content type, and size.
Step 1: POST /v1/assets
Configure a multipart/form-data request with the signed fixture.
The setup is the same as testing file upload APIs.
Add assertions for:
- HTTP status
201 - Expected response schema
- A usable delivery URL
Use a post-response script to save the URL for the next request:
const body = pm.response.json();
pm.environment.set("ASSET_URL", body.url);
pm.test("upload returns a delivery URL", function () {
pm.expect(body.url).to.be.a("string").and.to.include("https://");
});
Step 2: GET {{ASSET_URL}}
Assert that:
- The response status is
200. -
Content-Typematches the expected output format. - The returned file size is close to the uploaded file size.
const uploadedBytes = Number(pm.environment.get("FIXTURE_BYTES"));
const returnedBytes = pm.response.responseSize;
pm.test("asset was not silently re-encoded", function () {
pm.expect(returnedBytes).to.be.above(uploadedBytes * 0.9);
});
Size is only a heuristic, not cryptographic verification. It catches obvious re-encoding failures cheaply and keeps the signal in the same suite as your other API checks.
For more assertion patterns, see API assertions.
Layer two: verify bytes in CI
An HTTP test client should orchestrate requests, but signature verification requires parsing the asset container. That is c2patool’s job.
Run it in CI against the file fetched during the round trip:
# .github/workflows/provenance.yml
name: provenance
on: [pull_request]
jobs:
c2pa-round-trip:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install c2patool
run: cargo install c2patool
- name: Install Apidog CLI
run: npm install -g apidog-cli
- name: Run the round-trip scenario
run: |
apidog run --access-token "$APIDOG_ACCESS_TOKEN" \
-t "$SCENARIO_ID" \
-e "$ENV_ID" \
-r cli,html \
--out-dir ./apidog-reports
env:
APIDOG_ACCESS_TOKEN: ${{ secrets.APIDOG_ACCESS_TOKEN }}
SCENARIO_ID: ${{ vars.PROVENANCE_SCENARIO_ID }}
ENV_ID: ${{ vars.APIDOG_ENV_ID }}
- name: Verify the manifest survived
run: |
set -euo pipefail
curl -sS "$ASSET_URL" -o /tmp/roundtrip.png
c2patool /tmp/roundtrip.png > /tmp/report.json
jq -e '.validation_status == null or (.validation_status | length) == 0' /tmp/report.json
set -euo pipefail is important. Without it, a failed c2patool command can become a warning while the workflow still passes—the exact outcome this test is meant to prevent.
For Apidog pipeline setup, see automating API tests in GitHub Actions.
Layer three: expose verification as an endpoint
If provenance verification is a product feature rather than only an internal control, add a small verification endpoint to your service.
The endpoint can run a c2pa library and return structured JSON:
{
"asset_id": "img_9f2c41",
"provenance": {
"status": "verified",
"standard": "c2pa",
"signer": "Anthropic",
"signature_valid": true,
"checked_at": "2026-08-11T09:14:22Z",
"tool": "c2patool/0.9"
}
}
Keep at least three distinct result states:
| Status | Meaning |
|---|---|
verified |
A manifest exists and its signature validates. |
absent |
No manifest was found. |
invalid |
A manifest exists, but validation failed. |
Do not collapse absent and invalid into one boolean. They represent different operational and security signals.
If verification can fail because the verifier is unavailable, add an unchecked status so an outage is not reported as a clean result.
Document this response shape in your OpenAPI definition and validate it in CI. See How to validate OpenAPI specs.
Keep these four fixtures
A useful provenance test suite needs broken inputs, not only a happy path.
Valid signed file
Expectverified. This catches accidental stripping.Stripped file
Use the same image but remove metadata withexiftool -all=. Expectabsent, not an error and neververified.Tampered file
Modify a byte after signing. Expectinvalid. This proves you validate the signature instead of merely checking for a metadata block.Unsupported format
Use a format with no manifest support. Expect a cleanabsentresult rather than a500.
Commit these fixtures alongside the API scenario. They are small, stable, and ensure your tests validate real behavior.
Why this matters
Your product claim
If your UI displays a provenance badge but your resize pipeline strips manifests, that badge is wrong for transformed assets. Users will eventually discover the mismatch.
Your compliance controls
If you rely on C2PA for Article 50-related workflows, stripping manifests means a control is not functioning as intended. The provider and deployer responsibilities are covered in EU AI Act Article 50 for API developers.
The provenance signal
Provenance works only when the chain remains intact from generation through delivery. Pipelines that quietly remove manifests reduce the usefulness of the ecosystem for everyone, including teams that need to verify assets later.
Download Apidog to build the API round-trip scenario against your endpoints, then add c2patool verification behind it in CI.
FAQ
Does resizing an image remove C2PA metadata?
Yes, by default in common image libraries. Retaining the metadata block requires an explicit option, and maintaining a valid signature requires re-signing the transformed output.
How do I check whether a file has C2PA metadata?
Run:
c2patool <file>
You can also upload the file to the Content Credentials verification page.
Can I preserve C2PA metadata through a resize?
Yes, but metadata preservation alone is insufficient. Preserve the block, then re-sign the transformed output with an action assertion such as c2pa.resized using a c2pa library. Otherwise, the original signature does not match the new bytes.
Do CDNs strip Content Credentials?
Many do when auto-optimization is enabled. Some now preserve and re-sign Content Credentials natively. Test the delivery URL your users receive, not only the origin file.
What is the difference between a stripped and invalid manifest?
A stripped manifest means no manifest was found, so there is no provenance signal to verify. An invalid manifest means a manifest exists but its signature does not match the asset bytes, indicating that the asset changed after signing.
Keep those states separate.
Can Apidog verify a C2PA signature directly?
Apidog can orchestrate the upload/download round trip and assert HTTP responses, including JSON from your own verification endpoint. Use c2patool or a c2pa library for container parsing and signature verification.
Should I strip EXIF for privacy but retain C2PA?
Yes. Use selective metadata removal. A blanket -strip operation can remove both EXIF and C2PA data, so remove the specific EXIF blocks you need to exclude while retaining the provenance manifest.
The takeaway
C2PA provenance metadata can arrive at your API intact and leave your delivery pipeline missing or invalid, with no monitoring alert to tell you.
The practical fix is:
- Keep a signed fixture.
- Upload it through the real endpoint.
- Download it through the real delivery URL.
- Run
c2patoolagainst the returned file. - Fail CI when the manifest is absent or invalid.
A short setup turns a provenance claim in your UI into a guarantee your pipeline actively enforces.
Top comments (0)