Claude attache désormais des métadonnées de provenance C2PA signées aux fichiers qu'il génère. Il en va de même pour les modèles d'images d'OpenAI, et pour Gemini. Ce qui signifie qu'un véritable signal de provenance arrive pour la première fois à votre point de terminaison de téléchargement, et il y a de fortes chances que votre pipeline le supprime avant que quiconque ne le voie.
Pas malicieusement. Par défaut. sharp().resize() produit un fichier propre sans métadonnées, sauf si vous demandez le contraire. Il en va de même pour ImageMagick. Il en va de même pour Pillow. Il en va de même pour la plupart des CDN d'images. Le manifeste entre, un JPEG plus petit sort, et rien dans vos logs n'en fait mention.
Il s'agit d'une défaillance testable, et le test n'est pas compliqué. Voici comment les métadonnées disparaissent dans un pipeline normal, comment prouver que cela se produit, et comment intégrer une vérification de bout en bout (round-trip) dans la CI afin que cela ne puisse plus se reproduire. Apidog gère l'orchestration ; c2patool gère la vérification au niveau des octets.
button
Ce qui est réellement détruit
Un manifeste C2PA est un bloc signé cryptographiquement intégré dans le conteneur du fichier. Il enregistre qui a signé l'actif et ce qui a été revendiqué à son sujet, et parce qu'il est signé, modifier les octets sans re-signer rompt la signature d'une manière que tout lecteur peut détecter.
Le "niveau conteneur" est l'expression clé. Réécrivez le conteneur et le manifeste disparaît.
| Opération | Le manifeste survit-il par défaut ? |
|---|---|
| Copie ou déplacement bit-à-bit | Oui |
sharp().resize().toBuffer() |
Non |
ImageMagick convert / magick |
Non |
Pillow Image.save() |
Non |
| PNG vers WebP, JPEG vers AVIF | Non |
| Optimisation automatique par CDN d'images | Généralement non |
| Capture d'écran | Non |
| Réenregistrement depuis un éditeur d'images | Non |
| Téléchargement S3 sans transformation | Oui |
Tout ce qui figure dans la colonne "non" est une action qu'une application web normale effectue sur chaque image qu'elle accepte. Miniatures, variantes responsives, négociation de format, suppression des métadonnées EXIF pour la confidentialité. Chaque action est raisonnable isolément, et chacune met fin silencieusement à la chaîne de provenance.
Il est à noter que l'habitude de supprimer (-strip) pour des raisons de confidentialité est souvent délibérée, car les données EXIF contiennent les coordonnées GPS et les numéros de série des appareils photo. Supprimer toutes les métadonnées pour éliminer les données de localisation supprime également le manifeste de provenance. Ces deux objectifs sont désormais en conflit, et les résoudre signifie être sélectif plutôt que de supprimer tout le bloc.
Prouvez-le en deux minutes
Avant de construire quoi que ce soit, confirmez que vous avez le problème. Vous avez besoin d'un fichier avec un manifeste valide. Toute image générée par Claude fonctionne, ou procurez-vous un échantillon signé auprès de l'Initiative pour l'Authenticité du Contenu.
Installez l'interface de ligne de commande de référence :
cargo install c2patool
Vérifiez que le fichier est réellement signé :
c2patool fixtures/signed-sample.png
Vous devriez obtenir un rapport JSON indiquant le générateur de la revendication et le statut de la signature. Poussez-le maintenant à travers votre propre stack et vérifiez l'autre extrémité :
# Téléchargement via votre véritable point de terminaison
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
# Récupérez-le via l'URL que votre frontend utiliserait
ASSET_URL=$(jq -r '.url' /tmp/upload.json)
curl -sS "$ASSET_URL" -o /tmp/roundtrip.png
# Le manifeste a-t-il survécu ?
c2patool /tmp/roundtrip.png
Trois résultats possibles, et ils signifient des choses différentes :
- Un rapport valide. Le manifeste a survécu. Bien.
- Aucun manifeste trouvé. Quelque chose dans votre pipeline l'a supprimé. C'est le cas le plus courant.
- Une erreur de validation. Un manifeste est présent mais sa signature ne correspond plus aux octets. Quelque chose a modifié le fichier et a laissé l'ancien manifeste attaché, ce qui est pire que de le supprimer, car cela ressemble à une altération pour tout vérificateur en aval.
Ce troisième résultat est celui à rechercher. Cela signifie généralement qu'une bibliothèque de transformation a préservé le bloc de métadonnées tout en réécrivant les pixels.
Trouvez l'étape qui le fait
Si le test de bout en bout échoue, segmentez le pipeline. Vérifiez le manifeste immédiatement après chaque étape plutôt que de deviner.
Suspects typiques par ordre de probabilité :
1. L'étape de redimensionnement ou de miniature. Le coupable le plus probable. Dans sharp, les métadonnées sont supprimées à moins que vous ne les conserviez explicitement :
// Supprime le manifeste C2PA
await sharp(input).resize(1200).toFile(output);
// Préserve le bloc de métadonnées
await sharp(input).resize(1200).keepMetadata().toFile(output);
Préserver le bloc est nécessaire mais non suffisant. Les pixels ont changé, donc la signature originale n'est plus valide par rapport aux nouveaux octets. Pour maintenir une chaîne de provenance fonctionnelle, vous devez re-signer la sortie et enregistrer la transformation comme une assertion d'action, typiquement c2pa.resized. Les bibliothèques c2pa pour Rust, Python, JavaScript et C prennent toutes en charge cela.
2. Conversion de format. Servir de l'AVIF ou du WebP signifie un nouveau conteneur. Même règle : préserver et re-signer, ou accepter que la chaîne se termine là et le signaler.
3. Le CDN. De nombreux CDN d'images réécrivent le contenu à la livraison. Certains conservent et re-signent désormais les Content Credentials nativement ; la plupart les supprimaient historiquement. Testez via l'URL de livraison que vos utilisateurs utilisent réellement, et non via l'origine, sinon vous obtiendrez un résultat positif qui ne signifie rien.
4. Normalisation au téléchargement. Les services qui réencodent à l'ingestion pour standardiser les formats sont faciles à oublier, car le code réside dans un dépôt d'infrastructure que personne ne lit.
Faites-en un test permanent
Un curl ponctuel prouve l'état actuel. Cela n'empêche pas quelqu'un d'ajouter une étape de redimensionnement au prochain sprint. La vérification doit résider dans la CI.
Divisez-le en deux couches, car deux outils différents excellent dans des domaines différents.
Couche un : le test de bout en bout, dans Apidog
L'orchestration est un test API enchaîné normal : téléchargez un fichier de test, capturez l'URL renvoyée, récupérez l'actif via le chemin de livraison réel, et vérifiez ce qui revient.
Dans Apidog, il s'agit d'un scénario de test en deux étapes.
Étape 1 : POST /v1/assets
- Corps :
multipart/form-dataavec votre fichier signé joint. La mécanique est la même que dans le test des API de téléchargement de fichiers. - Assertions : le statut est
201, et la réponse correspond à votre schéma. - Script post-réponse pour transmettre l'URL à l'étape suivante :
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://");
});
Étape 2 : GET {{ASSET_URL}}
- Assertions : le statut est
200,Content-Typeest le format attendu, et la taille du corps est proche de ce que vous avez téléchargé. Une baisse spectaculaire de la taille est un indice fort que le fichier a été réencodé.
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);
});
La taille est une heuristique, pas une preuve. Elle détecte les échecs flagrants à moindre coût et s'exécute dans la même suite que tout le reste. Les modèles d'assertion standard sont traités dans les assertions API.
Couche deux : la vérification au niveau des octets, dans la CI
Vérifier une signature signifie analyser le conteneur, ce qui est le travail de c2patool, et non d'un client HTTP. Exécutez-le comme une étape de pipeline sur le fichier récupéré par le test de bout en bout :
# .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 est important. Sans cela, un échec de c2patool sur un fichier dépouillé produirait un avertissement et une construction réussie (green build), ce qui est précisément l'échec que vous essayiez de prévenir. Si vous débutez avec l'exécution de scénarios Apidog dans un pipeline, l'automatisation des tests API dans GitHub Actions couvre la configuration.
Couche trois, optionnel : un point de terminaison de vérification
Si la provenance est une fonctionnalité du produit plutôt qu'une vérification interne, la conception la plus propre est un petit point de terminaison dans votre propre service qui exécute la bibliothèque c2pa et renvoie un résultat structuré. Ensuite, l'ensemble peut être testé comme du JSON ordinaire, et votre frontend obtient une vraie réponse au lieu d'une supposition.
{
"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"
}
}
Gardez trois états, pas deux. verified, absent et invalid signifient des choses réellement différentes, et fusionner absent et invalid en un seul booléen élimine le signal le plus intéressant dont vous disposez. Ajoutez unchecked si votre vérificateur peut être indisponible, afin qu'une panne ne se masque pas en tant que résultat propre.
Documentez la structure dans votre définition OpenAPI et validez-la, afin que les champs ne disparaissent pas lors d'une refactorisation. Comment valider les spécifications OpenAPI couvre cet aspect.
Les quatre fichiers de test à conserver
Une suite de provenance nécessite des entrées volontairement corrompues, pas seulement un chemin nominal.
- Fichier signé valide. Attendez
verified. Détecte les suppressions trop zélées. - Fichier dépouillé. Même image, manifeste supprimé avec
exiftool -all=. Attendezabsent, pas une erreur et certainement pasverified. - Fichier altéré. Fichier signé avec un octet modifié après la signature. Attendez
invalid. C'est celui qui prouve que vous vérifiez la signature plutôt que de simplement vérifier qu'un bloc existe. - Format non pris en charge. Quelque chose sans aucun support de manifeste. Attendez un
absentpropre plutôt qu'une erreur 500.
Commitez les quatre fichiers dans le dépôt à côté du scénario de test. Ils sont petits, ils ne changent jamais, et ils font la différence entre un test qui passe et un test qui a un sens.
Pourquoi s'en soucier
Trois raisons, par ordre croissant de ce qu'elles vous coûteront.
La promesse de votre produit. Si votre interface utilisateur affiche un badge de provenance, et que votre pipeline supprime les manifestes, le badge est incorrect pour chaque actif qui a été redimensionné. C'est un problème de confiance que vous découvrirez par un utilisateur.
Votre conformité. Si vous vous appuyez sur C2PA pour tout ce qui concerne l'Article 50, un manifeste supprimé est un contrôle qui ne fonctionne pas. La distinction entre fournisseur et déployeur dans l'Article 50 de l'EU AI Act pour les développeurs d'API explique quelles obligations vous incombent réellement.
Le signal lui-même. La provenance ne fonctionne que si la chaîne est maintenue de bout en bout. Chaque pipeline qui supprime silencieusement les manifestes rend l'ensemble de l'écosystème moins utile, y compris pour vous lorsque vous essayez de vérifier quelque chose.
Téléchargez Apidog pour construire le scénario de test de bout en bout contre vos propres points de terminaison, puis intégrez l'étape c2patool derrière.
FAQ
Le redimensionnement d'une image supprime-t-il les métadonnées C2PA ? Oui, par défaut dans chaque bibliothèque courante. La conservation du bloc de métadonnées nécessite un drapeau explicite, et la conservation d'une signature valide nécessite de re-signer la sortie transformée.
Comment vérifier si un fichier contient des métadonnées C2PA ? Exécutez c2patool <file> depuis la ligne de commande, ou déposez le fichier sur la page de vérification des Content Credentials.
Puis-je conserver les métadonnées C2PA lors d'un redimensionnement ? Oui, mais pas uniquement en les préservant. Préservez le bloc, puis re-signez la sortie avec une assertion d'action telle que c2pa.resized en utilisant l'une des bibliothèques c2pa. Sinon, l'ancienne signature ne correspondra plus aux nouveaux octets.
Les CDN suppriment-ils les Content Credentials ? Beaucoup le font lors de l'optimisation automatique. Certains les conservent et les re-signent désormais nativement. Testez via l'URL de livraison que vos utilisateurs utilisent, et non via l'origine.
Quelle est la différence entre un manifeste supprimé et un manifeste invalide ? Supprimé signifie qu'aucun manifeste n'a été trouvé, ce qui ne vous dit rien sur l'origine du fichier. Invalide signifie qu'un manifeste existe mais que sa signature ne correspond pas aux octets, ce qui signifie que le fichier a été modifié après la signature. Maintenez-les comme des états séparés.
Apidog peut-il vérifier directement une signature C2PA ? Il orchestre le test de bout en bout et effectue des assertions sur les réponses HTTP, y compris le JSON d'un point de terminaison de vérification. L'analyse de la signature elle-même est le travail de c2patool, exécutée comme une étape CI ou au sein de votre propre service. Utilisez les deux ensemble.
Dois-je supprimer les données EXIF pour la confidentialité mais conserver les C2PA ? C'est le bon objectif et cela nécessite une approche sélective. Un -strip général supprime les deux. Supprimez spécifiquement les blocs EXIF qui vous intéressent et laissez le manifeste C2PA intact.
Le point à retenir
Les métadonnées de provenance arrivent intactes à votre API et en repartent généralement en morceaux, et rien dans votre surveillance ne vous le dira. La solution est un fichier de test, un test de bout en bout via le chemin de livraison réel, et une vérification par c2patool qui fait échouer la construction.
Vingt minutes de configuration, et cela transforme une affirmation que vous faites dans votre interface utilisateur en une garantie que votre pipeline applique réellement.
button
