watermarks-remover

Remover marcas de proveniência de IA de vários fornecedores: higiene de texto Unicode, ganchos de reescrita estatística e metadados C2PA de PNG/JPEG/SVG/PDF/DOCX/HTML/MD

Documentação

_ _ _ ____ ___ ____ ____ _  _ ____ ____ _  _ ____    ____ ____ _  _ ____ _  _ ____ ____
| | | |__|  |  |___ |__/ |\/| |__| |__/ |_/  [__  __ |__/ |___ |\/| |  | |  | |___ |__/
|_|_| |  |  |  |___ |  \ |  | |  | |  \ | \_ ___]    |  \ |___ |  | |__|  \/  |___ |  \

watermarks-remover

CI Release Stars Forks

Habilidade de agente + serviço Python stdlib para remover marcas de proveniência de IA de múltiplos fornecedores de textos e arquivos — para privacidade e higiene em conteúdo que você possui. A habilidade é um cliente leve: ela aciona a maquinaria via HTTP, então o host do agente não precisa de Python.

CamadaAlvoComo
AUnicode invisível, espaços exóticos, bidi, caracteres de tagScripts Python determinísticos
BMarcas d'água estatísticas de texto (amostragem de tokens)Reescrita pelo agente + hook opcional rewrite_text.py
ArquivosC2PA / EXIF / XMP / propriedades de documentosPNG, JPEG, WebP, AVIF, HEIC, BMP, GIF, TIFF, SVG, PDF, DOCX, XLSX, PPTX, EPUB, ODT, HTML, Markdown, MP4/MOV/M4A/M4V, WAV, MP3, FLAC

Fornecedores / ecossistemas (nível de classe): Claude, Gemini / SynthID-Text, superfícies de proveniência OpenAI, marcas estilo Kirchenbauer (lista verde) e Gumbel-chaveado / EXP (Aaronson) de LLMs abertos.

Última versão: v0.7.0

Caminho da habilidade: skills/remove-ai-marks/
Caminho do serviço: service/
(migração: anteriormente remove-claude-marks; alias de barra /remove-claude-marks ainda documentado)

Instalação (habilidade do agente)

A habilidade não inclui código — ela chama o serviço via HTTP. Instale a habilidade (somente markdown) e inicie o serviço, depois defina WATERMARKS_SERVICE_URL se não for http://127.0.0.1:8765.

No Claude Code, o caminho mais rápido é o marketplace de plugins incluído — sem clone, e ele atualiza no lugar. Em qualquer outro lugar, um único instalador cobre todos os hosts suportados (Python 3.10+ stdlib, sem dependências):

python3 install_skill.py --skill remove-ai-marks --target claude-code
HostAlvoOnde fica
Claude Code (pessoal)--target claude-code~/.claude/skills/<skill> (respeita CLAUDE_CONFIG_DIR)
Claude Code (projeto)--target claude-project --project-dir PATHPATH/.claude/skills/<skill>
Cowork, claude.ai, sessões na nuvem, rotinas--target coworkdist/<skill>.zip para enviar em Customize → Skills
Cursor--target cursor (padrão)~/.cursor/skills/<skill>

Habilidades incluídas: remove-ai-marks (completa, com suporte do serviço) e clean-user-facing-text (somente texto, autossuficiente). --list as exibe. Instalações existentes são preservadas a menos que você passe --force; a substituição é encenada primeiro e a instalação anterior é mantida como um backup com nome único. --link cria um link simbólico para este checkout em vez de copiar, então as edições são detectadas ao vivo. No Windows, use py install_skill.py ...; o wrapper install-skill.sh é fornecido para shells macOS/Linux.

Antes de escrever qualquer coisa, o instalador valida a habilidade contra as regras de empacotamento de Agent Skills que os uploads do claude.ai e a API de Skills aplicam: frontmatter somente de especificação (name, description, license, compatibility, metadata, allowed-tools), um name minúsculo com hífen de no máximo 64 caracteres correspondendo ao diretório, um description não vazio de no máximo 1024 caracteres. O pacote do Cowork também precisa caber no limite de upload de 30 MB, que o empacotador aplica.

Limpeza automática via hook (determinística)

Uma habilidade é uma instrução: o modelo decide se a invoca, e o modelo é quem produz as marcas. Um hook é executado pelo harness em cada chamada de ferramenta correspondente, sem exigir cooperação. Isso torna o hook a metade determinística deste fluxo de trabalho.

O plugin registra um hook PostToolUse em Write|Edit|MultiEdit|NotebookEdit que executa service/scripts/hook_written_file.py contra o arquivo que o agente acabou de escrever. Dois modos, seguindo a convenção do pre-commit de verificar por padrão:

ModoComportamento
check (padrão)Relata marcas de proveniência, deixa o arquivo intacto. Os achados vão para o modelo (saída 2), para que ele possa oferecer a limpeza.
cleanRemove as marcas no lugar e informa ao modelo que o arquivo no disco mudou.

Defina o modo nas configurações do plugin (Hook mode em /plugin manage, lido pelo hook como CLAUDE_PLUGIN_OPTION_HOOK_MODE), ou com WATERMARKS_HOOK_MODE=clean no ambiente. O comando do hook deliberadamente não interpola ${user_config.hook_mode}: o Claude Code se recusa a executar um hook que referencia uma opção que o usuário nunca abriu /plugin manage para definir — um default declarado não satisfaz isso — então interpolá-lo significaria que o hook silenciosamente nunca roda em uma instalação nova. A detecção reutiliza audit_lib's scan_file / is_actionable, então o hook, o gate de pre-commit e a exportação SARIF do CI concordam sobre o que conta como acionável; a limpeza delega para clean_file.py, então nenhuma lógica de limpeza é duplicada. O modo clean escreve em um arquivo temporário irmão e troca somente em uma diferença real, então arquivos que já estavam limpos mantêm seu mtime e não reacionam os observadores de arquivo.

Sem o plugin, conecte-o em ~/.claude/settings.json (ou em um .claude/settings.json de projeto) você mesmo:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit|NotebookEdit",
        "hooks": [
          {
            "type": "command",
            "command": "python3",
            "args": ["/path/to/watermarks-remover/service/scripts/hook_written_file.py",
                     "--mode", "check"],
            "timeout": 30
          }
        ]
      }
    ]
  }
}

No Windows, substitua python3 por py.

O que um hook não pode fazer. Nenhum hook pode reescrever a mensagem de chat do assistente antes de você lê-la. O hook Stop do Claude Code recebe last_assistant_message somente leitura, e não há filtro de pré-envio para respostas finais — o mesmo limite que este projeto já documenta para regras do Cursor. Portanto, a garantia determinística cobre arquivos que o agente escreve, além do gate de pre-commit para qualquer coisa a caminho do git. Texto que só existe no transcript do chat ainda depende do fluxo de trabalho da habilidade, que é baseado em instruções do modelo e, portanto, de melhor esforço.

Plugin do Claude Code (marketplace)

O repositório também é um plugin do Claude Code e um marketplace de plugin único (.claude-plugin/), então ambas as habilidades instalam e atualizam em dois comandos, sem clone ou script necessário:

/plugin marketplace add guillaumemeyer/watermarks-remover
/plugin install watermarks-remover@watermarks-remover

As habilidades então carregam com namespace: /watermarks-remover:remove-ai-marks e /watermarks-remover:clean-user-facing-text (o /remove-ai-marks puro também funciona quando nada mais reivindica o nome). /plugin marketplace update watermarks-remover puxa versões posteriores. O mesmo funciona pela CLI com claude plugin marketplace add … / claude plugin install …, e de um checkout local passando um caminho em vez de owner/repo.

Mantenedores: make plugin-validate executa claude plugin validate . --strict contra ambos os manifestos; tests/test_plugin_manifest.py cobre os mesmos arquivos sem precisar da CLI.

Claude Code

# Personal — available in all your projects
python3 install_skill.py --skill remove-ai-marks --target claude-code
# or: make install-claude-code-skill

# Project — commit .claude/skills/ to share it with the repo
python3 install_skill.py --skill remove-ai-marks --target claude-project \
  --project-dir /path/to/project
# or: make install-claude-project-skill PROJECT=/path/to/project

O Claude Code detecta habilidades pessoais e de projeto sem reiniciar; /skills lista o que foi carregado. Invoque com /remove-ai-marks ou peça para "remover marcas d'água de IA / C2PA / marcas do Claude / texto classe SynthID." Uma instalação de projeto também é o que as sessões na nuvem leem, já que elas clonam o repositório e carregam seu .claude/skills/.

Cowork (e claude.ai, sessões na nuvem, rotinas)

Sessões do Cowork não leem ~/.claude/skills na sua máquina — elas carregam as habilidades habilitadas para sua conta claude.ai, sincronizadas quando a sessão inicia. Então instale lá enviando um pacote:

python3 install_skill.py --skill remove-ai-marks --target cowork
# writes dist/remove-ai-marks.zip   (make package-cowork-skill)

Depois, no aplicativo Claude Desktop, abra Customize → Skills → Add e envie o zip (as mesmas configurações de habilidade no claude.ai também funcionam). O pacote é reproduzível e contém um único diretório remove-ai-marks/ de nível superior com SKILL.md em sua raiz, que é o layout que o upload espera.

A acessibilidade do serviço importa mais aqui do que em uma instalação local: a habilidade é um cliente HTTP leve, então a sessão deve conseguir alcançar WATERMARKS_SERVICE_URL. Sessões do Cowork que rodam localmente na sua máquina alcançam um make serve local; sessões na nuvem e rotinas rodam remotamente e precisam de uma URL de serviço alcançável de lá (e WATERMARKS_SERVER_API_KEY definido nele). Se você quiser uma habilidade sem serviço algum, envie clean-user-facing-text em vez disso — ela é somente texto e inclui seus próprios scripts:

python3 install_skill.py --skill clean-user-facing-text --target cowork

Grok

# Grok Build / project-local
mkdir -p .grok/skills
ln -sfn "$(pwd)/skills/remove-ai-marks" .grok/skills/remove-ai-marks

# User-global Grok
mkdir -p ~/.grok/skills
ln -sfn "$(pwd)/skills/remove-ai-marks" ~/.grok/skills/remove-ai-marks

Habilidade opcional somente texto

skills/clean-user-facing-text/ é uma habilidade autossuficiente para manuscritos autorizados, documentação e cópia web. Ela exclui ferramentas de imagem, C2PA, serviço e modelo externo, e executa seus próprios scripts da Camada A embutidos em vez de chamar o serviço.

python3 install_skill.py --skill clean-user-facing-text --target claude-code
python3 install_skill.py --skill clean-user-facing-text --target cursor

A invocação da habilidade é selecionada pelo modelo. Projetos que adotam explicitamente este fluxo de trabalho no Cursor também podem copiar a regra opcional:

mkdir -p /path/to/project/.cursor/rules
cp integrations/cursor/clean-user-facing-text.mdc \
  /path/to/project/.cursor/rules/clean-user-facing-text.mdc

Para todos os projetos, coloque a mesma instrução nas User Rules do Cursor. Regras melhoram a consistência, mas permanecem instruções do modelo; o Cursor não expõe um filtro determinístico de pré-envio para respostas finais de chat.

Iniciar o serviço

O caminho mais rápido é um servidor HTTP local (somente Python 3.10+ stdlib — sem dependências, sem Docker):

make serve                 # http://127.0.0.1:8765
# or directly:
python3 service/scripts/server.py --host 127.0.0.1 --port 8765

Windows (sem Docker)

Veja docs/windows-autostart.md para iniciar automaticamente o serviço no login do Windows sem Docker.

Para toda a infraestrutura (núcleo + backends opcionais de harness/pesados), veja Docker / compose abaixo.

Ferramentas de sistema opcionais (usadas automaticamente quando presentes — pré-instaladas na imagem Docker do núcleo):

FerramentaPapel
c2patoolInspecionar manifestos C2PA
exiftoolRemoção de metadados residuais (especialmente PDF)
qpdfReconstrução estrutural de PDF — necessária para uma remoção real de PDF (veja abaixo)

Scripts do núcleo precisam somente de Python 3.10+ stdlib. Chamadas de modelo da Camada B são opcionais.

Uso rápido (scripts)

SCRIPTS=service/scripts

# Unified inspect / clean
python3 "$SCRIPTS/inspect_file.py" draft.md
python3 "$SCRIPTS/clean_file.py" draft.md -o draft.cleaned.md
python3 "$SCRIPTS/clean_file.py" photo.png -o photo.cleaned.png
python3 "$SCRIPTS/clean_file.py" notes.docx -o notes.cleaned.docx

# Text Layer A
python3 "$SCRIPTS/inspect_text.py" draft.md
python3 "$SCRIPTS/clean_text.py" draft.md -o draft.cleaned.md --stats

# Layer B rewrite hook (default: print prompt only — no model required)
python3 "$SCRIPTS/rewrite_text.py" draft.md --backend print-prompt --tactic paraphrase
# Optional local Ollama (loopback only by default — remote endpoints require
# WATERMARKS_REWRITE_ALLOW_REMOTE=1 or --allow-remote):
# WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2 \
#   python3 "$SCRIPTS/rewrite_text.py" draft.md -o draft.rewritten.md
# API keys are read from WATERMARKS_REWRITE_API_KEY only (never argv).

# Images
python3 "$SCRIPTS/inspect_image.py" shot.png
python3 "$SCRIPTS/clean_image.py" shot.png -o shot.cleaned.png

Ferramentas de texto recusam entrada binária

inspect_text.py, clean_text.py e rewrite_text.py operam em texto. Apontadas para um .docx, .pdf ou imagem, elas costumavam decodificar os bytes compactados e relatar quaisquer codepoints que saíssem — ruído que rastreia a compactação, não o conteúdo — e clean_text.py então escrevia esses bytes corrompidos de volta, destruindo o arquivo. Elas agora recusam entrada binária e nomeiam a ferramenta que a trata:

python3 "$SCRIPTS/inspect_text.py" report.docx
# refusing to treat report.docx as text: it looks like a ZIP container (DOCX, ODT, …).
# Use inspect_file.py / clean_file.py, which route by format,
# or pass --force-text to scan the raw bytes anyway.

A detecção é por número mágico mais uma proporção de bytes de controle, então texto em codificações diferentes de UTF-8 continua funcionando. --force-text substitui isso em todos os lugares.

Formatos não reconhecidos nunca são limpos automaticamente

classify() rotula bytes que não correspondem a nenhum formato de texto, imagem ou contêiner suportado como unknown — não cai mais para "texto". No modo automático, clean_file.py recusa tais arquivos (saída 2, nenhuma saída escrita) em vez de decodificá-los como UTF-8 e escrever bytes corrompidos de volta; --as text ou --force-text são as opções explícitas. inspect_file.py relata o arquivo como unknown (saída 0), e o serviço HTTP responde /inspect com kind: "unknown" mas rejeita /clean de formatos desconhecidos (400 — envie um nome de arquivo com uma extensão conhecida, ex.: notes.txt).

Serviço HTTP

A mesma maquinaria roda como um serviço HTTP stdlib (service/scripts/server.py) — a interface que a habilidade usa e a maneira como qualquer aplicativo web pode integrar sem vendoring:

MétodoCaminhoCorpoRetorna
GET/health—{"ok": true, "version": ...}
GET/capabilities—ferramentas/backends opcionais utilizáveis (cada ferramenta é verificada por versão, não apenas encontrada em PATH)
GET/openapi.json—especificação OpenAPI 3.0.3 gerada dinamicamente
POST/inspect{"file": "<base64>", "name": "notes.md"}{"ok", "kind", "suspicious", "report"}
POST/detect{"file": "<base64>", "name": "notes.txt"}{"ok", "kind", "detections": [...]}
POST/clean{"file": "<base64>", "name": "notes.md", "options": {...}}{"ok", "kind", "cleaned": "<base64>", "report"}
POST/watermark{"text": "...", "keys": [118, 504, ...], "options": {...}} ou {"file": "<base64>", ...}{"ok", "kind", "watermarked_text", "report": {"scheme_used", ...}}
POST/inspect/batch{"files": [{"file": "<base64>", "name": "notes.md"}, ...]}{"ok", "results": [{"name", "ok", "kind", "suspicious", "report"}, ...]}
POST/detect/batch{"files": [{"file": "<base64>", "name": "notes.txt"}, ...]}{"ok", "results": [{"name", "ok", "kind", "detections", "report"}, ...]}
POST/clean/batch{"files": [{"file": "<base64>", "name": "notes.md", "options": {...}}, ...]}{"ok", "results": [{"name", "ok", "kind", "cleaned", "report"}, ...]}
POST/watermark/batch{"files": [{"text": "...", "keys": [...]}, {"file": "<base64>"}, ...]}{"ok", "results": [{"name", "ok", "kind", "watermarked_text", "report": {"scheme_used", ...}}, ...]}
Batch endpoints percorrem o mesmo pipeline por arquivo que /inspect, /detect, /clean e /watermark, limitados a WATERMARKS_MAX_BATCH_FILES arquivos por requisição (padrão 50). Uma entrada malformada (base64 inválido, opção desconhecida, formato não reconhecido) aparece como o "ok": false dessa entrada com uma string "error" — nunca aborta o restante do lote.
WM="http://127.0.0.1:8765"
curl -s "$WM/health"                       # {"ok": true, "version": "..."}
curl -s "$WM/openapi.json"                 # machine-readable OpenAPI 3.0.3 contract
curl -s -X POST "$WM/clean" -H 'Content-Type: application/json' \
  -d "{\"file\": \"$(base64 < notes.md | tr -d '\n')\", \"name\": \"notes.md\"}"

O serviço roteia pela extensão do nome do arquivo e depois pelos magic bytes, então texto / imagem / contêiner são detectados automaticamente. Defina WATERMARKS_SERVER_API_KEY para exigir Authorization: Bearer <key> em toda requisição. Bind somente em loopback por padrão (--host para sobrescrever); destinado a uma rede confiável.

Detecção de marca d'água (/detect e detect_before / detect_after)

A detecção é uma etapa separada da limpeza — o serviço nunca chama APIs de fornecedores a menos que você peça:

  • POST /detect executa os detectores de marca d'água configurados em um arquivo. Texto → detectores de fornecedores + estilometria; imagem → pontuação de pixel SynthID.
  • /inspect aceita um flag "detect": true opt-in que anexa resultados do detector ao relatório de texto (e pode inverter suspicious).
  • /clean aceita opções "detect_before" / "detect_after" para pontuar a entrada e a saída limpa, para que você possa medir o que uma limpeza realmente alterou.
  • /clean executa a reescrita de texto da Camada B após a Camada A por padrão (é uma etapa obrigatória para texto). Uma opção "strategy" (uma lista tactic@intensity ordenada, ex.: "paraphrase@0.8,mlm@0.2") sobrescreve o padrão do arquivo de configuração de estratégia (veja abaixo). Quando o backend/modelo de reescrita para uma etapa não está configurado, /clean retorna um 400.

Detectores de texto (veja /capabilities → text_detectors):

Detectores de texto (veja /capabilities → text_detectors):

DetectorAtivado porNotas
markllmMARKLLM_DIR (checkout do host)Harness de pesquisa (esquemas KGW / SynthID), somente mesma-configuração — não é um oráculo de fornecedor.
gumbelWATERMARKS_GUMBEL_KEYReplay de mesma chave sem modelo do esquema keyed-Gumbel (Aaronson EXP) (veja detect_gumbel.py), somente stdlib — engines auto-hospedados como arbi-serve; somente mesma-chave, não é um oráculo de fornecedor.
claude-text— (placeholder)A Anthropic anunciou uma API de detecção de marca d'água; esta costura é ativada quando ela for lançada.

Pontuação de imagem: quando WATERMARKS_SYNTHID_SCORER_URL está definido, o serviço pontua imagens através do sidecar wr-synthid-score (perfil pesado); com um REVERSE_SYNTHID_DIR local, usa o checkout diretamente. A detecção é fail-soft: detectores não configurados, com timeout ou com erro relatam {"available": false, "error": ...} e nunca bloqueiam a limpeza.

Geração de marca d'água (/watermark e /watermark/batch)

Gera texto com marca d'água para avaliação de benchmark e testes de ida e volta. Quando WATERMARKS_SYNTHID_TEXT_URL está definido, o serviço delega a geração ao sidecar wr-synthid-text (perfil harness); com um MARKLLM_DIR local, usa o checkout diretamente. Como a detecção, a geração é fail-soft: um gerador não configurado relata {"ok": false, "error": ...}.

Docker / compose

Imagens publicadas (GHCR):

Tag da imagemConteúdoPublicada?
ghcr.io/guillaumemeyer/watermarks-remover:<tag> / :latestServiço HTTP principal + todos os limpadores + exiftool / qpdf / c2patoolSim
…:markllm-<tag> / :markllm-latestHarness de marca d'água de texto MarkLLM (upstream Apache-2.0)Sim
…:markdiffusion-<tag> / :markdiffusion-latestHarness de imagem MarkDiffusion (upstream Apache-2.0)Sim
watermarks-remover-ctrlregen:localRemoção de pixel CtrlRegen — nunca publicada (noai-watermark não inclui LICENSE)Somente build local
watermarks-remover-synthid-scorer:localPontuador reverse-SynthID — nunca publicado (Licença de Pesquisa não comercial)Somente build local (CLI de pontuação + sidecar HTTP opcional wr-synthid-score sob o perfil heavy)

Build e execução do serviço principal:

make docker-core-build
docker run --rm -p 127.0.0.1:8765:8765 --read-only --tmpfs /tmp watermarks-remover
# any CLI stays runnable by overriding the command:
docker run --rm -v "$(pwd):/data" watermarks-remover \
  /app/scripts/clean_file.py /data/notes.md -o /data/notes.cleaned.md

Subida de toda a infraestrutura:

docker compose up -d                         # core HTTP service only
docker compose --profile harness up -d       # + markllm / markdiffusion / wr-synthid-text sidecar
docker compose --profile heavy up -d         # + ctrlregen / synthid (local builds)
docker compose --profile harness --profile heavy up -d   # all services

O stack do compose mapeia o serviço principal para 127.0.0.1:8765. Serviços persistentes rodam como daemons em segundo plano (wr-core e o sidecar wr-synthid-text sob o perfil harness). Os serviços restantes de harness/pesados são CLIs de uso único — invoque com docker compose run --rm <service> … quando precisar de verificação ou trabalho de pixel.

Valide o stack em execução (somente código de saída, sem saída em caso de sucesso):

make compose-check        # or: ./compose-check.sh

Verifica wr-core via GET /health e executa cada serviço de harness/pesado com --help, exigindo saída 0.

Configuração (variáveis de ambiente para docker compose)

A limpeza de texto exige configuração da Camada B — a reescrita da Camada B é uma etapa obrigatória para POST /clean em texto, então o serviço principal precisa do backend de reescrita configurado, ou a limpeza de texto retorna HTTP 400. A limpeza de metadados de imagem/contêiner funciona pronta para uso. Para texto, você deve configurar as dependências da estratégia da Camada B: transformers + roberta-large (para a etapa padrão mlm) e a configuração de LLM WATERMARKS_REWRITE_* (para a etapa paraphrase):

echo "Hello\u200bWorld\u00ad!" > /tmp/sample.txt
curl -s -X POST http://127.0.0.1:8765/clean -H 'Content-Type: application/json' \
  -d "{\"file\": \"$(base64 < /tmp/sample.txt | tr -d '\n')\", \"name\": \"sample.txt\"}"

Idiomas cuja tipografia depende de espaço não separável (francês « … », o espaço antes de ; : ! ?) devem passar "options": {"normalize_spaces": false}, o equivalente HTTP de clean_text.py --no-normalize-spaces. Carreadores invisíveis ainda são removidos; apenas a reescrita de espaço é ignorada.

Todo o resto é opcional e vive em um arquivo .env na raiz do repositório. docker compose carrega automaticamente .env e interpola as referências ${VAR} em compose.yaml a partir dele (exports de shell vencem .env se ambos estiverem definidos).

cp .env.example .env       # then edit
docker compose up -d       # picks up .env automatically

.env é gitignored (negação por padrão) — nunca o commite. Para execuções de CLI no host (rewrite_text.py, a skill), exporte o mesmo arquivo para o ambiente:

set -a; . ./.env; set +a; python3 service/scripts/rewrite_text.py /tmp/x.txt -o /tmp/x.rewritten.txt
VarAlcançaPropósito
WATERMARKS_SERVER_API_KEYwr-core (via compose environment)Exigir Authorization: Bearer <key> na API HTTP
WATERMARKS_GEMINI_*—Removida em ago/2026: o Google aposentou a marca d'água de texto SynthID na API (veja vendor-notes.md)
WATERMARKS_SYNTHID_SCORER_URLwr-coreApontar o principal para o sidecar wr-synthid-score para pontuação de imagem SynthID (ex.: http://wr-synthid-score:8766 sob o perfil pesado)
WATERMARKS_SYNTHID_SCORER_API_KEYwr-core + wr-synthid-scoreChave bearer compartilhada para o sidecar de pontuação (vazio = sem autenticação)
WATERMARKS_SYNTHID_TEXT_URLwr-coreApontar o principal para o sidecar wr-synthid-text para marca d'água de texto SynthID (ex.: http://wr-synthid-text:8767 sob o perfil harness)
WATERMARKS_SYNTHID_TEXT_API_KEYwr-core + wr-synthid-textChave bearer compartilhada para o sidecar de marca d'água de texto (vazio = sem autenticação)
WATERMARKS_SYNTHID_TEXT_TIMEOUTwr-coreSegundos para aguardar o sidecar wr-synthid-text (padrão 120)
WATERMARKS_MARKLLM_SCHEMEtext_detectors.py (host)Esquema MarkLLM para /detect: kgw (padrão) / synthid
HF_TOKENserviços harness/pesadosToken do Hugging Face para modelos restritos
WATERMARKS_SERVICE_URLsomente cliente (skill / curl)Onde alcançar o serviço; padrão http://127.0.0.1:8765
WATERMARKS_REWRITE_BACKENDhook rewrite_text.pyprint-prompt (padrão) / ollama / openai-compatible
WATERMARKS_REWRITE_MODELhook rewrite_text.pyNome do modelo (ex.: deepseek-v4-flash)
WATERMARKS_REWRITE_BASE_URLhook rewrite_text.pyBase da API (ex.: https://api.deepseek.com)
WATERMARKS_REWRITE_API_KEYhook rewrite_text.pyChave da API — somente ambiente, nunca em argv
WATERMARKS_REWRITE_ALLOW_REMOTEhook rewrite_text.py1 para permitir endpoints fora de loopback
WATERMARKS_REWRITE_REASONING_EFFORThook rewrite_text.pynone (padrão) / low / medium / high / off
WATERMARKS_CLEAN_STRATEGY_FILEserver.py /cleanCaminho para o JSON de configuração da estratégia da Camada B (padrão config/clean_strategy.json)
WATERMARKS_GUMBEL_KEYdetect_gumbel.py / text_detectors.pyChave secreta para replay de mesma chave keyed-Gumbel (EXP) (ex.: 0x…); preferida sobre argv — nunca registrada em log

A Camada B é obrigatória para limpeza de texto. /clean sempre aplica a estratégia padrão (de config/clean_strategy.json, {"default_strategy": "paraphrase@0.8,mlm@0.2"}) a um arquivo de texto após a Camada A, a menos que a requisição passe sua própria opção "strategy" (uma lista tactic@intensity ordenada). Uma etapa de estratégia é tactic@intensity; a etapa mlm precisa de transformers + roberta-large, e qualquer etapa de LLM (paraphrase, humanize, …) precisa da configuração WATERMARKS_REWRITE_*. Se o backend/modelo obrigatório não estiver configurado — ou nenhuma estratégia estiver disponível — /clean rejeita a requisição com um 400. Precedência para o caminho de configuração: flag de CLI --strategy-config > variável de ambiente WATERMARKS_CLEAN_STRATEGY_FILE > o padrão config/clean_strategy.json.

Imagens são publicadas automaticamente em tags v* via .github/workflows/release-images.yml.

Pontuação de pixel SynthID opcional

inspect_image.py e clean_image.py podem relatar uma pontuação de confiança SynthID no domínio de pixel quando um checkout externo de aloshdenny/reverse-SynthID está disponível. O pontuador não é empacotado: é carregado em tempo de execução do seu checkout, e seu código permanece sob a Licença de Pesquisa não comercial do projeto upstream.

Opção 1: bootstrap de um comando (sem Docker)

SCRIPTS=service/scripts

# Clones upstream, creates a venv, and installs scorer-only dependencies.
"$SCRIPTS/setup_synthid.sh"

# Score an image (default checkout: ~/reverse-SynthID).
REVERSE_SYNTHID_DIR=~/reverse-SynthID \
~/reverse-SynthID/.venv/bin/python "$SCRIPTS/score_synthid.py" shot.png

# Or surface the score from inspect / clean (same venv Python).
REVERSE_SYNTHID_DIR=~/reverse-SynthID \
~/reverse-SynthID/.venv/bin/python "$SCRIPTS/inspect_image.py" shot.png

setup_synthid.sh aceita --dir PATH, --ref REF e --full (instale o requirements.txt upstream completo, que adiciona torch/diffusers para o bypass de VAE upstream que este projeto não usa).

No Windows use setup_synthid.ps1 (-Dir, -Ref, -Full), que cria o venv em .venv\Scripts\ — o layout que image_meta.py já procura em os.name == "nt".

Opção 2: build Docker local

make docker-synthid-build
# Run unprivileged and with a read-only rootfs; the scorer only needs to read
# /data and write to stdout/tmp.
docker run --rm \
  --user "$(id -u):$(id -g)" \
  --read-only --tmpfs /tmp \
  -v "$(pwd):/data" \
  watermarks-remover-synthid-scorer /data/shot.png

A imagem é construída localmente a partir do código-fonte upstream no momento do build. Não é publicada, então não redistribui o código upstream.

Opção 3: sidecar HTTP de pontuação (docker compose)

Sob o perfil heavy, o stack do compose também executa o pontuador como um sidecar HTTP (wr-synthid-score) para que o serviço principal publicado possa pontuar imagens antes/depois da limpeza sem empacotar o código upstream não comercial. Aponte wr-core para ele e compartilhe uma chave bearer (veja .env.example):

# .env
WATERMARKS_SYNTHID_SCORER_URL=http://wr-synthid-score:8766
WATERMARKS_SYNTHID_SCORER_API_KEY=change-me

docker compose --profile heavy up -d

Então POST /clean com {"options": {"detect_before": true, "detect_after": true}} returns synthid_before / synthid_after no relatório, e POST /detect em uma imagem retorna a pontuação SynthID. Fail-soft: se o sidecar estiver inativo ou não configurado, os relatórios carregam {"available": false, "error": ...} e a limpeza ainda é bem-sucedida.

A pontuação V4 usa artifacts/spectral_codebook_v4.npz do checkout upstream (`220 MB). Isso é somente detecção/pontuação — não remove marcas d'água de pixel.

Remoção de pixel CtrlRegen opcional

Para marcas d'água de imagem no domínio de pixel (classe SynthID, StegaStamp, Tree-Ring, StableSignature), um backend externo opcional executa o pipeline CtrlRegen (regeneração controlável ControlNet + DINOv2 IP-Adapter). O backend é mertizci/noai-watermark, uma reimplementação mantida do método CtrlRegen do ICLR 2025 com tiling automático.

O backend não é empacotado e não inclui arquivo LICENSE, então é tratado como todos-os-direitos-reservados: é clonado em um commit fixado e carregado em tempo de execução. Suas dependências fixadas da era de pesquisa (requirements-ctrlregen.txt — ex.: transformers==4.37.2, diffusers==0.27.2) carregam avisos publicados e são intencionalmente desatualizadas, então são instaladas apenas dentro do venv dedicado que este script cria e nunca na imagem principal do serviço; setup_ctrlregen.sh também reverifica o commit fixado em checkouts existentes, não apenas em clones novos.

Bootstrap

SCRIPTS=service/scripts

# Clones upstream (pinned commit), creates a venv, installs torch + deps.
"$SCRIPTS/setup_ctrlregen.sh"

# Standalone removal (default checkout: ~/noai-watermark).
NOAI_WATERMARK_DIR=~/noai-watermark \
~/noai-watermark/.venv/bin/python "$SCRIPTS/clean_ctrlregen.py" shot.png -o shot.ctrlregen.png

No Windows use setup_ctrlregen.ps1 (mesmas flags que -Dir, -Ref, -Python); o venv fica em .venv\Scripts\, que clean_image.py já resolve. Ele verifica os índices de wheels do PyTorch publicados e escolhe o mais alto igual ou abaixo da versão CUDA que nvidia-smi imprime e que realmente existe — esse número é o máximo que o driver suporta, e drivers são retrocompatíveis, então um driver reportando 13.1 (sem cu131 publicado) instala cu130. Abaixo da capacidade de computação 7.5, ele força cu126, o último índice cujos wheels ainda carregam kernels Maxwell/Pascal/Volta. Ele instala torch e torchvision juntos a partir desse índice para que a instalação de dependências não os troque por builds CPU do PyPI, e então verifica após a instalação se torch.cuda.is_available() é verdadeiro — se uma GPU foi detectada mas o torch acaba sendo apenas CPU, o script avisa alto e sai com código não-zero em vez de fingir que a configuração foi bem-sucedida.

A partir de clean_image.py

NOAI_WATERMARK_DIR=~/noai-watermark \
~/noai-watermark/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
  -o shot.cleaned.png --remove-pixel ctrlregen

Ordem das operações: remoção de metadados primeiro, depois remoção de pixels CtrlRegen, depois um reverse-SynthID opcional antes/depois da pontuação (quando REVERSE_SYNTHID_DIR também está definido).

A intensidade é conservadora por padrão (--ctrlregen-intensity 0.25), porque intensidade maior remove mais marca d'água, mas regenera mais da imagem. Presets documentados: 0.15 mínimo / 0.25 padrão / 0.35 equilibrado / 0.5 agressivo / 0.7 máximo (o padrão do backend é 0.5). --ctrlregen-steps padrão é 50 (etapas efetivas de denoising ≈ etapas × intensidade).

Tamanho da imagem (limite nativo 512×512)

CtrlRegen é um Stable Diffusion 1.5 ControlNet 512×512. O backend resolve isso para entradas arbitrárias, então nenhum tiling extra é exposto aqui:

  • ≤512 px: passagem única — centralizar-cortar/redimensionar para 512, regenerar, redimensionar de volta.
  • >512 px: tiling sobreposto automático (tiles de 512 px, sobreposição de 192 px), largura/altura alinhadas a múltiplos de 8, depois emendas com mistura de cosseno.
  • Qualquer caminho: a saída é redimensionada para o tamanho original e com cores correspondentes à imagem original.

Imagens muito grandes (ex.: 4K) produzem muitos tiles, então as execuções escalam com a contagem de tiles (mais lentas e com maior VRAM). Reduza a escala de entradas grandes quando prático; o tamanho do tile e a sobreposição são fixos no upstream e não são expostos como flags.

Computação, modelos com acesso restrito e verificação

Espere ~10 GB de downloads de modelos; uma GPU é fortemente recomendada e execuções em CPU são lentas. Alguns modelos upstream têm acesso restrito, então exporte HF_TOKEN (apenas env — nunca argv). clean_ctrlregen.py se recusa a instalar dependências automaticamente; execute setup_ctrlregen.sh primeiro.

Não há detector local para StegaStamp/Tree-Ring/StableSignature, então o único sinal local é a pontuação reverse-SynthID (um substituto). Quando disponível, clean_image.py --remove-pixel ctrlregen relata essa pontuação antes/depois; o verificador oficial do Google SynthID continua sendo a autoridade final.

Docker

make docker-ctrlregen-build
docker run --rm -e HF_TOKEN="$HF_TOKEN" \
  --user "$(id -u):$(id -g)" \
  -v "$(pwd):/data" \
  watermarks-remover-ctrlregen /data/shot.png -o /data/shot.ctrlregen.png

Verificação opcional de marca d'água de texto MarkLLM

Para experimentos controlados, um harness externo opcional envolve THU-BPM/MarkLLM (Apache-2.0) para marcar texto de teste e re-detectá-lo após uma reescrita da Camada B — ex.: provar que uma marca KGW (Kirchenbauer, sua linha "open-LLM") ou SynthID-Text (linha Gemini) desaparece sob sua reescrita. É um harness de verificação, não um oráculo: a detecção MarkLLM só é válida contra a mesma configuração de esquema + chaves usadas na geração, e não pode certificar que um detector de fornecedor falhará.

O backend não é incluído. setup_markllm.sh clona o upstream em um commit fixado, cria um venv e instala dependências fixadas (torch + transformers); o modelo de pontuação (padrão facebook/opt-1.3b, Apache-2.0) baixa do Hugging Face na primeira execução.

SCRIPTS=service/scripts

# Bootstrap (clones upstream, creates ~/MarkLLM/.venv, installs deps).
"$SCRIPTS/setup_markllm.sh"

# Generate watermarked + unwatermarked sample text under the KGW scheme.
MARKLLM_DIR=~/MarkLLM \
  ~/MarkLLM/.venv/bin/python "$SCRIPTS/detect_text_watermark.py" watermark prompt.txt \
    --scheme kgw -o wm.txt -o2 plain.txt

# Detect the scheme mark in a text file.
MARKLLM_DIR=~/MarkLLM \
  ~/MarkLLM/.venv/bin/python "$SCRIPTS/detect_text_watermark.py" detect wm.txt --scheme kgw --json

Verificação em torno de uma reescrita da Camada B: passe --markllm-scheme para rewrite_text.py (com --markllm-dir), e ele registra a detecção MarkLLM antes/depois mais um flag cleared:

export WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2
MARKLLM_DIR=~/MarkLLM \
  python3 "$SCRIPTS/rewrite_text.py" wm.txt -o wm.rewritten.txt \
    --markllm-scheme kgw --markllm-dir "$HOME/MarkLLM" --json-stats

Reescrita iterativa guiada por detecção: a Camada B agora reescreve iterativamente e para assim que uma tentativa passa na avaliação. Cada rodada de avaliação gera --candidates variantes (padrão 1, WATERMARKS_REWRITE_CANDIDATES) e --max-loops limita quantas rodadas são executadas antes que a variante de melhor esforço seja retornada (padrão 1, WATERMARKS_REWRITE_LOOPS). Cada variante é uma chamada de reescrita mais uma avaliação, e uma rodada sai cedo na primeira tentativa que o avaliador relata como não marcada — então aumentar --max-loops tenta novas variantes até que uma avaliação passe (uma reescrita limpa típica custa uma tentativa). O avaliador é escolhido por prioridade:

  1. MarkLLM — detecção de pesquisa de mesma configuração, quando --markllm-scheme é passado (com --markllm-dir). Um slot de detector de fornecedor é reservado acima do MarkLLM para o detector SynthID-text do Google, que o Google aposentou em sua API em agosto de 2026 — um endpoint futuro de fornecedor pode ser conectado ali.
  2. Divergência lexical bigram-Jaccard — quando nenhum detector está configurado; sem veredito de passar/falhar, então toda tentativa é gerada e a mais lexicalmente divergente é selecionada (o comportamento original).

--json-stats relata o avaliador, tentativas feitas, passar/falhar e registros por tentativa:

{
  "evaluator": "markllm",
  "candidates": 1,
  "max_loops": 2,
  "attempts_made": 2,
  "passed": true,
  "candidate_scores": [
    {
      "lexical_divergence": 0.91,
      "selection_score": 0.91,
      "selected": false,
      "passed": false,
      "evaluation": {"detector": "markllm", "available": true, "scheme": "kgw",
                     "is_watermarked": true, "score": 4.3, "threshold": 3.0}
    },
    {
      "lexical_divergence": 0.84,
      "selection_score": 0.84,
      "selected": true,
      "passed": true,
      "evaluation": {"detector": "markllm", "available": true, "scheme": "kgw",
                     "is_watermarked": false, "score": 1.7, "threshold": 3.0}
    }
  ],
  "markllm": {"scheme": "kgw", "before": {"...": "..."}, "after": {"...": "..."},
              "cleared": true, "note": "same-config only"}
}

Um detector que não está configurado, expira ou dá erro produz uma entrada "available": false com um motivo error e nunca falha a reescrita — essa tentativa simplesmente não pode passar, e o loop recai para seleção por divergência lexical. Quando o máximo é esgotado sem uma aprovação, a tentativa menos marcada (menor pontuação) é retornada como melhor esforço com uma nota.

Se o backend não está configurado ou suas dependências estão ausentes, a reescrita prossegue e o relatório observa que a verificação não estava disponível. Uma GPU é recomendada; execuções em CPU funcionam, mas são lentas, e o download do modelo é de alguns GB.

Opções de endurecimento:

  • --offline no adaptador (ou qualquer execução MarkLLM) carrega o modelo de pontuação do cache do Hugging Face apenas — zero saída de rede; falha rápido se não estiver em cache. Código remoto personalizado nunca é executado (transformers trust_remote_code é nunca habilitado).
  • WATERMARKS_MARKLLM_RLIMIT_AS=<bytes> (env, POSIX) aplica um limite de espaço de endereço ao subprocesso do detector MarkLLM. Desligado por padrão porque torch/CUDA geralmente precisa de grandes espaços de endereço.
  • Arquivos de configuração são limitados a 1 MiB; o checkout upstream e a imagem base são fixados por SHA/digest.

Docker

make docker-markllm-build
docker run --rm --user "$(id -u):$(id -g)" -v "$(pwd):/data" \
  watermarks-remover-markllm detect /data/wm.txt --scheme kgw --json

Verificação de mesma chave Keyed-Gumbel (Aaronson EXP)

O relatório técnico da ARBI descreve a marca d'água de texto keyed-Gumbel ("exponencial") — agora disponível no mecanismo open-source arbi-serve (ARBI_WATERMARK_KEY) — onde o ruído do amostrador é derivado de um hash com chave da janela de contexto dos últimos 4 tokens. A detecção é uma replay sem modelo: recalcule u = PRF(Hash(key, window), token) a partir do texto sozinho e teste a cauda Gamma, então não precisa de GPU, modelo ou logits. Este repositório envia esse detector como detect_gumbel.py (apenas stdlib; o valor-p é a identidade exata de soma de Poisson para uma forma Gamma inteira):

# Text mode (deterministic word/run tokenizer) — quick checks and rewrite-loop
# evaluation; exact replay against a real engine needs its tokenizer:
python3 service/scripts/detect_gumbel.py draft.txt --key 0x... --json

# Exact replay: pass the engine's token ids (JSON array or one per line).
python3 service/scripts/detect_gumbel.py ids.json --tokens --key 0x... --json

Mesma ressalva de honestidade que MarkLLM: este é um replay de mesma chave — válido apenas contra a mesma chave, tokenizador e layout PRF usados na geração, e um resultado negativo não estabelece nada. O layout HMAC-SHA256 aqui é uma instanciação auditável, não bit-compatível com nenhum kernel de mecanismo específico (veja o docstring do módulo para o que adaptar para replay exato).

Reescrita guiada por detecção: passe --gumbel-key para rewrite_text.py (env: WATERMARKS_GUMBEL_KEY, preferido) e o loop de reescrita iterativa é conduzido pelo replay Gumbel de mesma chave — a prioridade do avaliador se torna gumbel > MarkLLM > divergência lexical — com um relatório gumbel.before/after/cleared:

export WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2
export WATERMARKS_GUMBEL_KEY=0x...
python3 "$SCRIPTS/rewrite_text.py" wm.txt -o wm.rewritten.txt --json-stats

A chave nunca aparece em estatísticas ou logs. Operadores auto-hospedados que possuem a chave de seu mecanismo podem verificar se uma reescrita limpou uma marca Gumbel; todos os outros tratam a Camada B como apenas melhor esforço.

Benchmark opcional de remoção SynthID-text

bench_synthid_text.py mede quão efetivamente uma reescrita da Camada B limpa marcas d'água SynthID-text-class e a que custo. Ele gera amostras marcadas + não marcadas com o esquema MarkLLM SynthID (detecção de mesma configuração, com verificação de sanidade), executa suas variantes de reescrita (tática × tentativas máximas de reescrita; o loop para cedo na aprovação) mais controles (sem remoção, apenas Camada A, verificação opcional de re-marcação), e escreve um report.md / results.json / results.csv compartilhável. Guia completo: docs/synthid-text-benchmark.md.

Requer um checkout MarkLLM (setup_markllm.sh / MARKLLM_DIR) e um backend de reescrita. O modelo de reescrita é um LLM que você configura — o mesmo backend rewrite_text.py que a skill usa. O padrão do MarkLLM facebook/opt-1.3b (--markllm-model) é apenas o gerador/detector de marca d'água; ele nunca reescreve. Configure o modelo de reescrita via variáveis de ambiente ou flags do benchmark (eles espelham a tabela de configuração acima):

Variável de ambienteFlag do benchmarkPadrãoSignificado
WATERMARKS_REWRITE_BACKEND--rewrite-backendollamaollama ou openai-compatible
WATERMARKS_REWRITE_MODEL--rewrite-model(obrigatório)O LLM que realiza a reescrita (ex.: llama3.2, deepseek-v4-flash)
WATERMARKS_REWRITE_BASE_URL--rewrite-base-urlhttp://127.0.0.1:11434Endpoint; o padrão do Ollama é loopback
WATERMARKS_REWRITE_API_KEY--rewrite-api-key—Chave de API (apenas env no processo filho, nunca argv)
WATERMARKS_REWRITE_ALLOW_REMOTE=1--rewrite-allow-remotedesligadoNecessário para enviar conteúdo para endpoints não-loopback
# Ollama (loopback):
python3 service/scripts/bench_synthid_text.py --markllm-dir ~/MarkLLM \
  --rewrite-backend ollama --rewrite-model llama3.2

# OpenAI-compatible API (remote):
WATERMARKS_REWRITE_API_KEY=... python3 service/scripts/bench_synthid_text.py \
  --markllm-dir ~/MarkLLM --rewrite-backend openai-compatible \
  --rewrite-model deepseek-v4-flash --rewrite-base-url https://api.deepseek.com \
  --rewrite-allow-remote

Use um modelo não-origem para reescrita (não reescreva com o mesmo modelo marcado que gerou o texto) ou a reescrita pode re-marcar a saída; --restamp-control mede isso.

Harness opcional de marca d'água de imagem MarkDiffusion

Para experimentos controlados em imagens, um harness externo opcional envolve THU-BPM/MarkDiffusion (Apache-2.0), um kit de ferramentas de marcação d'água generativa para modelos de difusão latente (ele incorpora marcas — não as remove). Nós o usamos para três coisas:

  1. Harness de verificação (como MarkLLM, mas para imagens): marque uma imagem de teste com um esquema, execute a remoção e re-detecte com a mesma configuração de esquema — ex.: prove que uma marca da classe Tree-Ring é limpa sob seu pipeline. É um harness de verificação, não um oráculo: a detecção requer o modelo gerador (e chaves para esquemas baseados em chave), então não pode certificar que um detector de fornecedor falhará em uma imagem arbitrária.
  2. Mecanismo opcional de remoção de pixels: seu ataque de regeneração DiffusionPurification é exposto como clean_image.py --remove-pixel diffusion, uma alternativa ao CtrlRegen. É regeneração cega (sem condicionamento ControlNet), então desvia mais o conteúdo da imagem do que o CtrlRegen — intensidade conservadora padrão (0.3), tratada como fallback/comparação, nunca uma garantia.
  3. Detector local de mesmo esquema para marcas da classe Tree-Ring, preenchendo parcialmente a lacuna "sem detector local para StegaStamp/Tree-Ring/StableSignature" (ele cobre Tree-Ring/Ring-ID/Gaussian-Shading etc., não StegaStamp / StableSignature / SynthID-media).

O backend não é incluído. setup_markdiffusion.sh cria um venv e instala markdiffusion==1.0.2 do PyPI (fixado), com torch instalado do índice de plataforma correto; --checkout instala um clone editável em um commit fixado em vez disso. O modelo Stable Diffusion (padrão huanzi05/stable-diffusion-2-1-base) baixa do Hugging Face na primeira execução.

SCRIPTS=service/scripts

# Bootstrap (PyPI pin default; creates ~/markdiffusion/.venv, installs deps).
"$SCRIPTS/setup_markdiffusion.sh"

# 1. Generate a Tree-Ring watermarked image (+ unwatermarked control).
echo "a red fox in snow" > /tmp/prompt.txt
MARKDIFFUSION_DIR=~/markdiffusion \
  ~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" watermark \
    /tmp/prompt.txt -o wm.png -o2 plain.png --scheme tr --json

# 2. Remove with the DiffusionPurification regeneration attack.
MARKDIFFUSION_DIR=~/markdiffusion \
  ~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" purify \
    wm.png -o wm.purified.png --purification-intensity 0.3 --json

# 3. Re-detect with the SAME scheme config.
MARKDIFFUSION_DIR=~/markdiffusion \
  ~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" detect \
    wm.purified.png --scheme tr --detector-type l1_distance --json

Ou execute a purificação como parte do pipeline de imagem normal:

MARKDIFFUSION_DIR=~/markdiffusion \
  ~/markdiffusion/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
    -o shot.cleaned.png --remove-pixel diffusion

Opções de endurecimento espelham o harness MarkLLM: --offline carrega o modelo do cache do Hugging Face apenas (zero saída de rede, sem código remoto), HF_TOKEN é apenas env (nunca argv), configurações de algoritmo são limitadas a 1 MiB, e o subprocesso recebe os mesmos limites de recursos mais altos que o CtrlRegen.

Docker

make docker-markdiffusion-build
docker run --rm --user "$(id -u):$(id -g)" -v "$(pwd):/data" \
  watermarks-remover-markdiffusion detect /data/wm.png --scheme tr --json

A imagem instala uma torch para CPU; usuários de CUDA devem executar setup_markdiffusion.sh no host. Os downloads de modelos ainda acessam o hub da HF na primeira execução.

Matriz de cobertura

CanalClaudeGemini/SynthIDOpenAIOpen-LLM
Texto baseado em Unicode / ediçãoCamada ACamada ACamada ACamada A
Amostragem estatística de textoCamada B melhor esforço (costura Claude quando a API de detecção da Anthropic for lançada)Camada B melhor esforço (+ harness MarkLLM mesma configuração; Google aposentou o detector do fornecedor em ago/2026)Camada B se presenteCamada B melhor esforço + harness MarkLLM opcional
C2PA / metadados de arquivoSim (formatos listados)Sim quando presenteSim quando presenteSim quando presente
Marcas de imagem em pixelsFora do escopoPontuação SynthID opcional + remoção CtrlRegen (externo); detecção MarkDiffusion opcional mesmo esquema + remoção DiffusionPurification (externo)Fora do escopoRemoção CtrlRegen / MarkDiffusion opcional (externo)
Backdoors de treinamentoFora do escopoFora do escopoFora do escopoFora do escopo

Detalhes: skills/remove-ai-marks/references/vendor-notes.md, mark-classes.md.


Como funciona a marcação de texto (resumo)

Marcas d'água modernas em LLMs frequentemente escondem um sinal em quais tokens são escolhidos (viés generativo / de amostragem), não apenas em caracteres invisíveis. Esquemas baseados em edição injetam regras de Unicode ou sinônimos. Esquemas de arquivo anexam C2PA ou metadados do gerador.

  • Camada A remove portadores Unicode baseados em edição (testável).
  • Camada B ataca marcas de amostragem via reescrita pesada (melhor esforço; ataques padrão da literatura, como paráfrase / retro-tradução).
  • Limpadores de arquivo removem C2PA/XMP/props de contêineres suportados.

Até que os fornecedores publiquem detectores e chaves públicas, nenhuma ferramenta pode certificar honestamente "isso falha na verificação oficial". Relatórios devem separar trabalho verificável de melhor esforço.

Prefira um modelo não-origem para a Camada B (não reescreva texto do Claude com o Claude se você está tentando evitar re-marcação).


Aviso: o que custa remover uma marca d'água de texto

Marcas d'água de texto vivem na própria redação: o sinal está espalhado pelas escolhas de tokens, então quase toda frase carrega um pouco dele. Duas consequências se seguem, e são por isso que a Camada B é honestamente descrita como melhor esforço em vez de uma borracha mágica.

  1. Remoção significa reescrever, não reestruturar. Embaralhar parágrafos, mudar títulos ou pequenos retoques mal movem o sinal. Remover uma marca estatística exige reescrever uma fração substancial do texto — frase por frase, não seção por seção.

  2. Reescrever degrada o texto. Qualquer reescrita substitui as escolhas de palavras originais pelas do modelo de reescrita, o que achata tom, voz e precisão. Em texto de produção (SEO, marketing, trabalho de cliente), essa degradação é real e frequentemente visível para quem mais se importa com a escrita. É como pegar texto de um modelo de primeira linha e pedir a um modelo menos capaz para reescrevê-lo do zero: o resultado não pode exceder o teto do modelo de reescrita.

O que leva à pergunta honesta de ciclo completo:

Se o plano é reescrever o texto com um modelo mais barato de qualquer forma, por que pagar por um modelo premium em primeiro lugar? Gerar diretamente com o modelo mais barato é mais simples, mais barato e produz o mesmo — ou melhor — resultado final.

A Camada B faz sentido quando você especificamente quer o pensamento e rascunho do modelo premium e aceita uma passada de reescrita para satisfazer um requisito de higiene ou privacidade — não como um caminho barato para texto sem marca.

Quando pular a Camada B:

  • Qualidade importa mais que higiene: use o caminho sem perdas — limpeza Unicode da Camada A mais os limpadores de metadados de arquivo — e mantenha a prosa original.
  • Reescrevendo de qualquer forma: use um modelo não-origem (reescrever com o modelo de origem pode re-marcar o texto) e lembre que o risco residual permanece — nenhuma ferramenta pode certificar que um detector do fornecedor falhará.

Formatos de arquivo

FormatoInspecionarLimpar
PNG / JPEG / WebPChunks C2PA / APP11 / RIFF C2PA, dicas XMP da AIRemover segmentos de metadados
AVIF / HEICCaixas ISOBMFF jumb / XMP uuidRemover caixas
BMPBytes não-imagem no final (sem canal padronizado)Truncar metadados finais, corrigir campo de tamanho do arquivo
GIFExtensões de aplicação Comentário / XMPRemover comentário e XMP, manter loop NETSCAPE2.0
TIFF (clássico + BigTIFF)Tags IFD: XMP, EXIF, GPS, IPTC, MakerNoteRemover tags, zerar payloads, manter strips
SVG<metadata>, XMPRemover blocos
PDFByte/XMP + ferramentas opcionaisexiftool depois qpdf, depois ghostscript para metadados dentro de imagens incorporadas; cada ferramenta ausente degrada uma camada diferente (remoção de documento, reescrita estrutural, imagens incorporadas)
DOCXdocProps / customXmlLimpar props, remover customXml
EPUBMetadados OPF, meta/JSON-LD em XHTML, mídia incorporadaLimpar OPF, remover meta em XHTML, limpar mídia + Camada A (pula partes criptografadas)
ODTmeta.xmlRemover meta de gerador / IA
HTMLmeta, JSON-LD, data-ai*, comentários <!-- --> nomeando uma ferramenta de IA ou marcados como gerados por IA / C2PA / credencial de conteúdoRemover tags/atributos/comentários
MarkdownChaves de IA no frontmatter YAML, comentários <!-- --> nomeando uma ferramenta de IA ou marcados como gerados por IA / C2PA / credencial de conteúdo (fora de blocos de código)Remover chaves e comentários + Camada A no corpo
MP4 / MOV / M4A / M4VCaixas ISOBMFF jumb/uuid (mesmo mecanismo de AVIF/HEIC) + tags de gerador moov/udtaRemover caixas
WAVChunks RIFF C2PA / LIST INFO, chunk id3\x20 incorporadoRemover chunks
MP3Frames ID3v2 (v2.3/v2.4 por frame; v2.2 tag inteira)Remover frames correspondentes ou tag inteira
FLACManifesto C2PA em um frame ID3v2 GEOBRemover o frame correspondente ou a tag ID3v2 inteira

O suporte a FLAC cobre o carrier ID3v2 padronizado do C2PA. Blocos de metadados nativos do FLAC, Comentários Vorbis e marcas d'água no domínio de forma de onda são deixados intocados.

Por que o PDF precisa de qpdf, não apenas exiftool

O ExifTool escreve PDFs incrementalmente. exiftool -all= anexa um bloco %BeginExifToolUpdate que libera o objeto Info e remove /Info do trailer — mas os bytes originais de metadados permanecem no arquivo verbatim, e o próprio exiftool pode desfazer a edição com -PDF-update:all=. O comando sai com 0, visualizadores não mostram metadados, e o arquivo fica maior, o que é a pista.

Para uma ferramenta de remoção de proveniência, isso é um vazamento silencioso, então clean_pdf segue a passada do exiftool com qpdf --linearize, que re-serializa o documento a partir do grafo de objetos e remove os objetos agora não referenciados. Sem qpdf instalado, a limpeza ainda roda, mas avisa:

warning: exiftool PDF edits are incremental — the original metadata bytes
remain recoverable; install qpdf for a structural rewrite

Por que qpdf não é suficiente para imagens dentro do PDF

Ambas as passadas acima trabalham no documento: o dicionário Info, o pacote XMP, o grafo de objetos. Nenhuma desce para um XObject de imagem, então um scan ou uma exportação do Photoshop — uma página que é um grande JPEG — mantém o que a imagem carrega. Em um PDF real exportado do Photoshop, isso deixa 27 tags no lugar após uma limpeza "bem-sucedida", IFD0:Software, os timestamps de captura e uma miniatura de pré-visualização entre elas; um manifesto C2PA anexado à mesma imagem também sobrevive.

Então clean_pdf adiciona uma terceira passada, deep_images, dirigida pelo pdfwrite do Ghostscript. Ela roda em dois níveis e para assim que o arquivo está limpo:

  1. Sem perdas. pdfwrite com pass-through reconstrói o documento a partir do grafo de objetos enquanto copia os dados de imagem comprimidos byte a byte — verificado por hash dos streams antes e depois. Isso limpa tudo que o PDF envolveu ao redor da imagem. O pass-through cobre os codecs que o Ghostscript suporta para isso, JPEG (DCTDecode) e JPEG2000 (JPXDecode); imagens Flate, CCITT e LZW são decodificadas e re-encodadas, o que é sem perdas na prática para esses codecs, mas não idêntico em bytes. never é a opção para um documento cujos streams devem sobreviver intocados.
  2. Re-encode, apenas com evidência. Qualquer coisa vivendo nos próprios segmentos APPn do JPEG — EXIF em APP1, um manifesto C2PA em APP11, recursos do Photoshop em APP13 — viaja com os bytes aos quais está anexada, então o pass-through preserva isso. O nível 2 roda a mesma passada com pass-through desligado, e apenas quando o nível 1 demonstravelmente deixou algo para trás: um marcador AI/C2PA em qualquer modo, ou, sob always, qualquer metadado APPn sobrevivente. APP0 (JFIF) e APP2 (ICC) são deixados em paz — o primeiro é estrutural e o segundo decide como as cores são lidas. Pixels são gastos em evidência, nunca em suspeita.

deep_images aceita auto (padrão: nível 1 apenas quando marcadores sobreviveram à remoção do documento, depois nível 2 se eles sobreviverem), always (nível 1 para todo PDF, escalando para nível 2 também para EXIF de câmera e editor), lossless (nível 1 apenas — nunca re-comprimir, e reportar o que sobreviver através dos campos usuais still_has_c2pa / post_findings) e never. Um valor não reconhecido é rejeitado em vez de silenciosamente tratado como auto. O relatório diz quais níveis rodaram via meta.deep_image_pass e meta.images_reencoded, e quando a passada é pulada, nomeia a opção que iria mais longe:

deep image pass not needed for AI/C2PA markers; pass deep_images="always"
to also clear non-AI EXIF inside images

Sem o Ghostscript instalado, a limpeza ainda roda e diz o que não pôde alcançar:

warning: metadata inside embedded images left in place; install ghostscript
for the deep image pass

A remoção de marca d'água no domínio de pixels agora está disponível como um backend externo opcional CtrlRegen (veja acima); é um removedor regenerador, não uma garantia. Vinculação suave C2PA (marca d'água no conteúdo que pode re-vincular um manifesto remoto de Content Credentials após os metadados serem removidos) permanece fora do escopo. Remover C2PA de vinculação rígida não limpa esses canais.

Risco residual após uma limpeza

Esta ferramenta reporta remoções verificáveis (contagens Unicode, ações de metadados) e reescritas de melhor esforço da Camada B. Ela não pode certificar que detectores de fornecedores falharão.

Para verificar sinais residuais você mesmo (opcional, externo):

CanalO que removemosO que pode permanecerVerificação externa (exemplos)
C2PA / EXIF / XMP de vinculação rígidaSimMarcas de vinculação suave / pixelsc2patool, verificação de Content Credentials
Mídia classe SynthIDRemoção de pixels opcional (CtrlRegen externo); pontuação local caso contrárioMarca d'água de áudio/vídeo; marca d'água de pixels residual após remoçãoFerramentas do fornecedor (ex.: Google SynthID / detector Vertex onde oferecido); pontuador local opcional reverse-SynthID
Texto estatísticoReescrita de melhor esforçoMarcas fortes após edição leveNenhum detector universal público; ferramentas de fornecedores quando disponíveis

Contexto industrial de duas camadas (C2PA + marca d'água imperceptível): guia do Institute of AI PM.


Detectores de marca d'água

Verificadores fornecidos por fornecedores para verificar se o conteúdo carrega marcas de proveniência de IA:

  • Claude: Verifique se um arquivo foi feito com Claude — lê credenciais de conteúdo C2PA em imagens, vídeo e áudio para reportar se o Claude esteve envolvido na produção do arquivo; roda no navegador. A API de detecção de marca d'água de texto do Claude está atualmente em pré-visualização privada.
  • OpenAI: Verifique conteúdo gerado pela OpenAI — envie um arquivo de imagem ou áudio e verifique sinais de proveniência da OpenAI (metadados C2PA e marcas d'água SynthID). Uma API programática também está disponível.
  • Google DeepMind: SynthID — a tecnologia de marca d'água do Google para imagens, áudio, texto e vídeo gerados por IA, com uma visão geral de como as marcas imperceptíveis são incorporadas e detectadas.
  • Gemini: Verifique imagens, vídeos e áudio gerados por IA — o guia do Google para verificar arquivos no aplicativo Gemini usando marcas d'água SynthID e Content Credentials, incluindo limites de upload e como ler os resultados.

Opções de remoção (resumo)

OpçãoRemoveObservações
Limpeza Unicode (Camada A)ZWSP, bidi, tags, espaços exóticos, …Padrão seguro para texto
Reescrita (Camada B)Marcas estatísticas de token (melhor esforço)Sempre oferecida pela skill; custa estilo — veja Aviso legal
Remoção de metadados de contêinerProveniência de arquivoVeja a tabela de formatos
Remoção de pixels CtrlRegen (opcional)Marcas de imagem no domínio de pixels (classe SynthID, StegaStamp, Tree-Ring, StableSignature)Backend externo; computação pesada; intensidade padrão conservadora
Remoção de pixels DiffusionPurification (opcional)Marcas de imagem no domínio de pixels (classe Tree-Ring)Backend MarkDiffusion; regeneração cega (mais deriva que CtrlRegen); intensidade padrão conservadora
Modelos locais de peso abertoEvita re-marcação com o modelo de origemAlternativa operacional

Matriz: skills/remove-ai-marks/references/removal-matrix.md.

Ética e aviso legal

Veja skills/remove-ai-marks/references/ethics.md. Para privacidade e pesquisa em conteúdo seu — não fraude acadêmica ou alegações falsas de “escrito por humano”.

Uso responsável: Este projeto é para conteúdo que você possui ou está autorizado a processar. Os usuários devem cumprir as regulamentações locais e usá-lo com responsabilidade. Os desenvolvedores se isentam de qualquer responsabilidade por uso indevido por parte dos usuários.

Ecossistema

Projetos de terceiros que envolvem ou complementam este repositório, listados apenas para descoberta. Eles não são mantidos, endossados ou suportados por este projeto. Este projeto não revisa o código deles, não garante seu comportamento ou promessas, nem se responsabiliza por qualquer coisa que você instale ou execute a partir desta lista. Cada projeto é regido por sua própria licença, mantenedores e documentação — leia-os antes de usar.

MetaClean — GUI para desktop

MetaClean é um aplicativo de desktop independente Rust/Tauri licenciado sob MIT (Windows, macOS, Linux) que fornece uma GUI nativa empacotada para limpeza de metadados por arrastar e soltar, com bandeja do sistema e integração com o Explorer. É um código-base separado: não chama o serviço Python deste repositório, e seus formatos suportados e garantias de limpeza diferem dos deste projeto. Veja o README dele para detalhes.

unmark-web — interface web para navegador

unmark-web é um cliente web estático independente, licenciado sob MIT. Ele remove marcas Unicode invisíveis de texto e remove metadados de proveniência de imagens inteiramente no navegador, e pode opcionalmente chamar o serviço HTTP deste repositório para os formatos que não trata localmente. É um código-base separado e não é afiliado a este projeto; veja o README dele para escopo e limites.

DropMarks — GUI para macOS

DropMarks é um aplicativo independente macOS SwiftUI licenciado sob MIT. Ele chama o inspect_file.py / clean_file.py deste repositório (e opcionalmente rewrite_text.py) por meio de um instantâneo fornecido desses scripts stdlib. É um código-base separado e não é afiliado a este projeto; veja o README dele para escopo e limites.

unmark-checker — ferramenta de medição

unmark-checker é uma ferramenta Python independente licenciada sob MIT que planta uma marca d'água estatística de texto da classe SynthID-Text publicada com uma chave própria e pontua o que uma remoção deixou para trás. Ele inclui um executor (integrations/watermarks-remover/run.py) que entrega uma amostra marcada a este repositório, seja por meio do serviço HTTP /clean ou chamando clean_text.py e rewrite_text.py em um checkout, e relata a pontuação do detector ao lado de quanto do significado, dos fatos e do comprimento sobreviveram. Quais camadas foram executadas faz parte do resultado: o serviço /clean executa ambas as camadas, enquanto uma execução de checkout sempre faz a camada A e só faz a camada B quando WATERMARKS_REWRITE_BACKEND está definido e --layer-a-only está ausente. O executor rotula cada execução com as camadas que mediu, para que uma pontuação apenas da camada A nunca seja lida como uma de pipeline completo. Ele apenas mede; nunca remove nada. É um código-base separado e não é afiliado a este projeto; veja o README dele para escopo e limites.

Simple Unmark: SaaS que preserva privacidade

Simple Unmark disponibiliza watermarks-remover por meio de um aplicativo web simples, atualmente apenas para texto, sem instalação ou configuração de servidor. O serviço preserva a privacidade por padrão, sem reter conteúdo enviado. O modo confidencial executa o removedor em um ambiente de execução confiável (TEE), adicionando verificação criptográfica e proteção baseada em hardware contra operadores de infraestrutura que acessam conteúdo durante o processamento. Um projeto independente com um núcleo de código aberto. Veja a arquitetura de privacidade para garantias e escopo.

Adicionando um projeto

Para registrar um projeto aqui, abra um PR adicionando uma entrada curta — nome do projeto, o que ele envolve ou adiciona, e um link para o repositório dele. Mantenha as entradas breves e factuais; não reivindique compatibilidade com, ou endosso por, este projeto. Um projeto listado deve construir sobre ou integrar este repositório — por exemplo, chamando seu serviço ou reutilizando seu mecanismo de detecção — em vez de apenas abordar o mesmo problema de forma independente. Evite nomes que comecem com ou se assemelhem muito a watermarks-remover — nomes semelhantes dificultam distinguir qual projeto é qual.

Hook de pré-commit

A verificação de CI já existe (exportação SARIF do audit_dir.py, veja o contexto da Matriz de cobertura) — os hooks de pre-commit abaixo detectam a mesma classe de problema mais cedo, antes mesmo de um arquivo marcado ser commitado. Ambos envolvem os CLIs existentes (audit_dir.py / clean_file.py) — sem lógica de detecção separada.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/guillaumemeyer/watermarks-remover
    rev: v0.5.0   # pin to a tag/commit
    hooks:
      - id: watermarks-remover-check   # fails the commit if marks are found
      # - id: watermarks-remover-clean # opt-in: cleans staged files in place instead

watermarks-remover-check falha o commit e lista descobertas; watermarks-remover-clean é opt-in e reescreve arquivos preparados no lugar (sai com código 1 para você revisar o diff e re-preparar — a mesma convenção de hooks de correção automática como ruff --fix). Quando o limpador não consegue processar um arquivo de forma alguma — travou, foi morto ou não produziu relatório — watermarks-remover-clean nomeia esse arquivo e sai com código 3, para que um limpador que falhou nunca seja confundido com um arquivo já limpo. Execute qualquer um manualmente com python3 service/scripts/check_staged.py <files...> / clean_staged.py <files...>.

Testes

python3 -m venv .venv && .venv/bin/pip install pytest
.venv/bin/python -m pytest          # or: make test
make smoke                          # quick CLI smoke on fixtures

Changelog

v0.7.0 — /clean Reescrita da Camada B, módulo de roubo de marca d'água, remoção de marca d'água de áudio/vídeo e amplitude de benchmark/ferramentas

v0.7.0 traz a reescrita de marca estatística da Camada B para dentro do próprio serviço /clean, impulsionada por uma estratégia configurável e ajustada por benchmark (paraphrase@0.8,mlm@0.2). Junto com isso: um módulo de roubo de marca d'água de caixa-preta, remoção destrutiva de marca d'água de áudio e vídeo por quadro, um benchmark de reescrita substancialmente mais rico e uma pilha de correções de endurecimento, segurança e ferramentas.

Reescrita da Camada B no serviço

  • /clean executa a reescrita da Camada B para texto após a Camada A. O padrão vem de config/clean_strategy.json; um options.strategy por solicitação o substitui, e /clean rejeita com 400 quando o backend necessário não está configurado (#315). Precedência de configuração: --strategy-config > WATERMARKS_CLEAN_STRATEGY_FILE > config/clean_strategy.json.
  • Nova tática de reescrita mlm: mascarar uma fração de palavras de conteúdo e preencher com roberta-large — uma edição local não autorregressiva, então a saída mistura o fluxo de tokens original com previsões de LM mascarado (#311).
  • A tática humanize agora aplica a passagem da skill de humanização deterministicamente (aspas retas, sem travessões em/em, colapsos de preenchimento, utilize→use) e nomeia as regras de escritor humano no prompt (#311). rewrite_text.py ganhou um caminho CLI --strategy.
  • Correção de reescrita: tokenização de palavras Unicode em divergência lexical (#305); comparar margens brutas antes de arredondar e registrar metadados de seleção / p-valores classificados (#249).

Benchmark

  • Pesquisa de receita SynthID + medição robusta (#280); vocabulário de reescrita renomeado, pesquisa entre entradas e ordenação humanize-por-último (#302); recomendar apenas estratégias que ainda passam após o polimento de humanização (#307).
  • API em massa de pangramas como backend de semelhança humana (#296); benchmark de nível de reescrita mínima endurecido com corpus de 30 documentos (#257); grade de pesos validada + pesquisa de receita ampliada (#294); corpus de benchmark polonês (#295).

Roubo de marca d'água

  • Novo módulo de roubo de marca d'água de caixa-preta e downloader de corpus de prompts (#303); limpar estado obsoleto em falha de sonda de reinício (#310).

Áudio / vídeo / imagem

  • Cadeia destrutiva de remoção de marca d'água de áudio para silentcipher/AudioSeal/WavMark (tempo + tom + EQ + re-encode de baixa taxa de bits → M4A) (#266).
  • Purificação de vídeo TrustMark por quadro que colapsa o voto temporal (#265).
  • Caixa uuid de proveniência de conteúdo C2PA reconhecida em MP4/MOV/AVIF/HEIC (#264).
  • Preservar caudas MP4 truncadas durante a remoção (#242); manter o destino de re-encode de áudio distinto do destino de limpeza de contêiner (#278).
  • Pular saída descartada do exiftool e SynthID redundante na varredura pós-limpeza (#261); degradar graciosamente quando o exiftool não consegue processar um PDF (#281).
  • Limitar PNG descomprimido zTXt/iTXt a 1 MiB (#308); remover declarações XML DOCTYPE/ENTITY de SVG (#288); manter membros binários DOCX seguros em bytes (#314); preservar OOXML AppVersion (#289).

Serviço HTTP & CLI

  • Opção /clean para manter espaços exóticos, espelhando o CLI (#274); /inspect expõe classes de evidência explícitas no payload suspeito (#277); carimbos de data/hora em logs de solicitação HTTP (#256); encadear bytes de payload na pontuação SynthID HTTP e inspect_* para evitar uma releitura redundante.
  • clean_file.py ganhou -q/--quiet/--only-changed (#254).

Skills, plugin & hooks

  • Pontuação de estilometria e alavancas de detector para clean-user-facing-text (#258); lançador de hook PostToolUse tornado multiplataforma (#255); hook de pré-commit trata arquivos não-texto limpos byte-idênticos como alterados (#238).

Auditoria

  • audit_dir.py verifica arquivos de origem, docs e i18n que o roteador ignorou (#284); verifica .ts/.tsx/.jsx/.gd e alinha a confiança de espaço entre formatos (#273); suporte a audit_website.py --sarif (#194); endurecer backups no lugar, status de arquivo limpo, veredito SynthID, ID3v2 truncado e roteamento zip (#201).

Segurança

  • Remover ReDoS polinomial em varreduras de data-URI e JSON-LD (#306); bloquear redirecionamentos HTTP no pontuador SynthID para prevenir SSRF (#252).

CI, ferramentas & docs

  • CI falha quando requisitos de backend opcionais não podem ser resolvidos (#301); imagem Docker relata ffmpeg como utilizável e instala Ghostscript (#272); atualizações de dependências (cython #299, scipy #298, ruff #297, docker/setup-buildx-action #237).
  • Docs: seção Watermark Detectors, referência ao blog ETH SRI "Probing SynthID", política do Ecossistema (remover ClaudeWatermarks; exigir que projetos listados usem este repositório) (#292).

v0.6.0 — cobertura de formato mais ampla, endurecimento da Camada A, distribuição de plugin & hook e reescrita guiada por detecção

Cobertura de formato e contêiner

  • AVIF / HEIC: metadados nativos da stdlib e remoção de C2PA (#84, #85)
  • BMP / GIF / TIFF: detecção, inspeção e limpeza de metadados via stdlib — comentários GIF/extensões XMP são removidos enquanto NETSCAPE2.0 looping e outros chunks de animação são preservados; metadados IFD do TIFF (XMP/EXIF/GPS/IPTC/MakerNote) são removidos com payloads zerados e offsets de strip mantidos, tanto para TIFF clássico quanto BigTIFF; metadados finais do BMP são truncados com o campo de tamanho de arquivo reescrito (#107)
  • EPUB: limpeza de contêiner via stdlib — metadados OPF e meta/JSON-LD em XHTML removidos, mídia raster/SVG incorporada removida, Camada A aplicada ao texto do corpo XHTML, partes de metadados com marcadores removidas e partes criptografadas por OCF passadas intactas (#107)
  • XLSX / PPTX / DOCX (OOXML): limpeza nativa de metadados, texto e mídia incorporada do contêiner via stdlib; sempre esvaziar campos de proveniência docProps do DOCX; podar relacionamentos pendentes após a remoção de customXml; executar Camada A no texto do corpo de DOCX/ODT; decodificar entidades XML antes da limpeza da Camada A (#91, #100, #76, #83, #73, #80, #74, #81, #142)
  • Contêineres SGML/vetoriais: remoção de metadados em tempo linear para SVG/ODT (GHSA-7vpp-96qp-j9wh) (#147); inspecionar e limpar recursivamente URIs de dados raster incorporados em SVGs, HTML e Markdown (#87, #88)
  • Áudio / vídeo: remoção de metadados AI/C2PA para MP4/MOV, WAV e MP3 (#139); detecção e remoção de chunk C2PA em RIFF WAV; suporte a metadados C2PA em FLAC; rejeitar análise parcial de frames ID3v2 (#232); preservar offsets de mídia MP4 ao remover metadados (#183)
  • PDF: alcançar metadados dentro de imagens incorporadas e parar de redimensionar o PDF para remover XMP; executar a passagem de imagem profunda independentemente de o exiftool estar instalado; respeitar bytes de preenchimento de marcadores JPEG e compartilhar um único walker de segmentos
  • PNG: detectar nomes de produtos de geradores de IA em metadados de texto PNG; detectar marcadores de IA em texto PNG compactado (#127); manter a cauda truncada em vez de descartá-la em strips png/isobmff (#182)

Endurecimento da Camada A (Unicode invisível)

  • Endurecimento consolidado da Camada A (#133): remover pontos de código reservados Default_Ignorable sem uso legítimo de intercâmbio (U+2065, U+FFF0–U+FFF8, U+E0000, U+E0080–U+E00FF, U+E01F0–U+E0FFF — relatados como reserved_ignorable), os 66 não-caracteres (U+FDD0–U+FDEF mais U+FFFE/U+FFFF por plano — relatados como noncharacter) e três portadores Default_Ignorable de renderização em branco que o catch-all Cf nunca viu (U+180F, U+3164, U+FFA0). Cada um tem a mesma preservação em contexto que seus irmãos já cobertos, para que texto de sílabas parciais não seja corrompido, e cada um é aplicado tanto ao mecanismo de serviço quanto à cópia leve da skill embarcada
  • Parar de remover controles de formato de layout visível próximos ao seu próprio script: controles de quadrat de hieróglifos egípcios (U+13430–U+1343F), controles de taquigrafia Duployan (U+1BCA0–U+1BCA3) e controles musicais de beam/tie/slur/phrase (U+1D173–U+1D17A) agora são preservados quando adjacentes ao seu próprio script e ainda removidos (e sinalizados) quando flutuam entre textos não relacionados; o modo paranóico --strip-emoji-glue ainda os remove em todos os lugares
  • Polimento de emoji / script: preservar VS16 após singletons de emoji fora dos intervalos de bloco; preservar joiners de script, emojis de bandeira e marcas Cf árabes; preservar Unicode multilíngue durante a limpeza de texto (#34)

Reescrita da Camada B e detecção de marcas d'água

  • Reescrita iterativa da Camada B guiada por detecção: cada rodada gera --candidates variantes (padrão 1, WATERMARKS_REWRITE_CANDIDATES) e --max-loops (padrão 1, WATERMARKS_REWRITE_LOOPS) limita as rodadas de avaliação, parando assim que uma tentativa passa na detecção. Prioridade do avaliador: MarkLLM (--markllm-scheme) > divergência lexical bigram-Jaccard (fallback). rewrite_text.py --json-stats agora relata evaluator / max_loops / attempts_made / passed e candidate_scores por tentativa (#153)
  • Verificação de mesma chave Keyed-Gumbel (Aaronson EXP): novo detect_gumbel.py somente stdlib implementa o teste de replay sem modelo (u = PRF(Hash(key, window), token); valor-p exato de cauda Gamma; mascaramento de janela repetida) sem GPU, modelo ou logits. rewrite_text.py --gumbel-key (env WATERMARKS_GUMBEL_KEY, preferido) o torna o avaliador do loop iterativo (prioridade: gumbel > markllm > divergência lexical) e é exposto como gumbel em /capabilities e /detect. Somente mesma chave — não um oráculo de fornecedor; a chave nunca é registrada (#190)
  • Benchmarks: benchmark e detecção de texto MarkLLM multi-esquema (#188) e um benchmark reproduzível de remoção de texto SynthID (#145); variantes padrão paraphrase:3; relatório e CSV carregam tentativas por documento (colunas mean_attempts / att, attempts / evaluator / passed); --rewrite-loops espelha --max-loops
  • Detecção: detecção de marca d'água de texto de fornecedores (Gemini SynthID, Claude seam, MarkLLM) além de um scorer de imagem SynthID sidecar (#109); novo detector de texto AI estatístico e estilométrico sem LLM para CI e auditorias (#68, #69)

Distribuição: plugin, hooks e instalação de skills

  • O repositório agora é um plugin Claude Code e um marketplace de plugin único (.claude-plugin/plugin.json + marketplace.json), então ambas as skills instalam com /plugin marketplace add guillaumemeyer/watermarks-remover e depois /plugin install watermarks-remover@watermarks-remover, e atualizam no lugar. make plugin-validate executa claude plugin validate . --strict; tests/test_plugin_manifest.py verifica os manifestos sem o CLI
  • install_skill.py ganhou um --target (claude-code, claude-project, cowork, cursor) e um seletor --skill cobrindo ambas as skills enviadas, além de --list, --link e CLAUDE_CONFIG_DIR. O alvo cowork constrói um pacote de upload reproduzível (dist/<skill>.zip, diretório único de skill de nível superior); cada alvo valida contra as regras de empacotamento de Agent Skills e o limite de upload de 30 MB. Novos alvos make: install-claude-code-skill, install-claude-code-text-skill, install-claude-project-skill, package-cowork-skill, package-cowork-text-skill
  • Auto-limpeza determinística via hook PostToolUse (hooks/hooks.json + service/scripts/hook_written_file.py): após o agente escrever um arquivo, o harness executa o hook independentemente de o modelo cooperar. check (padrão) relata marcas ao modelo; clean as remove no lugar e informa ao modelo que o arquivo foi movido, trocando apenas em uma diferença real para que arquivos limpos mantenham seu mtime. O modo vem da configuração hook_mode do plugin ou WATERMARKS_HOOK_MODE; a detecção reutiliza audit_lib.scan_file / is_actionable, então o hook, o gate de pre-commit e a exportação SARIF do CI concordam. Um hook ainda não pode reescrever a mensagem de chat do assistente — esse ponto de hook não existe — então esse caminho permanece best-effort
  • Integração de hook pre-commit para verificação/limpeza de arquivos staged (#138); skill de texto Cursor leve (#35); a descrição de clean-user-facing-text não nomeia mais Cursor como o único host

Serviço HTTP

  • Endpoints de lote: POST /clean/batch, /inspect/batch (#137) e POST /detect/batch (#151)
  • Preservar extensões de formato de imagem em /clean e usar gravações seguras em av_meta (#150); usar base64 portátil no exemplo curl /detect (e corrigir a portabilidade realpath do macOS nos bootstraps, #185)

Auditoria / inspeção e segurança

  • audit_dir.py ganhou concorrência multi-worker e exportação SARIF 2.1.0 (#101, #102)
  • Roteirizar formatos binários de sites para seus scanners reais (#177); recusar bombas DTD/entity no parser de sitemap (GHSA-pjg6-92pm-mmcf) (#146); um limpador que falha bloqueia o commit em vez de ser lido como limpo (#179); um arquivo de texto ilegível é uma varredura falha, não uma limpa (#169)

Correções de confiabilidade e correção

  • Uma segunda execução de --in-place preserva o .bak original; manter evidências coletadas quando um membro zip posterior falha ao ler (#175); contêineres ISOBMFF truncados ainda executam o fallback de varredura de bytes C2PA (#176); distinguir um limpador que falhou de um arquivo já limpo (#159, #161); tratar uma execução falha do c2patool como inconclusiva em vez de "sem C2PA" (#156); validar tipos de opções limpas (#111); nunca selecionar automaticamente dispositivo MPS para detecção de marca d'água de texto (#99); portabilidade macOS — stdout --json puro para o scorer SynthID e sonda BSD realpath (#70); corrigir um caminho subprocess_creationflags do Windows em _ghostscript_usable e impedir que processos filhos abram uma janela de console no Windows
  • Endurecimento comportamental: preservar modo keep de comentários JPEG benignos; corrigir o flag engolido bench-synthid-text; simplificar o repasse de flags para a sonda Ghostscript e noqa desnecessário do clean_text (lint)

CI / ferramentas / docs

  • Lint e formatação Ruff com aplicação no CI (#103); adicionar macOS à matriz de testes (#152); adicionar configuração CodeRabbit para revisões automatizadas de PR (#222); CODEOWNERS para CODE_OF_CONDUCT/LICENSE e owners de revisão principal; atribuir direitos autorais a Guillaume Meyer e contribuidores (#228)
  • Docs: orientação de reescrita preservando voz e protegendo escolhas de voz/acessibilidade; adições ao Ecossistema (ClaudeWatermarks, unmark-web) e uma nota desencorajando nomes semelhantes; referência arXiv 2402.14904; guia de inicialização automática no Windows via Task Scheduler; base64 portátil em exemplos curl; fixar o mecanismo de texto da skill Cursor embarcada à cópia de serviço (#96)

Não lançado

  • Hook de limpeza pre-commit (watermarks-remover-clean / clean_staged.py): usar digests de conteúdo (SHA-256) e detecção de ação ativa para que arquivos limpos no disco sejam reconhecidos sem exigir re-staging infinito (#173)
  • Preservação de contêiner OOXML: manter <AppVersion> intacto em docProps/app.xml durante a limpeza de metadados DOCX, XLSX e PPTX para satisfazer as restrições de esquema ECMA-376 e evitar erros de "conteúdo ilegível" do Microsoft Word/Office (#283)

v0.5.0 — distribuição de serviço e Docker, API HTTP e harnesses de verificação

Distribuição de serviço / Docker

  • Divisão de skill/serviço: a skill (skills/remove-ai-marks/) agora é um cliente remoto sem código sobre HTTP; toda a implementação foi movida para service/scripts/ e roda atrás de server.py, um entrypoint HTTP stdlib (/health, /inspect, /clean, /capabilities)
  • Serviço HTTP: service/scripts/server.py expõe o pipeline de limpeza via JSON/base64; o endurecimento espelha os CLIs (limites de tamanho, guarda binária, gravações atômicas, loopback padrão, autenticação bearer opcional WATERMARKS_SERVER_API_KEY)
  • OpenAPI: GET /openapi.json serve uma especificação OpenAPI 3.0.3 gerada dinamicamente (construída a partir da tabela de rotas + configuração ao vivo, para que nunca se desvie dos endpoints reais); o CI valida com openapi-spec-validator
  • Imagem Docker principal (service/Dockerfile): serviço de limpeza completo com exiftool / qpdf / c2patool pré-instalados; qualquer CLI permanece executável ao sobrescrever o comando
  • Docker / compose: compose.yaml sobe toda a infraestrutura (core sempre; markllm / markdiffusion atrás de profile: harness; ctrlregen / synthid atrás de profile: heavy como builds apenas locais); os serviços são prefixados com wr-; serviços harness/pesados usam command: ["--help"] por padrão para que docker compose up --profile harness --profile heavy saia limpo (CLIs de uso único são executados com docker compose run); novo make compose-check / compose-check.sh valida a stack em execução (apenas código de saída)
  • Publicação GHCR: .github/workflows/release-images.yml publica imagens core, markllm, markdiffusion em tags v*; ctrlregen / synthid nunca são publicados (licenciamento upstream)
  • Configuração de ambiente: .env.example + guia de configuração de serviço; docker compose carrega automaticamente .env; .env é gitignored (negação por padrão)
  • Higiene do repositório: .gitignore e service/.dockerignore agora são negação por padrão — apenas caminhos explicitamente permitidos podem ser commitados ou enviados em um contexto de build (contextos de imagem apenas enviam service/scripts/, que é tudo que os Dockerfiles COPY)
  • Testes: tests/test_http_server.py (13 casos) para o serviço HTTP; todas as suítes re-apontadas para service/scripts/

Harness de marca d'água de imagem MarkDiffusion (opcional)

  • Novo harness opcional (THU-BPM/MarkDiffusion externo, Apache-2.0): markdiffusion_harness.py com subcomandos watermark / detect / purify para nove esquemas de imagem (Tree-Ring, Ring-ID, ROBIN, WIND, SFW, Gaussian-Shading, GaussMarker, PRC, SEAL)
  • clean_image.py --remove-pixel diffusion executa o ataque de regeneração DiffusionPurification do MarkDiffusion como um motor alternativo de remoção de pixels (intensidade conservadora padrão 0.3)
  • Bootstrap setup_markdiffusion.sh (pin PyPI 1.0.2; clone editável --checkout em commit fixado) + requirements-markdiffusion.txt + Dockerfile.markdiffusion e Makefile bootstrap-markdiffusion / smoke-markdiffusion / docker-markdiffusion-build / docker-markdiffusion-help
  • Testes baseados em mock (tests/test_markdiffusion_harness.py) — sem torch no CI; documento de referência references/markdiffusion.md
  • Docs: ressalva de verificação apenas com mesmo esquema (não é um oráculo de detecção de fornecedor) e ressalva de deriva de regeneração cega no README, SKILL.md, removal-matrix.md, markdiffusion.md

Harness de marca d'água de texto MarkLLM (opcional)

  • Novo harness opcional (checkout THU-BPM/MarkLLM externo, Apache-2.0): detect_text_watermark.py com subcomandos detect / watermark para esquemas KGW e SynthID
  • rewrite_text.py --markllm-scheme executa detecção antes/depois em torno de uma reescrita Layer B e detecção por candidato quando --candidates N>1 (controlado por env; relata cleared)
  • Bootstrap setup_markllm.sh + requirements-markllm.txt (dependências fixadas) + Dockerfile.markllm e Makefile bootstrap-markllm / smoke-markllm / docker-markllm-build / docker-markllm-help
  • Endurecimento: carregamento de modelo apenas em cache --offline (sem egress HF, sem código remoto), limite de configuração de 1 MiB, WATERMARKS_MARKLLM_RLIMIT_AS opcional no subprocesso de reescrita, torch fixado no Dockerfile e verificação de SHA do clone em Dockerfile.markllm
  • Testes baseados em mock (tests/test_markllm_detect.py, 21 casos) — sem torch no CI; ressalva de harness de verificação (apenas mesma configuração, não um oráculo de detecção de fornecedor) documentada no README, SKILL.md, removal-matrix.md, vendor-notes.md

Correções e polimento

  • Layer B: rewrite_text.py agora envia reasoning_effort: "none" por padrão para backends openai-compatible (--reasoning-effort / WATERMARKS_REWRITE_REASONING_EFFORT; off omite). Modelos de raciocínio como deepseek-v4-flash queimam ~100s de chain-of-thought em uma reescrita de uma linha (9.894 vs 12 tokens de conclusão)
  • Correção do build da imagem markllm: requirements-markllm.txt fixou tokenizers==0.23.1, que conflita com transformers==5.15.0 (limita tokenizers<=0.23.0; não existe release 0.23.0) — agora fixado tokenizers==0.22.2; torch movido para o índice de wheels CPU (torch==2.13.0.*) para que a imagem seja apenas CPU como Dockerfile.markdiffusion
  • Correção do build da imagem ctrlregen: os pins de pesquisa da era 2023 (safetensors==0.4.3, transformers==4.37.2 → tokenizers<0.19) não possuem wheels Python 3.14, então a imagem base agora é python:3.11-slim (digest-pinned, multi-arch)
  • Correção das imagens harness em runtime: Dockerfile.markllm e Dockerfile.markdiffusion nunca copiaram common.py para /app (bug pré-existente) — adicionado
  • WebP: inspeção e limpeza de metadados apenas stdlib para chunks RIFF C2PA, XMP, EXIF e perfil ICC (#37)
  • BMP / GIF / TIFF: detecção, inspeção e limpeza de metadados apenas stdlib — extensões de comentário/XMP GIF são removidas enquanto o loop NETSCAPE2.0 é preservado; metadados IFD TIFF (XMP/EXIF/GPS/IPTC/MakerNote) são removidos com payloads zerados e offsets de strip mantidos, para TIFF clássico e BigTIFF; metadados finais BMP são truncados com o campo de tamanho de arquivo reescrito
  • EPUB: limpeza de contêiner apenas stdlib — metadados OPF e meta XHTML/JSON-LD limpos, mídia raster/SVG incorporada removida, Layer A aplicada ao texto do corpo XHTML, partes de metadados com marcadores removidas e partes criptografadas OCF passadas intactas
  • Sanitização de nomes de arquivo: o serviço HTTP recusa nomes de saída inseguros fornecidos pelo cliente
  • Correção do cleaner de frontmatter markdown que travava e vazava chaves AI aninhadas (#25)
  • Ferramentas de texto recusam entrada binária; --force-text sobrescreve (#24)
  • --json não suprime mais o código de saída do sinal residual (#30)
  • inspect_file imprime o nome do arquivo na saída (#50)
  • Preserva tags meta de gerador CMS com maiúsculas/minúsculas mistas (#42)
  • Preserva invisíveis de script essenciais, remove PUA na Layer A (#38, #52)
  • Preserva joiners de script, emoji de bandeira e marcas Cf árabes na Layer A (#28)
  • Endurece auditoria de site contra SSRF e bombas gzip (#49)
  • SECURITY.md apenas referencia o canal privado de advisories (#51)
  • Windows: ports PowerShell dos bootstraps de configuração (#40)
  • Docs: adiciona shields de estrelas/forks e remove gráfico de histórico de estrelas; adiciona MarkLLM às referências do README; template de pull request; plano para Docker CLI + implantação de API

v0.4.0 — remoção de pixels, confiança de achados, Windows e correções de falsos positivos

Remoção de pixels CtrlRegen opcional (backend externo)

  • Remoção opcional de marca d'água no domínio de pixels via checkout mertizci/noai-watermark externo: adaptador clean_ctrlregen.py + bootstrap setup_ctrlregen.sh (commit fixado, sparse checkout, venv, verificação SHA), além de Dockerfile.ctrlregen e make bootstrap-ctrlregen / docker-ctrlregen-build / smoke-ctrlregen
  • clean_image.py --remove-pixel ctrlregen executa remoção de metadados → remoção CtrlRegen → pontuação antes/depois reverse-SynthID opcional; inspect_image.py sugere a flag em pontuação SynthID alta
  • Intensidade padrão conservadora 0.25 (presets 0.15/0.25/0.35/0.5/0.7); o pipeline nativo 512×512 é automaticamente dividido em tiles pelo backend para imagens maiores; o subprocesso torch recebe limites de recursos maiores sobrescrevíveis por env
  • O backend nunca é empacotado: noai-watermark não inclui arquivo LICENSE (tratado como todos os direitos reservados), e seus caminhos de código de auto-instalação/reinício são ignorados usando CtrlRegenEngine diretamente

Confiança de achados e auditorias agregadas

  • Achados agora são classificados como confirmed / probable / informational / likely_false_positive, expostos em JSON de texto/imagem/contêiner e relatórios humanos
  • Novos relatórios agregados audit_dir.py (árvore recursiva) e audit_website.py (descoberta de sitemap + crawl); documentados no SKILL.md

Correções de falsos positivos

  • DOCX: escanear apenas docProps/customXml, não o corpo visível (#14)
  • Texto Layer A: preservar emoji VS16/ZWJ após uma base de emoji; nova flag paranoica --strip-emoji-glue (#22)
  • HTML: tratar tags de gerador CMS como informativas, não metadados AI (#13)
  • PDF: excluir payloads de stream da varredura de bytes de marcadores AI (#13)
  • Relatórios de inspeção observam caminhos não suportados/melhor esforço

Suporte Windows

  • Bloquear preexec_fn e os.fchmod apenas POSIX para que gravações e ferramentas opcionais rodem no Windows (#15, #23)
  • Reconfigurar stdio para UTF-8 para que streams Windows redirecionados não levantem mais erros em Unicode invisível; perna CI Windows + smoke run CLI (#23)

Docs e cadeia de suprimentos

  • Seção README CtrlRegen + referências de pesquisa (CtrlRegen, UnMarker, ressalva de stealth forense), aviso de uso responsável; atualizações SKILL/matriz/notas de fornecedor/ética
  • Config Dependabot + CODEOWNERS de caminho de segurança; bump scipy/numpy/opencv-python/scikit-learn/pywavelets e imagem base para Python 3.14-slim
  • Testes CtrlRegen baseados em mock (sem torch no CI)

v0.3.2 — endurecimento de segurança (gravações seguras, cliente HTTP, cadeia de suprimentos CI)

  • Gravações de saída seguras e atômicas: todo cleaner agora grava via arquivo temporário + rename atômico (safe_write_bytes / safe_write_text), recusa destinos symlinked e cria backups .bak pelo mesmo caminho seguro — symlinks pré-colocados (ex.: em /tmp ou diretórios de download) não podem mais redirecionar uma gravação limpa para um arquivo arbitrário
  • Endurecimento do cliente HTTP rewrite_text.py: redirecionamentos são recusados diretamente, para que uma chave de API no header Authorization nunca seja reenviada a um host não validado; endpoints não-loopback são negados por padrão (opt-in com --allow-remote ou WATERMARKS_REWRITE_ALLOW_REMOTE=1); apenas esquemas http(s) são aceitos; --api-key foi removido — chaves são apenas via env com WATERMARKS_REWRITE_API_KEY
  • Limites de recursos: entrada máxima padrão 1 GiB → 256 MiB, novo limite de stdin de 64 MiB, orçamento zip DOCX/ODT 512 MiB → 128 MiB e RLIMIT_AS/RLIMIT_FSIZE aplicados a subprocessos exiftool/c2patool/SynthID (todos os limites sobrescrevíveis por env)
  • Cadeia de suprimentos: ações CI com SHA-pinned com permissions: contents: read, dependências de desenvolvimento fixadas (requirements-dev.txt), etapa pip-audit e novo workflow CodeQL; a imagem Docker agora roda como usuário sem privilégios com pip fixado
  • Dependências do scorer: Pillow atualizado 10.4.0 → 12.3.0 (24 CVEs conhecidas); uso da API verificado contra o commit upstream fixado
  • Testes: 18 novos testes de regressão de segurança (60 no total, todos passando)

v0.3.1 — reescrita mais forte de marca d'água estatística Layer B

  • rewrite_text.py a paráfrase padrão agora realiza um ataque explícito de escolha de palavras + sintaxe (ordem das cláusulas, conectores, palavras de transição, limites de frases, palavras funcionais) em vez de uma reescrita genérica
  • Novo --tactic humanize: passagem zero-shot "escreva como um humano" visando frases formulaicas típicas de IA
  • Novo --tactic code: reescreve comentários, docstrings e literais de string, e renomeia identificadores locais preservando o comportamento e os nomes da API pública
  • A passagem estrutural agora emite "prosa humana natural e variada" em vez do típico "estilo profissional claro" de IA
  • Novo --temperature (padrão 0.9) para backends Ollama e compatíveis com OpenAI
  • Novo --candidates N: gera N reescritas e seleciona a mais lexicalmente divergente (distância Jaccard de bigramas) com proteção contra deriva de comprimento
  • Higiene de modelo mais forte: prefira modelos locais de peso aberto e evite qualquer fornecedor conhecido por marcas d'água, não apenas a origem suspeita
  • O relatório de risco residual agora distingue texto curto/altamente previsível (risco menor) de prosa longa e de alta entropia (risco maior)
  • Documentação atualizada em SKILL.md, removal-matrix.md e vendor-notes.md; testes cobrem novos prompts, pontuação de divergência e seleção de candidatos

v0.3.0 — pontuação opcional de pixels SynthID

  • Pontuador SynthID opcional no domínio de pixels via checkout externo aloshdenny/reverse-SynthID (score_synthid.py); exibido em inspect_image.py / clean_image.py com REVERSE_SYNTHID_DIR ou --synthid-dir
  • Bootstrap setup_synthid.sh (dependências apenas do pontuador; --full instala requisitos upstream); Dockerfile.synthid mais make docker-synthid-build / docker-synthid-help
  • Alvos smoke-synthid e bootstrap-synthid do Makefile
  • Testes para o adaptador do pontuador, caminho indisponível da CLI, análise JSON e erros de tempo de execução
  • Documentação: detecção/pontuação apenas (sem remoção de pixels); o código upstream não é incluído e permanece sob sua Licença de Pesquisa não comercial

v0.2.0 — correção de falso positivo do c2patool

  • image_meta.py: has_manifest não sinaliza mais Error: No claim found / No JUMBF data found como manifesto (bug de precedência de operador: os marcadores negativos agora vetam cada ramo positivo)
  • Novo tests/test_c2patool_report.py (4 casos: sem reivindicação, sem JUMBF, manifesto genuíno, ferramenta ausente)
  • Documentação: corrigidos links c2patool (repositório movido para contentauth/c2pa-rs); adicionado aviso sobre o custo de qualidade da remoção de marcas d'água de texto

v0.1.0 — polimento de empacotamento + honestidade de proveniência

  • Makefile (test / smoke / install-skill) e pytest.ini
  • Amostras de fixture para Markdown, HTML, SVG; teste de limpeza degradada de PDF
  • Documentação: modelo industrial de duas camadas (C2PA de ligação rígida vs ligação suave / SynthID-media)
  • Tabela de risco residual no README + links para ferramentas de verificação externas
  • Referência: guia C2PA/SynthID do Institute of AI PM
  • Ligação suave e marcas d'água de pixel/áudio/vídeo explicitamente fora do escopo em habilidade/matriz/ética

v0.0.1 — lançamento inicial multi-vendor

  • Habilidade de agente remove-ai-marks (substitui remove-claude-marks apenas para Claude)
  • Camada A: Unicode invisível / bidi / caracteres de tag / homóglifos de espaço (inspect_text / clean_text)
  • Camada B: orientação de reescrita + rewrite_text.py opcional (print-prompt, Ollama, compatível com OpenAI)
  • Arquivos: remoção de metadados C2PA/AI para PNG, JPEG, SVG, PDF, DOCX, ODT, HTML, Markdown
  • inspect_file.py / clean_file.py unificados
  • Documentação multi-vendor (Claude, Gemini/SynthID-class, OpenAI, open-LLM)
  • Scripts primeiro na stdlib; c2patool / exiftool opcionais

Licença

MIT — veja LICENSE.

Bibliografia