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

CI Release Stars Forks

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.

CapaObjetivoCómo
AUnicode invisible, espacios exóticos, bidi, caracteres de etiquetaScripts Python deterministas
BMarcas de agua de texto estadísticas (muestreo de tokens)Reescritura del agente + hook opcional rewrite_text.py
ArchivosC2PA / EXIF / XMP / propiedades de documentosPNG, JPEG, WebP, AVIF, HEIC, BMP, GIF, TIFF, SVG, PDF, DOCX, XLSX, PPTX, EPUB, ODT, HTML, Markdown, MP4/MOV/M4A/M4V, WAV, MP3, FLAC

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
HostDestinoAterriza en
Claude Code (personal)--target claude-code~/.claude/skills/<skill> (respeta CLAUDE_CONFIG_DIR)
Claude Code (proyecto)--target claude-project --project-dir PATHPATH/.claude/skills/<skill>
Cowork, claude.ai, sesiones en la nube, rutinas--target coworkdist/<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:

ModoComportamiento
check (predeterminado)Informa marcas de procedencia, deja el archivo intacto. Los hallazgos van al modelo (salida 2), para que pueda ofrecer limpiarlos.
cleanElimina 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):

HerramientaRol
c2patoolInspeccionar manifiestos C2PA
exiftoolEliminación de metadatos residuales (especialmente PDF)
qpdfReconstrucció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étodoRutaCuerpoDevuelve
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 /detect ejecuta los detectores de marcas de agua configurados en un archivo. Texto → detectores de proveedor + estilometría; imagen → puntuación de píxeles SynthID.
  • /inspect acepta una marca "detect": true de opt-in que agrega resultados del detector al informe de texto (y puede cambiar suspicious).
  • /clean acepta opciones "detect_before" / "detect_after" para puntuar la entrada y la salida limpiada, de modo que pueda medir qué cambió realmente una limpieza.
  • /clean ejecuta 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 ordenada tactic@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, /clean devuelve un 400.

Detectores de texto (ver /capabilities → text_detectors):

Detectores de texto (ver /capabilities → text_detectors):

DetectorActivado porNotas
markllmMARKLLM_DIR (checkout del host)Harness de investigación (esquemas KGW / SynthID), solo misma configuración — no es un oráculo de proveedor.
gumbelWATERMARKS_GUMBEL_KEYReproducció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 imagenContenido¿Publicada?
ghcr.io/guillaumemeyer/watermarks-remover:<tag> / :latestServicio HTTP principal + todos los limpiadores + exiftool / qpdf / c2patoolSí
…:markllm-<tag> / :markllm-latestHarness de marcas de agua de texto MarkLLM (Apache-2.0 upstream)Sí
…:markdiffusion-<tag> / :markdiffusion-latestHarness de imágenes MarkDiffusion (Apache-2.0 upstream)Sí
watermarks-remover-ctrlregen:localEliminación de píxeles CtrlRegen — nunca publicada (noai-watermark no incluye LICENCIA)Solo compilación local
watermarks-remover-synthid-scorer:localPuntuador 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
VarAlcanzaPropósito
WATERMARKS_SERVER_API_KEYwr-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_URLwr-coreApuntar 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_KEYwr-core + wr-synthid-scoreClave bearer compartida para el sidecar puntuador (vacío = sin autenticación)
WATERMARKS_SYNTHID_TEXT_URLwr-coreApuntar 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_KEYwr-core + wr-synthid-textClave bearer compartida para el sidecar de marcado de agua de texto (vacío = sin autenticación)
WATERMARKS_SYNTHID_TEXT_TIMEOUTwr-coreSegundos de espera para el sidecar wr-synthid-text (120 por defecto)
WATERMARKS_MARKLLM_SCHEMEtext_detectors.py (host)Esquema MarkLLM para /detect: kgw (predeterminado) / synthid
HF_TOKENservicios harness/pesadosToken de Hugging Face para modelos restringidos
WATERMARKS_SERVICE_URLsolo cliente (habilidad / curl)Dónde alcanzar el servicio; predeterminado http://127.0.0.1:8765
WATERMARKS_REWRITE_BACKENDhook rewrite_text.pyprint-prompt (predeterminado) / ollama / openai-compatible
WATERMARKS_REWRITE_MODELhook rewrite_text.pyNombre del modelo (p. ej. deepseek-v4-flash)
WATERMARKS_REWRITE_BASE_URLhook rewrite_text.pyBase de API (p. ej. https://api.deepseek.com)
WATERMARKS_REWRITE_API_KEYhook rewrite_text.pyClave de API — solo entorno, nunca en argv
WATERMARKS_REWRITE_ALLOW_REMOTEhook rewrite_text.py1 para permitir endpoints que no sean loopback
WATERMARKS_REWRITE_REASONING_EFFORThook rewrite_text.pynone (predeterminado) / low / medium / high / off
WATERMARKS_CLEAN_STRATEGY_FILEserver.py /cleanRuta al JSON de configuración de estrategia de la Capa B (predeterminado config/clean_strategy.json)
WATERMARKS_GUMBEL_KEYdetect_gumbel.py / text_detectors.pyClave 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:

  1. MarkLLM — detección de investigación de misma configuración, cuando --markllm-scheme se 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í.
  2. 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:

  • --offline en 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 (transformers trust_remote_code nunca 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 entornoIndicador del benchmarkPredeterminadoSignificado
WATERMARKS_REWRITE_BACKEND--rewrite-backendollamaollama 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-urlhttp://127.0.0.1:11434Endpoint; 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-remoteoffRequerido 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:

  1. 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.
  2. Motor opcional de eliminación de píxeles: su ataque de regeneración DiffusionPurification se expone como clean_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.
  3. 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

CanalClaudeGemini/SynthIDOpenAIOpen-LLM
Texto basado en Unicode / ediciónCapa ACapa ACapa ACapa A
Texto por muestreo estadísticoCapa 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á presenteCapa B mejor esfuerzo + arnés MarkLLM opcional
C2PA / metadatos de archivoSí (formatos listados)Sí cuando está presenteSí cuando está presenteSí cuando está presente
Marcas de píxeles en imágenesFuera de alcancePuntuación SynthID opcional + eliminación CtrlRegen (externo); detección MarkDiffusion de mismo esquema opcional + eliminación DiffusionPurification (externo)Fuera de alcanceEliminación CtrlRegen / MarkDiffusion opcional (externo)
Puertas traseras de entrenamientoFuera de alcanceFuera de alcanceFuera de alcanceFuera 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.

  1. 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.

  2. 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

FormatoInspeccionarLimpiar
PNG / JPEG / WebPFragmentos C2PA / APP11 / RIFF C2PA, pistas XMP de AIEliminar segmentos de metadatos
AVIF / HEICCajas ISOBMFF jumb / XMP uuidEliminar cajas
BMPBytes no-imagen al final (sin canal estandarizado)Truncar metadatos finales, corregir campo de tamaño de archivo
GIFExtensiones de aplicación de comentario / XMPEliminar comentario y XMP, conservar bucle NETSCAPE2.0
TIFF (clásico + BigTIFF)Etiquetas IFD: XMP, EXIF, GPS, IPTC, MakerNoteEliminar etiquetas, poner cargas a cero, conservar tiras
SVG<metadata>, XMPEliminar bloques
PDFByte/XMP + herramientas opcionalesexiftool 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)
DOCXdocProps / customXmlLimpiar propiedades, eliminar customXml
EPUBMetadatos OPF, meta XHTML/JSON-LD, medios incrustadosLimpiar OPF, eliminar meta XHTML, limpiar medios + Capa A (omite partes cifradas)
ODTmeta.xmlEliminar generador / meta similar a AI
HTMLmeta, JSON-LD, data-ai*, comentarios <!-- --> que nombran una herramienta de AI o marcados como generados por AI / C2PA / credencial de contenidoEliminar etiquetas/atributos/comentarios
MarkdownClaves 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 / M4VCajas ISOBMFF jumb/uuid (mismo mecanismo que AVIF/HEIC) + etiquetas de generador moov/udtaEliminar cajas
WAVFragmentos RIFF C2PA / LIST INFO, fragmento id3\x20 incrustadoEliminar fragmentos
MP3Marcos ID3v2 (v2.3/v2.4 por marco; v2.2 etiqueta completa)Eliminar marcos coincidentes o etiqueta completa
FLACManifiesto C2PA en un marco ID3v2 GEOBEliminar 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:

  1. Sin pérdida. pdfwrite con 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. never es la opción para un documento cuyos flujos deben sobrevivir intactos.
  2. 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):

CanalQué eliminamosQué puede permanecerVerificación externa (ejemplos)
C2PA de enlace duro / EXIF / XMPSíEnlace suave / marcas de píxelesc2patool, verificación de Content Credentials
Medios clase SynthIDEliminación de píxeles opcional (CtrlRegen externo); puntuación local de otro modoMarca de agua de audio/video; marca de píxeles residual después de la eliminaciónHerramientas del proveedor (p. ej. Google SynthID / detector Vertex donde se ofrezca); puntuador local opcional reverse-SynthID
Texto estadísticoReescritura de mejor esfuerzoMarcas fuertes después de edición ligeraSin 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ónEliminaNotas
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 contenedorProcedencia de archivosVer 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 abiertoEvitar re-marcado con el modelo de origenAlternativa 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

  • /clean ejecuta la reescritura de Capa B para texto después de la Capa A. El valor predeterminado proviene de config/clean_strategy.json; un options.strategy por solicitud lo anula, y /clean rechaza 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 con roberta-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 humanize ahora 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.py ganó 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 uuid de 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/iTXt de PNG descomprimido a 1 MiB (#308); eliminar declaraciones XML DOCTYPE/ENTITY de SVG (#288); mantener miembros binarios DOCX seguros en bytes (#314); preservar AppVersion de OOXML (#289).

Servicio HTTP y CLI

  • Opción /clean para mantener espacios exóticos, reflejando el CLI (#274); /inspect expone 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 y inspect_* para evitar una relectura redundante.
  • clean_file.py ganó -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.py escanea archivos fuente, documentación e i18n que el enrutador pasó por alto (#284); escanea .ts/.tsx/.jsx/.gd y alinea la confianza de espacios entre formatos (#273); soporte audit_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.0 y 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 docProps de DOCX; podar relaciones colgantes después de la eliminación de customXml; 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_Ignorable sin uso legítimo de intercambio (U+2065, U+FFF0–U+FFF8, U+E0000, U+E0080–U+E00FF, U+E01F0–U+E0FFF — reportados como reserved_ignorable), los 66 no caracteres (U+FDD0–U+FDEF más U+FFFE/U+FFFF por plano — reportados como noncharacter), y tres portadores Default_Ignorable de renderizado en blanco que el catch-all Cf nunca 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-glue aú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 --candidates variantes (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-stats ahora reporta evaluator / max_loops / attempts_made / passed y candidate_scores por intento (#153)
  • Verificación de misma clave Keyed-Gumbel (Aaronson EXP): nuevo detect_gumbel.py solo 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 (env WATERMARKS_GUMBEL_KEY, preferido) lo convierte en el evaluador del bucle iterativo (prioridad: gumbel > markllm > divergencia léxica) y se expone como gumbel en /capabilities y /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 (columnas mean_attempts / att, attempts / evaluator / passed); --rewrite-loops refleja --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-remover luego /plugin install watermarks-remover@watermarks-remover, y se actualizan en su lugar. make plugin-validate ejecuta claude plugin validate . --strict; tests/test_plugin_manifest.py verifica los manifiestos sin la CLI
  • install_skill.py creció un --target (claude-code, claude-project, cowork, cursor) y un selector --skill que cubre ambas habilidades enviadas, más --list, --link y CLAUDE_CONFIG_DIR. El objetivo cowork construye 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 objetivos make: 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; clean las 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ón hook_mode del plugin o WATERMARKS_HOOK_MODE; la detección reutiliza audit_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-text ya no nombra a Cursor como el único host

Servicio HTTP

  • Endpoints por lotes: POST /clean/batch, /inspect/batch (#137) y POST /detect/batch (#151)
  • Preservar extensiones de formato de imagen en /clean y usar escrituras seguras en av_meta (#150); usar base64 portátil en el ejemplo curl de /detect (y corregir la portabilidad de realpath en macOS en los bootstraps, #185)

Auditoría / inspección y seguridad

  • audit_dir.py ganó 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-place preserva el .bak original; 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 --json puro para el puntuador SynthID y sonda BSD realpath (#70); corregir una ruta subprocess_creationflags de Windows en _ghostscript_usable y 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 en docProps/app.xml durante 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ó a service/scripts/ y se ejecuta detrás de server.py, un punto de entrada HTTP de stdlib (/health, /inspect, /clean, /capabilities)
  • Servicio HTTP: service/scripts/server.py expone 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 opcional WATERMARKS_SERVER_API_KEY)
  • OpenAPI: GET /openapi.json sirve 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 con openapi-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.yaml levanta toda la infraestructura (core siempre; markllm / markdiffusion detrás de profile: harness; ctrlregen / synthid detrás de profile: heavy como builds solo locales); los servicios tienen el prefijo wr-; los servicios harness/pesados usan command: ["--help"] por defecto para que docker compose up --profile harness --profile heavy salga limpiamente (los CLIs de un solo uso se ejecutan con docker compose run); el nuevo make compose-check / compose-check.sh valida el stack en ejecución (solo código de salida)
  • Publicación GHCR: .github/workflows/release-images.yml publica las imágenes core, markllm, markdiffusion en tags v*; ctrlregen / synthid nunca se publican (licencias upstream)
  • Configuración de entorno: .env.example + guía de configuración de servicios; docker compose carga automáticamente .env; .env está en gitignore (denegación por defecto)
  • Higiene del repositorio: .gitignore y service/.dockerignore ahora 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ían service/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 a service/scripts/

Harness de marca de agua de imágenes MarkDiffusion (opcional)

  • Nuevo harness opcional (THU-BPM/MarkDiffusion externo, Apache-2.0): markdiffusion_harness.py con subcomandos watermark / detect / purify para nueve esquemas de imagen (Tree-Ring, Ring-ID, ROBIN, WIND, SFW, Gaussian-Shading, GaussMarker, PRC, SEAL)
  • clean_image.py --remove-pixel diffusion ejecuta el ataque de regeneración DiffusionPurification de MarkDiffusion como motor alternativo de eliminación de píxeles (intensidad conservadora 0.3 por defecto)
  • Bootstrap setup_markdiffusion.sh (pin de PyPI 1.0.2; clon editable --checkout en commit fijado) + requirements-markdiffusion.txt + Dockerfile.markdiffusion y objetivos de Makefile bootstrap-markdiffusion / smoke-markdiffusion / docker-markdiffusion-build / docker-markdiffusion-help
  • Pruebas basadas en mocks (tests/test_markdiffusion_harness.py) — sin torch en CI; documento de referencia references/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/MarkLLM externo, Apache-2.0): detect_text_watermark.py con subcomandos detect / watermark para los esquemas KGW y SynthID
  • rewrite_text.py --markllm-scheme ejecuta detección antes/después alrededor de una reescritura de Layer B y detección por candidato cuando --candidates N>1 (controlado por entorno; reporta cleared)
  • Bootstrap setup_markllm.sh + requirements-markllm.txt (dependencias fijadas) + Dockerfile.markllm y objetivos de Makefile bootstrap-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_AS opcional en el subproceso de reescritura, torch fijado en el Dockerfile y verificación de SHA del clon en Dockerfile.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.py ahora envía reasoning_effort: "none" por defecto para backends openai-compatible (--reasoning-effort / WATERMARKS_REWRITE_REASONING_EFFORT; off lo omite). Los modelos de razonamiento como deepseek-v4-flash de 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.txt fijó tokenizers==0.23.1, que entra en conflicto con transformers==5.15.0 (limita tokenizers<=0.23.0; no existe la versión 0.23.0) — ahora fijado tokenizers==0.22.2; torch se movió al índice de wheels de CPU (torch==2.13.0.*) para que la imagen sea solo CPU como Dockerfile.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 es python:3.11-slim (pin de digest, multi-arquitectura)
  • Corrección de imágenes harness en tiempo de ejecución: Dockerfile.markllm y Dockerfile.markdiffusion nunca copiaron common.py en /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-text lo sobrescribe (#24)
  • --json ya no suprime el código de salida de señal residual (#30)
  • inspect_file imprime 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-watermark externo: adaptador clean_ctrlregen.py + bootstrap setup_ctrlregen.sh (commit fijado, checkout disperso, venv, verificación SHA), además de Dockerfile.ctrlregen y make bootstrap-ctrlregen / docker-ctrlregen-build / smoke-ctrlregen
  • clean_image.py --remove-pixel ctrlregen ejecuta limpieza de metadatos → eliminación CtrlRegen → puntuación opcional antes/después de SynthID inverso; inspect_image.py sugiere 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-watermark no incluye archivo LICENSE (tratado como todos los derechos reservados), y sus rutas de código de autoinstalación/reinicio se omiten usando CtrlRegenEngine directamente

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) y audit_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/ZWJ despué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_fn y os.fchmod solo 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 .bak a través de la misma ruta segura — los symlinks precolocados (por ejemplo, en /tmp o 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 encabezado Authorization nunca puede reenviarse a un host no validado; los endpoints que no son de loopback están denegados por defecto (optar con --allow-remote o WATERMARKS_REWRITE_ALLOW_REMOTE=1); solo se aceptan esquemas http(s); --api-key se eliminó — las claves son solo de entorno mediante WATERMARKS_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_FSIZE aplicados 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 paso pip-audit y 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.py la 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 (predeterminada 0.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.md y vendor-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 en inspect_image.py / clean_image.py con REVERSE_SYNTHID_DIR o --synthid-dir
  • Bootstrap de setup_synthid.sh (dependencias solo de puntuador; --full instala requisitos ascendentes); Dockerfile.synthid más make docker-synthid-build / docker-synthid-help
  • Objetivos de Makefile smoke-synthid y bootstrap-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_manifest ya no marca Error: No claim found / No JUMBF data found como 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 a contentauth/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) y pytest.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 a remove-claude-marks solo 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.py opcional (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.py unificados
  • Documentación multi-proveedor (Claude, Gemini/SynthID-clase, OpenAI, LLM abierto)
  • Scripts primero en stdlib; c2patool / exiftool opcionales

Licencia

MIT — ver LICENSE.

Bibliografía