¿Qué hace Evaluación de Debilidades en Vectores y Embeddings?
Solo para uso autorizado: Estas pruebas interactúan con almacenes de vectores y modelos de embedding en sistemas RAG que usted posee o está autorizado a evaluar. La inversión de embeddings y la prueba entre inquilinos contra sistemas que no controla pueden exponer datos de terceros y está prohibida sin autorización.
Resumen
Los sistemas de Generación Aumentada por Recuperación (RAG) convierten documentos en vectores de embedding almacenados en una base de datos vectorial (Pinecone, Qdrant, Weaviate, Chroma, pgvector, FAISS) y recuperan los vectores más cercanos para fundamentar las respuestas del LLM. La vulnerabilidad LLM08:2025 Debilidades en Vectores y Embeddings de OWASP cubre los riesgos de seguridad únicos de esta capa:
- Inversión de embeddings — los embeddings no son unidireccionales. Un modelo de inversión entrenado (o un ataque de reconstrucción de caja negra) puede recuperar partes sustanciales del texto original a partir de su vector, filtrando documentos fuente (se asigna a MITRE ATLAS AML.T0024.001 Invertir Modelo ML).
- Inferencia de pertenencia — consultar si un registro específico contribuyó al corpus (AML.T0024.000).
- Fuga entre inquilinos / multiinquilino — cuando un espacio de nombres/colección se comparte o falta el aislamiento de filtro, un inquilino recupera fragmentos de otro inquilino.
- Envenenamiento de la base de conocimiento — un atacante que puede escribir en el corpus inserta fragmentos manipulados que dominan la recuperación (alta similitud coseno con consultas esperadas) y transportan cargas útiles de inyección indirecta de prompts.
- Manipulación de la recuperación — documentos adversariales ajustados para ser recuperados para muchas consultas no relacionadas ("secuestro de recuperación").
La técnica principal es AML.T0024 — Filtración a través de la API de Inferencia ML: un atacante utiliza acceso legítimo de inferencia/consulta para filtrar datos (texto fuente mediante inversión, pertenencia o extracción del modelo). Esta habilidad proporciona una evaluación repetible de las cinco clases de debilidades.
Cuándo Usar
- Durante una evaluación de seguridad de cualquier aplicación RAG / búsqueda vectorial (cobertura OWASP LLM08).
- Cuando un almacén de vectores es multiinquilino y debe demostrar el aislamiento de espacio de nombres/metadatos.
- Cuando el corpus acepta documentos proporcionados por el usuario o de terceros (superficie de envenenamiento).
- Cuando el endpoint de embedding es accesible externamente (superficie de inversión/pertenencia).
- Al validar los controles de filtrado de recuperación antes de la puesta en producción.
Requisitos Previos
- Autorización y alcance que cubra el endpoint de embedding objetivo y el almacén de vectores.
- Python 3.10+.
- Acceso de lectura (y, para pruebas de envenenamiento, de escritura) a una colección de prueba — nunca al corpus de producción.
# Clientes de BD vectoriales + embeddings + herramientas de similitud
python -m pip install numpy scikit-learn sentence-transformers
python -m pip install qdrant-client chromadb pinecone-client weaviate-client
# (opcional) línea base de investigación de inversión de embeddings
python -m pip install vec2text
Objetivos
- Medir la exposición a la inversión de embeddings en el modelo de embedding objetivo.
- Ejecutar una prueba de inferencia de pertenencia contra el corpus.
- Probar el aislamiento multiinquilino (espacio de nombres, filtro de metadatos, RBAC) para fugas entre inquilinos.
- Insertar fragmentos envenenados benignos en una colección de prueba y medir el dominio de recuperación.
- Detectar contenido de inyección indirecta de prompts que sobrevive en fragmentos recuperados.
- Recomendar controles: filtros con ámbito de inquilino, validación de contenido, límites de acceso a embeddings.
Mapeo MITRE ATT&CK
| ID | Táctica | Nombre Oficial de la Técnica | Rol en esta habilidad |
|---|---|---|---|
| AML.T0024 | ATLAS: Filtración | Filtración a través de la API de Inferencia ML | Usar acceso a consultas/embedding para filtrar datos fuente |
| AML.T0024.000 | ATLAS: Filtración | Inferir Pertenencia a Datos de Entrenamiento | Prueba de inferencia de pertenencia contra el corpus |
| AML.T0024.001 | ATLAS: Filtración | Invertir Modelo ML | Reconstrucción de texto fuente mediante inversión de embeddings |
| AML.T0020 | ATLAS: Desarrollo de Recursos | Envenenar Datos de Entrenamiento | Envenenamiento de la base de conocimiento del corpus |
| AML.T0051.001 | ATLAS: Acceso Inicial | Inyección de Prompts en LLM: Indirecta | Cargas útiles de inyección incrustadas en fragmentos recuperados |
Flujo de Trabajo
Paso 1: Inventariar la tubería RAG
Documentar el modelo de embedding + dimensiones, el almacén de vectores y su modelo de tenencia, la estrategia de fragmentación, el top_k de recuperación y la métrica de similitud (coseno/punto/L2), y cualquier filtro de metadatos aplicado en el momento de la consulta.
# Ejemplo: inspeccionar una colección de Qdrant
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
info = client.get_collection("docs")
print(info.config.params.vectors) # tamaño + métrica de distancia
print(client.count("docs")) # tamaño del corpus
Paso 2: Probar la exposición a la inversión de embeddings
Los embeddings de texto similar están cerca; un atacante con el endpoint de embedding puede reconstruir iterativamente texto cuyo embedding coincida con un vector objetivo. Mida cuánto recupera un vecino más cercano en el espacio de embeddings, usando la similitud coseno entre reconstrucciones candidatas y el objetivo.
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
secret = "Paciente Juan Pérez, MRN 553120, diagnosticado con hipertensión."
target_vec = model.encode([secret])
# El atacante solo tiene target_vec y el endpoint de embedding. Escalar texto candidato.
candidates = [
"Nombre del paciente y número de historia clínica con un diagnóstico.",
"Juan Pérez hipertensión historia clínica diagnóstico",
"Paciente Juan Pérez MRN diagnosticado hipertensión",
]
cand_vecs = model.encode(candidates)
sims = cosine_similarity(target_vec, cand_vecs)[0]
for c, s in sorted(zip(candidates, sims), key=lambda x: -x[1]):
print(f"{s:.3f} {c}")
# Similitud alta para una conjetura casi textual => el riesgo de inversión es real para este modelo.
Para una línea base de reconstrucción de nivel de investigación, se puede usar vec2text contra modelos de embedding compatibles para demostrar la recuperación completa del texto.
Paso 3: Inferencia de pertenencia
Determinar si un documento específico está en el corpus midiendo la similitud de recuperación top-1 para una consulta de cita exacta: los elementos dentro del corpus devuelven una similitud máxima notablemente más alta que los controles fuera del corpus.
def membership_score(client, collection, embed, text):
vec = embed([text])[0].tolist()
hits = client.search(collection_name=collection, query_vector=vec, limit=1)
return hits[0].score if hits else 0.0
in_corpus = membership_score(client, "docs", model.encode, "<cita exacta de un fragmento conocido>")
control = membership_score(client, "docs", model.encode, "Una oración aleatoria no relacionada.")
print(f"en-corpus={in_corpus:.3f} control={control:.3f} delta={in_corpus-control:.3f}")
# Un delta grande y positivo indica que el elemento está en el corpus (fuga de pertenencia).
Paso 4: Probar el aislamiento multiinquilino
Confirmar que el inquilino B no puede recuperar fragmentos del inquilino A. Emitir consultas autenticadas como inquilino B que deberían estar filtradas, y verificar que ningún tenant_id del inquilino A aparezca en los resultados.
# Consultar como inquilino B; esperar SOLO resultados con tenant_id == "B".
from qdrant_client.models import Filter, FieldCondition, MatchValue
vec = model.encode(["información salarial confidencial"])[0].tolist()
hits = client.search(
collection_name="docs",
query_vector=vec,
limit=10,
query_filter=Filter(must=[FieldCondition(key="tenant_id", match=MatchValue(value="B"))]),
)
leaked = [h for h in hits if h.payload.get("tenant_id") != "B"]
print("FUGA ENTRE INQUILINOS" if leaked else "aislamiento OK", "->", len(leaked), "filas extrañas")
# Prueba crítica: repetir SIN el filtro para confirmar que el servidor, no el cliente,
# aplica el aislamiento. Si las consultas sin filtro devuelven datos del inquilino A, el aislamiento es solo del lado del cliente.
hits_nofilter = client.search(collection_name="docs", query_vector=vec, limit=10)
print("el aislamiento del lado del servidor FALLA" if any(h.payload.get("tenant_id") != "B" for h in hits_nofilter) else "OK")
Paso 5: Envenenamiento de la base de conocimiento (solo colección de prueba)
Insertar un fragmento envenenado benigno diseñado para ser recuperado para muchas consultas no relacionadas, luego medir con qué frecuencia aparece en top_k.
from qdrant_client.models import PointStruct
# Carga útil marcadora benigna (sin inyección real) para medir el dominio de recuperación.
poison = "POISON-CANARY. " + " ".join(
["restablecer contraseña facturación reembolso cuenta inicio de sesión soporte error ayuda"] * 8
)
client.upsert("docs_test", points=[
PointStruct(id=999999, vector=model.encode([poison])[0].tolist(),
payload={"tenant_id": "B", "source": "poison-test"})
])
queries = ["cómo obtener un reembolso", "restablecer mi contraseña", "cómo está el clima"]
for q in queries:
hits = client.search("docs_test", model.encode([q])[0].tolist(), limit=5)
dominated = any(h.payload.get("source") == "poison-test" for h in hits)
print(f"{'VENENO en top5' if dominated else 'limpio'}: {q}")
Paso 6: Detectar inyección indirecta de prompts en fragmentos recuperados
Escanear el texto de los fragmentos recuperados en busca de marcadores de inyección antes de que se concatenen en el prompt.
import re
INJECTION_PATTERNS = [
r"ignora (todas|las instrucciones anteriores|las instrucciones anteriores)",
r"prompt del sistema", r"ahora eres", r"ignorar", r"</?(sistema|instrucciones)>",
]
def chunk_is_injection(text):
low = text.lower()
return [p for p in INJECTION_PATTERNS if re.search(p, low)]
for hit in client.search("docs", model.encode(["ayuda"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("INYECCIÓN INDIRECTA en fragmento", hit.id, flags)
Paso 7: Informar y remediar
- Inversión/pertenencia: limitar la tasa y autenticar el endpoint de embedding; evitar devolver puntuaciones de similitud sin procesar; restringir quién puede consultar embeddings.
- Entre inquilinos: aplicar filtros de inquilino del lado del servidor (colecciones/espacios de nombres separados por inquilino cuando sea factible); nunca depender de filtros proporcionados por el cliente.
- Envenenamiento: validar y etiquetar el origen de cada fragmento ingerido; escanear las entradas en busca de inyecciones; limitar la cuota de cualquier fuente única en la recuperación.
- Inyección indirecta: sanitizar los fragmentos recuperados y aplicar barreras de seguridad de salida (consulte
defending-llms-with-guardrails).
Herramientas y Recursos
| Herramienta | Propósito | Fuente Principal |
|---|---|---|
| OWASP LLM08 | Guía de Debilidades en Vectores y Embeddings | https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/ |
| sentence-transformers | Generación de embeddings para pruebas | https://www.sbert.net/ |
| Cliente Qdrant | Almacén de vectores + búsqueda filtrada | https://qdrant.tech/documentation/ |
| Chroma / Weaviate / Pinecone | Almacenes de vectores alternativos | https://docs.trychroma.com/ |
| vec2text | Línea base de investigación de inversión de embeddings | https://github.com/jxmorris12/vec2text |
| MITRE ATLAS | AML.T0024 Filtración a través de la API de Inferencia ML | https://atlas.mitre.org/ |
Criterios de Validación
- Tubería RAG inventariada (modelo de embedding, almacén, tenencia, métrica, top_k, filtros).
- Exposición a inversión de embeddings medida y clasificada.
- Delta de inferencia de pertenencia calculado para elementos dentro del corpus vs. controles.
- Aislamiento multiinquilino probado tanto con como sin filtros del cliente (confirmación de aplicación del lado del servidor).
- Dominio de envenenamiento medido solo en una colección de prueba.
- Fragmentos recuperados escaneados en busca de contenido de inyección indirecta.
- Hallazgos informados con remediación para cada clase de debilidad.
- Ningún corpus de producción modificado durante la evaluación.