Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
nullorigin — Research-only AI watermark robustness toolkit: local reverse proxy strips C2PA/EXIF/XMP, Unicode, image/audio stego, OOXML/PDF metadata, and scans Trojan Source. | Kitploit
Herramientas/GitHubGitHub/rakib-nyc/nullorigin
Análisis Estático de Código (SAST)Proxies Web e InterceptaciónEsteganografíaPrivacidadAprendizaje y EducaciónSeguridad de IAAtaque Adversario
GitHubrakib-nyc/nullorigin

nullorigin

Research-only AI watermark robustness toolkit: local reverse proxy strips C2PA/EXIF/XMP, Unicode, image/audio stego, OOXML/PDF metadata, and scans Trojan Source.

Ver Repositorio
4hace 3 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

NullOrigin 🛡️

Middleware universal de procedencia y saneamiento de marcas de agua para IA Artefacto de investigación: solo para evaluación de robustez de marcas de agua.

CI License: Apache 2.0 Python 3.10+ Tests Code style: ruff


🔬 Solo para uso en investigación

NullOrigin es un artefacto de investigación. Se publica para apoyar el estudio académico e independiente de la robustez de las marcas de agua, y para ningún otro fin.

Los esquemas de marcas de agua son afirmaciones de seguridad, y las afirmaciones de seguridad solo tienen sentido una vez que alguien ha intentado romperlas. La literatura que este proyecto implementa — Kirchenbauer et al. sobre KGW, Krishna et al. sobre ataques de paráfrasis, Boucher & Anderson sobre Trojan Source — existe porque los investigadores publicaron ataques funcionales para que los defensores pudieran medir la robustez real en lugar de asumirla. Esa es la tradición a la que pertenece este repositorio.

Usos previstos

  • Medir cuánto degrada la detectabilidad de una marca de agua bajo un ataque declarado
  • Reproducir y ampliar resultados publicados sobre robustez
  • Evaluar la fuga de metadatos de procedencia en tus propios documentos antes de compartirlos
  • Auditar código fuente para ataques Trojan Source y homoglifos (CVE-2021-42574, CVE-2021-42694)
  • Enseñar cómo funcionan estos esquemas y dónde se mantienen

No previsto, y no compatible

  • Hacer pasar trabajo generado como propio cuando una persona o institución te ha pedido que no lo hagas
  • Anular señales de procedencia en contenido que pretendes tergiversar
  • Eliminar la atribución de un trabajo que no es tuyo
  • Eludir cualquier política de plataforma, licencia o integridad académica

Nada de esto es un control técnico sobre cómo se ejecuta el código. Es una declaración de los términos bajo los cuales se ofrece, y de lo que su autor apoyará y no apoyará. El software se proporciona "TAL CUAL, sin garantía de ningún tipo" — consulte LICENSE.

Lea Alcance y limitaciones honestas antes de sacar ninguna conclusión de un número que esta herramienta imprima. Varios de los esquemas a los que se dirige no pueden verificarse contra un detector público, y el README lo dice en lugar de dar a entender lo contrario.


Contenidos

  • Solo para uso en investigación
  • Alcance y limitaciones honestas
  • Fundamentos teóricos
  • Instalación
  • Inicio rápido
  • Docker
  • Despliegue más allá de localhost
  • CLI
  • Benchmarking
  • Fidelidad semántica
  • Configuración
  • Solución de problemas
  • Arquitectura
  • Pruebas
  • Citación y reutilización
  • Estado del proyecto
  • Referencias
  • Aviso legal y atribución

⚠️ Alcance y limitaciones honestas

Lea esto antes de sacar conclusiones de cualquier número que esta herramienta imprima.

Sobre el detector KGW

KGWStatisticalDetector es una implementación matemáticamente fiel y autoconsistente del esquema de lista verde/roja de Kirchenbauer et al. sobre tokens de espacios en blanco. No es un decodificador para la marca de agua de producción de ningún proveedor — esas dependen de un secreto privado y del propio vocabulario BPE del modelo.

Su propósito es hacer que el benchmark sea real: KGWWatermarkEmbedder planta una marca de agua genuina, la canalización la ataca, y el detector correspondiente mide la reducción real. Esa es una medición verdadera del ataque contra este esquema. No se transfiere a la marca de agua de un proveedor.


📖 Fundamentos teóricos

Texto — Lista verde/roja KGW

El vocabulario $V$ se particiona en cada paso $t$ mediante un hash con semilla basado en el contexto precedente:

$$s_t = \text{Hash}(w_{t-k}, \dots, w_{t-1})$$

en una lista verde $G_t$ de tamaño $\gamma|V|$ y una lista roja $R_t$. Se añade un sesgo $\delta > 0$ a los logits verdes:

$$\tilde{l}{t,v} = \begin{cases} l{t,v} + \delta, & v \in G_t \\ l_{t,v}, & v \in R_t \end{cases}$$

La detección cuenta los aciertos verdes. Bajo $H_0$ son $\text{Binomial}(T, \gamma)$, por lo que:

$$z = \frac{|S_G| - \gamma T}{\sqrt{T\gamma(1-\gamma)}}$$

con $z > 4.0$ ($p < 3\times10^{-5}$) marcado como sintético.

Por qué la paráfrasis lo ataca: la marca de agua reside por completo en transiciones locales de n-gramas. Reescribir la forma superficial con un modelo sin marcar re-simienta cada posición. Este es el ataque de robustez estándar en la literatura de marcas de agua.

Por qué importa la longitud: $z$ crece como $\sqrt{T}$. Un pasaje de 100 tokens con una fracción verde de 0,70 solo alcanza $z \approx 3.9$ — por debajo del umbral. La detección necesita unos pocos cientos de tokens, y también los fixtures de benchmark significativos.

Imagen — C2PA e incrustación en el dominio de la frecuencia

  • C2PA / JUMBF: manifiestos firmados en segmentos APP11 de JPEG, chunks tEXt/iTXt de PNG, o cajas c2pa de WebP/AVIF. Dado que la firma cubre los datos de píxeles, recodificar desde un buffer de muestras desnudo la elimina sin analizar JUMBF en absoluto.
  • SynthID-Image: desplazamientos en coeficientes DCT/DWT. Se ataca mediante umbral suave (soft-thresholding) en las subbandas de detalle de alta frecuencia.
  • Tree-Ring: un patrón circularmente simétrico en el latente de difusión inicial, que sobrevive como estructura coherente en la fase de Fourier. Se ataca aleatorizando la fase en anillos de frecuencia media mientras se conserva la magnitud — por eso la imagen permanece visualmente idéntica.

Audio — AudioSeal / SynthID-Audio

Modulación de fase subumbral y adiciones espectrales de baja amplitud. Se ataca mediante aleatorización de fase por encima de la fundamental del habla, desplazamiento de muescas de banda detenida en bandas no críticas y recuantificación psicoacústica.


📦 Instalación

Requisitos

Python3.10, 3.11 o 3.12
SOLinux, macOS (Intel y Apple Silicon), Windows mediante WSL2
OpcionalOllama o cualquier servidor compatible con OpenAI —

Desde el código fuente```bash

git clone https://github.com/rakib-nyc/nullorigin.git cd nullorigin

python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate

pip install -e .

root@kitploit:~
### Extras opcionales```bash
pip install -e ".[dev]"       # pytest, pytest-asyncio, ruff  — needed to run the tests
pip install -e ".[nli]"       # torch + sentence-transformers, for the fidelity gate
pip install -e ".[metrics]"   # torch, transformers, sentence-transformers
pip install -e ".[llama]"     # llama-cpp-python for in-process GGUF inference
pip install -e ".[dev,metrics]"

Sin [nli], la puerta de fidelidad se ejecuta solo con invariantes — sigue siendo una comprobación real, pero ciega a los intercambios de roles. Ver Fidelidad semántica.

Verificar la instalación```bash

nullorigin --version nullorigin --help pytest -q # requires the [dev] extra

root@kitploit:~
---

## 🚀 Inicio rápido

### 1. Configurar un modelo de reescritura local

La eliminación de marcas de agua de texto necesita un modelo local sin marca de agua. Sin uno, NullOrigin elimina
los caracteres invisibles pero **deja intacta la marca de agua estadística** — y lo dice.```bash
ollama serve                    # in a separate terminal
ollama pull llama3.2:3b         # or any instruct model you prefer

¿Usando un modelo diferente? Apunta NullOrigin hacia él:```bash export NULLORIGIN_PARAPHRASER_MODEL=qwen3:4b export NULLORIGIN_PARAPHRASER_TIMEOUT=900 # reasoning models are slow

root@kitploit:~
### 2. Inicia el proxy```bash
nullorigin run

No se proporcionó contenido en el bloque de entrada para traducir.```console NullOrigin 1.0.0 — proxy listening on 127.0.0.1:8080 providers: anthropic, gemini, openai text engine: unicode=True backend=ollama media: metadata=True stego=True telemetry: open (loopback) health: http://127.0.0.1:8080/health

root@kitploit:~
### 3. Apunta tu cliente a él```python
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8080/v1", api_key="your-upstream-api-key")

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Write an essay about privacy."}],
    extra_headers={"x-nullorigin-provider": "openai"},
)
print(response.choices[0].message.content)

Anthropic:```python from anthropic import Anthropic

client = Anthropic(base_url="http://localhost:8080", api_key="your-upstream-api-key") message = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, messages=[{"role": "user", "content": "Write an essay about privacy."}], extra_headers={"x-nullorigin-provider": "anthropic"}, )

root@kitploit:~
curl:```bash
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "x-nullorigin-provider: openai" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}'

El streaming (SSE) y Gemini (/v1beta/models/...) se manejan de la misma manera. El encabezado x-nullorigin-provider selecciona el upstream y se elimina antes del reenvío; tus encabezados de autenticación pasan sin cambios.


🐳 Docker```bash

git clone https://github.com/rakib-nyc/nullorigin.git cd nullorigin

docker compose up -d docker compose exec ollama ollama pull llama3.2:3b # first run only curl http://localhost:8080/health

root@kitploit:~
El stack de Compose ejecuta NullOrigin más un sidecar de Ollama en una red bridge privada.
El contenedor proxy se vincula a `0.0.0.0` — correcto dentro de un contenedor — y solo el puerto 8080 se publica en tu host.

Imagen independiente:```bash
docker build -t nullorigin:1.0.0 .
docker run -d -p 8080:8080 \
  -e NULLORIGIN_PARAPHRASER_BACKEND=none \
  nullorigin:1.0.0

Comandos útiles:```bash docker compose logs -f nullorigin docker compose down # stop docker compose down -v # stop and delete the Ollama model volume

root@kitploit:~
---

## 🔒 Despliegue más allá de localhost

**NullOrigin usa `127.0.0.1` por defecto y se niega a vincular una interfaz pública sin un
token de telemetría.** Reenvía tus credenciales de API ascendentes, por lo que esto es deliberado:```console
$ nullorigin run --host 0.0.0.0
Error: Refusing to bind 0.0.0.0 without a telemetry token.

Choose one:
  - bind loopback:    nullorigin run --host 127.0.0.1
  - set a token:      export NULLORIGIN_TELEMETRY_TOKEN=$(openssl rand -hex 32)
  - accept the risk:  nullorigin run --host 0.0.0.0 --allow-public-bind

Para exponerlo correctamente:```bash export NULLORIGIN_TELEMETRY_TOKEN=$(openssl rand -hex 32) nullorigin run --host 0.0.0.0 --port 8080

root@kitploit:~
luego colócalo detrás de nginx, Caddy o Traefik que proporcionen **terminación TLS**, **límite
de tasa** y una **capa de autenticación**.

### Modelo de amenazas

NullOrigin es un **proxy inverso local que retransmite las credenciales de tu API upstream**. Ese
único hecho define su postura de seguridad.

| Control | Por defecto | Por qué |
| --- | --- | --- |
| Dirección de enlace | `127.0.0.1` | Solo loopback; se rechaza un enlace público a menos que se establezca un token de telemetría o se pase `--allow-public-bind`. |
| `/telemetry`, `/telemetry/reset` | Abierto en loopback | Protegido por `X-NullOrigin-Token`, comparado en tiempo constante, siempre que `proxy.telemetry_token` esté establecido. |
| `/health` | Siempre abierto | Las sondas de contenedor lo necesitan; expone la versión y los motores habilitados, sin secretos. |
| Tamaño del cuerpo de la solicitud | 100 MiB | El proxy almacena en búfer los cuerpos para reenviarlos; las entradas más grandes se rechazan con `413`. |
| Búfer SSE | 1 MiB | Un upstream que nunca termina un marco se vacía, no se almacena en búfer indefinidamente. |
| Usuario del contenedor | non-root | El proxy no necesita privilegios elevados. |

Limitaciones conocidas, por diseño y no por defectos:

* **Sin TLS.** Reenvía `Authorization` y `x-api-key` tal cual a través de HTTP sin cifrar. Colócalo
  detrás de un proxy inverso que termine HTTPS en cualquier red no confiable.
* **Sin autenticación en la ruta del proxy.** Cualquiera que pueda alcanzar el puerto puede actuar como proxy
  a través de él, usando sus propias credenciales — NullOrigin ni almacena ni inyecta claves.
* **Sin límite de tasa.** Aplícalo en el proxy inverso.
* **Las credenciales nunca se persisten.** Ninguna clave de API se escribe en disco o en registros; la telemetría
  solo cuenta solicitudes y eventos de sanitización.
* La verificación TLS del upstream permanece activada y no se siguen las redirecciones.

Para informar un problema de seguridad, envía un correo a **[email protected]** con `[NullOrigin Security]` en
el asunto.

Telemetría con un token configurado:```bash
curl -H "X-NullOrigin-Token: $NULLORIGIN_TELEMETRY_TOKEN" http://localhost:8080/telemetry

/health nunca está sujeto a control de acceso, por lo que los sondeos de contenedores siguen funcionando.


🛠️ CLI```bash

nullorigin run [--host H] [--port P] [--config FILE] [--allow-public-bind] nullorigin purge INPUT -o OUTPUT [--verify] [--no-paraphrase] [--flatten-typography] nullorigin inspect INPUT [--json] nullorigin benchmark [--section text|media|audio] [-o report.json] nullorigin build-datasets [--root DIR] nullorigin test [pytest args...]

root@kitploit:~
### Formatos admitidos

| Tipo | Extensiones | Notas |
| --- | --- | --- |
| **Imágenes** | `.png` `.jpg` `.jpeg` `.jfif` `.webp` `.tif` `.tiff` `.bmp` `.gif` `.ico` `.avif` `.jp2` | Todos los modos PIL (RGB, RGBA, L, LA, P, 1, I;16, CMYK, YCbCr). Los GIF/WebP animados y los TIFF de varias páginas conservan cada fotograma y su sincronización. Las imágenes de menos de 64px conservan las dimensiones exactas. |
| **Audio** | `.wav` `.wave` | Enteros de 8/16/32 bits, flotante de 32 bits; de mono a multicanal; cualquier frecuencia de muestreo. Los archivos de longitud cero se conservan sin cambios. |
| **Documentos** | `.docx` `.docm` `.dotx` `.pptx` `.pptm` `.xlsx` `.xlsm` | Los tres dialectos OOXML. Los fragmentos de texto se sanean en cuerpo, encabezados, pies de página, notas al pie, notas y cadenas compartidas; los metadatos de `docProps` se eliminan; el resto de partes se copia byte a byte. |
| **PDF** | `.pdf` | El diccionario `/Info`, el paquete XMP, los archivos adjuntos incrustados y JavaScript se eliminan; las páginas, el texto y la geometría se conservan. Los archivos protegidos por contraseña se rechazan. Consulta la salvedad que aparece más abajo. |
| **Código fuente** | `.py` `.js` `.ts` `.go` `.rs` `.java` `.c` `.cpp` `.rb` `.php` `.sh` `.sql` + 50 más | Escaneo de Trojan Source y homoglifos. **Sin NFKC, sin paráfrasis** — ver más abajo. |
| **Texto** | cualquier otra cosa decodificable | UTF-8, UTF-8 BOM, UTF-16, UTF-32, CP1252, Latin-1 — autodetectado y **reescrito en la misma codificación**. |

Todo lo demás se **rechaza con indicaciones específicas** en lugar de leerse como UTF-8 y
corromperse — `.mp3` apunta a `ffmpeg -i in.mp3 out.wav`, los `.doc`/`.ppt`/`.xls` heredados a
volver a guardarlos como OOXML. Un archivo rechazado nunca produce salida.

Verificado con un corpus de 69 archivos que abarca todos los formatos anteriores: **59 procesados correctamente,
10 rechazados limpiamente, cero fallos, cero salidas corruptas.**

### El código se mantiene fuera del reescritor

Las respuestas del asistente mezclan prosa y código en una sola cadena. Si se pasa todo a un
modelo de paráfrasis, este reescribe el código junto con la prosa — y el z-score cae
de todos modos, así que nada posterior lo nota.

Por lo tanto, las respuestas se segmentan antes de reescribir nada:

| Segmento | Tratamiento |
| --- | --- |
| Prosa | Limpiada de Unicode y luego reescrita |
| Bloques con vallas (``` y ~~~) | Se eliminan los caracteres invisibles y bidireccionales. **Sin NFKC, nunca se reescriben.** |
| Tramos de `` `code` `` en línea | Igual |

Esto también se cumple en la ruta de streaming, donde una valla se abre en un delta y se cierra
varios deltas después. Un delta que cruza el límite se divide por líneas, de modo que el
``` de cierre y la prosa posterior se tratan de forma distinta. Una valla sin cerrar falla de forma segura:
el resto se protege en lugar de reescribirse.

Desactívelo con `text.protect_code_blocks: false` si quieres el comportamiento anterior.

### Archivos de código fuente: un escaneo de seguridad, no una eliminación de marcas de agua

**No hay ninguna marca de agua en el código fuente generado por IA.** Ningún proveedor pone marcas de agua en el código
de salida, y no existe ningún detector público. Cualquiera que afirme eliminar una te está vendiendo algo.

Lo que el código fuente *sí* tiene es una superficie de ataque real y publicada:

* **Trojan Source** ([CVE-2021-42574](https://nvd.nist.gov/vuln/detail/CVE-2021-42574),
  Boucher y Anderson, 2021) — los caracteres de control bidireccionales reordenan cómo se *muestra* el código
  sin cambiar cómo se *compila*. Un revisor aprueba un programa; el compilador construye otro.
* **Identificadores homoglifos** ([CVE-2021-42694](https://nvd.nist.gov/vuln/detail/CVE-2021-42694))
  — la `а` cirílica en lugar de la `a` latina crea dos nombres que se representan de forma idéntica.```console
$ nullorigin purge auth.py -o auth_clean.py --verify
Scanning source file auth.py...
  bidi controls removed:   4
  invisible chars removed: 0

  TROJAN SOURCE DETECTED (CVE-2021-42574): 4 bidirectional control character(s).
  This file rendered differently than it compiled. Review the diff.

Findings:
  CRITICAL line 3:25  U+202E RIGHT-TO-LEFT OVERRIDE — reorders displayed text
      if access_level != "user‮ ⁦// Check if admin⁩⁦":

Despu\u00e9s de purgar, la l\u00ednea queda if access_level != "user // Check if admin": \u2014 el "comentario" estaba dentro de la cadena desde el principio.

Tres cosas que la ruta de c\u00f3digo deliberadamente no hace, porque la ruta de texto gen\u00e9rica hac\u00eda las tres y cada una es un error en el c\u00f3digo fuente:

  1. Sin normalizaci\u00f3n NFKC. Reescribe los valores de los literales de cadena: "Hello" se convirti\u00f3 en "Hello", "office" se convirti\u00f3 en "office". Eso cambia lo que un programa compara, hashea, y transmite.
  2. Sin par\u00e1frasis. Enviar c\u00f3digo a un modelo de reescritura produce c\u00f3digo que no se ejecuta.
  3. Los homoglifos se notifican, no se pliegan. Plegar una а cir\u00edlica fusiona dos identificadores que el compilador actualmente trata como distintos \u2014 cambiando silenciosamente el comportamiento. La severidad es MEDIUM solo para tokens de escritura mixta (totаl), la firma real del ataque; una palabra escrita por completo en otra escritura es texto extranjero ordinario y punt\u00faa INFO. Act\u00edvalo con --fold-homoglyphs-in-code una vez que los hayas revisado.

inspect --json emite la severidad, l\u00ednea, columna y punto de c\u00f3digo de cada hallazgo, por lo que se integra en CI como puerta de pre-commit o PR.

PDF: qu\u00e9 se elimina y qu\u00e9 no

Eliminado, de forma verificable: el diccionario /Info (Author, Title, Subject, Keywords, Creator, Producer, CreationDate, ModDate), el paquete XMP en /Root/Metadata, los archivos adjuntos incrustados y el JavaScript a nivel de documento. Las p\u00e1ginas, el texto y la geometr\u00eda de p\u00e1gina se conservan exactamente; la operaci\u00f3n es idempotente y estable a nivel de bytes.

Detectado pero NO eliminado: caracteres invisibles dentro de las secuencias de contenido de las p\u00e1ginas. El PDF dibuja el texto glifo a glifo mediante una codificaci\u00f3n espec\u00edfica de la fuente \u2014 un espacio de ancho cero en una fuente con clave CID es un \u00edndice de glifo de dos bytes, no un U+200B literal \u2014 por lo que una reescritura gen\u00e9rica corromper\u00eda el dise\u00f1o en lugar de limpiarlo. inspect informa el recuento; purge imprime una advertencia en lugar de permanecer en silencio, porque el silencio se interpretar\u00eda como "no hab\u00eda ninguno". Para eliminarlos, extrae el texto, ejecuta nullorigin purge sobre \u00e9l y regenera el PDF.

--strip-annotations est\u00e1 disponible pero desactivado por defecto: las anotaciones incluyen enlaces y campos de formulario, no solo comentarios, por lo que eliminarlas cambia el comportamiento del documento.

La tipograf\u00eda no es una marca de agua

Las rayas, las comillas curvas y los puntos suspensivos son salida ordinaria de un procesador de texto. NullOrigin los conserva por defecto y los informa por separado de los hallazgos genuinos, porque aplanarlos degrada un documento sin sanear nada. Usa --flatten-typography si quieres espec\u00edficamente una salida ASCII.

Los confundibles entre escrituras son diferentes \u2014 una о cir\u00edlica en medio de una palabra en texto ingl\u00e9s no tiene uso leg\u00edtimo \u2014 y esos se pliegan por defecto.

--verify informa mediciones antes/despu\u00e9s en lugar de afirmar el \u00e9xito:```console $ nullorigin purge article.txt -o clean.txt --verify Cleaning text structure and token transitions in article.txt... removed 14 invisible characters, folded 3 homoglyphs applying semantic restructuring via ollama backend... restructuring complete Saved clean text to clean.txt

Verification (KGW statistical detector): z-score before: +5.3021 (p=5.73e-08) z-score after: +0.8874 (p=0.187) detected before/after (z>4.0): True -> False

root@kitploit:~
Si el backend no está disponible, eso se informa como una advertencia en stderr — una alternativa
silenciosa se vería idéntica a una sanitización exitosa.

---

## 📊 Evaluación de rendimiento```bash
nullorigin build-datasets
nullorigin benchmark

Cada valor se mide en el momento: el texto se marca con KGWWatermarkEmbedder, se ejecuta a través del pipeline real y se vuelve a puntuar con el detector correspondiente. El ejecutor termina con un código distinto de cero cuando no se cumplen los umbrales y explica el motivo.

Umbrales (de la directiva del proyecto):

MétricaObjetivo
Puntuación z posterior a la sanitización$\lvert z\rvert \le 1.5$
Similitud semántica$\ge 0.92$
SSIM de imagen$\ge 0.95$
PSNR de imagen$\ge 36$ dB

Resultados medidos

Sección de texto completo sobre datasets/text/watermarked_kgw.json, reescrita mediante Ollama (qwen3:4b) en un MacBook de la serie M (~150 s por pasaje):```text sample z_before z_after reduced detected kgw_000 4.212 -0.065 4.277 no kgw_001 5.297 0.484 4.813 no kgw_002 6.120 -0.482 6.601 no kgw_003 4.711 1.271 3.440 no kgw_004 5.696 0.209 5.486 no invisible_payload -0.447 1.091 -1.538 no

mean z: 4.2647 -> 0.4182 max |z| after: 1.271 (target: <= 1.5) still detected at z > 4.0: 0 of 5 invisible chars remaining: 0

pass_z_threshold: PASS pass_no_detection: PASS pass_unicode_purge: PASS OVERALL: PASS (3/3)

root@kitploit:~
Cada muestra con marca de agua pasó de detectada a no detectada. Nótese `kgw_003` en
z = 1.271 — por debajo del umbral, pero la más cercana a él, lo cual es la forma honesta de este
ataque: es estadístico, no una garantía.

Media, medida en las imágenes de prueba:```text
sample                      ssim   psnr_dB  meta_clear
c2pa_tagged.png           0.9950     46.84         yes
exif_tagged.jpg           0.9690     40.54         yes
clean_control.png         0.9951     46.90         yes

Both image thresholds pass (SSIM ≥ 0.95, PSNR ≥ 36 dB). Numbers will vary with the model, hardware, and passage.

Fidelidad semántica: una caída del z-score no es evidencia de significado preservado

Una reescritura que cambia un hecho obtiene exactamente la misma puntuación que una fiel en la métrica de marca de agua. La comprobación obvia para esto no funciona, y tampoco la menos obvia. Medido en seis casos de deriva más un control fiel:

La superposición léxica está invertida. Cada edición que destruye el significado obtuvo una puntuación más alta que la reescritura fiel, porque una buena paráfrasis comparte pocos n-gramas con su fuente, mientras que una corrupta comparte casi todos.

El coseno de embeddings no lo soluciona. Tres de los seis casos corruptos superan un umbral de 0.92. "Alice paid Bob" y "Bob paid Alice" son la misma bolsa de palabras y obtienen 0.985; "must not disable" → "must disable" obtiene 0.947. Los embeddings de oraciones codifican la relación temática, no la verdad.

Por lo tanto, la fidelidad se comprueba en dos capas, y ninguna es el coseno:

  1. Invariantes — números, cantidad de negaciones, fuerza modal, fuerza de cuantificadores, URLs, correos electrónicos, identificadores, entidades nombradas. Determinista, sin dependencias y explicable (negation count changed: 1 → 0). Los modales y cuantificadores se comparan por clase de significado, de modo que may → might pasa y may → must falla. Ciego a los intercambios de rol donde todas las entidades sobreviven.
  2. Implicación bidireccional — el significado se preserva si y solo si cada texto implica al otro. Detecta lo que el recuento no puede, incluidos los intercambios de rol, y la asimetría es diagnóstica (la eliminación del atenuador obtuvo a⇒b 0.991, b⇒a 0.011). Requiere nullorigin[nli]; sin él, la limitación se informa, no se oculta.

Es una puerta, no un informe

Medir la deriva a posteriori no ayuda si el texto dañado ya ha sido devuelto. Una comprobación fallida reintenta a una temperatura más baja — la deriva está impulsada por la temperatura — y tras el presupuesto de reintentos devuelve el original, con ok=False y el motivo.```yaml text: fidelity: enabled: true max_retries: 2 temperature_step: 0.25 use_nli: true nli_threshold: 0.5

root@kitploit:~
Esto también significa que el ataque y el riesgo comparten un solo control: subir la temperatura reduce el z-score *y* aumenta la tasa de deriva. El benchmark los reporta juntos en lugar de como comprobaciones independientes.

### Otras métricas

* **Perplexity** — PPL real de GPT-2 con `torch` + `transformers`, de lo contrario
  `unigram_entropy_proxy`, marcado como aproximado y **no** comparable con la PPL publicada.
* **Similitud coseno** se sigue reportando como `mean_cosine_or_lexical`, solo como referencia.
  Ya no es un umbral de aprobado/fallo, por las razones de la tabla anterior.

## ⚙️ Configuración

Orden de resolución, de menor a mayor prioridad:

1. Valores predeterminados integrados
2. `nullorigin.yaml` (buscado en `./`, `../`, `/app/` o `$NULLORIGIN_CONFIG`)
3. Variables de entorno `NULLORIGIN_*`
4. Banderas explícitas de la CLI

### Ajustes clave

| Ajuste | Por defecto | Notas |
| --- | --- | --- |
| `proxy.host` | `127.0.0.1` | Loopback. La vinculación pública se rechaza sin un token de telemetría. |
| `proxy.port` | `8080` | |
| `proxy.default_provider` | `openai` | Se usa cuando no se envía el encabezado `x-nullorigin-provider`. |
| `proxy.telemetry_token` | `""` | Protege `/telemetry` y `/telemetry/reset`. |
| `proxy.max_request_bytes` | `104857600` | 100 MiB; los cuerpos más grandes reciben `413`. |
| `text.paraphraser.backend` | `ollama` | `none` \| `ollama` \| `openai_compatible` \| `llama_cpp` \| `lexical`. `none` deja intacta la marca de agua estadística. `lexical` no necesita modelo, pero es un ataque mucho más débil. |
| `text.clean_unicode` | `true` | Eliminación de caracteres de ancho cero y del bloque Tags. |
| `text.fold_homoglyphs` | `true` | Confusables cirílicos/griegos a ASCII. |
| `text.stream_window_tokens` | `40` | Deltas almacenados en búfer antes de reescribir un tramo de streaming. |
| `media.crop_mode` | `trim` | `trim` desplaza las coordenadas sin remuestrear; `resample` restaura las dimensiones exactas, pero cuesta aproximadamente SSIM 0.81 / PSNR 31 dB incluso con un recorte del 0.5%; `none` desactiva el paso geométrico. |
| `audio.low_cut_hz` | `800.0` | La fase por debajo de este valor se conserva para la inteligibilidad. |

### Variables de entorno```bash
NULLORIGIN_CONFIG                  # path to nullorigin.yaml
NULLORIGIN_HOST                    # bind address
NULLORIGIN_PORT
NULLORIGIN_TELEMETRY_TOKEN
NULLORIGIN_MAX_REQUEST_BYTES
NULLORIGIN_DEFAULT_PROVIDER
NULLORIGIN_PARAPHRASER_BACKEND     # none | ollama | openai_compatible | llama_cpp | lexical
NULLORIGIN_PARAPHRASER_ENDPOINT    # alias: NULLORIGIN_OLLAMA_ENDPOINT
NULLORIGIN_PARAPHRASER_MODEL
NULLORIGIN_PARAPHRASER_MODEL_PATH  # llama_cpp GGUF path
NULLORIGIN_PARAPHRASER_API_KEY
NULLORIGIN_PARAPHRASER_TIMEOUT
NULLORIGIN_PARAPHRASER_TEMPERATURE
NULLORIGIN_CLEAN_UNICODE
NULLORIGIN_FOLD_HOMOGLYPHS
NULLORIGIN_PURGE_METADATA
NULLORIGIN_DISRUPT_STEGO
NULLORIGIN_DISRUPT_AUDIO

🩺 Solución de problemas

model 'llama3.2:3b' not found El modelo configurado no se ha descargado. Ejecuta ollama list para ver qué tienes y luego haz ollama pull llama3.2:3b o define NULLORIGIN_PARAPHRASER_MODEL con un modelo que ya tengas.

WARNING: ollama backend unavailable (ReadTimeout) La reescritura superó text.paraphraser.timeout_seconds (120 s por defecto). Los modelos de razonamiento como qwen3 suelen tardar 150 s o más por párrafo en CPU. Súbelo con export NULLORIGIN_PARAPHRASER_TIMEOUT=900 o usa un modelo instruct más pequeño.

nullorigin benchmark termina con el código 1 y pass_no_detection: FAIL Es el comportamiento previsto. No se pudo alcanzar ningún backend de reescritura, así que solo se ejecutó la capa Unicode y la marca de agua estadística sobrevivió. Arranca Ollama o configura el backend como lexical para una comparación sin dependencias.

semantic_check: INCONCLUSIVE Es de esperar sin el extra [metrics]. Consulta Honestidad de las métricas.

Error: Refusing to bind 0.0.0.0 without a telemetry token Intencionado. Consulta Despliegue más allá de localhost.

Multiple top-level packages discovered in a flat-layout Tienes una versión antigua del repositorio. pyproject.toml define una lista explícita de paquetes; haz pull de la última versión.

Las pruebas asíncronas notifican un UsageError sobre la falta de un plugin asíncrono Es deliberado: sin él, pytest da por superadas las pruebas async def sin esperarlas. pip install -e ".[dev]".

Docker: curl: (7) Failed to connect justo después de compose up El healthcheck tiene un período de arranque de 10 s. Espera y luego revisa docker compose logs nullorigin.


🏗️ Arquitectura```text

root@kitploit:~
                Client / Application
                        |
          [http://localhost:8080/v1/...]
                        v
    +===================================================+
    |               NULLORIGIN CORE PROXY               |
    |  HTTP/SSE interceptor · provider schema adapter   |
    |  /health · /telemetry · transparent auth passthru |
    +===================================================+
                        |
          [request forwarded unmodified]
                        v
          Upstream Provider API (Anthropic / OpenAI / Gemini)
                        |
              [watermarked payload]
                        v
    +===================================================+
    |            SANITIZATION PIPELINE ROUTER           |
    +===================================================+
       /                 |                      \

(text/JSON+SSE) (image/*) (audio/wav) v v v +----------------+ +------------------+ +------------------+ | MODULE B: TEXT | | MODULE C: MEDIA | | MODULE D: AUDIO | | unicode purge | | C2PA/EXIF scrub | | phase randomize | | homoglyph fold | | DWT threshold | | notch shifting | | KGW detector | | Fourier phase | | psychoacoustic | | SLM rewriter | | dither | | requantization | +----------------+ +------------------+ +------------------+ \ | / +----------------+---------------------+ v Schema reconstruction (SSE framing preserved) v Sanitized stream / file

root@kitploit:~
### Diseño```text
nullorigin/
├── cli.py                        # run, purge, inspect, benchmark, build-datasets, test
├── config.py                     # Pydantic v2 settings + env overrides
├── proxy/
│   ├── server.py                 # FastAPI reverse proxy, /health, /telemetry
│   ├── interceptors.py           # SSE frame parser + sliding-window rewriter
│   ├── telemetry.py              # thread-safe runtime counters
│   └── schemas.py                # provider request/response models
├── engines/
│   ├── text/
│   │   ├── unicode_cleaner.py    # invisible chars, Tags block, homoglyphs
│   │   ├── paraphraser.py        # pluggable rewrite backends
│   │   └── kgw_detector.py       # detector + Viterbi embedder
│   ├── media/
│   │   ├── c2pa_remover.py       # JUMBF/EXIF/XMP stripping + inspection
│   │   └── stego_breaker.py      # DWT thresholding, Fourier phase, dither
│   └── audio/
│       └── audio_cleaner.py      # phase randomization, notch shifting
└── evaluation/
    ├── metrics.py                # SSIM, PSNR, PPL, semantic similarity
    ├── datasets.py               # deterministic fixture generation
    └── runner.py                 # measured benchmark harness

🧪 Pruebas```bash

pytest -q

root@kitploit:~
384 pruebas. El conjunto de pruebas cubre el analizador de tramas SSE frente a límites de fragmentos adversariales, el ciclo de vida del streaming a través del proxy, todos los modos de imagen PIL, SSIM tanto contra un valor de forma cerrada como contra una implementación de referencia por fuerza bruta, y la detectabilidad de marcas de agua en el conjunto de datos.

Las pruebas asíncronas fallan de forma ruidosa si no hay un plugin asíncrono instalado, en lugar de omitirse silenciosamente.

---

## 📖 Citación y reutilización

Con licencia Apache-2.0, que permite su uso, modificación y redistribución siempre que se conserve el aviso de copyright y la atribución a **Muhammad Rakibul Islam**. Consulte [LICENSE](https://github.com/rakib-nyc/nullorigin/blob/HEAD/LICENSE) y [NOTICE](https://github.com/rakib-nyc/nullorigin/blob/HEAD/NOTICE).

Si este trabajo respalda una publicación, cítelo como:```bibtex
@software{islam_nullorigin_2026,
  author  = {Islam, Muhammad Rakibul},
  title   = {{NullOrigin}: Universal AI Provenance and Watermark
             Sanitization Middleware},
  year    = {2026},
  version = {1.2.0},
  url     = {https://github.com/rakib-nyc/nullorigin},
  note    = {Research artifact for watermarking robustness evaluation}
}

Este repositorio se publica como un artefacto de investigación terminado y no acepta pull requests. Eres libre de hacerle fork bajo los términos de la licencia. Las preguntas y los hallazgos son bienvenidos por correo electrónico en [email protected].


🗺️ Estado del proyecto

Versión 1.0.0. La suite está completa según su especificación y totalmente probada, con estos límites conocidos:

  • La efectividad contra SynthID, Tree-Ring y AudioSeal no es verificable aquí — no existe un detector público para medirla. Las perturbaciones están implementadas; no se hace esa afirmación.
  • La deriva semántica está controlada por invariantes más un NLI opcional. Sin [nli], los intercambios de roles que preservan cada entidad son indetectables, y el informe así lo indica.
  • La latencia con un modelo de razonamiento local (~150 s por pasaje) hace que la vía de texto no sea adecuada para uso interactivo. Un modelo instruct más pequeño es mucho más rápido.
  • Sin TLS y sin autenticación de solicitudes — por diseño; ejecútelo detrás de un proxy inverso.

Consulte CHANGELOG.md para ver el historial de versiones.


📚 Referencias

  • Kirchenbauer et al., A Watermark for Large Language Models (2023) — el esquema KGW
  • Dathathri et al., Scalable watermarking for identifying large language model outputs (Nature, 2024) — SynthID-Text
  • Wen et al., Tree-Rings Watermarks: Invisible Fingerprints for Diffusion Images (2023)
  • San Roman et al., Proactive Detection of Voice Cloning with Localized Watermarking (2024) — AudioSeal
  • Krishna et al., Paraphrasing evades detectors of AI-generated text (2023)
  • Wang et al., Image Quality Assessment: From Error Visibility to Structural Similarity (2004) — SSIM
  • Especificación C2PA

📜 Aviso legal y atribución

  • Autor y creador: Muhammad Rakibul Islam
  • Contacto: [email protected]
  • Licencia: Apache License, Version 2.0

Este repositorio es un artefacto de nivel de investigación publicado para investigación estadística, evaluación de privacidad, pruebas comparativas de robustez de marcas de agua y pruebas de resiliencia criptográfica.```text Copyright 2026 Muhammad Rakibul Islam [email protected]

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

root@kitploit:~
http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

root@kitploit:~
**SIN GARANTÍAS.** EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO,
EXPRESA O IMPLÍCITA.
Descargar herramienta
CapaLo que realmente hace
Caracteres invisiblesTotalmente eficaz. Las cargas útiles de ancho cero, control bidi, selectores de variación y bloques Unicode Tags se eliminan por completo, con un recuento informado. Los confusables homoglifos entre escrituras (cirílico/griego que se muestran como ASCII) se normalizan.
Metadatos de documento (.docx)Totalmente eficaz. Autor, último editor, recuento de revisiones, marcas de tiempo, plantilla y versión de la aplicación se eliminan de docProps, conservando el formato byte a byte.
C2PA / EXIF / XMPTotalmente eficaz. La imagen se reconstruye a partir de muestras de píxeles sin procesar en un contenedor nuevo, por lo que los manifiestos JUMBF firmados y todos los metadatos desaparecen. Verificado mediante pruebas contra muestras etiquetadas.
Marca de agua estadística KGWDepende por completo del backend de reescritura. Sin un modelo local configurado, la marca de agua estadística sobrevive — la herramienta lo dice en lugar de dar a entender lo contrario.
SynthID-Text / SynthID-Image / Tree-RingNo verificable aquí. Estos usan claves privadas y decodificadores propietarios. NullOrigin aplica las perturbaciones que la literatura describe, pero no se afirma que derroten a los detectores reales, porque no hay un detector público contra el que medir.
AudioSeal / SynthID-AudioNo verificable aquí, por la misma razón.
necesario para eliminar marcas de agua de texto
OpcionalDocker 20.10+ con Compose v2
casolexical_f1coseno de embeddingsNLI bidireccional
negación eliminada0.700.77 ✓0.000 ✓
número 5 → 500.820.81 ✓0.000 ✓
intercambio de entidad/rol0.810.985 ✗0.000 ✓
cuantificador todos → algunos0.880.91 ✓0.000 ✓
atenuador eliminado0.270.953 ✗0.011 ✓
"must not" → "must"0.830.947 ✗0.000 ✓
reescritura fiel0.330.931 ✓0.998 ✓