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
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.
| Camada | Alvo | Como |
|---|---|---|
| A | Unicode invisível, espaços exóticos, bidi, caracteres de tag | Scripts Python determinísticos |
| B | Marcas d'água estatísticas de texto (amostragem de tokens) | Reescrita pelo agente + hook opcional rewrite_text.py |
| Arquivos | C2PA / EXIF / XMP / propriedades de documentos | PNG, 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
| Host | Alvo | Onde fica |
|---|---|---|
| Claude Code (pessoal) | --target claude-code | ~/.claude/skills/<skill> (respeita CLAUDE_CONFIG_DIR) |
| Claude Code (projeto) | --target claude-project --project-dir PATH | PATH/.claude/skills/<skill> |
| Cowork, claude.ai, sessões na nuvem, rotinas | --target cowork | dist/<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:
| Modo | Comportamento |
|---|---|
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. |
clean | Remove 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):
| Ferramenta | Papel |
|---|---|
c2patool | Inspecionar manifestos C2PA |
exiftool | Remoção de metadados residuais (especialmente PDF) |
qpdf | Reconstruçã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étodo | Caminho | Corpo | Retorna |
|---|---|---|---|
| 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 /detectexecuta os detectores de marca d'água configurados em um arquivo. Texto → detectores de fornecedores + estilometria; imagem → pontuação de pixel SynthID./inspectaceita um flag"detect": trueopt-in que anexa resultados do detector ao relatório de texto (e pode invertersuspicious)./cleanaceita 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./cleanexecuta 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 listatactic@intensityordenada, 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,/cleanretorna um 400.
Detectores de texto (veja /capabilities → text_detectors):
Detectores de texto (veja /capabilities → text_detectors):
| Detector | Ativado por | Notas |
|---|---|---|
markllm | MARKLLM_DIR (checkout do host) | Harness de pesquisa (esquemas KGW / SynthID), somente mesma-configuração — não é um oráculo de fornecedor. |
gumbel | WATERMARKS_GUMBEL_KEY | Replay 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 imagem | Conteúdo | Publicada? |
|---|---|---|
ghcr.io/guillaumemeyer/watermarks-remover:<tag> / :latest | Serviço HTTP principal + todos os limpadores + exiftool / qpdf / c2patool | Sim |
…:markllm-<tag> / :markllm-latest | Harness de marca d'água de texto MarkLLM (upstream Apache-2.0) | Sim |
…:markdiffusion-<tag> / :markdiffusion-latest | Harness de imagem MarkDiffusion (upstream Apache-2.0) | Sim |
watermarks-remover-ctrlregen:local | Remoção de pixel CtrlRegen — nunca publicada (noai-watermark não inclui LICENSE) | Somente build local |
watermarks-remover-synthid-scorer:local | Pontuador 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
| Var | Alcança | Propósito |
|---|---|---|
WATERMARKS_SERVER_API_KEY | wr-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_URL | wr-core | Apontar 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_KEY | wr-core + wr-synthid-score | Chave bearer compartilhada para o sidecar de pontuação (vazio = sem autenticação) |
WATERMARKS_SYNTHID_TEXT_URL | wr-core | Apontar 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_KEY | wr-core + wr-synthid-text | Chave bearer compartilhada para o sidecar de marca d'água de texto (vazio = sem autenticação) |
WATERMARKS_SYNTHID_TEXT_TIMEOUT | wr-core | Segundos para aguardar o sidecar wr-synthid-text (padrão 120) |
WATERMARKS_MARKLLM_SCHEME | text_detectors.py (host) | Esquema MarkLLM para /detect: kgw (padrão) / synthid |
HF_TOKEN | serviços harness/pesados | Token do Hugging Face para modelos restritos |
WATERMARKS_SERVICE_URL | somente cliente (skill / curl) | Onde alcançar o serviço; padrão http://127.0.0.1:8765 |
WATERMARKS_REWRITE_BACKEND | hook rewrite_text.py | print-prompt (padrão) / ollama / openai-compatible |
WATERMARKS_REWRITE_MODEL | hook rewrite_text.py | Nome do modelo (ex.: deepseek-v4-flash) |
WATERMARKS_REWRITE_BASE_URL | hook rewrite_text.py | Base da API (ex.: https://api.deepseek.com) |
WATERMARKS_REWRITE_API_KEY | hook rewrite_text.py | Chave da API — somente ambiente, nunca em argv |
WATERMARKS_REWRITE_ALLOW_REMOTE | hook rewrite_text.py | 1 para permitir endpoints fora de loopback |
WATERMARKS_REWRITE_REASONING_EFFORT | hook rewrite_text.py | none (padrão) / low / medium / high / off |
WATERMARKS_CLEAN_STRATEGY_FILE | server.py /clean | Caminho para o JSON de configuração da estratégia da Camada B (padrão config/clean_strategy.json) |
WATERMARKS_GUMBEL_KEY | detect_gumbel.py / text_detectors.py | Chave 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:
- 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. - 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:
--offlineno 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 (transformerstrust_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 ambiente | Flag do benchmark | Padrão | Significado |
|---|---|---|---|
WATERMARKS_REWRITE_BACKEND | --rewrite-backend | ollama | ollama 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-url | http://127.0.0.1:11434 | Endpoint; 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-remote | desligado | Necessá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:
- 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.
- Mecanismo opcional de remoção de pixels: seu ataque de regeneração
DiffusionPurificationé exposto comoclean_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. - 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
| Canal | Claude | Gemini/SynthID | OpenAI | Open-LLM |
|---|---|---|---|---|
| Texto baseado em Unicode / edição | Camada A | Camada A | Camada A | Camada A |
| Amostragem estatística de texto | Camada 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 presente | Camada B melhor esforço + harness MarkLLM opcional |
| C2PA / metadados de arquivo | Sim (formatos listados) | Sim quando presente | Sim quando presente | Sim quando presente |
| Marcas de imagem em pixels | Fora do escopo | Pontuação SynthID opcional + remoção CtrlRegen (externo); detecção MarkDiffusion opcional mesmo esquema + remoção DiffusionPurification (externo) | Fora do escopo | Remoção CtrlRegen / MarkDiffusion opcional (externo) |
| Backdoors de treinamento | Fora do escopo | Fora do escopo | Fora do escopo | Fora 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.
-
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.
-
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
| Formato | Inspecionar | Limpar |
|---|---|---|
| PNG / JPEG / WebP | Chunks C2PA / APP11 / RIFF C2PA, dicas XMP da AI | Remover segmentos de metadados |
| AVIF / HEIC | Caixas ISOBMFF jumb / XMP uuid | Remover caixas |
| BMP | Bytes não-imagem no final (sem canal padronizado) | Truncar metadados finais, corrigir campo de tamanho do arquivo |
| GIF | Extensões de aplicação Comentário / XMP | Remover comentário e XMP, manter loop NETSCAPE2.0 |
| TIFF (clássico + BigTIFF) | Tags IFD: XMP, EXIF, GPS, IPTC, MakerNote | Remover tags, zerar payloads, manter strips |
| SVG | <metadata>, XMP | Remover blocos |
| Byte/XMP + ferramentas opcionais | exiftool 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) | |
| DOCX | docProps / customXml | Limpar props, remover customXml |
| EPUB | Metadados OPF, meta/JSON-LD em XHTML, mídia incorporada | Limpar OPF, remover meta em XHTML, limpar mídia + Camada A (pula partes criptografadas) |
| ODT | meta.xml | Remover meta de gerador / IA |
| HTML | meta, JSON-LD, data-ai*, comentários <!-- --> nomeando uma ferramenta de IA ou marcados como gerados por IA / C2PA / credencial de conteúdo | Remover tags/atributos/comentários |
| Markdown | Chaves 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 / M4V | Caixas ISOBMFF jumb/uuid (mesmo mecanismo de AVIF/HEIC) + tags de gerador moov/udta | Remover caixas |
| WAV | Chunks RIFF C2PA / LIST INFO, chunk id3\x20 incorporado | Remover chunks |
| MP3 | Frames ID3v2 (v2.3/v2.4 por frame; v2.2 tag inteira) | Remover frames correspondentes ou tag inteira |
| FLAC | Manifesto C2PA em um frame ID3v2 GEOB | Remover 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:
- Sem perdas.
pdfwritecom 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. - 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):
| Canal | O que removemos | O que pode permanecer | Verificação externa (exemplos) |
|---|---|---|---|
| C2PA / EXIF / XMP de vinculação rígida | Sim | Marcas de vinculação suave / pixels | c2patool, verificação de Content Credentials |
| Mídia classe SynthID | Remoção de pixels opcional (CtrlRegen externo); pontuação local caso contrário | Marca d'água de áudio/vídeo; marca d'água de pixels residual após remoção | Ferramentas do fornecedor (ex.: Google SynthID / detector Vertex onde oferecido); pontuador local opcional reverse-SynthID |
| Texto estatístico | Reescrita de melhor esforço | Marcas fortes após edição leve | Nenhum 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ção | Remove | Observaçõ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êiner | Proveniência de arquivo | Veja 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 aberto | Evita re-marcação com o modelo de origem | Alternativa 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
/cleanexecuta a reescrita da Camada B para texto após a Camada A. O padrão vem deconfig/clean_strategy.json; umoptions.strategypor solicitação o substitui, e/cleanrejeita 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 comroberta-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
humanizeagora 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.pyganhou 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
uuidde 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/iTXta 1 MiB (#308); remover declarações XML DOCTYPE/ENTITY de SVG (#288); manter membros binários DOCX seguros em bytes (#314); preservar OOXMLAppVersion(#289).
Serviço HTTP & CLI
- Opção
/cleanpara manter espaços exóticos, espelhando o CLI (#274);/inspectexpõ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 einspect_*para evitar uma releitura redundante. clean_file.pyganhou-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.pyverifica arquivos de origem, docs e i18n que o roteador ignorou (#284); verifica.ts/.tsx/.jsx/.gde alinha a confiança de espaço entre formatos (#273); suporte aaudit_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.0looping 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
docPropsdo DOCX; podar relacionamentos pendentes após a remoção decustomXml; 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_Ignorablesem uso legítimo de intercâmbio (U+2065,U+FFF0–U+FFF8,U+E0000,U+E0080–U+E00FF,U+E01F0–U+E0FFF— relatados comoreserved_ignorable), os 66 não-caracteres (U+FDD0–U+FDEFmaisU+FFFE/U+FFFFpor plano — relatados comononcharacter) e três portadores Default_Ignorable de renderização em branco que o catch-allCfnunca 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-glueainda 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
--candidatesvariantes (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-statsagora relataevaluator/max_loops/attempts_made/passedecandidate_scorespor tentativa (#153) - Verificação de mesma chave Keyed-Gumbel (Aaronson EXP): novo
detect_gumbel.pysomente 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(envWATERMARKS_GUMBEL_KEY, preferido) o torna o avaliador do loop iterativo (prioridade: gumbel > markllm > divergência lexical) e é exposto comogumbelem/capabilitiese/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 (colunasmean_attempts/att,attempts/evaluator/passed);--rewrite-loopsespelha--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-removere depois/plugin install watermarks-remover@watermarks-remover, e atualizam no lugar.make plugin-validateexecutaclaude plugin validate . --strict;tests/test_plugin_manifest.pyverifica os manifestos sem o CLI install_skill.pyganhou um--target(claude-code,claude-project,cowork,cursor) e um seletor--skillcobrindo ambas as skills enviadas, além de--list,--linkeCLAUDE_CONFIG_DIR. O alvocoworkconstró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 alvosmake: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;cleanas 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çãohook_modedo plugin ouWATERMARKS_HOOK_MODE; a detecção reutilizaaudit_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-textnão nomeia mais Cursor como o único host
Serviço HTTP
- Endpoints de lote:
POST /clean/batch,/inspect/batch(#137) ePOST /detect/batch(#151) - Preservar extensões de formato de imagem em
/cleane usar gravações seguras emav_meta(#150); usar base64 portátil no exemplo curl/detect(e corrigir a portabilidaderealpathdo macOS nos bootstraps, #185)
Auditoria / inspeção e segurança
audit_dir.pyganhou 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-placepreserva o.bakoriginal; 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--jsonpuro para o scorer SynthID e sonda BSDrealpath(#70); corrigir um caminhosubprocess_creationflagsdo Windows em_ghostscript_usablee 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 emdocProps/app.xmldurante 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 paraservice/scripts/e roda atrás deserver.py, um entrypoint HTTP stdlib (/health,/inspect,/clean,/capabilities) - Serviço HTTP:
service/scripts/server.pyexpõ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 opcionalWATERMARKS_SERVER_API_KEY) - OpenAPI:
GET /openapi.jsonserve 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 comopenapi-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.yamlsobe toda a infraestrutura (coresempre;markllm/markdiffusionatrás deprofile: harness;ctrlregen/synthidatrás deprofile: heavycomo builds apenas locais); os serviços são prefixados comwr-; serviços harness/pesados usamcommand: ["--help"]por padrão para quedocker compose up --profile harness --profile heavysaia limpo (CLIs de uso único são executados comdocker compose run); novomake compose-check/compose-check.shvalida a stack em execução (apenas código de saída) - Publicação GHCR:
.github/workflows/release-images.ymlpublica imagenscore,markllm,markdiffusionem tagsv*;ctrlregen/synthidnunca são publicados (licenciamento upstream) - Configuração de ambiente:
.env.example+ guia de configuração de serviço;docker composecarrega automaticamente.env;.envé gitignored (negação por padrão) - Higiene do repositório:
.gitignoreeservice/.dockerignoreagora 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 enviamservice/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 paraservice/scripts/
Harness de marca d'água de imagem MarkDiffusion (opcional)
- Novo harness opcional (
THU-BPM/MarkDiffusionexterno, Apache-2.0):markdiffusion_harness.pycom subcomandoswatermark/detect/purifypara nove esquemas de imagem (Tree-Ring, Ring-ID, ROBIN, WIND, SFW, Gaussian-Shading, GaussMarker, PRC, SEAL) clean_image.py --remove-pixel diffusionexecuta o ataque de regeneraçãoDiffusionPurificationdo MarkDiffusion como um motor alternativo de remoção de pixels (intensidade conservadora padrão 0.3)- Bootstrap
setup_markdiffusion.sh(pin PyPI1.0.2; clone editável--checkoutem commit fixado) +requirements-markdiffusion.txt+Dockerfile.markdiffusione Makefilebootstrap-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ênciareferences/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/MarkLLMexterno, Apache-2.0):detect_text_watermark.pycom subcomandosdetect/watermarkpara esquemas KGW e SynthID rewrite_text.py --markllm-schemeexecuta detecção antes/depois em torno de uma reescrita Layer B e detecção por candidato quando--candidates N>1(controlado por env; relatacleared)- Bootstrap
setup_markllm.sh+requirements-markllm.txt(dependências fixadas) +Dockerfile.markllme Makefilebootstrap-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_ASopcional no subprocesso de reescrita, torch fixado no Dockerfile e verificação de SHA do clone emDockerfile.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.pyagora enviareasoning_effort: "none"por padrão para backendsopenai-compatible(--reasoning-effort/WATERMARKS_REWRITE_REASONING_EFFORT;offomite). Modelos de raciocínio comodeepseek-v4-flashqueimam ~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.txtfixoutokenizers==0.23.1, que conflita comtransformers==5.15.0(limitatokenizers<=0.23.0; não existe release 0.23.0) — agora fixadotokenizers==0.22.2; torch movido para o índice de wheels CPU (torch==2.13.0.*) para que a imagem seja apenas CPU comoDockerfile.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.markllmeDockerfile.markdiffusionnunca copiaramcommon.pypara/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-textsobrescreve (#24) --jsonnão suprime mais o código de saída do sinal residual (#30)inspect_fileimprime 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-watermarkexterno: adaptadorclean_ctrlregen.py+ bootstrapsetup_ctrlregen.sh(commit fixado, sparse checkout, venv, verificação SHA), além deDockerfile.ctrlregenemake bootstrap-ctrlregen/docker-ctrlregen-build/smoke-ctrlregen clean_image.py --remove-pixel ctrlregenexecuta remoção de metadados → remoção CtrlRegen → pontuação antes/depois reverse-SynthID opcional;inspect_image.pysugere 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-watermarknã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 usandoCtrlRegenEnginediretamente
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) eaudit_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/ZWJapó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_fneos.fchmodapenas 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.bakpelo mesmo caminho seguro — symlinks pré-colocados (ex.: em/tmpou 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 headerAuthorizationnunca seja reenviada a um host não validado; endpoints não-loopback são negados por padrão (opt-in com--allow-remoteouWATERMARKS_REWRITE_ALLOW_REMOTE=1); apenas esquemas http(s) são aceitos;--api-keyfoi removido — chaves são apenas via env comWATERMARKS_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_FSIZEaplicados 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), etapapip-audite 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.pya 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ão0.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.mdevendor-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 eminspect_image.py/clean_image.pycomREVERSE_SYNTHID_DIRou--synthid-dir - Bootstrap
setup_synthid.sh(dependências apenas do pontuador;--fullinstala requisitos upstream);Dockerfile.synthidmaismake docker-synthid-build/docker-synthid-help - Alvos
smoke-synthidebootstrap-synthiddo 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_manifestnão sinaliza maisError: No claim found/No JUMBF data foundcomo 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 paracontentauth/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) epytest.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(substituiremove-claude-marksapenas 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.pyopcional (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.pyunificados- Documentação multi-vendor (Claude, Gemini/SynthID-class, OpenAI, open-LLM)
- Scripts primeiro na stdlib;
c2patool/exiftoolopcionais
Licença
MIT — veja LICENSE.
Bibliografia
- Como o Claude marca conteúdo gerado por IA (Anthropic)
- Dathathri et al., Marcas d'água escaláveis para identificar saídas de grandes modelos de linguagem (SynthID-Text, Nature 2024)
- Google AI for Developers, Salvaguardas SynthID (documentação da API Gemini)
- C2PA / c2patool
- Kirchenbauer et al., Uma Marca d'Água para Grandes Modelos de Linguagem
- Evseev, D. (Arbitration City), Marca d'Água de Texto de IA Precisa, Sem Custo e Invisível para Inferência de IA Auto-hospedada (relatório técnico, agosto de 2026) — marca d'água keyed-Gumbel enviada no mecanismo open-source arbi-serve, com detecção de teste exato e suporte a decodificação especulativa — PDF
- THU-BPM/MarkLLM (kit de ferramentas unificado para avaliar algoritmos de marca d'água de LLM)
- Pan et al., MarkDiffusion: Um Kit de Ferramentas Open-Source para Marca d'Água Generativa de Modelos de Difusão Latente (JMLR) — o kit de ferramentas de incorporação que o harness opcional de marca d'água de imagem deste repositório envolve — código, documentação
- Zhang et al., Marcas d'Água na Areia: Impossibilidade de Marca d'Água Forte para Modelos Generativos (ICML 2024)
- Sander et al., Marcas d'Água Tornam Modelos de Linguagem Radioativos — marcas d'água sobrevivem ao fine-tuning e marcam modelos downstream treinados em dados com marca d'água
- Pan et al., As Marcas d'Água de LLM Podem Prevenir Robustamente a Destilação de Conhecimento Não Autorizada? — proveniência baseada em marca d'água e proteção contra destilação de conhecimento
- google-deepmind/synthid-text (referência de pesquisa; não usado para detecção aqui)
- aloshdenny/reverse-SynthID (referência de pesquisa)
- ETH Zurich SRI, Sondando SynthID (blog de pesquisa sobre a detectabilidade de marcas d'água SynthID)
- Liu et al., Marcas d'Água de Imagem São Removíveis Usando Regeneração Controlável a partir de Ruído Limpo (ICLR 2025) — o método de regeneração de pixels que o backend opcional CtrlRegen implementa — código
- Kassis & Hengartner, UnMarker: Um Ataque Universal a Marcas d'Água Defensivas de Imagem (arXiv:2405.08363; IEEE S&P 2025) — um ataque universal de marca d'água comparado em uma métrica diferente do CtrlRegen
- Goonatilake & Ateniese, Remover a Marca d'Água Não É Suficiente: Furtividade Forense na Remoção de Marcas d'Água de IA Generativa (arXiv:2605.09203) — motiva o padrão de intensidade conservadora: a remoção ainda pode deixar vestígios forenses
- mertizci/noai-watermark (kit de ferramentas CLI/Python para remoção de SynthID/StableSignature/TreeRing e remoção de metadados de IA)
- 0xROOTPLS/DeSynth (remoção de SynthID para imagens OpenAI/Google)
- Institute of AI PM, Proveniência e Marca d'Água de Conteúdo de IA: O Guia do PM para C2PA e SynthID (modelo industrial de duas camadas: C2PA + marca d'água imperceptível / ligação suave; contexto SB 942 / EU AI Act Art. 50)