Claude agora anexa metadados de proveniência C2PA assinados aos arquivos que gera. O mesmo acontece com os modelos de imagem da OpenAI, e o Gemini também. O que significa que um sinal de proveniência real está chegando ao seu endpoint de upload pela primeira vez, e há uma boa chance de seu pipeline estar o excluindo antes que alguém o veja.
Não maliciosamente. Por padrão. sharp().resize() produz um arquivo limpo sem metadados, a menos que você solicite o contrário. O mesmo acontece com ImageMagick. O mesmo acontece com Pillow. O mesmo acontece com a maioria das CDNs de imagem. O manifesto entra, um JPEG menor sai, e nada em seus logs menciona isso.
Esta é uma falha testável, e o teste não é complicado. Veja como os metadados morrem em um pipeline normal, como provar que isso está acontecendo e como configurar uma verificação de ida e volta no CI para que não volte a ocorrer. Apidog lida com a orquestração; c2patool lida com a verificação em nível de byte.
O que realmente é destruído
Um manifesto C2PA é um bloco criptograficamente assinado incorporado no contêiner do arquivo. Ele registra quem assinou o ativo e o que foi reivindicado sobre ele, e como é assinado, alterar os bytes sem reassinar quebra a assinatura de uma forma que qualquer leitor pode detectar.
"Em nível de contêiner" é a frase operativa. Reescreva o contêiner e o manifesto se foi.
| Operação | Manifesto sobrevive por padrão? |
|---|---|
| Cópia ou movimentação byte a byte | Sim |
sharp().resize().toBuffer() |
Não |
ImageMagick convert / magick |
Não |
Pillow Image.save() |
Não |
| PNG para WebP, JPEG para AVIF | Não |
| Otimização automática de CDN de imagem | Geralmente não |
| Captura de tela | Não |
| Salvar novamente de um editor de imagem | Não |
| Upload para S3 sem transformação | Sim |
Tudo na coluna "não" é algo que um aplicativo web normal faz com cada imagem que aceita. Miniaturas, variantes responsivas, negociação de formato, remoção de EXIF para privacidade. Cada um é razoável isoladamente, e cada um silenciosamente encerra a cadeia de proveniência.
Vale ressaltar: o hábito de -strip motivado pela privacidade é muitas vezes deliberado, porque o EXIF carrega coordenadas GPS e números de série de câmeras. Remover todos os metadados para eliminar dados de localização também remove o manifesto de proveniência. Esses dois objetivos agora entram em conflito, e resolvê-lo significa ser seletivo em vez de anular todo o bloco.
Prove em dois minutos
Antes de construir qualquer coisa, confirme que você tem o problema. Você precisa de um arquivo com um manifesto válido. Qualquer imagem que Claude gera funciona, ou pegue uma amostra assinada da Content Authenticity Initiative.
Instale a CLI de referência:
cargo install c2patool
Verifique se o arquivo está realmente assinado:
c2patool fixtures/signed-sample.png
Você deve receber um relatório JSON nomeando o gerador da reivindicação e o status da assinatura. Agora, passe-o por sua própria pilha e verifique o outro lado:
# Faça upload através do seu endpoint real
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
# Recupere-o através da URL que seu frontend usaria
ASSET_URL=$(jq -r '.url' /tmp/upload.json)
curl -sS "$ASSET_URL" -o /tmp/roundtrip.png
# O manifesto sobreviveu?
c2patool /tmp/roundtrip.png
Três resultados possíveis, e eles significam coisas diferentes:
- Um relatório válido. O manifesto sobreviveu. Bom.
- Manifesto não encontrado. Algo em seu pipeline o removeu. Este é o caso comum.
- Um erro de validação. Um manifesto está presente, mas sua assinatura não corresponde mais aos bytes. Algo modificou o arquivo e deixou o manifesto antigo anexado, o que é pior do que removê-lo, porque parece adulteração para qualquer verificador downstream.
Esse terceiro resultado é o que se deve procurar. Geralmente significa que uma biblioteca de transformação preservou o bloco de metadados ao reescrever os pixels.
Encontre a etapa que faz isso
Se a ida e volta falhar, faça uma busca binária no pipeline. Verifique o manifesto imediatamente após cada estágio, em vez de adivinhar.
Suspeitos típicos em ordem de probabilidade:
1. A etapa de redimensionamento ou miniatura. Culpado mais provável. No sharp, os metadados são descartados, a menos que você os mantenha explicitamente:
// Descarta o manifesto C2PA
await sharp(input).resize(1200).toFile(output);
// Preserva o bloco de metadados
await sharp(input).resize(1200).keepMetadata().toFile(output);
Preservar o bloco é necessário, mas não suficiente. Os pixels mudaram, então a assinatura original não valida mais contra os novos bytes. Para manter uma cadeia de proveniência funcionando, você reassina a saída e registra a transformação como uma asserção de ação, tipicamente c2pa.resized. As bibliotecas c2pa para Rust, Python, JavaScript e C suportam isso.
2. Conversão de formato. Servir AVIF ou WebP significa um novo contêiner. Mesma regra: preservar e reassinar, ou aceitar que a cadeia termina ali e declare isso.
3. O CDN. Muitas CDNs de imagem reescrevem na entrega. Algumas agora preservam e reassinam as Credenciais de Conteúdo nativamente; a maioria historicamente as removeu. Teste através da URL de entrega que seus usuários realmente acessam, não através da origem, ou você obterá um resultado verde que não significa nada.
4. Normalização de upload. Serviços que recodificam na ingestão para padronizar formatos são fáceis de esquecer, porque o código vive em um repositório de infraestrutura que ninguém lê.
Torne-o um teste permanente
Um curl avulso prova o estado hoje. Não impede que alguém adicione uma etapa de redimensionamento no próximo sprint. A verificação precisa estar no CI.
Divida-o em duas camadas, porque duas ferramentas diferentes são boas em duas coisas diferentes.
Camada um: a ida e volta, no Apidog
A orquestração é um teste de API encadeado normal: faça upload de um arquivo, capture a URL retornada, recupere o ativo através do caminho de entrega real, faça asserções sobre o que retorna.
No Apidog, este é um cenário de teste com duas etapas.
Etapa 1: POST /v1/assets
- Corpo:
multipart/form-datacom seu arquivo assinado anexado. A mecânica é a mesma de testar APIs de upload de arquivos. - Assertivas: o status é
201, e a resposta corresponde ao seu esquema. - Script pós-resposta para passar a URL para a próxima etapa:
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://");
});
Etapa 2: GET {{ASSET_URL}}
- Assertivas: o status é
200,Content-Typeé o formato esperado, e o tamanho do corpo está próximo do que você enviou. Uma queda drástica no tamanho é um forte indício de que o arquivo foi recodificado.
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);
});
O tamanho é uma heurística, não uma prova. Ele captura as falhas óbvias de forma barata e é executado no mesmo conjunto que todo o resto. Padrões de asserção padrão são abordados em asserções de API.
Camada dois: a verificação em nível de byte, no CI
Verificar uma assinatura significa analisar o contêiner, que é o trabalho do c2patool, não de um cliente HTTP. Execute-o como uma etapa do pipeline contra o arquivo que a ida e volta buscou:
# .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 importa. Sem ele, o c2patool falhando em um arquivo removido produz um aviso e uma construção verde, que é a falha exata que você estava tentando evitar. Se você é novo em executar cenários do Apidog em um pipeline, automatizar testes de API no GitHub Actions cobre a configuração.
Camada três, opcional: um endpoint de verificação
Se a proveniência é um recurso do produto em vez de uma verificação interna, o design mais limpo é um pequeno endpoint em seu próprio serviço que executa a biblioteca c2pa e retorna um resultado estruturado. Assim, a coisa toda é testável como JSON comum, e seu frontend obtém uma resposta real em vez de uma suposição.
{
"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"
}
}
Mantenha três estados, não dois. verified, absent e invalid significam coisas genuinamente diferentes, e colapsar absent e invalid em um booleano joga fora o sinal mais interessante que você tem. Adicione unchecked se seu verificador puder estar indisponível, para que uma interrupção não se mascare como um resultado limpo.
Documente o formato em sua definição OpenAPI e valide-o, para que os campos não possam desaparecer em uma refatoração. Como validar especificações OpenAPI cobre esse lado.
Os quatro arquivos de teste que valem a pena manter
Uma suíte de proveniência precisa de entradas deliberadamente quebradas, não apenas de um caminho feliz.
- Arquivo assinado válido. Espere
verified. Captura remoções excessivamente zelosas. - Arquivo removido. Mesma imagem, manifesto removido com
exiftool -all=. Espereabsent, não um erro e definitivamente nãoverified. - Arquivo adulterado. Arquivo assinado com um byte alterado após a assinatura. Espere
invalid. Este é o que prova que você está verificando a assinatura em vez de apenas verificar se um bloco existe. - Formato não suportado. Algo sem suporte a manifesto. Espere um
absentlimpo em vez de um erro 500.
Confirme todos os quatro no repositório ao lado do cenário de teste. Eles são pequenos, nunca mudam e são a diferença entre um teste que passa e um teste que significa algo.
Por que se preocupar
Três razões, em ordem crescente de quanto elas custarão a você.
Sua afirmação de produto. Se sua UI mostra um distintivo de proveniência, e seu pipeline remove manifestos, o distintivo está errado para cada ativo que passou por um redimensionamento. Isso é um problema de confiança que você descobrirá por meio de um usuário.
Sua história de conformidade. Se você está contando com C2PA para algo relacionado ao Artigo 50, um manifesto removido é um controle que não está funcionando. A divisão entre provedor e implementador em Lei de IA da UE Artigo 50 para desenvolvedores de API explica quais deveres realmente cabem a você.
O próprio sinal. A proveniência só funciona se a cadeia se mantiver de ponta a ponta. Cada pipeline que silenciosamente descarta manifestos torna todo o ecossistema menos útil, inclusive para você quando você é quem tenta verificar algo.
Baixe o Apidog para construir o cenário de ida e volta contra seus próprios endpoints, então conecte a etapa c2patool atrás dele.
FAQ
Redimensionar uma imagem remove os metadados C2PA? Sim, por padrão em todas as bibliotecas comuns. Preservar o bloco de metadados requer uma flag explícita, e manter uma assinatura válida requer reassinar a saída transformada.
Como verifico se um arquivo tem metadados C2PA? Execute c2patool <arquivo> na linha de comando, ou solte o arquivo na página de verificação de Credenciais de Conteúdo.
Posso manter os metadados C2PA durante um redimensionamento? Sim, mas não apenas preservando. Preserve o bloco, então reassine a saída com uma asserção de ação como c2pa.resized usando uma das bibliotecas c2pa. Caso contrário, a assinatura antiga não corresponderá mais aos novos bytes.
As CDNs removem as Credenciais de Conteúdo? Muitas sim, quando otimizam automaticamente. Algumas agora preservam e reassinam nativamente. Teste através da URL de entrega que seus usuários acessam, não através da origem.
Qual a diferença entre um manifesto removido e um inválido? Removido significa que nenhum manifesto foi encontrado, o que não diz nada sobre a origem do arquivo. Inválido significa que um manifesto existe, mas sua assinatura não corresponde aos bytes, o que significa que o arquivo mudou após a assinatura. Mantenha-os como estados separados.
O Apidog pode verificar uma assinatura C2PA diretamente? Ele orquestra a ida e volta e faz asserções sobre respostas HTTP, incluindo o JSON de um endpoint de verificação. A análise da assinatura em si é trabalho do c2patool, executado como uma etapa de CI ou dentro do seu próprio serviço. Use ambos em conjunto.
Devo remover EXIF por privacidade, mas manter C2PA? Esse é o objetivo certo e requer uma abordagem seletiva. Um -strip genérico remove ambos. Remova os blocos EXIF que lhe interessam especificamente e deixe o manifesto C2PA intacto.
A Conclusão
Os metadados de proveniência chegam à sua API intactos e geralmente saem em pedaços, e nada em seu monitoramento o avisará. A solução é um arquivo de teste, uma ida e volta através do caminho de entrega real e uma verificação c2patool que falha na construção.
Vinte minutos de configuração, e isso transforma uma afirmação que você faz em sua UI em uma garantia que seu pipeline realmente impõe.
