DEV Community

Cover image for rag-sanitizer v0.2: escáner de corpus RAG pre-ingestión con embeddings reales y CLIP
Fenix
Fenix

Posted on

rag-sanitizer v0.2: escáner de corpus RAG pre-ingestión con embeddings reales y CLIP

rag-sanitizer v0.2: escáner de corpus RAG pre-ingestión con embeddings reales y CLIP

Detecta documentos poison en un corpus RAG antes del chunk+embed, no después de la recuperación. v0.2 añade embeddings reales (MiniLM) y detección multimodal (CLIP).

El problema

La mayoría de la defensa contra envenenamiento de RAG opera post-retrieval o en inferencia. Pero el corpus se puede envenar antes de la ingesta: un documento insertado con texto que imita el estilo del corpus (semantic mimicry), sustituye entidades conocidas (entity-swap), o lleva una imagen cuyo contenido contradice el texto (visual poison). Una vez embebido, el daño ya está en el vector store.

rag-sanitizer cubre ese tramo: un escáner determinista y barato que marca o pone en cuarentena el corpus antes de chunk + embed.

Qué hace

Tres detectores, combinables:

  • Semantic mimicry: mide la distancia del documento al centroide del perfil de confianza en espacio de embeddings. Umbral k·sigma.
  • Entity-swap: marca entidades que no aparecen en el perfil conocido del usuario (cierra el caso de una sola palabra, p.ej. Madrid → Beijing).
  • Multimodal (CLIP): compara el texto del documento con su imagen declarada en espacio CLIP; si la similitud cae por debajo del umbral, sospecha de visual poison.

Cómo funciona (v0.2)

# install
pip install -e '.[real-embeddings,multimodal]'

# fast suite (Dummy + heurístico, sin descargar modelos)
rag-sanitizer fixtures/corpus.jsonl --embedder dummy

# batería real
rag-sanitizer fixtures/corpus.jsonl --embedder real --multimodal clip
Enter fullscreen mode Exit fullscreen mode

El reporte es por documento: CLEAN / SUSPECT / POISON, con las razones. La salida va por --out (.json o .md); el corpus es un dir, un .jsonl, o un .txt/.md.

Resultados medidos (sin inflar)

  • Batería real_embeddings (MiniLM + CLIP, contra sentence-transformers/torch): 22 tests pasan.
  • En clone limpio sin extras pesados: 15 tests fast + 7 skipped (los real_embeddings se saltan). ruff check y ruff format --check limpios; bandit 0 issues.
  • CLIP carga el modelo con revision pinneada (commit 3d74acf9... de openai/clip-vit-base-patch32), así la mitigación de B615 es el pin, no un #nosec.

Lo que queda abierto (honestamente)

Documentado en KNOWN_ISSUES.md, no oculto:

  • KI-9: MiniLM real no separa gibberish fluido de prosa real cuando la longitud es similar. No te fíes de mimicry para ese vector de ataque concreto; combínalo con entity-swap.
  • KI-10: el umbral de CLIP (0.25) está calibrado sobre imágenes sintéticas de 64px; el margen es estrecho (~0.03). Recalíbralo contra tu corpus real antes de producción.
  • Perfil pequeño: el detector de mimicry exige n >= 10 documentos de confianza; con menos, el veredicto lleva low_confidence y el scanner lo señala en ScanReport.warnings en vez de emitir un veredicto silencioso.

Gobernanza: el bug lo encontró la auditoría, no el self-report

El ciclo siguió implementa → audita en clone limpio → corrige → re-audita → aprueba → merge. En la auditoría independiente (Claude, sobre SHA 22db1fb) apareció un bug real: tras corregir sigmoid(logit) (que saturaba a 1.0 para todos los pares) por coseno escalado por temperatura en clip_embedder.py, el umbral de ClipMultimodalDetector se había quedado en 0.5 y con la nueva escala (match ~0.27–0.32, mismatch ~0.16–0.24) habría marcado SUSPECT prácticamente cualquier imagen. Se recalibró a 0.25, se añadió el test de caso positivo que faltaba, y se reverificó. Ese es el valor de la auditoría externa: atrapa lo que el self-report no ve.

Stack

Componente Uso
Typer CLI
sentence-transformers (MiniLM) embeddings reales
transformers + torch CLIP
ruff + bandit calidad y SAST

Enlaces


Licencia: AGPL-3.0-or-later. Autor: Pedro Sordo Martínez.

Top comments (0)