Que fait Évaluation des faiblesses des vecteurs et des embeddings ?
Utilisation autorisée uniquement : Ces tests interagissent avec des magasins de vecteurs et des modèles d'embedding dans les systèmes RAG que vous possédez ou que vous êtes autorisé à évaluer. L'inversion d'embedding et le sondage entre locataires contre des systèmes que vous ne contrôlez pas peuvent exposer des données tierces et sont interdits sans autorisation.
Aperçu
Les systèmes de génération augmentée par récupération (RAG) convertissent les documents en vecteurs d'embedding stockés dans une base de données vectorielle (Pinecone, Qdrant, Weaviate, Chroma, pgvector, FAISS) et récupèrent les vecteurs les plus proches pour étayer les réponses des LLM. La catégorie OWASP LLM08:2025 Faiblesses des vecteurs et des embeddings couvre les risques de sécurité propres à cette couche :
- Inversion d'embedding — les embeddings ne sont pas unidirectionnels. Un modèle d'inversion entraîné (ou une attaque de reconstruction en boîte noire) peut récupérer des parties substantielles du texte original à partir de son vecteur, divulguant les documents sources (correspond à MITRE ATLAS AML.T0024.001 Inverser le modèle ML).
- Inférence d'appartenance — interroger si un enregistrement spécifique a contribué au corpus (AML.T0024.000).
- Fuite entre locataires / multi-locataires — lorsqu'un espace de noms/collection est partagé ou que l'isolation par filtre est absente, un locataire récupère les fragments d'un autre locataire.
- Empoisonnement de la base de connaissances — un attaquant qui peut écrire dans le corpus insère des fragments conçus qui dominent la récupération (similarité cosinus élevée avec les requêtes attendues) et transportent des charges utiles d'injection indirecte.
- Manipulation de la récupération — des documents adverses conçus pour être récupérés pour de nombreuses requêtes non liées (« détournement de récupération »).
La technique parente est AML.T0024 — Exfiltration via une API d'inférence ML : un attaquant utilise un accès légitime à l'inférence/requête pour exfiltrer des données (texte source via inversion, appartenance ou extraction de modèle). Cette compétence fournit une évaluation reproductible des cinq classes de faiblesses.
Quand utiliser
- Lors d'une évaluation de sécurité de toute application RAG / recherche vectorielle (couverture OWASP LLM08).
- Lorsqu'un magasin de vecteurs est multi-locataire et que vous devez prouver l'isolation des espaces de noms/métadonnées.
- Lorsque le corpus accepte des documents fournis par l'utilisateur ou par des tiers (surface d'empoisonnement).
- Lorsque le point de terminaison d'embedding est accessible de l'extérieur (surface d'inversion/appartenance).
- Lors de la validation des contrôles de filtrage de récupération avant le lancement.
Prérequis
- Autorisation et périmètre couvrant le point de terminaison d'embedding cible et le magasin de vecteurs.
- Python 3.10+.
- Accès en lecture (et, pour les tests d'empoisonnement, en écriture) à une collection de test — jamais le corpus de production.
# Clients de bases vectorielles + embeddings + outils de similarité
python -m pip install numpy scikit-learn sentence-transformers
python -m pip install qdrant-client chromadb pinecone-client weaviate-client
# (optionnel) base de recherche d'inversion d'embedding textuelle
python -m pip install vec2text
Objectifs
- Mesurer l'exposition à l'inversion d'embedding sur le modèle d'embedding cible.
- Exécuter un sondage d'inférence d'appartenance contre le corpus.
- Tester l'isolation multi-locataires (espace de noms, filtre de métadonnées, RBAC) pour les fuites entre locataires.
- Injecter des fragments empoisonnés bénins dans une collection de test et mesurer la dominance de récupération.
- Détecter le contenu d'injection indirecte de prompt survivant dans les fragments récupérés.
- Recommander des contrôles : filtres limités au locataire, validation du contenu, limites d'accès aux embeddings.
Cartographie MITRE ATT&CK
| ID | Tactique | Nom officiel de la technique | Rôle dans cette compétence |
|---|---|---|---|
| AML.T0024 | ATLAS : Exfiltration | Exfiltration via API d'inférence ML | Utilisation de l'accès aux requêtes/embeddings pour exfiltrer des données source |
| AML.T0024.000 | ATLAS : Exfiltration | Inférer l'appartenance aux données d'entraînement | Sondage d'inférence d'appartenance contre le corpus |
| AML.T0024.001 | ATLAS : Exfiltration | Inverser le modèle ML | Reconstruction par inversion d'embedding du texte source |
| AML.T0020 | ATLAS : Développement de ressources | Empoisonner les données d'entraînement | Empoisonnement de la base de connaissances du corpus |
| AML.T0051.001 | ATLAS : Accès initial | Injection indirecte de prompt LLM | Charges utiles d'injection intégrées dans les fragments récupérés |
Flux de travail
Étape 1 : Inventorier le pipeline RAG
Documentez le modèle d'embedding + dimensions, le magasin de vecteurs et son modèle de location, la stratégie de fragmentation, le top_k de récupération et la métrique de similarité (cosinus/point/L2), ainsi que tous les filtres de métadonnées appliqués au moment de la requête.
# Exemple : inspecter une collection Qdrant
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
info = client.get_collection("docs")
print(info.config.params.vectors) # taille + métrique de distance
print(client.count("docs")) # taille du corpus
Étape 2 : Tester l'exposition à l'inversion d'embedding
Les embeddings de textes similaires sont proches ; un attaquant disposant du point de terminaison d'embedding peut reconstruire itérativement un texte dont l'embedding correspond à un vecteur cible. Mesurez dans quelle mesure un voisin le plus proche dans l'espace d'embedding se rapproche, en utilisant la similarité cosinus entre les reconstructions candidates et la cible.
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
secret = "Patient John Doe, MRN 553120, diagnostiqué avec une hypertension."
target_vec = model.encode([secret])
# L'attaquant n'a que target_vec et le point de terminaison d'embedding. Escalade de colline du texte candidat.
candidates = [
"Nom du patient et numéro de dossier médical avec un diagnostic.",
"John Doe dossier médical hypertension diagnostic",
"Patient John Doe MRN diagnostiqué hypertension",
]
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}")
# Similarité élevée pour une estimation quasi textuelle => le risque d'inversion est réel pour ce modèle.
Pour une base de reconstruction de niveau recherche, vec2text peut être utilisé contre des modèles d'embedding compatibles pour démontrer une récupération complète du texte.
Étape 3 : Inférence d'appartenance
Déterminez si un document spécifique est dans le corpus en mesurant la similarité de récupération top-1 pour une requête de citation exacte : les éléments dans le corpus renvoient une similarité maximale nettement plus élevée que les témoins hors 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, "<citation exacte d'un fragment connu>")
control = membership_score(client, "docs", model.encode, "Une phrase aléatoire non liée.")
print(f"dans-corpus={in_corpus:.3f} control={control:.3f} delta={in_corpus-control:.3f}")
# Un delta positif important indique que l'élément est dans le corpus (fuite d'appartenance).
Étape 4 : Tester l'isolation multi-locataires
Confirmez que le locataire B ne peut pas récupérer les fragments du locataire A. Émettez des requêtes authentifiées par le locataire B qui devraient être filtrées, et vérifiez qu'aucun tenant_id du locataire A n'apparaît dans les résultats.
# Interroger en tant que locataire B ; attendre UNIQUEMENT les résultats avec tenant_id == "B".
from qdrant_client.models import Filter, FieldCondition, MatchValue
vec = model.encode(["informations salariales confidentielles"])[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("FUITE ENTRE LOCATAIRES" if leaked else "isolation OK", "->", len(leaked), "lignes étrangères")
# Test critique : répéter SANS le filtre pour confirmer que le serveur, et non le client,
# applique l'isolation. Si les requêtes non filtrées renvoient des données du locataire A, l'isolation est uniquement côté client.
hits_nofilter = client.search(collection_name="docs", query_vector=vec, limit=10)
print("l'isolation côté serveur ÉCHOUE" if any(h.payload.get("tenant_id") != "B" for h in hits_nofilter) else "OK")
Étape 5 : Empoisonnement de la base de connaissances (collection de test uniquement)
Insérez un fragment empoisonné bénin conçu pour être récupéré pour de nombreuses requêtes non liées, puis mesurez sa fréquence d'apparition dans top_k.
from qdrant_client.models import PointStruct
# Charge utile de marqueur bénin (pas de véritable injection) pour mesurer la dominance de récupération.
poison = "POISON-CANARY. " + " ".join(
["réinitialisation mot de passe facturation remboursement compte connexion support erreur aide"] * 8
)
client.upsert("docs_test", points=[
PointStruct(id=999999, vector=model.encode([poison])[0].tolist(),
payload={"tenant_id": "B", "source": "poison-test"})
])
queries = ["comment obtenir un remboursement", "réinitialiser mon mot de passe", "quel temps fait-il"]
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"{'POISON dans top5' if dominated else 'propre'} : {q}")
Étape 6 : Détecter l'injection indirecte de prompt dans les fragments récupérés
Analysez le texte des fragments récupérés pour détecter des marqueurs d'injection avant qu'il ne soit concaténé dans le prompt.
import re
INJECTION_PATTERNS = [
r"ignore (all|previous|the above) instructions",
r"system prompt", r"you are now", r"disregard", r"</?(system|instructions)>",
]
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(["aide"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("INJECTION INDIRECTE dans le fragment", hit.id, flags)
Étape 7 : Rapport et correction
- Inversion/appartenance : limitez de débit et authentifiez le point de terminaison d'embedding ; évitez de renvoyer les scores de similarité bruts ; restreignez qui peut interroger les embeddings.
- Entre locataires : appliquez les filtres de locataire côté serveur (collections/espaces de noms séparés par locataire lorsque c'est faisable) ; ne fiez jamais aux filtres fournis par le client.
- Empoisonnement : validez et étiquetez la provenance de chaque fragment ingéré ; analysez les entrées pour détecter les injections ; plafonnez la part de récupération d'une source unique.
- Injection indirecte : nettoyez les fragments récupérés et appliquez des garde-fous de sortie (voir
defending-llms-with-guardrails).
Outils et ressources
| Outil | Objectif | Source principale |
|---|---|---|
| OWASP LLM08 | Guide des faiblesses des vecteurs et embeddings | https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/ |
| sentence-transformers | Génération d'embeddings pour les tests | https://www.sbert.net/ |
| Client Qdrant | Magasin de vecteurs + recherche filtrée | https://qdrant.tech/documentation/ |
| Chroma / Weaviate / Pinecone | Magasins de vecteurs alternatifs | https://docs.trychroma.com/ |
| vec2text | Base de recherche d'inversion d'embedding | https://github.com/jxmorris12/vec2text |
| MITRE ATLAS | AML.T0024 Exfiltration via API d'inférence ML | https://atlas.mitre.org/ |
Critères de validation
- Pipeline RAG inventorié (modèle d'embedding, magasin, location, métrique, top_k, filtres).
- Exposition à l'inversion d'embedding mesurée et évaluée.
- Delta d'inférence d'appartenance calculé pour les éléments du corpus par rapport aux témoins.
- Isolation multi-locataires testée à la fois avec et sans filtres clients (application côté serveur confirmée).
- Dominance d'empoisonnement mesurée dans une collection de test uniquement.
- Fragments récupérés analysés pour détecter un contenu d'injection indirecte.
- Résultats rapportés avec des corrections pour chaque classe de faiblesse.
- Aucun corpus de production modifié pendant l'évaluation.