Como detectar sua API removendo metadados C2PA em testes

Claude, Gemini e OpenAI agora anexam manifestos C2PA assinados a arquivos gerados, então a proveniência real está chegando ao seu endpoint de upload e seu pipeline provavelmente está excluindo-a. Um curl de dois minutos prova isso, e um teste de três camadas o mantém corrigido.

Ashley Innocent

Ashley Innocent

12 agosto 2026

Como detectar sua API removendo metadados C2PA em testes

Apidog para empresas

Implantação local

SSO & RBAC

Conforme SOC 2

Explorar Apidog Enterprise

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.

button

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:

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

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}}

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.

  1. Arquivo assinado válido. Espere verified. Captura remoções excessivamente zelosas.
  2. Arquivo removido. Mesma imagem, manifesto removido com exiftool -all=. Espere absent, não um erro e definitivamente não verified.
  3. 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.
  4. Formato não suportado. Algo sem suporte a manifesto. Espere um absent limpo 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.

button

Pratique o design de API no Apidog

Descubra uma forma mais fácil de construir e usar APIs