watermarks-remover
Elimina marcas de procedencia de IA de múltiples proveedores: higiene de texto Unicode, enlaces de reescritura estadística y metadatos C2PA de PNG/JPEG/SVG/PDF/DOCX/HTML/MD
Documentación
_ _ _ ____ ___ ____ ____ _ _ ____ ____ _ _ ____ ____ ____ _ _ ____ _ _ ____ ____
| | | |__| | |___ |__/ |\/| |__| |__/ |_/ [__ __ |__/ |___ |\/| | | | | |___ |__/
|_|_| | | | |___ | \ | | | | | \ | \_ ___] | \ |___ | | |__| \/ |___ | \
watermarks-remover
Habilidad de agente + servicio Python de stdlib para eliminar marcas de procedencia de IA de múltiples proveedores de texto y archivos — para privacidad e higiene en contenido que tú posees. La habilidad es un cliente ligero: impulsa la maquinaria a través de HTTP, por lo que el host del agente no necesita Python.
| Capa | Objetivo | Cómo |
|---|---|---|
| A | Unicode invisible, espacios exóticos, bidi, caracteres de etiqueta | Scripts Python deterministas |
| B | Marcas de agua de texto estadísticas (muestreo de tokens) | Reescritura del agente + hook opcional rewrite_text.py |
| Archivos | C2PA / EXIF / XMP / propiedades 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 |
Proveedores / ecosistemas (a nivel de clase): Claude, Gemini / SynthID-Text, superficies de procedencia de OpenAI, marcas de LLM abiertos estilo Kirchenbauer (lista verde) y Gumbel con clave / EXP (Aaronson).
Última versión: v0.7.0
Ruta de la habilidad: skills/remove-ai-marks/
Ruta del servicio: service/
(migración: anteriormente remove-claude-marks; el alias de barra /remove-claude-marks sigue documentado)
Instalación (habilidad de agente)
La habilidad no incluye código — llama al servicio a través de HTTP. Instala la habilidad (solo markdown) e inicia el servicio, luego establece WATERMARKS_SERVICE_URL si no es http://127.0.0.1:8765.
En Claude Code, la ruta más rápida es el marketplace de plugins incluido — sin clonar, y se actualiza en el lugar. En cualquier otro lugar, un solo instalador cubre todos los hosts compatibles (Python 3.10+ stdlib, sin dependencias):
python3 install_skill.py --skill remove-ai-marks --target claude-code
| Host | Destino | Aterriza en |
|---|---|---|
| Claude Code (personal) | --target claude-code | ~/.claude/skills/<skill> (respeta CLAUDE_CONFIG_DIR) |
| Claude Code (proyecto) | --target claude-project --project-dir PATH | PATH/.claude/skills/<skill> |
| Cowork, claude.ai, sesiones en la nube, rutinas | --target cowork | dist/<skill>.zip para subir bajo Personalizar → Habilidades |
| Cursor | --target cursor (predeterminado) | ~/.cursor/skills/<skill> |
Habilidades incluidas: remove-ai-marks (completa, respaldada por servicio) y
clean-user-facing-text (solo texto, autónoma). --list las imprime.
Las instalaciones existentes se conservan a menos que pases --force; el reemplazo se
prepara primero y la instalación anterior se conserva como copia de seguridad con nombre único.
--link enlaza simbólicamente este checkout en lugar de copiar, por lo que las ediciones se recogen
en vivo. En Windows, usa py install_skill.py ...; el envoltorio install-skill.sh
se proporciona para shells de macOS/Linux.
Antes de escribir nada, el instalador valida la habilidad contra las
reglas de empaquetado de Agent Skills que aplican las subidas de claude.ai
y la API de Skills: frontmatter solo de especificación (name, description,
license, compatibility, metadata, allowed-tools), un
name en minúsculas con guiones de como máximo 64 caracteres que coincida con el directorio, un
description no vacío de como máximo 1024 caracteres. El paquete de Cowork además debe
caber en el límite de subida de 30 MB, que el empaquetador aplica.
Limpieza automática mediante hook (determinista)
Una habilidad es una instrucción: el modelo decide si invocarla, y el modelo es quien produce las marcas. Un hook es ejecutado por el arnés en cada llamada de herramienta coincidente, sin requerir cooperación. Eso hace que el hook sea la mitad determinista de este flujo de trabajo.
El plugin registra un hook PostToolUse en Write|Edit|MultiEdit|NotebookEdit
que ejecuta service/scripts/hook_written_file.py
contra el archivo que el agente acaba de escribir. Dos modos, que coinciden con la
convención de pre-commit de verificar por defecto:
| Modo | Comportamiento |
|---|---|
check (predeterminado) | Informa marcas de procedencia, deja el archivo intacto. Los hallazgos van al modelo (salida 2), para que pueda ofrecer limpiarlos. |
clean | Elimina las marcas en el lugar, luego informa al modelo que el archivo en disco cambió. |
Establece el modo desde la configuración del plugin (Modo de hook en /plugin manage,
leído por el hook como CLAUDE_PLUGIN_OPTION_HOOK_MODE), o con
WATERMARKS_HOOK_MODE=clean en el entorno. El comando del hook deliberadamente
no interpola ${user_config.hook_mode}: Claude Code se niega a ejecutar
un hook que haga referencia a una opción que el usuario nunca ha abierto /plugin manage para
establecer — un default declarado no lo satisface — por lo que interpolarlo significaría
que el hook nunca se ejecuta silenciosamente en una instalación nueva. La detección reutiliza audit_lib's
scan_file / is_actionable, por lo que el hook, la puerta de pre-commit y la exportación
SARIF de CI coinciden en lo que cuenta como accionable; la limpieza se delega a
clean_file.py, por lo que no se duplica lógica de limpieza. El modo clean escribe en un
archivo temporal hermano y solo intercambia ante una diferencia real, por lo que los archivos que ya
estaban limpios conservan su mtime y no vuelven a activar los observadores de archivos.
Sin el plugin, conéctalo en ~/.claude/settings.json (o un proyecto
.claude/settings.json) tú mismo:
{
"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
}
]
}
]
}
}
En Windows, reemplaza python3 con py.
Lo que un hook no puede hacer. Ningún hook puede reescribir el mensaje de chat del asistente
antes de que lo leas. El hook Stop de Claude Code recibe last_assistant_message
de solo lectura, y no hay filtro de pre-envío para respuestas finales — el mismo límite
que este proyecto ya documenta para las reglas de Cursor. Así que la garantía determinista
cubre los archivos que el agente escribe, más la
puerta de pre-commit para cualquier cosa en camino a git. El texto que
solo existe en la transcripción del chat todavía depende del flujo de trabajo de la habilidad,
que se basa en instrucciones del modelo y por lo tanto es de mejor esfuerzo.
Plugin de Claude Code (marketplace)
El repositorio también es un plugin de Claude Code y un
marketplace de un solo plugin (.claude-plugin/), por lo que ambas habilidades se instalan y actualizan en dos
comandos, sin clonar ni script requerido:
/plugin marketplace add guillaumemeyer/watermarks-remover
/plugin install watermarks-remover@watermarks-remover
Las habilidades luego se cargan con espacio de nombres: /watermarks-remover:remove-ai-marks y
/watermarks-remover:clean-user-facing-text (el /remove-ai-marks simple también
funciona cuando nada más reclama el nombre). /plugin marketplace update watermarks-remover trae versiones posteriores. Lo mismo funciona desde la CLI con
claude plugin marketplace add … / claude plugin install …, y desde un checkout
local pasando una ruta en lugar de owner/repo.
Mantenedores: make plugin-validate ejecuta claude plugin validate . --strict
contra ambos manifiestos; tests/test_plugin_manifest.py cubre los mismos archivos
sin necesidad de la 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
Claude Code detecta habilidades personales y de proyecto sin reiniciar; /skills
lista lo que cargó. Invoca con /remove-ai-marks o pide "eliminar marcas de agua de IA / C2PA / marcas de Claude / texto clase SynthID". Una instalación de proyecto es
también lo que las sesiones en la nube
leen, ya que clonan el repositorio y cargan su .claude/skills/.
Cowork (y claude.ai, sesiones en la nube, rutinas)
Las sesiones de Cowork no leen ~/.claude/skills en tu máquina — cargan
las habilidades habilitadas para tu cuenta de claude.ai, sincronizadas cuando la sesión comienza.
Así que instala allí subiendo un paquete:
python3 install_skill.py --skill remove-ai-marks --target cowork
# writes dist/remove-ai-marks.zip (make package-cowork-skill)
Luego, en la aplicación de Claude Desktop, abre Personalizar → Habilidades → Agregar y sube
el zip (la misma configuración de habilidades en claude.ai también funciona). El paquete es
reproducible y contiene un único directorio remove-ai-marks/ de nivel superior con
SKILL.md en su raíz, que es el diseño que la subida espera.
La accesibilidad del servicio importa más aquí que en una instalación local: la habilidad es un
cliente HTTP ligero, por lo que la sesión debe poder alcanzar WATERMARKS_SERVICE_URL.
Las sesiones de Cowork que se ejecutan localmente en tu máquina alcanzan un make serve local;
las sesiones en la nube y las rutinas se ejecutan de forma remota y necesitan una URL de servicio alcanzable desde
allí (y WATERMARKS_SERVER_API_KEY establecido en ella). Si quieres una habilidad sin
servicio en absoluto, sube clean-user-facing-text en su lugar — es solo texto y
incluye sus propios 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
Habilidad opcional solo texto
skills/clean-user-facing-text/ es una
habilidad autónoma para manuscritos autorizados, documentación y copia
web. Excluye herramientas de imagen, C2PA, servicio y modelos externos, y ejecuta
sus propios scripts de Capa A incluidos en lugar de llamar al servicio.
python3 install_skill.py --skill clean-user-facing-text --target claude-code
python3 install_skill.py --skill clean-user-facing-text --target cursor
La invocación de la habilidad es seleccionada por el modelo. Los proyectos que adoptan explícitamente este flujo de trabajo en Cursor también pueden copiar la regla 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 los proyectos, coloca la misma instrucción en Reglas de usuario de Cursor en su lugar. Las reglas mejoran la consistencia pero siguen siendo instrucciones del modelo; Cursor no expone un filtro determinista de pre-envío para respuestas finales de chat.
Iniciar el servicio
La ruta más rápida es un servidor HTTP local (solo Python 3.10+ stdlib — sin dependencias, sin Docker):
make serve # http://127.0.0.1:8765
# or directly:
python3 service/scripts/server.py --host 127.0.0.1 --port 8765
Windows (sin Docker)
Consulta docs/windows-autostart.md para auto-iniciar el servicio al iniciar sesión en Windows sin Docker.
Para toda la infraestructura (núcleo + backends opcionales de arnés/pesados), consulta Docker / compose a continuación.
Herramientas de sistema opcionales (auto-usadas cuando están presentes — preinstaladas en la imagen Docker del núcleo):
| Herramienta | Rol |
|---|---|
c2patool | Inspeccionar manifiestos C2PA |
exiftool | Eliminación de metadatos residuales (especialmente PDF) |
qpdf | Reconstrucción estructural de PDF — requerida para una eliminación real de PDF (ver a continuación) |
Los scripts del núcleo necesitan Python 3.10+ stdlib solamente. Las llamadas al modelo de Capa B son opcionales.
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
Las herramientas de texto rechazan entrada binaria
inspect_text.py, clean_text.py y rewrite_text.py operan sobre texto. Apuntadas
a un .docx, .pdf o imagen solían decodificar los bytes comprimidos e informar
los puntos de código que salieran — ruido que rastrea la compresión, no el
contenido — y clean_text.py luego escribía esos bytes mutilados de vuelta, destruyendo el
archivo. Ahora rechazan entrada binaria y nombran la herramienta que la maneja:
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.
La detección es por número mágico más una proporción de bytes de control, por lo que el texto en codificaciones
distintas de UTF-8 sigue funcionando. --force-text lo anula en todas partes.
Los formatos no reconocidos nunca se limpian automáticamente
classify() etiqueta los bytes que no coinciden con ningún formato de texto, imagen o contenedor
compatible como unknown — ya no recurre a "texto". En modo automático
clean_file.py rechaza tales archivos (salida 2, sin salida escrita) en lugar de
decodificarlos como UTF-8 y escribir bytes mutilados de vuelta; --as text o
--force-text son las opciones explícitas. inspect_file.py informa el archivo
como unknown (salida 0), y el servicio HTTP responde /inspect con
kind: "unknown" pero rechaza /clean de formatos desconocidos (400 — envía un
nombre de archivo con una extensión conocida, p. ej. notes.txt).
Servicio HTTP
La misma maquinaria se ejecuta como un servicio HTTP de stdlib (service/scripts/server.py) — la interfaz que usa la habilidad y la forma en que cualquier aplicación web puede integrarse sin incluir código:
| Método | Ruta | Cuerpo | Devuelve |
|---|---|---|---|
| GET | /health | — | {"ok": true, "version": ...} |
| GET | /capabilities | — | herramientas/backends opcionales utilizables (cada herramienta se prueba por versión, no solo se encuentra en PATH) |
| GET | /openapi.json | — | especificación OpenAPI 3.0.3 generada dinámicamente |
| 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": {...}} o {"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", ...}}, ...]} |
Los endpoints por lotes recorren el mismo pipeline por archivo que /inspect, /detect, /clean y /watermark, con un límite de WATERMARKS_MAX_BATCH_FILES archivos por solicitud (50 por defecto). Una entrada malformada (base64 incorrecto, opción desconocida, formato no reconocido) se refleja como el "ok": false de esa entrada con una cadena "error" — nunca aborta el resto del 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\"}"
El servicio enruta por extensión de nombre de archivo y luego por bytes mágicos, por lo que texto / imagen / contenedor se detectan automáticamente. Establezca WATERMARKS_SERVER_API_KEY para requerir Authorization: Bearer <key> en cada solicitud. Enlace solo a loopback por defecto (--host para anular); destinado a una red de confianza.
Detección de marcas de agua (/detect y detect_before / detect_after)
La detección es un paso separado de la limpieza — el servicio nunca llama a las API de los proveedores a menos que se lo pida:
POST /detectejecuta los detectores de marcas de agua configurados en un archivo. Texto → detectores de proveedor + estilometría; imagen → puntuación de píxeles SynthID./inspectacepta una marca"detect": truede opt-in que agrega resultados del detector al informe de texto (y puede cambiarsuspicious)./cleanacepta opciones"detect_before"/"detect_after"para puntuar la entrada y la salida limpiada, de modo que pueda medir qué cambió realmente una limpieza./cleanejecuta la reescritura de texto de la Capa B después de la Capa A por defecto (es un paso obligatorio para texto). Una opción"strategy"(una lista ordenadatactic@intensity, p. ej."paraphrase@0.8,mlm@0.2") anula el valor predeterminado del archivo de configuración de estrategia (ver más abajo). Cuando el backend/modelo de reescritura para un paso no está configurado,/cleandevuelve un 400.
Detectores de texto (ver /capabilities → text_detectors):
Detectores de texto (ver /capabilities → text_detectors):
| Detector | Activado por | Notas |
|---|---|---|
markllm | MARKLLM_DIR (checkout del host) | Harness de investigación (esquemas KGW / SynthID), solo misma configuración — no es un oráculo de proveedor. |
gumbel | WATERMARKS_GUMBEL_KEY | Reproducción sin modelo de la misma clave del esquema keyed-Gumbel (Aaronson EXP) (ver detect_gumbel.py), solo stdlib — motores autoalojados como arbi-serve; solo misma clave, no es un oráculo de proveedor. |
claude-text | — (placeholder) | Anthropic ha anunciado una API de detección de marcas de agua; esta costura se activa cuando se lance. |
Puntuación de imágenes: cuando WATERMARKS_SYNTHID_SCORER_URL está configurado, el servicio
puntúa imágenes a través del sidecar wr-synthid-score (perfil pesado); con un
REVERSE_SYNTHID_DIR local usa el checkout directamente. La detección es
fail-soft: los detectores no configurados, con tiempo de espera agotado o con errores informan
{"available": false, "error": ...} y nunca bloquean la limpieza.
Generación de marcas de agua (/watermark y /watermark/batch)
Genera texto con marca de agua para evaluación de benchmarks y pruebas de ida y vuelta.
Cuando WATERMARKS_SYNTHID_TEXT_URL está configurado, el servicio delega la generación al
sidecar wr-synthid-text (perfil harness); con un MARKLLM_DIR local usa el
checkout directamente. Como la detección, la generación es fail-soft: un generador no configurado
informa {"ok": false, "error": ...}.
Docker / compose
Imágenes publicadas (GHCR):
| Etiqueta de imagen | Contenido | ¿Publicada? |
|---|---|---|
ghcr.io/guillaumemeyer/watermarks-remover:<tag> / :latest | Servicio HTTP principal + todos los limpiadores + exiftool / qpdf / c2patool | Sí |
…:markllm-<tag> / :markllm-latest | Harness de marcas de agua de texto MarkLLM (Apache-2.0 upstream) | Sí |
…:markdiffusion-<tag> / :markdiffusion-latest | Harness de imágenes MarkDiffusion (Apache-2.0 upstream) | Sí |
watermarks-remover-ctrlregen:local | Eliminación de píxeles CtrlRegen — nunca publicada (noai-watermark no incluye LICENCIA) | Solo compilación local |
watermarks-remover-synthid-scorer:local | Puntuador reverse-SynthID — nunca publicada (Licencia de Investigación no comercial) | Solo compilación local (CLI puntuador + sidecar HTTP opcional wr-synthid-score bajo el perfil heavy) |
Compilar y ejecutar el servicio 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
Puesta en marcha de toda la infraestructura:
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
El stack de compose mapea el servicio principal a 127.0.0.1:8765. Los servicios persistentes se ejecutan como daemons en segundo plano (wr-core y el sidecar wr-synthid-text bajo el perfil harness). Los servicios restantes de harness/pesados son CLI de un solo uso — invóquelos con docker compose run --rm <service> … cuando necesite verificación o trabajo de píxeles.
Validar el stack en ejecución (solo código de salida, sin salida en caso de éxito):
make compose-check # or: ./compose-check.sh
Comprueba wr-core vía GET /health y ejecuta cada servicio harness/pesado con --help, requiriendo salida 0.
Configuración (variables de entorno para docker compose)
La limpieza de texto requiere configuración de la Capa B — la reescritura de la Capa B es un
paso obligatorio para POST /clean en texto, por lo que el servicio principal necesita el backend
de reescritura configurado, o la limpieza de texto devuelve HTTP 400. La limpieza de metadatos
de imagen/contenedor funciona de fábrica. Para texto debe configurar las dependencias
de la estrategia de la Capa B: transformers + roberta-large (para el paso mlm predeterminado) y
la configuración LLM WATERMARKS_REWRITE_* (para el paso 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\"}"
Los idiomas cuya tipografía depende de un espacio de no separación (francés « … », el
espacio antes de ; : ! ?) deben pasar "options": {"normalize_spaces": false}, el
equivalente HTTP de clean_text.py --no-normalize-spaces. Los portadores invisibles se
eliminan igualmente; solo se omite la reescritura del espacio.
Todo lo demás es opcional y vive en un archivo .env en la raíz del repositorio. docker compose auto-carga .env e interpola las referencias ${VAR} en compose.yaml desde él (las exportaciones de shell ganan sobre .env si ambos están configurados).
cp .env.example .env # then edit
docker compose up -d # picks up .env automatically
.env está en gitignore (denegar por defecto) — nunca lo confirme. Para ejecuciones CLI del lado del host (rewrite_text.py, la habilidad), exporte el mismo archivo al entorno:
set -a; . ./.env; set +a; python3 service/scripts/rewrite_text.py /tmp/x.txt -o /tmp/x.rewritten.txt
| Var | Alcanza | Propósito |
|---|---|---|
WATERMARKS_SERVER_API_KEY | wr-core (vía compose environment) | Requerir Authorization: Bearer <key> en la API HTTP |
WATERMARKS_GEMINI_* | — | Eliminado en agosto de 2026: Google retiró el marcado de agua de texto SynthID en la API (ver vendor-notes.md) |
WATERMARKS_SYNTHID_SCORER_URL | wr-core | Apuntar el núcleo al sidecar wr-synthid-score para puntuación de imágenes SynthID (p. ej. http://wr-synthid-score:8766 bajo el perfil pesado) |
WATERMARKS_SYNTHID_SCORER_API_KEY | wr-core + wr-synthid-score | Clave bearer compartida para el sidecar puntuador (vacío = sin autenticación) |
WATERMARKS_SYNTHID_TEXT_URL | wr-core | Apuntar el núcleo al sidecar wr-synthid-text para marcado de agua de texto SynthID (p. ej. http://wr-synthid-text:8767 bajo el perfil harness) |
WATERMARKS_SYNTHID_TEXT_API_KEY | wr-core + wr-synthid-text | Clave bearer compartida para el sidecar de marcado de agua de texto (vacío = sin autenticación) |
WATERMARKS_SYNTHID_TEXT_TIMEOUT | wr-core | Segundos de espera para el sidecar wr-synthid-text (120 por defecto) |
WATERMARKS_MARKLLM_SCHEME | text_detectors.py (host) | Esquema MarkLLM para /detect: kgw (predeterminado) / synthid |
HF_TOKEN | servicios harness/pesados | Token de Hugging Face para modelos restringidos |
WATERMARKS_SERVICE_URL | solo cliente (habilidad / curl) | Dónde alcanzar el servicio; predeterminado http://127.0.0.1:8765 |
WATERMARKS_REWRITE_BACKEND | hook rewrite_text.py | print-prompt (predeterminado) / ollama / openai-compatible |
WATERMARKS_REWRITE_MODEL | hook rewrite_text.py | Nombre del modelo (p. ej. deepseek-v4-flash) |
WATERMARKS_REWRITE_BASE_URL | hook rewrite_text.py | Base de API (p. ej. https://api.deepseek.com) |
WATERMARKS_REWRITE_API_KEY | hook rewrite_text.py | Clave de API — solo entorno, nunca en argv |
WATERMARKS_REWRITE_ALLOW_REMOTE | hook rewrite_text.py | 1 para permitir endpoints que no sean loopback |
WATERMARKS_REWRITE_REASONING_EFFORT | hook rewrite_text.py | none (predeterminado) / low / medium / high / off |
WATERMARKS_CLEAN_STRATEGY_FILE | server.py /clean | Ruta al JSON de configuración de estrategia de la Capa B (predeterminado config/clean_strategy.json) |
WATERMARKS_GUMBEL_KEY | detect_gumbel.py / text_detectors.py | Clave secreta para reproducción de misma clave keyed-Gumbel (EXP) (p. ej. 0x…); preferida sobre argv — nunca registrada |
La Capa B es obligatoria para la limpieza de texto. /clean siempre aplica la estrategia
predeterminada (de config/clean_strategy.json, {"default_strategy": "paraphrase@0.8,mlm@0.2"}) a un archivo de texto después de la Capa A, a menos que la solicitud pase su propia opción "strategy" (una lista ordenada tactic@intensity). Un paso de estrategia es tactic@intensity; el paso mlm necesita transformers + roberta-large, y cualquier paso LLM (paraphrase, humanize, …) necesita la configuración WATERMARKS_REWRITE_*. Si el backend/modelo requerido no está configurado — o no hay estrategia disponible — /clean rechaza la solicitud con un 400. Precedencia para la ruta de configuración: bandera CLI --strategy-config > variable de entorno WATERMARKS_CLEAN_STRATEGY_FILE > el predeterminado config/clean_strategy.json.
Las imágenes se publican automáticamente en etiquetas v* vía .github/workflows/release-images.yml.
Puntuación de píxeles SynthID opcional
inspect_image.py y clean_image.py pueden informar una puntuación de confianza SynthID
en el dominio de píxeles cuando hay un checkout externo de
aloshdenny/reverse-SynthID
disponible. El puntuador no está incluido: se carga en tiempo de ejecución desde su
checkout, y su código permanece bajo la Licencia de Investigación no comercial
del proyecto upstream.
Opción 1: bootstrap de un comando (sin 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 acepta --dir PATH, --ref REF y --full (instale el
requirements.txt upstream completo, que agrega torch/diffusers para
el bypass VAE upstream que este proyecto no usa).
En Windows use setup_synthid.ps1 (-Dir, -Ref, -Full), que crea el
venv en .venv\Scripts\ — el diseño que image_meta.py ya busca en
os.name == "nt".
Opción 2: compilación 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
La imagen se compila localmente desde el código fuente upstream en tiempo de compilación. No se publica, por lo que no redistribuye el código upstream.
Opción 3: sidecar HTTP puntuador (docker compose)
Bajo el perfil heavy, el stack de compose también ejecuta el puntuador como sidecar HTTP
(wr-synthid-score) para que el servicio principal publicado pueda puntuar
imágenes antes/después de la limpieza sin incluir el código upstream no comercial.
Apunte wr-core a él y comparta una clave bearer (ver .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
Luego POST /clean con {"options": {"detect_before": true, "detect_after": true}} returns synthid_before / synthid_after en el
informe, y POST /detect en una imagen devuelve la puntuación SynthID. Fail-soft:
si el sidecar está caído o no configurado, los informes llevan
{"available": false, "error": ...} y la limpieza aún tiene éxito.
La puntuación V4 usa artifacts/spectral_codebook_v4.npz del checkout upstream
(`220 MB). Esto es solo detección/puntuación — no elimina marcas de agua
de píxeles.
Eliminación de píxeles CtrlRegen opcional
Para marcas de agua de imágenes en el dominio de píxeles (clase SynthID, StegaStamp, Tree-Ring,
StableSignature), un backend externo opcional ejecuta el pipeline CtrlRegen
(ControlNet + regeneración controlable IP-Adapter DINOv2). El backend es
mertizci/noai-watermark, una
reimplementación mantenida del método
CtrlRegen de ICLR 2025 con teselado automático.
El backend no está incluido y no incluye archivo de LICENCIA, por lo que se trata como
todos los derechos reservados: se clona en un commit fijado y se carga en tiempo de ejecución.
Sus fijaciones de dependencias de la era de investigación (requirements-ctrlregen.txt — p. ej.
transformers==4.37.2, diffusers==0.27.2) llevan avisos publicados y
están intencionalmente desactualizadas, por lo que solo se instalan dentro del
venv dedicado que este script crea y nunca en la imagen del servicio principal;
setup_ctrlregen.sh también re-verifica el commit fijado en checkouts
existentes, no solo en clones nuevos.
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
En Windows usa setup_ctrlregen.ps1 (mismos indicadores que -Dir, -Ref, -Python);
el venv termina en .venv\Scripts\, que clean_image.py ya resuelve.
Sondea los índices publicados de wheels de PyTorch y elige el más alto en o
por debajo de la versión de CUDA que nvidia-smi imprime y que realmente existe — ese número
es el máximo que soporta el driver, y los drivers son retrocompatibles, así que un
driver que reporta 13.1 (sin cu131 publicado) instala cu130. Por debajo de la capacidad
de cómputo 7.5 fuerza cu126, el último índice cuyos wheels aún incluyen
kernels Maxwell/Pascal/Volta. Instala torch y torchvision
juntos desde ese índice para que la instalación de dependencias no los cambie por builds
de CPU desde PyPI, y luego verifica tras la instalación que torch.cuda.is_available()
sea verdadero — si se detectó una GPU pero torch termina siendo solo CPU, el script advierte
en voz alta y sale con código distinto de cero en lugar de fingir que la configuración fue exitosa.
Desde 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
Orden de operaciones: primero la eliminación de metadatos, luego la eliminación de píxeles de CtrlRegen, y luego
un SynthID inverso opcional antes/después de la puntuación (cuando REVERSE_SYNTHID_DIR también
está configurado).
La intensidad es conservadora por defecto (--ctrlregen-intensity 0.25), porque
una mayor intensidad elimina más marca de agua pero regenera más de la imagen.
Presets documentados: 0.15 mínimo / 0.25 predeterminado / 0.35 equilibrado /
0.5 agresivo / 0.7 máximo (el predeterminado del backend es 0.5). --ctrlregen-steps
por defecto es 50 (pasos efectivos de denoising ≈ pasos × intensidad).
Tamaño de imagen (límite nativo 512×512)
CtrlRegen es un ControlNet de Stable Diffusion 1.5 de 512×512. El backend resuelve esto para entradas arbitrarias, por lo que no se expone aquí ningún mosaico adicional:
- ≤512 px: una sola pasada — recorte/redimensionamiento centrado a 512, regenerar, redimensionar de vuelta.
- >512 px: mosaico automático con superposición (mosaicos de 512 px, superposición de 192 px), ancho/alto alineados a múltiplos de 8, luego uniones con fusión coseno.
- Cualquier ruta: la salida se redimensiona al tamaño original y se iguala el color con la imagen original.
Imágenes muy grandes (por ejemplo, 4K) producen muchos mosaicos, por lo que las ejecuciones escalan con el número de mosaicos (más lentas y mayor VRAM). Reduce la escala previa de entradas grandes cuando sea práctico; el tamaño del mosaico y la superposición están codificados rígidamente en el upstream y no se exponen como indicadores.
Cómputo, modelos restringidos y verificación
Espera ~10 GB de descargas de modelos; se recomienda encarecidamente una GPU y las ejecuciones
en CPU son lentas. Algunos modelos upstream están restringidos, así que exporta HF_TOKEN (solo env —
nunca argv). clean_ctrlregen.py se niega a instalar dependencias automáticamente; ejecuta
setup_ctrlregen.sh primero.
No hay detector local para StegaStamp/Tree-Ring/StableSignature, por lo que
la única señal local es la puntuación inversa de SynthID (un sustituto). Cuando esté disponible,
clean_image.py --remove-pixel ctrlregen reporta esa puntuación antes/después; el
verificador oficial de Google SynthID sigue siendo la autoridad 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
Verificación opcional de marca de agua de texto MarkLLM
Para experimentos controlados, un arnés externo opcional envuelve
THU-BPM/MarkLLM (Apache-2.0) para
marcar texto de prueba con marca de agua y volver a detectarlo después de una reescritura de Capa B — por ejemplo, probar que
una marca KGW (Kirchenbauer, tu fila "open-LLM") o SynthID-Text (fila Gemini) desaparece
bajo tu reescritura. Es un arnés de verificación, no un oráculo:
la detección de MarkLLM solo es válida contra la misma configuración de esquema + claves usadas en la
generación, y no puede certificar que un detector de proveedor fallará.
El backend no está incluido. setup_markllm.sh clona upstream en un commit
fijado, crea un venv e instala dependencias fijadas (torch + transformers); el
modelo de puntuación (predeterminado facebook/opt-1.3b, Apache-2.0) se descarga de Hugging
Face en la primera ejecución.
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
Verificación alrededor de una reescritura de Capa B: pasa --markllm-scheme a
rewrite_text.py (con --markllm-dir), y registra la detección de MarkLLM
antes/después más un indicador 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
Reescritura iterativa guiada por detección: la Capa B ahora reescribe iterativamente y
se detiene tan pronto como un intento pasa la evaluación. Cada ronda de evaluación genera
--candidates variantes (predeterminado 1, WATERMARKS_REWRITE_CANDIDATES)
y --max-loops limita cuántas rondas se ejecutan antes de que se devuelva la variante de mejor esfuerzo
(predeterminado 1, WATERMARKS_REWRITE_LOOPS). Cada variante es una
llamada de reescritura más una evaluación, y una ronda sale temprano en el primer intento
que el evaluador reporta como no marcado con agua — así que aumentar --max-loops reintenta
nuevas variantes hasta que una evaluación pase (una reescritura limpia típica cuesta un
intento). El evaluador se elige por prioridad:
- MarkLLM — detección de investigación de misma configuración, cuando
--markllm-schemese pasa (con--markllm-dir). Una ranura de detector de proveedor está reservada por encima de MarkLLM para el detector SynthID-text de Google, que Google retiró en su API en agosto de 2026 — un endpoint futuro de proveedor puede conectarse allí. - Divergencia léxica bigram-Jaccard — cuando no hay detector configurado; sin veredicto de aprobado/fallido, por lo que cada intento se genera y el más divergente léxicamente se selecciona (el comportamiento original).
--json-stats reporta el evaluador, intentos realizados, aprobado/fallido y registros por intento:
{
"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"}
}
Un detector que no está configurado, agota el tiempo o da error produce una
entrada "available": false con una razón error y nunca falla la
reescritura — ese intento simplemente no puede pasar, y el bucle cae en la
selección de divergencia léxica. Cuando el máximo se agota sin un aprobado, el
intento menos marcado (puntuación más baja) se devuelve como mejor esfuerzo con una
nota.
Si el backend no está configurado o faltan sus dependencias, la reescritura procede y el informe nota que la verificación no estaba disponible. Se recomienda una GPU; las ejecuciones en CPU funcionan pero son lentas, y la descarga del modelo es de unos pocos GB.
Perillas de endurecimiento:
--offlineen el adaptador (o cualquier ejecución de MarkLLM) carga el modelo de puntuación desde la caché de Hugging Face solamente — cero salida de red; falla rápido si no está en caché. El código remoto personalizado nunca se ejecuta (transformerstrust_remote_codenunca se habilita).WATERMARKS_MARKLLM_RLIMIT_AS=<bytes>(env, POSIX) aplica un límite de espacio de direcciones al subproceso del detector MarkLLM. Desactivado por defecto porque torch/CUDA generalmente necesita espacios de direcciones grandes.- Los archivos de configuración están limitados a 1 MiB; el checkout upstream y la imagen base están fijados 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
Verificación de misma clave Keyed-Gumbel (Aaronson EXP)
El informe técnico de ARBI describe la
marca de agua de texto keyed-Gumbel ("exponencial") — ahora incluida en el motor
arbi-serve de código abierto (ARBI_WATERMARK_KEY) — donde el ruido del muestreador se deriva
de un hash con clave del contexto de los últimos 4 tokens. La detección es una
repetición sin modelo: recalcula u = PRF(Hash(key, window), token) del
texto solo y prueba la cola Gamma, por lo que no necesita GPU, modelo ni logits.
Este repositorio incluye ese detector como detect_gumbel.py (solo stdlib; el valor p
es la identidad exacta de suma de Poisson para una forma Gamma entera):
# 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
La misma advertencia de honestidad que MarkLLM: esta es una repetición de misma clave — válida solo contra la misma clave, tokenizador y diseño de PRF usados en la generación, y un resultado negativo no establece nada. El diseño HMAC-SHA256 aquí es una instanciación auditable, no bit-compatible con ningún kernel de motor específico (consulta el docstring del módulo para saber qué adaptar para una repetición exacta).
Reescritura guiada por detección: pasa --gumbel-key a rewrite_text.py
(env: WATERMARKS_GUMBEL_KEY, preferido) y el bucle de reescritura iterativa es
impulsado por la repetición Gumbel de misma clave — la prioridad del evaluador se convierte en gumbel >
MarkLLM > divergencia léxica — con un informe 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
La clave nunca aparece en estadísticas o registros. Los operadores autoalojados que tienen la clave de su motor pueden verificar que una reescritura eliminó una marca Gumbel; todos los demás tratan la Capa B como solo de mejor esfuerzo.
Benchmark opcional de eliminación de SynthID-text
bench_synthid_text.py mide cuán
efectivamente una reescritura de Capa B elimina marcas de agua de clase SynthID-text y a
qué costo. Genera muestras marcadas + no marcadas con el esquema SynthID de MarkLLM
(detección de misma configuración, con compuerta de cordura), ejecuta tus variantes de reescritura
(táctica × intentos máximos de reescritura; el bucle se detiene temprano al aprobar) más
controles (sin eliminación, solo Capa A, verificación opcional de re-marcado), y escribe un
report.md / results.json / results.csv compartible. Guía completa:
docs/synthid-text-benchmark.md.
Requiere un checkout de MarkLLM (setup_markllm.sh / MARKLLM_DIR) y un
backend de reescritura. El modelo de reescritura es un LLM que tú configuras — el mismo
backend rewrite_text.py que usa la habilidad. El facebook/opt-1.3b predeterminado de MarkLLM
(--markllm-model) es solo el generador/detector de marca de agua; nunca reescribe. Configura el modelo de reescritura mediante variables
de entorno o indicadores del benchmark (reflejan la
tabla de configuración anterior):
| Variable de entorno | Indicador del benchmark | Predeterminado | Significado |
|---|---|---|---|
WATERMARKS_REWRITE_BACKEND | --rewrite-backend | ollama | ollama o openai-compatible |
WATERMARKS_REWRITE_MODEL | --rewrite-model | (requerido) | El LLM que realiza la reescritura (por ejemplo, llama3.2, deepseek-v4-flash) |
WATERMARKS_REWRITE_BASE_URL | --rewrite-base-url | http://127.0.0.1:11434 | Endpoint; el predeterminado de Ollama es loopback |
WATERMARKS_REWRITE_API_KEY | --rewrite-api-key | — | Clave API (solo env en el proceso hijo, nunca argv) |
WATERMARKS_REWRITE_ALLOW_REMOTE=1 | --rewrite-allow-remote | off | Requerido para enviar contenido a endpoints que no son 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
Usa un modelo que no sea de origen para reescribir (no reescribas con el mismo
modelo marcado que generó el texto) o la reescritura puede re-marcar la
salida; --restamp-control mide esto.
Arnés opcional de marca de agua de imagen MarkDiffusion
Para experimentos controlados en imágenes, un arnés externo opcional envuelve
THU-BPM/MarkDiffusion (Apache-2.0),
un kit de herramientas de marcado de agua generativo para modelos de difusión latente (incrusta marcas
— no las elimina). Lo usamos para tres cosas:
- Arnés de verificación (como MarkLLM, pero para imágenes): marca una imagen de prueba con un esquema, ejecuta la eliminación y vuelve a detectar con la misma configuración de esquema — por ejemplo, probar que una marca de clase Tree-Ring se elimina bajo tu pipeline. Es un arnés de verificación, no un oráculo: la detección requiere el modelo generador (y claves para esquemas basados en claves), por lo que no puede certificar que un detector de proveedor fallará en una imagen arbitraria.
- Motor opcional de eliminación de píxeles: su ataque de regeneración
DiffusionPurificationse expone comoclean_image.py --remove-pixel diffusion, una alternativa a CtrlRegen. Es regeneración ciega (sin condicionamiento ControlNet), por lo que desvía más el contenido de la imagen que CtrlRegen — intensidad conservadora predeterminada (0.3), tratada como respaldo/comparación, nunca una garantía. - Detector local de mismo esquema para marcas de clase Tree-Ring, llenando parcialmente la brecha de "sin detector local para StegaStamp/Tree-Ring/StableSignature" (cubre Tree-Ring/Ring-ID/Gaussian-Shading etc., no StegaStamp / StableSignature / SynthID-media).
El backend no está incluido. setup_markdiffusion.sh crea un venv e
instala markdiffusion==1.0.2 desde PyPI (fijado), con torch instalado desde
el índice de plataforma correcto; --checkout instala un clon editable en un commit
fijado en su lugar. El modelo Stable Diffusion (predeterminado
huanzi05/stable-diffusion-2-1-base) se descarga de Hugging Face en la primera ejecución.
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
O ejecuta la purificación como parte del pipeline de imagen normal:
MARKDIFFUSION_DIR=~/markdiffusion \
~/markdiffusion/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
-o shot.cleaned.png --remove-pixel diffusion
Las perillas de endurecimiento reflejan el arnés MarkLLM: --offline carga el modelo desde
la caché de Hugging Face solamente (cero salida de red, sin código remoto), HF_TOKEN
es solo env (nunca argv), las configuraciones de algoritmo están limitadas a 1 MiB, y el
subproceso obtiene los mismos límites de recursos más altos que 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
La imagen instala un torch para CPU; los usuarios de CUDA deben ejecutar setup_markdiffusion.sh
en el host en su lugar. Las descargas de modelos aún acceden al hub de HF en la primera ejecución.
Matriz de cobertura
| Canal | Claude | Gemini/SynthID | OpenAI | Open-LLM |
|---|---|---|---|---|
| Texto basado en Unicode / edición | Capa A | Capa A | Capa A | Capa A |
| Texto por muestreo estadístico | Capa B mejor esfuerzo (costura de Claude cuando la API de detección de Anthropic esté disponible) | Capa B mejor esfuerzo (+ arnés MarkLLM de misma configuración; Google retiró el detector del proveedor en agosto de 2026) | Capa B si está presente | Capa B mejor esfuerzo + arnés MarkLLM opcional |
| C2PA / metadatos de archivo | Sí (formatos listados) | Sí cuando está presente | Sí cuando está presente | Sí cuando está presente |
| Marcas de píxeles en imágenes | Fuera de alcance | Puntuación SynthID opcional + eliminación CtrlRegen (externo); detección MarkDiffusion de mismo esquema opcional + eliminación DiffusionPurification (externo) | Fuera de alcance | Eliminación CtrlRegen / MarkDiffusion opcional (externo) |
| Puertas traseras de entrenamiento | Fuera de alcance | Fuera de alcance | Fuera de alcance | Fuera de alcance |
Detalles: skills/remove-ai-marks/references/vendor-notes.md, mark-classes.md.
Cómo funciona el marcado de texto (resumen)
Las marcas de agua de los LLM modernos a menudo ocultan una señal en qué tokens se eligen (sesgo generativo / de muestreo), no solo en caracteres invisibles. Los esquemas basados en edición inyectan reglas Unicode o de sinónimos. Los esquemas de archivo adjuntan C2PA o metadatos del generador.
- Capa A elimina portadores Unicode basados en edición (comprobable).
- Capa B ataca marcas de agua de muestreo mediante reescritura intensiva (mejor esfuerzo; ataques estándar de la literatura como paráfrasis / retro-traducción).
- Limpiadores de archivos eliminan C2PA/XMP/propiedades de contenedores compatibles.
Hasta que los proveedores publiquen detectores y claves públicas, ninguna herramienta puede certificar honestamente "esto falla la verificación oficial". Los informes deben separar el trabajo verificable del de mejor esfuerzo.
Prefiera un modelo no originario para la Capa B (no reescriba texto de Claude con Claude si intenta evitar re-marcar).
Descargo de responsabilidad: qué cuesta eliminar una marca de agua de texto
Las marcas de agua de texto viven en la redacción misma: la señal se distribuye entre las elecciones de tokens, por lo que casi cada oración lleva un poco de ella. Dos consecuencias se derivan, y son la razón por la que la Capa B se describe honestamente como mejor esfuerzo en lugar de un borrador mágico.
-
Eliminar significa reformular, no reestructurar. Barajar párrafos, cambiar encabezados o retoques ligeros apenas mueven la señal. Eliminar una marca estadística requiere reescribir una fracción sustancial del texto — oración por oración, no sección por sección.
-
La reformulación degrada el texto. Cualquier reescritura reemplaza las elecciones de palabras originales con las del modelo de reescritura, lo que aplana tono, voz y precisión. En texto de producción (SEO, marketing, trabajo de clientes) esa degradación es real y a menudo visible para quienes más se preocupan por la escritura. Es como tomar texto de un modelo de primer nivel y pedirle a un modelo menos capaz que lo reescriba desde cero: el resultado no puede superar el techo del modelo de reescritura.
Lo que lleva a la pregunta honesta de círculo completo:
Si el plan es reescribir el texto con un modelo más barato de todos modos, ¿por qué pagar por un modelo premium en primer lugar? Generar directamente con el modelo más barato es más simple, más barato y produce el mismo — o mejor — resultado final.
La Capa B tiene sentido cuando específicamente desea el pensamiento y borrador del modelo premium y acepta una pasada de reescritura para cumplir un requisito de higiene o privacidad — no como una ruta barata hacia texto sin marca.
Cuándo omitir la Capa B:
- La calidad importa más que la higiene: use la ruta sin pérdida — limpieza Unicode de Capa A más los limpiadores de metadatos de archivo — y conserve la prosa original.
- Reescribir de todos modos: use un modelo no originario (reescribir con el modelo originario puede re-marcar el texto) y recuerde que el riesgo residual permanece — ninguna herramienta puede certificar que un detector de proveedor fallará.
Formatos de archivo
| Formato | Inspeccionar | Limpiar |
|---|---|---|
| PNG / JPEG / WebP | Fragmentos C2PA / APP11 / RIFF C2PA, pistas XMP de AI | Eliminar segmentos de metadatos |
| AVIF / HEIC | Cajas ISOBMFF jumb / XMP uuid | Eliminar cajas |
| BMP | Bytes no-imagen al final (sin canal estandarizado) | Truncar metadatos finales, corregir campo de tamaño de archivo |
| GIF | Extensiones de aplicación de comentario / XMP | Eliminar comentario y XMP, conservar bucle NETSCAPE2.0 |
| TIFF (clásico + BigTIFF) | Etiquetas IFD: XMP, EXIF, GPS, IPTC, MakerNote | Eliminar etiquetas, poner cargas a cero, conservar tiras |
| SVG | <metadata>, XMP | Eliminar bloques |
| Byte/XMP + herramientas opcionales | exiftool luego qpdf, luego ghostscript para metadatos dentro de imágenes incrustadas; cada herramienta faltante degrada una capa diferente (limpieza de documento, reescritura estructural, imágenes incrustadas) | |
| DOCX | docProps / customXml | Limpiar propiedades, eliminar customXml |
| EPUB | Metadatos OPF, meta XHTML/JSON-LD, medios incrustados | Limpiar OPF, eliminar meta XHTML, limpiar medios + Capa A (omite partes cifradas) |
| ODT | meta.xml | Eliminar generador / meta similar a AI |
| HTML | meta, JSON-LD, data-ai*, comentarios <!-- --> que nombran una herramienta de AI o marcados como generados por AI / C2PA / credencial de contenido | Eliminar etiquetas/atributos/comentarios |
| Markdown | Claves AI en frontmatter YAML, comentarios <!-- --> que nombran una herramienta de AI o marcados como generados por AI / C2PA / credencial de contenido (fuera de bloques de código) | Eliminar claves y comentarios + Capa A en el cuerpo |
| MP4 / MOV / M4A / M4V | Cajas ISOBMFF jumb/uuid (mismo mecanismo que AVIF/HEIC) + etiquetas de generador moov/udta | Eliminar cajas |
| WAV | Fragmentos RIFF C2PA / LIST INFO, fragmento id3\x20 incrustado | Eliminar fragmentos |
| MP3 | Marcos ID3v2 (v2.3/v2.4 por marco; v2.2 etiqueta completa) | Eliminar marcos coincidentes o etiqueta completa |
| FLAC | Manifiesto C2PA en un marco ID3v2 GEOB | Eliminar el marco coincidente o la etiqueta ID3v2 completa |
La compatibilidad con FLAC cubre el portador ID3v2 estandarizado de C2PA. Los bloques de metadatos nativos de FLAC, los comentarios Vorbis y las marcas de agua en el dominio de forma de onda se dejan intactos.
Por qué PDF necesita qpdf, no solo exiftool
ExifTool escribe PDFs de forma incremental. exiftool -all= añade un
bloque %BeginExifToolUpdate que libera el objeto Info y elimina /Info del
trailer — pero los bytes de metadatos originales permanecen en el archivo verbatim, y
exiftool mismo puede deshacer la edición con -PDF-update:all=. El comando sale
0, los visores no muestran metadatos y el archivo se vuelve más grande, lo cual es la pista.
Para una herramienta de eliminación de procedencia, eso es una fuga silenciosa, por lo que clean_pdf sigue
la pasada de exiftool con qpdf --linearize, que re-serializa el documento
desde su grafo de objetos y elimina los objetos ahora sin referencia. Sin qpdf
instalado, la limpieza aún se ejecuta, pero lo indica:
warning: exiftool PDF edits are incremental — the original metadata bytes
remain recoverable; install qpdf for a structural rewrite
Por qué qpdf no es suficiente para imágenes dentro del PDF
Ambas pasadas anteriores trabajan sobre el documento: el diccionario Info, el paquete XMP,
el grafo de objetos. Ninguna desciende a un XObject de imagen, por lo que un escaneo o una
exportación de Photoshop — una página que es un gran JPEG — conserva lo que la imagen
lleva. En un PDF real exportado desde Photoshop, eso deja 27 etiquetas en su lugar después de una
limpieza "exitosa", IFD0:Software, las marcas de tiempo de captura y una miniatura
de vista previa entre ellas; un manifiesto C2PA adjunto a la misma imagen también sobrevive.
Por lo tanto, clean_pdf añade una tercera pasada, deep_images, impulsada por
pdfwrite de Ghostscript. Se ejecuta en dos niveles y se detiene tan pronto como el archivo está limpio:
- Sin pérdida.
pdfwritecon pass-through reconstruye el documento desde el grafo de objetos mientras copia los datos de imagen comprimidos byte por byte — verificado mediante hash de los flujos antes y después. Esto limpia todo lo que el PDF envolvió alrededor de la imagen. Pass-through cubre los códecs que Ghostscript soporta para ello, JPEG (DCTDecode) y JPEG2000 (JPXDecode); las imágenes Flate, CCITT y LZW se decodifican y re-codifican, lo cual es sin pérdida en la práctica para esos códecs pero no idéntico en bytes.neveres la opción para un documento cuyos flujos deben sobrevivir intactos. - Re-codificar, solo con evidencia. Cualquier cosa que viva en los propios segmentos APPn
del JPEG — EXIF en APP1, un manifiesto C2PA en APP11, recursos de Photoshop en
APP13 — viaja con los bytes a los que está adjunta, por lo que pass-through lo
preserva. El nivel 2 ejecuta la misma pasada con pass-through desactivado, y solo cuando el nivel 1
demostrablemente dejó algo atrás: un marcador AI/C2PA en cualquier modo, o, bajo
always, cualquier metadato APPn sobreviviente. APP0 (JFIF) y APP2 (ICC) se dejan solos — el primero es estructural y el segundo decide cómo se leen los colores. Los píxeles se gastan en evidencia, nunca en sospecha.
deep_images toma auto (predeterminado: nivel 1 solo cuando los marcadores sobrevivieron a la
limpieza del documento, luego nivel 2 si sobreviven a eso), always (nivel 1 para cada
PDF, escalando al nivel 2 para EXIF de cámara y editor también), lossless (nivel 1
solo — nunca re-comprimir, e informar lo que sobreviva a través de los campos habituales
still_has_c2pa / post_findings) y never. Un valor no reconocido se
rechaza en lugar de tratarse silenciosamente como auto. El informe indica qué niveles se ejecutaron
a través de meta.deep_image_pass y meta.images_reencoded, y cuando la pasada se
omite, nombra la opción que iría más lejos:
deep image pass not needed for AI/C2PA markers; pass deep_images="always"
to also clear non-AI EXIF inside images
Sin Ghostscript instalado, la limpieza aún se ejecuta e indica lo que no pudo alcanzar:
warning: metadata inside embedded images left in place; install ghostscript
for the deep image pass
La eliminación de marcas de agua en el dominio de píxeles ahora está disponible como un backend externo opcional CtrlRegen (ver arriba); es un removedor regenerador, no una garantía. El enlace suave C2PA (marca de agua en contenido que puede re-vincular un manifiesto remoto de Content Credentials después de que se eliminan los metadatos) permanece fuera de alcance. Eliminar C2PA de enlace duro no limpia esos canales.
Riesgo residual después de una limpieza
Esta herramienta informa eliminaciones verificables (conteos Unicode, acciones de metadatos) y reescrituras de Capa B de mejor esfuerzo. No puede certificar que los detectores de proveedores fallarán.
Para verificar señales residuales usted mismo (opcional, externo):
| Canal | Qué eliminamos | Qué puede permanecer | Verificación externa (ejemplos) |
|---|---|---|---|
| C2PA de enlace duro / EXIF / XMP | Sí | Enlace suave / marcas de píxeles | c2patool, verificación de Content Credentials |
| Medios clase SynthID | Eliminación de píxeles opcional (CtrlRegen externo); puntuación local de otro modo | Marca de agua de audio/video; marca de píxeles residual después de la eliminación | Herramientas del proveedor (p. ej. Google SynthID / detector Vertex donde se ofrezca); puntuador local opcional reverse-SynthID |
| Texto estadístico | Reescritura de mejor esfuerzo | Marcas fuertes después de edición ligera | Sin detector universal público; herramientas del proveedor cuando estén disponibles |
Contexto industrial de dos capas (C2PA + marca de agua imperceptible): guía del Instituto de IA PM.
Detectores de marcas de agua
Verificadores proporcionados por proveedores para comprobar si el contenido lleva marcas de procedencia de IA:
- Claude: Verificar si un archivo fue hecho con Claude — lee credenciales de contenido C2PA en imágenes, video y audio para informar si Claude estuvo involucrado en la producción del archivo; se ejecuta en el navegador. La API de detección de marcas de agua de texto de Claude está actualmente en vista previa privada.
- OpenAI: Verificar contenido generado por OpenAI — suba una imagen o archivo de audio y verifique señales de procedencia de OpenAI (metadatos C2PA y marcas de agua SynthID). También hay disponible una API programática.
- Google DeepMind: SynthID — la tecnología de marcas de agua de Google para imágenes, audio, texto y video generados por IA, con una descripción general de cómo se incrustan y detectan las marcas imperceptibles.
- Gemini: Verificar imágenes, videos y audio generados por IA — la guía de Google para verificar archivos en la aplicación Gemini usando marcas de agua SynthID y Content Credentials, incluidos los límites de carga y cómo leer los resultados.
Opciones de eliminación (resumen)
| Opción | Elimina | Notas |
|---|---|---|
| Limpieza Unicode (Capa A) | ZWSP, bidi, etiquetas, espacios exóticos, … | Valor predeterminado seguro para texto |
| Reescritura (Capa B) | Marcas de token estadísticas (mejor esfuerzo) | Siempre ofrecida por la habilidad; cuesta estilo — ver Descargo de responsabilidad |
| Eliminación de metadatos de contenedor | Procedencia de archivos | Ver tabla de formatos |
| Eliminación de píxeles CtrlRegen (opcional) | Marcas de imagen en dominio de píxeles (clase SynthID, StegaStamp, Tree-Ring, StableSignature) | Backend externo; cómputo pesado; intensidad predeterminada conservadora |
| Eliminación de píxeles DiffusionPurification (opcional) | Marcas de imagen en dominio de píxeles (clase Tree-Ring) | Backend MarkDiffusion; regeneración ciega (más deriva que CtrlRegen); intensidad predeterminada conservadora |
| Modelos locales de peso abierto | Evitar re-marcado con el modelo de origen | Alternativa operativa |
Matriz: skills/remove-ai-marks/references/removal-matrix.md.
Ética y descargo de responsabilidad
Ver skills/remove-ai-marks/references/ethics.md. Para privacidad e investigación sobre tu contenido — no fraude académico ni afirmaciones falsas de "escrito por humanos".
Uso responsable: Este proyecto es para contenido que posees o estás autorizado a procesar. Los usuarios deben cumplir con las regulaciones locales y usarlo de manera responsable. Los desarrolladores rechazan cualquier responsabilidad por posible uso indebido por parte de los usuarios.
Ecosistema
Proyectos de terceros que envuelven o complementan este repositorio, listados solo para descubrimiento. No son mantenidos, respaldados ni soportados por este proyecto. Este proyecto no revisa su código, no garantiza su comportamiento ni sus promesas, y no se hace responsable de nada que instales o ejecutes desde esta lista. Cada proyecto se rige por su propia licencia, mantenedores y documentación — léelos antes de usarlo.
MetaClean — GUI de escritorio
MetaClean es una aplicación de escritorio independiente con licencia MIT, construida con Rust/Tauri (Windows, macOS, Linux), que proporciona una GUI nativa empaquetada para limpieza de metadatos por arrastrar y soltar, con bandeja del sistema e integración con el Explorador. Es un código base separado: no llama al servicio Python de este repositorio, y sus formatos soportados y garantías de limpieza difieren de los de este proyecto. Consulta su README para más detalles.
unmark-web — interfaz web de navegador
unmark-web es un cliente web estático independiente con licencia MIT. Elimina marcas Unicode invisibles del texto y elimina metadatos de procedencia de imágenes completamente en el navegador, y puede opcionalmente llamar al servicio HTTP de este repositorio para los formatos que no maneja localmente. Es un código base separado y no está afiliado a este proyecto; consulta su README para alcance y límites.
DropMarks — GUI de macOS
DropMarks es una aplicación independiente con licencia MIT para macOS construida con SwiftUI. Llama a inspect_file.py / clean_file.py (y opcionalmente rewrite_text.py) de este repositorio a través de una instantánea incluida de esos scripts de stdlib. Es un código base separado y no está afiliado a este proyecto; consulta su README para alcance y límites.
unmark-checker — herramienta de medición
unmark-checker es una herramienta Python independiente con licencia MIT que planta una marca de agua de texto estadística de la clase SynthID-Text publicada con una clave propia y puntúa lo que queda después de una eliminación. Incluye un ejecutor (integrations/watermarks-remover/run.py) que entrega una muestra marcada a este repositorio, ya sea a través de su servicio HTTP /clean o llamando a clean_text.py y rewrite_text.py en un checkout, e informa la puntuación del detector junto con cuánto del significado, los hechos y la longitud sobrevivieron. Qué capas se ejecutaron es parte del resultado: el servicio /clean ejecuta ambas capas, mientras que una ejecución de checkout siempre hace la capa A y solo hace la capa B cuando WATERMARKS_REWRITE_BACKEND está configurado y --layer-a-only está ausente. El ejecutor etiqueta cada ejecución con las capas que midió, por lo que una puntuación solo de capa A nunca se lee como una de pipeline completo. Solo mide; nunca elimina nada. Es un código base separado y no está afiliado a este proyecto; consulta su README para alcance y límites.
Simple Unmark: SaaS que preserva la privacidad
Simple Unmark hace que watermarks-remover esté disponible a través de una aplicación web simple, actualmente solo para texto, sin instalación ni configuración de servidor. El servicio preserva la privacidad por defecto sin retener el contenido enviado. El modo confidencial ejecuta el removedor en un entorno de ejecución confiable (TEE), agregando verificación criptográfica y protección respaldada por hardware contra operadores de infraestructura que accedan al contenido durante el procesamiento. Un proyecto independiente con un núcleo de código abierto. Consulta la arquitectura de privacidad para garantías y alcance.
Agregar un proyecto
Para registrar un proyecto aquí, abre un PR agregando una entrada breve — nombre del proyecto, qué envuelve o agrega, y un enlace a su propio repositorio. Mantén las entradas breves y factuales; no afirmes compatibilidad con, o respaldo de, este proyecto. Un proyecto listado debe construirse sobre o integrarse con este repositorio — por ejemplo, llamando a su servicio o reutilizando su motor de detección — en lugar de simplemente abordar el mismo problema de manera independiente. Evita nombres que comiencen con o se parezcan mucho a watermarks-remover — los nombres similares dificultan distinguir qué proyecto es cuál.
Hook de pre-commit
La compuerta de CI ya existe (exportación SARIF de audit_dir.py, ver contexto de Matriz de cobertura) — los hooks de pre-commit a continuación detectan la misma clase de problema antes, antes de que un archivo marcado siquiera se confirme. Ambos envuelven los CLI existentes (audit_dir.py / clean_file.py) — sin lógica de detección 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 falla la confirmación y lista hallazgos; watermarks-remover-clean es opt-in y reescribe archivos en etapa en su lugar (sale con 1 para que revises el diff y vuelvas a agregar — la misma convención que los hooks de auto-corrección como ruff --fix). Cuando el limpiador no puede procesar un archivo en absoluto — se bloqueó, fue eliminado o no produjo informe — watermarks-remover-clean nombra ese archivo y sale con 3 en su lugar, para que un limpiador que falló nunca se confunda con un archivo ya limpio. Ejecuta cualquiera manualmente con python3 service/scripts/check_staged.py <files...> / clean_staged.py <files...>.
Pruebas
python3 -m venv .venv && .venv/bin/pip install pytest
.venv/bin/python -m pytest # or: make test
make smoke # quick CLI smoke on fixtures
Registro de cambios
v0.7.0 — /clean Reescritura de Capa B, módulo de robo de marca de agua, eliminación de marca de agua de audio/video, y amplitud de benchmark/herramientas
v0.7.0 trae la reescritura de marcas estadísticas de Capa B al servicio /clean en sí, impulsada por una estrategia configurable y ajustada por benchmark (paraphrase@0.8,mlm@0.2). Junto a esto: un módulo de robo de marca de agua de caja negra, eliminación destructiva de marca de agua de audio y video por fotograma, un benchmark de reescritura sustancialmente más rico, y una pila de correcciones de endurecimiento, seguridad y herramientas.
Reescritura de Capa B en el servicio
/cleanejecuta la reescritura de Capa B para texto después de la Capa A. El valor predeterminado proviene deconfig/clean_strategy.json; unoptions.strategypor solicitud lo anula, y/cleanrechaza con 400 cuando el backend requerido no está configurado (#315). Precedencia de configuración:--strategy-config>WATERMARKS_CLEAN_STRATEGY_FILE>config/clean_strategy.json.- Nueva táctica de reescritura
mlm: enmascarar una fracción de palabras de contenido y rellenar conroberta-large— una edición local no autorregresiva, por lo que la salida mezcla el flujo de tokens original con predicciones de LM enmascarado (#311). - La táctica
humanizeahora aplica el paso de habilidad humanizadora de manera determinista (comillas rectas, sin guiones em/en, colapsos de relleno,utilize→use) y nombra las reglas de escritor humano en el prompt (#311).rewrite_text.pyganó una ruta CLI--strategy. - Corrección de reescritura: tokenización de palabras Unicode en divergencia léxica (#305); comparar márgenes brutos antes de redondear y registrar metadatos de selección / valores p clasificados (#249).
Benchmark
- Búsqueda de recetas SynthID + medición robusta (#280); vocabulario de reescritura renombrado, búsqueda entre entradas y ordenamiento humanizar-último (#302); recomendar solo estrategias que aún pasen después del pulido humanizador (#307).
- API de pangramas por lotes como backend de similitud humana (#296); benchmark de nivel de reescritura mínima endurecido con corpus de 30 documentos (#257); cuadrícula de pesos validada + búsqueda de recetas ampliada (#294); corpus de benchmark polaco (#295).
Robo de marca de agua
- Nuevo módulo de robo de marca de agua de caja negra y descargador de corpus de prompts (#303); limpiar estado obsoleto en fallo de sonda de reinicio (#310).
Audio / video / imagen
- Cadena destructiva de eliminación de marca de agua de audio para silentcipher/AudioSeal/WavMark (tempo + tono + EQ + re-codificación de baja tasa de bits → M4A) (#266).
- Purificación de video TrustMark por fotograma que colapsa el voto temporal (#265).
- Caja
uuidde procedencia de contenido C2PA reconocida en MP4/MOV/AVIF/HEIC (#264). - Preservar colas MP4 truncadas durante el despojo (#242); mantener el destino de re-codificación de audio distinto del destino de limpieza de contenedor (#278).
- Omitir salida de exiftool descartada y SynthID redundante en el escaneo posterior a la limpieza (#261); degradar limpiamente cuando exiftool no puede procesar un PDF (#281).
- Limitar
zTXt/iTXtde PNG descomprimido a 1 MiB (#308); eliminar declaraciones XML DOCTYPE/ENTITY de SVG (#288); mantener miembros binarios DOCX seguros en bytes (#314); preservarAppVersionde OOXML (#289).
Servicio HTTP y CLI
- Opción
/cleanpara mantener espacios exóticos, reflejando el CLI (#274);/inspectexpone clases de evidencia explícitas en el payload sospechoso (#277); marcas de tiempo en registros de solicitudes HTTP (#256); pasar bytes de payload a la puntuación SynthID HTTP yinspect_*para evitar una relectura redundante. clean_file.pyganó-q/--quiet/--only-changed(#254).
Habilidades, plugin y hooks
- Puntuación de estilometría y palancas de detector para
clean-user-facing-text(#258); lanzador de hook PostToolUse multiplataforma (#255); hook de pre-commit trata archivos no textuales limpios idénticos en bytes como cambiados (#238).
Auditoría
audit_dir.pyescanea archivos fuente, documentación e i18n que el enrutador pasó por alto (#284); escanea.ts/.tsx/.jsx/.gdy alinea la confianza de espacios entre formatos (#273); soporteaudit_website.py --sarif(#194); endurecer copias de seguridad en el lugar, estado de archivo limpio, veredicto SynthID, ID3v2 truncado y enrutamiento zip (#201).
Seguridad
- Eliminar ReDoS polinomial en escaneos de data-URI y JSON-LD (#306); bloquear redirecciones HTTP en el puntuador SynthID para prevenir SSRF (#252).
CI, herramientas y documentación
- CI falla cuando los requisitos de backend opcionales no pueden resolverse (#301); la imagen Docker informa ffmpeg como utilizable e instala Ghostscript (#272); actualizaciones de dependencias (cython #299, scipy #298, ruff #297, docker/setup-buildx-action #237).
- Documentación: sección Watermark Detectors, referencia de blog ETH SRI "Probing SynthID", política de Ecosistema (eliminar ClaudeWatermarks; requerir que los proyectos listados usen este repositorio) (#292).
v0.6.0 — cobertura de formatos más amplia, endurecimiento de Capa A, distribución de plugin y hooks, y reescritura guiada por detección
Cobertura de formatos y contenedores
- AVIF / HEIC: metadatos nativos de stdlib y eliminación de C2PA (#84, #85)
- BMP / GIF / TIFF: detección, inspección y limpieza de metadatos con stdlib — las extensiones de comentario/XMP de GIF se eliminan mientras se conservan los bucles
NETSCAPE2.0y otros fragmentos de animación; los metadatos IFD de TIFF (XMP/EXIF/GPS/IPTC/MakerNote) se eliminan con cargas útiles puestas a cero y compensaciones de tira conservadas, tanto para TIFF clásico como BigTIFF; los metadatos finales de BMP se truncan y se reescribe el campo de tamaño de archivo (#107) - EPUB: limpieza de contenedor con stdlib — metadatos OPF y meta/JSON-LD de XHTML eliminados, medios raster/SVG incrustados eliminados, Capa A aplicada al texto del cuerpo XHTML, partes de metadatos con marcadores eliminadas y partes cifradas OCF pasadas sin cambios (#107)
- XLSX / PPTX / DOCX (OOXML): limpieza nativa de metadatos de contenedor, texto y medios incrustados con stdlib; siempre vaciar los campos de procedencia
docPropsde DOCX; podar relaciones colgantes después de la eliminación decustomXml; ejecutar Capa A sobre el texto del cuerpo de DOCX/ODT; decodificar entidades XML antes de la limpieza de Capa A (#91, #100, #76, #83, #73, #80, #74, #81, #142) - Contenedores SGML/vectoriales: eliminación de metadatos en tiempo lineal para SVG/ODT (GHSA-7vpp-96qp-j9wh) (#147); inspeccionar y limpiar recursivamente URI de datos raster incrustados en SVG, HTML y Markdown (#87, #88)
- Audio / video: eliminación de metadatos AI/C2PA para MP4/MOV, WAV y MP3 (#139); detección y eliminación de fragmentos C2PA RIFF en WAV; soporte de metadatos C2PA en FLAC; rechazar análisis parcial de marcos ID3v2 (#232); preservar compensaciones de medios MP4 al eliminar metadatos (#183)
- PDF: alcanzar metadatos dentro de imágenes incrustadas y dejar de redimensionar el PDF para eliminar XMP; ejecutar el pase de imagen profunda esté o no instalado exiftool; respetar los bytes de relleno de marcadores JPEG y compartir un único caminante de segmentos
- PNG: detectar nombres de productos de generadores de IA en metadatos de texto PNG; detectar marcadores de IA en texto PNG comprimido (#127); conservar la cola truncada en lugar de descartarla en tiras png/isobmff (#182)
Endurecimiento de Capa A (Unicode invisible)
- Endurecimiento consolidado de Capa A (#133): eliminar puntos de código reservados
Default_Ignorablesin uso legítimo de intercambio (U+2065,U+FFF0–U+FFF8,U+E0000,U+E0080–U+E00FF,U+E01F0–U+E0FFF— reportados comoreserved_ignorable), los 66 no caracteres (U+FDD0–U+FDEFmásU+FFFE/U+FFFFpor plano — reportados comononcharacter), y tres portadores Default_Ignorable de renderizado en blanco que el catch-allCfnunca vio (U+180F,U+3164,U+FFA0). Cada uno tiene la misma preservación en contexto que sus hermanos ya cubiertos, por lo que el texto de sílabas parciales no se corrompe, y cada uno se aplica tanto al motor de servicio como a la copia de habilidad ligera vendida - Dejar de eliminar controles de formato de diseño visible junto a su propia escritura: los controles de cuadratura de jeroglíficos egipcios (
U+13430–U+1343F), los controles de taquigrafía Duployan (U+1BCA0–U+1BCA3) y los controles musicales de barra/ligadura/ligadura de frase/ligadura de expresión (U+1D173–U+1D17A) ahora se conservan cuando están adyacentes a su propia escritura y aún se eliminan (y se marcan) cuando flotan entre texto no relacionado; el modo paranoico--strip-emoji-glueaún los elimina en todas partes - Pulido de emoji / escritura: preservar VS16 después de singletes emoji fuera de los rangos de bloque; preservar uniones de escritura, emoji de bandera y marcas Cf árabes; preservar Unicode multilingüe durante la limpieza de texto (#34)
Reescritura de Capa B y detección de marcas de agua
- Reescritura iterativa de Capa B guiada por detección: cada ronda genera
--candidatesvariantes (predeterminado 1,WATERMARKS_REWRITE_CANDIDATES) y--max-loops(predeterminado 1,WATERMARKS_REWRITE_LOOPS) limita las rondas de evaluación, deteniéndose tan pronto como un intento pase la detección. Prioridad del evaluador: MarkLLM (--markllm-scheme) > divergencia léxica bigram-Jaccard (respaldo).rewrite_text.py --json-statsahora reportaevaluator/max_loops/attempts_made/passedycandidate_scorespor intento (#153) - Verificación de misma clave Keyed-Gumbel (Aaronson EXP): nuevo
detect_gumbel.pysolo stdlib implementa la prueba de reproducción sin modelo (u = PRF(Hash(key, window), token); valor p exacto de cola Gamma; enmascaramiento de ventanas repetidas) sin GPU, modelo ni logits.rewrite_text.py --gumbel-key(envWATERMARKS_GUMBEL_KEY, preferido) lo convierte en el evaluador del bucle iterativo (prioridad: gumbel > markllm > divergencia léxica) y se expone comogumbelen/capabilitiesy/detect. Solo misma clave — no un oráculo de proveedor; la clave nunca se registra (#190) - Benchmarks: benchmark de texto MarkLLM multi-esquema y detección (#188) y un benchmark reproducible de eliminación de texto SynthID (#145); variantes predeterminadas
paraphrase:3; el informe y CSV llevan intentos por documento (columnasmean_attempts/att,attempts/evaluator/passed);--rewrite-loopsrefleja--max-loops - Detección: detección de marcas de agua de texto de proveedor (Gemini SynthID, costura Claude, MarkLLM) más un puntuador de imágenes SynthID secundario (#109); nuevo detector de texto AI estadístico y estilométrico sin LLM para CI y auditorías (#68, #69)
Distribución: plugin, hooks e instalaciones de habilidades
- El repositorio ahora es un plugin de Claude Code y un mercado de plugin único (
.claude-plugin/plugin.json+marketplace.json), por lo que ambas habilidades se instalan con/plugin marketplace add guillaumemeyer/watermarks-removerluego/plugin install watermarks-remover@watermarks-remover, y se actualizan en su lugar.make plugin-validateejecutaclaude plugin validate . --strict;tests/test_plugin_manifest.pyverifica los manifiestos sin la CLI install_skill.pycreció un--target(claude-code,claude-project,cowork,cursor) y un selector--skillque cubre ambas habilidades enviadas, más--list,--linkyCLAUDE_CONFIG_DIR. El objetivocoworkconstruye un paquete de carga reproducible (dist/<skill>.zip, directorio de habilidad de nivel superior único); cada objetivo valida contra las reglas de empaquetado de Agent Skills y el límite de carga de 30 MB. Nuevos objetivosmake:install-claude-code-skill,install-claude-code-text-skill,install-claude-project-skill,package-cowork-skill,package-cowork-text-skill- Auto-limpieza determinista mediante un hook
PostToolUse(hooks/hooks.json+service/scripts/hook_written_file.py): después de que el agente escribe un archivo, el arnés ejecuta el hook coopere o no el modelo.check(predeterminado) reporta marcas al modelo;cleanlas elimina en su lugar y le dice al modelo que el archivo se movió, intercambiando solo en una diferencia real para que los archivos limpios conserven su mtime. El modo proviene de la configuraciónhook_modedel plugin oWATERMARKS_HOOK_MODE; la detección reutilizaaudit_lib.scan_file/is_actionable, por lo que el hook, la puerta de pre-commit y la exportación SARIF de CI coinciden. Un hook aún no puede reescribir el mensaje de chat del asistente — no existe tal punto de hook — por lo que esa ruta sigue siendo de mejor esfuerzo - Integración de hook pre-commit para verificación/limpieza de archivos en etapa (#138); habilidad de texto Cursor ligera (#35); la descripción de
clean-user-facing-textya no nombra a Cursor como el único host
Servicio HTTP
- Endpoints por lotes:
POST /clean/batch,/inspect/batch(#137) yPOST /detect/batch(#151) - Preservar extensiones de formato de imagen en
/cleany usar escrituras seguras enav_meta(#150); usar base64 portátil en el ejemplo curl de/detect(y corregir la portabilidad derealpathen macOS en los bootstraps, #185)
Auditoría / inspección y seguridad
audit_dir.pyganó concurrencia multi-trabajador y exportación SARIF 2.1.0 (#101, #102)- Enrutar formatos binarios de sitios web a sus escáneres reales (#177); rechazar bombas DTD/entidad en el analizador de sitemap (GHSA-pjg6-92pm-mmcf) (#146); un limpiador bloqueado bloquea el commit en lugar de leerse como limpio (#179); un archivo de texto ilegible es un escaneo fallido, no uno limpio (#169)
Correcciones de confiabilidad y corrección
- Una segunda ejecución de
--in-placepreserva el.bakoriginal; conservar evidencia recopilada cuando un miembro zip posterior falla al leer (#175); los contenedores ISOBMFF truncados aún ejecutan el respaldo de escaneo de bytes C2PA (#176); distinguir un limpiador fallido de un archivo ya limpio (#159, #161); tratar una ejecución fallida de c2patool como inconclusa en lugar de "sin C2PA" (#156); validar tipos de opciones limpias (#111); nunca seleccionar automáticamente dispositivo MPS para detección de marcas de agua de texto (#99); portabilidad macOS — stdout--jsonpuro para el puntuador SynthID y sonda BSDrealpath(#70); corregir una rutasubprocess_creationflagsde Windows en_ghostscript_usabley evitar que procesos hijos abran una ventana de consola en Windows - Endurecimiento de comportamiento: preservar modo de conservación de comentarios JPEG benignos; corregir el indicador tragado
bench-synthid-text; simplificar el paso de indicadores para la sonda Ghostscript y noqa innecesario de clean_text (lint)
CI / herramientas / documentación
- Linting y formato Ruff con aplicación en CI (#103); agregar macOS a la matriz de pruebas (#152); agregar configuración CodeRabbit para revisiones de PR automatizadas (#222); CODEOWNERS para CODE_OF_CONDUCT/LICENSE y propietarios de revisión principal; atribuir derechos de autor a Guillaume Meyer y colaboradores (#228)
- Documentación: guía de reescritura que preserva la voz y protección de elecciones de voz/accesibilidad; adiciones al Ecosistema (ClaudeWatermarks, unmark-web) y una nota que desalienta nombres similares; referencia arXiv 2402.14904; guía de inicio automático de Windows mediante Programador de tareas; base64 portátil en ejemplos curl; fijar el motor de texto de la habilidad Cursor vendida a la copia de servicio (#96)
Sin publicar
- Hook de limpieza pre-commit (
watermarks-remover-clean/clean_staged.py): usar resúmenes de contenido (SHA-256) y detección de acción activa para que los archivos limpios en disco se reconozcan sin exigir re-etapado infinito (#173) - Preservación de contenedor OOXML: mantener
<AppVersion>intacto endocProps/app.xmldurante la limpieza de metadatos DOCX, XLSX y PPTX para satisfacer las restricciones de esquema ECMA-376 y evitar errores de "contenido ilegible" de Microsoft Word/Office (#283)
v0.5.0 — distribución de servicio y Docker, API HTTP y arneses de verificación
Distribución de servicio / Docker
- División de skill/servicio: la skill (
skills/remove-ai-marks/) ahora es un cliente remoto sin código sobre HTTP; toda la implementación se movió aservice/scripts/y se ejecuta detrás deserver.py, un punto de entrada HTTP de stdlib (/health,/inspect,/clean,/capabilities) - Servicio HTTP:
service/scripts/server.pyexpone el pipeline de limpieza sobre JSON/base64; el endurecimiento refleja los CLIs (límites de tamaño, guardia binaria, escrituras atómicas, loopback por defecto, autenticación bearer opcionalWATERMARKS_SERVER_API_KEY) - OpenAPI:
GET /openapi.jsonsirve una especificación OpenAPI 3.0.3 generada dinámicamente (construida desde la tabla de rutas + configuración en vivo, por lo que nunca se desvía de los endpoints reales); CI la valida conopenapi-spec-validator - Imagen Docker principal (
service/Dockerfile): servicio de limpieza completo con exiftool / qpdf / c2patool preinstalados; cualquier CLI sigue siendo ejecutable sobrescribiendo el comando - Docker / compose:
compose.yamllevanta toda la infraestructura (coresiempre;markllm/markdiffusiondetrás deprofile: harness;ctrlregen/synthiddetrás deprofile: heavycomo builds solo locales); los servicios tienen el prefijowr-; los servicios harness/pesados usancommand: ["--help"]por defecto para quedocker compose up --profile harness --profile heavysalga limpiamente (los CLIs de un solo uso se ejecutan condocker compose run); el nuevomake compose-check/compose-check.shvalida el stack en ejecución (solo código de salida) - Publicación GHCR:
.github/workflows/release-images.ymlpublica las imágenescore,markllm,markdiffusionen tagsv*;ctrlregen/synthidnunca se publican (licencias upstream) - Configuración de entorno:
.env.example+ guía de configuración de servicios;docker composecarga automáticamente.env;.envestá en gitignore (denegación por defecto) - Higiene del repositorio:
.gitignoreyservice/.dockerignoreahora son de denegación por defecto — solo las rutas explícitamente permitidas pueden ser commiteadas o enviadas en un contexto de build (los contextos de imagen solo envíanservice/scripts/, que es todo lo que los Dockerfiles COPY) - Pruebas:
tests/test_http_server.py(13 casos) para el servicio HTTP; todas las suites re-apuntadas aservice/scripts/
Harness de marca de agua de imágenes MarkDiffusion (opcional)
- Nuevo harness opcional (
THU-BPM/MarkDiffusionexterno, Apache-2.0):markdiffusion_harness.pycon subcomandoswatermark/detect/purifypara nueve esquemas de imagen (Tree-Ring, Ring-ID, ROBIN, WIND, SFW, Gaussian-Shading, GaussMarker, PRC, SEAL) clean_image.py --remove-pixel diffusionejecuta el ataque de regeneraciónDiffusionPurificationde MarkDiffusion como motor alternativo de eliminación de píxeles (intensidad conservadora 0.3 por defecto)- Bootstrap
setup_markdiffusion.sh(pin de PyPI1.0.2; clon editable--checkouten commit fijado) +requirements-markdiffusion.txt+Dockerfile.markdiffusiony objetivos de Makefilebootstrap-markdiffusion/smoke-markdiffusion/docker-markdiffusion-build/docker-markdiffusion-help - Pruebas basadas en mocks (
tests/test_markdiffusion_harness.py) — sin torch en CI; documento de referenciareferences/markdiffusion.md - Documentación: advertencia de verificación solo del mismo esquema (no es un oráculo de detección de proveedores) y advertencia de deriva de regeneración ciega en README, SKILL.md,
removal-matrix.md,markdiffusion.md
Harness de marca de agua de texto MarkLLM (opcional)
- Nuevo harness opcional (checkout
THU-BPM/MarkLLMexterno, Apache-2.0):detect_text_watermark.pycon subcomandosdetect/watermarkpara los esquemas KGW y SynthID rewrite_text.py --markllm-schemeejecuta detección antes/después alrededor de una reescritura de Layer B y detección por candidato cuando--candidates N>1(controlado por entorno; reportacleared)- Bootstrap
setup_markllm.sh+requirements-markllm.txt(dependencias fijadas) +Dockerfile.markllmy objetivos de Makefilebootstrap-markllm/smoke-markllm/docker-markllm-build/docker-markllm-help - Endurecimiento: carga de modelo solo caché
--offline(sin egress de HF, sin código remoto), límite de configuración de 1 MiB,WATERMARKS_MARKLLM_RLIMIT_ASopcional en el subproceso de reescritura, torch fijado en el Dockerfile y verificación de SHA del clon enDockerfile.markllm - Pruebas basadas en mocks (
tests/test_markllm_detect.py, 21 casos) — sin torch en CI; advertencia de harness de verificación (solo misma configuración, no un oráculo de detección de proveedores) documentada en README, SKILL.md,removal-matrix.md,vendor-notes.md
Correcciones y pulido
- Layer B:
rewrite_text.pyahora envíareasoning_effort: "none"por defecto para backendsopenai-compatible(--reasoning-effort/WATERMARKS_REWRITE_REASONING_EFFORT;offlo omite). Los modelos de razonamiento comodeepseek-v4-flashde otro modo queman ~100s de cadena de pensamiento en una reescritura de una línea (9,894 vs 12 tokens de finalización) - Corrección del build de imagen markllm:
requirements-markllm.txtfijótokenizers==0.23.1, que entra en conflicto contransformers==5.15.0(limitatokenizers<=0.23.0; no existe la versión 0.23.0) — ahora fijadotokenizers==0.22.2; torch se movió al índice de wheels de CPU (torch==2.13.0.*) para que la imagen sea solo CPU comoDockerfile.markdiffusion - Corrección del build de imagen ctrlregen: los pines de investigación de la era 2023 (
safetensors==0.4.3,transformers==4.37.2→tokenizers<0.19) no incluyen wheels de Python 3.14, por lo que la imagen base ahora espython:3.11-slim(pin de digest, multi-arquitectura) - Corrección de imágenes harness en tiempo de ejecución:
Dockerfile.markllmyDockerfile.markdiffusionnunca copiaroncommon.pyen/app(bug preexistente) — añadido - WebP: inspección y limpieza de metadatos solo stdlib para chunks RIFF
C2PA, XMP, EXIF y perfil ICC (#37) - BMP / GIF / TIFF: detección, inspección y limpieza de metadatos solo stdlib — las extensiones de comentario/XMP de GIF se eliminan mientras se preserva el bucle
NETSCAPE2.0; los metadatos IFD de TIFF (XMP/EXIF/GPS/IPTC/MakerNote) se eliminan con payloads puestos a cero y offsets de tira mantenidos, tanto para TIFF clásico como BigTIFF; los metadatos finales de BMP se truncan con el campo de tamaño de archivo reescrito - EPUB: limpieza de contenedor solo stdlib — metadatos OPF y meta/JSON-LD de XHTML limpiados, medios raster/SVG incrustados eliminados, Layer A aplicada al texto del cuerpo XHTML, partes de metadatos con marcadores eliminadas y partes cifradas OCF pasadas sin tocar
- Saneamiento de nombres de archivo: el servicio HTTP rechaza nombres de salida no seguros proporcionados por el cliente
- Corrección del limpiador de frontmatter de Markdown que fallaba y filtraba claves AI anidadas (#25)
- Las herramientas de texto rechazan entrada binaria;
--force-textlo sobrescribe (#24) --jsonya no suprime el código de salida de señal residual (#30)inspect_fileimprime el nombre del archivo en su salida (#50)- Preservar etiquetas meta de generador CMS con mayúsculas y minúsculas mixtas (#42)
- Preservar invisibles de script críticos, eliminar PUA en Layer A (#38, #52)
- Preservar joiners de script, emoji de bandera y marcas Cf árabes en Layer A (#28)
- Endurecer la auditoría de sitios web contra SSRF y bombas gzip (#49)
- SECURITY.md solo referencia el canal de avisos privados (#51)
- Windows: ports de PowerShell de los bootstraps de configuración (#40)
- Documentación: añadir shields de estrellas/forks y eliminar el gráfico de historial de estrellas; añadir MarkLLM a las referencias del README; plantilla de pull request; plan para CLI de Docker + despliegue de API
v0.4.0 — eliminación de píxeles, confianza de hallazgos, Windows y correcciones de falsos positivos
Eliminación de píxeles CtrlRegen opcional (backend externo)
- Eliminación opcional de marcas de agua en dominio de píxeles mediante un checkout
mertizci/noai-watermarkexterno: adaptadorclean_ctrlregen.py+ bootstrapsetup_ctrlregen.sh(commit fijado, checkout disperso, venv, verificación SHA), además deDockerfile.ctrlregenymake bootstrap-ctrlregen/docker-ctrlregen-build/smoke-ctrlregen clean_image.py --remove-pixel ctrlregenejecuta limpieza de metadatos → eliminación CtrlRegen → puntuación opcional antes/después de SynthID inverso;inspect_image.pysugiere la bandera en una puntuación SynthID alta- Intensidad conservadora por defecto
0.25(preajustes 0.15/0.25/0.35/0.5/0.7); el pipeline nativo de 512×512 se divide automáticamente en mosaicos por el backend para imágenes más grandes; el subproceso de torch obtiene límites de recursos más altos sobrescribibles por entorno - El backend nunca se incluye:
noai-watermarkno incluye archivo LICENSE (tratado como todos los derechos reservados), y sus rutas de código de autoinstalación/reinicio se omiten usandoCtrlRegenEnginedirectamente
Confianza de hallazgos y auditorías agregadas
- Los hallazgos ahora se clasifican
confirmed/probable/informational/likely_false_positive, expuestos en JSON de texto/imagen/contenedor e informes humanos - Nuevos informes agregados
audit_dir.py(árbol recursivo) yaudit_website.py(descubrimiento de sitemap + rastreo); documentados en SKILL.md
Correcciones de falsos positivos
- DOCX: escanear solo
docProps/customXml, no el cuerpo visible (#14) - Texto Layer A: preservar emoji
VS16/ZWJdespués de una base de emoji; nueva bandera paranoica--strip-emoji-glue(#22) - HTML: tratar las etiquetas de generador CMS como informativas, no metadatos AI (#13)
- PDF: excluir payloads de flujo del escaneo de bytes de marcadores AI (#13)
- Los informes de inspección notan rutas no soportadas/de mejor esfuerzo
Soporte de Windows
- Limitar
preexec_fnyos.fchmodsolo POSIX para que las escrituras y herramientas opcionales se ejecuten en Windows (#15, #23) - Reconfigurar stdio a UTF-8 para que los flujos redirigidos de Windows ya no fallen en Unicode invisible; tramo de CI de Windows + prueba de humo de CLI (#23)
Documentación y cadena de suministro
- Sección CtrlRegen del README + referencias de investigación (CtrlRegen, UnMarker, advertencia de sigilo forense), descargo de responsabilidad de uso responsable; actualizaciones de SKILL/matriz/notas de proveedores/ética
- Config de Dependabot + CODEOWNERS de ruta de seguridad; actualización de scipy/numpy/opencv-python/scikit-learn/pywavelets y la imagen base a Python 3.14-slim
- Pruebas CtrlRegen basadas en mocks (sin torch en CI)
v0.3.2 — endurecimiento de seguridad (escrituras seguras, cliente HTTP, cadena de suministro de CI)
- Escrituras de salida seguras y atómicas: cada limpiador ahora escribe mediante archivo temporal + renombrado atómico (
safe_write_bytes/safe_write_text), rechaza destinos con symlinks y crea copias de seguridad.baka través de la misma ruta segura — los symlinks precolocados (por ejemplo, en/tmpo directorios de descarga) ya no pueden redirigir una escritura limpia a un archivo arbitrario - Endurecimiento del cliente HTTP
rewrite_text.py: las redirecciones se rechazan por completo, por lo que una clave API en el encabezadoAuthorizationnunca puede reenviarse a un host no validado; los endpoints que no son de loopback están denegados por defecto (optar con--allow-remoteoWATERMARKS_REWRITE_ALLOW_REMOTE=1); solo se aceptan esquemas http(s);--api-keyse eliminó — las claves son solo de entorno medianteWATERMARKS_REWRITE_API_KEY - Límites de recursos: entrada máxima por defecto 1 GiB → 256 MiB, nuevo límite de stdin de 64 MiB, presupuesto zip DOCX/ODT 512 MiB → 128 MiB, y
RLIMIT_AS/RLIMIT_FSIZEaplicados a subprocesos de exiftool/c2patool/SynthID (todos los límites sobrescribibles por entorno) - Cadena de suministro: acciones de CI fijadas por SHA con
permissions: contents: read, dependencias de desarrollo fijadas (requirements-dev.txt), un pasopip-audity un nuevo flujo de trabajo CodeQL; la imagen Docker ahora se ejecuta como usuario sin privilegios con pip fijado - Dependencias del scorer: Pillow actualizado 10.4.0 → 12.3.0 (24 CVEs conocidos); uso de API verificado contra el commit upstream fijado
- Pruebas: 18 nuevas pruebas de regresión de seguridad (60 en total, todas pasando)
v0.3.1 — reescritura más fuerte de marca de agua estadística de Layer B
rewrite_text.pyla paráfrasis predeterminada ahora realiza un ataque explícito de elección de palabras + sintaxis (orden de cláusulas, conectores, palabras de transición, límites de oraciones, palabras funcionales) en lugar de una reescritura genérica- Nueva
--tactic humanize: pasada de cero disparos "escribir como humano" dirigida a frases formuladas de estilo típico de IA - Nueva
--tactic code: reescribe comentarios, docstrings y literales de cadena, y renombra identificadores locales preservando el comportamiento y los nombres de API pública - La pasada estructural ahora emite "prosa humana natural y variada" en lugar del típico "estilo profesional claro" de IA
- Nueva
--temperature(predeterminada0.9) para backends compatibles con Ollama y OpenAI - Nueva
--candidates N: genera N reescrituras y selecciona la más divergente léxicamente (distancia de Jaccard de bigramas) con un guardián de deriva de longitud - Higiene de modelos más estricta: preferir modelos locales de peso abierto y evitar cualquier proveedor con marca de agua conocida, no solo el origen sospechoso
- El informe de riesgo residual ahora distingue texto corto/altamente predecible (menor riesgo) de prosa larga y de alta entropía (mayor riesgo)
- Documentación actualizada en
SKILL.md,removal-matrix.mdyvendor-notes.md; las pruebas cubren nuevos prompts, puntuación de divergencia y selección de candidatos
v0.3.0 — puntuación opcional de píxeles SynthID
- Puntuador opcional de SynthID en dominio de píxeles mediante un checkout externo de
aloshdenny/reverse-SynthID(score_synthid.py); se muestra eninspect_image.py/clean_image.pyconREVERSE_SYNTHID_DIRo--synthid-dir - Bootstrap de
setup_synthid.sh(dependencias solo de puntuador;--fullinstala requisitos ascendentes);Dockerfile.synthidmásmake docker-synthid-build/docker-synthid-help - Objetivos de Makefile
smoke-synthidybootstrap-synthid - Pruebas para el adaptador de puntuador, ruta no disponible de CLI, análisis JSON y errores en tiempo de ejecución
- Documentación: detección/puntuación solamente (sin eliminación de píxeles); el código ascendente no se incluye y permanece bajo su Licencia de Investigación no comercial
v0.2.0 — corrección de falso positivo de c2patool
image_meta.py:has_manifestya no marcaError: No claim found/No JUMBF data foundcomo manifiesto (error de precedencia de operadores: los marcadores negativos ahora vetaron cada rama positiva)- Nueva
tests/test_c2patool_report.py(4 casos: sin reclamo, sin JUMBF, manifiesto genuino, herramienta ausente) - Documentación: corregidos enlaces de
c2patool(repositorio movido acontentauth/c2pa-rs); añadido un descargo sobre el costo de calidad de la eliminación de marcas de agua de texto
v0.1.0 — pulido de empaquetado + honestidad de procedencia
Makefile(test/smoke/install-skill) ypytest.ini- Muestras de fixture para Markdown, HTML, SVG; prueba de limpieza degradada de PDF
- Documentación: modelo industrial de dos capas (C2PA de enlace duro vs enlace suave / SynthID-media)
- Tabla de riesgo residual en README + enlaces a herramientas de verificación externas
- Referencia: guía de C2PA/SynthID del Instituto de IA PM
- Enlace suave y marcas de agua de píxeles/audio/video explícitamente fuera de alcance en habilidad/matriz/ética
v0.0.1 — lanzamiento inicial multi-proveedor
- Habilidad de agente
remove-ai-marks(reemplaza aremove-claude-markssolo de Claude) - Capa A: Unicode invisible / bidi / caracteres de etiqueta / homoglifos de espacio (
inspect_text/clean_text) - Capa B: guía de reescritura +
rewrite_text.pyopcional (print-prompt, Ollama, compatible con OpenAI) - Archivos: eliminación de metadatos C2PA/AI para PNG, JPEG, SVG, PDF, DOCX, ODT, HTML, Markdown
inspect_file.py/clean_file.pyunificados- Documentación multi-proveedor (Claude, Gemini/SynthID-clase, OpenAI, LLM abierto)
- Scripts primero en stdlib;
c2patool/exiftoolopcionales
Licencia
MIT — ver LICENSE.
Bibliografía
- Cómo Claude marca contenido generado por IA (Anthropic)
- Dathathri et al., Marcado de agua escalable para identificar salidas de modelos de lenguaje grandes (SynthID-Text, Nature 2024)
- Google AI para Desarrolladores, Salvaguardas de SynthID (documentación de API de Gemini)
- C2PA / c2patool
- Kirchenbauer et al., Una marca de agua para modelos de lenguaje grandes
- Evseev, D. (Arbitration City), Marcado de agua de texto de IA preciso, sin costo e invisible para inferencia de IA autoalojada (informe técnico, agosto de 2026) — marcado de agua Gumbel con clave implementado en el motor de código abierto arbi-serve, con detección de prueba exacta y soporte de decodificación especulativa — PDF
- THU-BPM/MarkLLM (kit de herramientas unificado para evaluar algoritmos de marcado de agua de LLM)
- Pan et al., MarkDiffusion: Un kit de herramientas de código abierto para marcado de agua generativo de modelos de difusión latente (JMLR) — el kit de herramientas de incrustación que el arnés opcional de marcas de agua de imágenes de este repositorio envuelve — código, documentación
- Zhang et al., Marcas de agua en la arena: Imposibilidad de marcado de agua fuerte para modelos generativos (ICML 2024)
- Sander et al., El marcado de agua hace que los modelos de lenguaje sean radiactivos — las marcas de agua sobreviven al ajuste fino y marcan modelos descendentes entrenados con datos marcados
- Pan et al., ¿Pueden las marcas de agua de LLM prevenir robustamente la destilación de conocimiento no autorizada? — procedencia basada en marcas de agua y protección contra la destilación de conocimiento
- google-deepmind/synthid-text (referencia de investigación; no se usa para detección aquí)
- aloshdenny/reverse-SynthID (referencia de investigación)
- ETH Zurich SRI, Probando SynthID (blog de investigación sobre la detectabilidad de marcas de agua SynthID)
- Liu et al., Las marcas de agua de imágenes son removibles usando regeneración controlable desde ruido limpio (ICLR 2025) — el método de regeneración de píxeles que implementa el backend opcional CtrlRegen — código
- Kassis & Hengartner, UnMarker: Un ataque universal a marcas de agua defensivas de imágenes (arXiv:2405.08363; IEEE S&P 2025) — un ataque de marca de agua universal comparado en una métrica diferente a CtrlRegen
- Goonatilake & Ateniese, Eliminar la marca de agua no es suficiente: sigilo forense en la eliminación de marcas de agua de IA generativa (arXiv:2605.09203) — motiva el valor predeterminado de intensidad conservadora: la eliminación aún puede dejar rastros forenses
- mertizci/noai-watermark (kit de herramientas CLI/Python para eliminación de SynthID/StableSignature/TreeRing y eliminación de metadatos de IA)
- 0xROOTPLS/DeSynth (eliminación de SynthID para imágenes de OpenAI/Google)
- Instituto de IA PM, Procedencia y marcado de agua de contenido de IA: La guía del PM para C2PA y SynthID (modelo industrial de dos capas: C2PA + marca de agua imperceptible / enlace suave; contexto de SB 942 / Art. 50 de la Ley de IA de la UE)