Avaliando Fraquezas de Vetores e Embeddings
Uso autorizado apenas: Estes testes interagem com armazenamentos de vetores e modelos de embedding em sistemas RAG que você possui ou está autorizado a avaliar. A inversão de embeddings e a sondagem entre inquilinos em sistemas que você não controla podem expor dados de terceiros e são proibidas sem autorização.
Visão Geral
Sistemas de Geração Aumentada por Recuperação (RAG) convertem documentos em vetores de embedding armazenados em um banco de dados de vetores (Pinecone, Qdrant, Weaviate, Chroma, pgvector, FAISS) e recuperam os vetores mais próximos para fundamentar as respostas do LLM. O LLM08:2025 Fraquezas de Vetores e Embeddings da OWASP cobre os riscos de segurança únicos desta camada:
- Inversão de embedding — embeddings não são unidirecionais. Um modelo de inversão treinado (ou um ataque de reconstrução de caixa-preta) pode recuperar porções substanciais do texto original a partir do seu vetor, vazando documentos fonte (mapeia para o MITRE ATLAS AML.T0024.001 Inverter Modelo ML).
- Inferência de associação — consultar se um registro específico contribuiu para o corpus (AML.T0024.000).
- Vazamento entre inquilinos / multi-inquilino — quando um namespace/coleção é compartilhada ou o isolamento por filtro está ausente, um inquilino recupera fragmentos de outro inquilino.
- Envenenamento da base de conhecimento — um atacante que pode escrever no corpus insere fragmentos criados que dominam a recuperação (alta similaridade de cosseno com consultas esperadas) e carregam payloads de injeção indireta de prompt.
- Manipulação de recuperação — documentos adversários ajustados para serem recuperados para muitas consultas não relacionadas ("sequestro de recuperação").
A técnica pai é a AML.T0024 — Exfiltração via API de Inferência ML: um atacante usa acesso legítimo de inferência/consulta para exfiltrar dados (texto fonte via inversão, associação ou extração de modelo). Esta habilidade fornece uma avaliação repetível de todas as cinco classes de fraquezas.
Quando Usar
- Durante uma avaliação de segurança de qualquer aplicação RAG / busca vetorial (cobertura OWASP LLM08).
- Quando um armazenamento de vetores é multi-inquilino e você precisa provar o isolamento de namespace/metadados.
- Quando o corpus aceita documentos fornecidos pelo usuário ou de terceiros (superfície de envenenamento).
- Quando o endpoint de embedding é acessível externamente (superfície de inversão/associação).
- Ao validar controles de filtragem de recuperação antes da implantação.
Pré-requisitos
- Autorização e escopo cobrindo o endpoint de embedding alvo e o armazenamento de vetores.
- Python 3.10+.
- Acesso de leitura (e, para testes de envenenamento, escrita) a uma coleção de teste — nunca ao corpus de produção.
# Clientes de banco de vetores + embeddings + ferramentas de similaridade
python -m pip install numpy scikit-learn sentence-transformers
python -m pip install qdrant-client chromadb pinecone-client weaviate-client
# (opcional) linha de base de pesquisa de inversão de embeddings de texto
python -m pip install vec2text
Objetivos
- Medir a exposição à inversão de embeddings no modelo de embedding alvo.
- Realizar uma sonda de inferência de associação contra o corpus.
- Testar o isolamento multi-inquilino (namespace, filtro de metadados, RBAC) para vazamento entre inquilinos.
- Injetar fragmentos envenenados benignos em uma coleção de teste e medir o domínio de recuperação.
- Detectar conteúdo de injeção indireta de prompt sobrevivendo em fragmentos recuperados.
- Recomendar controles: filtros com escopo de inquilino, validação de conteúdo, limites de acesso a embeddings.
Mapeamento MITRE ATT&CK
| ID | Tática | Nome Oficial da Técnica | Papel nesta habilidade |
|---|---|---|---|
| AML.T0024 | ATLAS: Exfiltração | Exfiltração via API de Inferência ML | Usar acesso a consultas/embeddings para exfiltrar dados fonte |
| AML.T0024.000 | ATLAS: Exfiltração | Inferir Associação de Dados de Treinamento | Sonda de inferência de associação contra o corpus |
| AML.T0024.001 | ATLAS: Exfiltração | Inverter Modelo ML | Reconstrução por inversão de embeddings do texto fonte |
| AML.T0020 | ATLAS: Desenvolvimento de Recursos | Envenenar Dados de Treinamento | Envenenamento da base de conhecimento do corpus |
| AML.T0051.001 | ATLAS: Acesso Inicial | Injeção de Prompt em LLM: Indireta | Payloads de injeção incorporados em fragmentos recuperados |
Fluxo de Trabalho
Passo 1: Inventariar o pipeline RAG
Documente o modelo de embedding + dimensões, o armazenamento de vetores e seu modelo de inquilino, a estratégia de fragmentação (chunking), o top_k de recuperação e a métrica de similaridade (cosseno/produto escalar/L2), e quaisquer filtros de metadados aplicados no momento da consulta.
# Exemplo: inspecionar uma coleção Qdrant
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
info = client.get_collection("docs")
print(info.config.params.vectors) # tamanho + métrica de distância
print(client.count("docs")) # tamanho do corpus
Passo 2: Testar exposição à inversão de embeddings
Embeddings de textos semelhantes estão próximos; um atacante com o endpoint de embedding pode iterativamente reconstruir texto cujo embedding corresponde a um vetor alvo. Meça o quanto uma recuperação por vizinho-mais-próximo no espaço de embedding recupera, usando similaridade de cosseno entre reconstruções candidatas e o alvo.
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
secret = "Paciente João Silva, prontuário 553120, diagnosticado com hipertensão."
target_vec = model.encode([secret])
# Atacante tem apenas target_vec e o endpoint de embedding. Escalada de texto candidato.
candidates = [
"Nome do paciente e número de prontuário com um diagnóstico.",
"João Silva prontuário médico diagnóstico hipertensão",
"Paciente João Silva prontuário diagnosticado hipertensão",
]
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}")
# Similaridade alta para um palpite quase literal => o risco de inversão é real para este modelo.
Para uma linha de base de reconstrução de nível de pesquisa, vec2text pode ser usado contra modelos de embedding compatíveis para demonstrar recuperação de texto completo.
Passo 3: Inferência de associação
Determine se um documento específico está no corpus medindo a similaridade de recuperação top-1 para uma consulta de citação exata: itens no corpus retornam uma similaridade máxima visivelmente maior do que controles fora do 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ção exata de um fragmento conhecido>")
control = membership_score(client, "docs", model.encode, "Uma frase aleatória não relacionada.")
print(f"no-corpus={in_corpus:.3f} controle={control:.3f} delta={in_corpus-control:.3f}")
# Um delta positivo grande indica que o item está no corpus (vazamento de associação).
Passo 4: Testar isolamento multi-inquilino
Confirme que o inquilino B não pode recuperar fragmentos do inquilino A. Emita consultas autenticadas como inquilino B que deveriam ser filtradas e verifique se nenhum tenant_id do inquilino A aparece nos resultados.
# Consultar como inquilino B; esperar APENAS resultados com tenant_id == "B".
from qdrant_client.models import Filter, FieldCondition, MatchValue
vec = model.encode(["informação 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("VAZAMENTO ENTRE INQUILINOS" if leaked else "isolamento OK", "->", len(leaked), "linhas estrangeiras")
# Teste crítico: repita SEM o filtro para confirmar que o servidor, não o cliente,
# impõe o isolamento. Se consultas sem filtro retornarem dados do inquilino A, o isolamento é apenas do lado do cliente.
hits_nofilter = client.search(collection_name="docs", query_vector=vec, limit=10)
print("isolamento do lado do servidor FALHA" if any(h.payload.get("tenant_id") != "B" for h in hits_nofilter) else "OK")
Passo 5: Envenenamento da base de conhecimento (apenas coleção de teste)
Insira um fragmento envenenado benigno criado para ser recuperado para muitas consultas não relacionadas e meça com que frequência ele aparece no top_k.
from qdrant_client.models import PointStruct
# Payload de marcador benigno (sem injeção real) para medir o domínio de recuperação.
poison = "POISON-CANARY. " + " ".join(
["password reset billing refund account login support error help"] * 8
)
client.upsert("docs_test", points=[
PointStruct(id=999999, vector=model.encode([poison])[0].tolist(),
payload={"tenant_id": "B", "source": "poison-test"})
])
queries = ["how do I get a refund", "reset my password", "what is the weather"]
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"{'ENVENENADO no top5' if dominated else 'limpo'}: {q}")
Passo 6: Detectar injeção indireta de prompt em fragmentos recuperados
Varra o texto do fragmento recuperado em busca de marcadores de injeção antes de ser concatenado ao prompt.
import re
PADROES_INJECAO = [
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 PADROES_INJECAO if re.search(p, low)]
for hit in client.search("docs", model.encode(["help"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("INJEÇÃO INDIRETA no fragmento", hit.id, flags)
Passo 7: Relatar e remediar
- Inversão/associação: limite de taxa e autentique o endpoint de embedding; evite retornar pontuações de similaridade brutas; restrinja quem pode consultar embeddings.
- Entre inquilinos: imponha filtros de inquilino no lado do servidor (coleções/namespaces separados por inquilino quando viável); nunca confie em filtros fornecidos pelo cliente.
- Envenenamento: valide e etiquete a proveniência de cada fragmento ingerido; varra entradas em busca de injeção; limite a participação de qualquer fonte única na recuperação.
- Injeção indireta: higienize fragmentos recuperados e aplique barreiras de proteção na saída (veja
defending-llms-with-guardrails).
Ferramentas e Recursos
| Ferramenta | Propósito | Fonte Principal |
|---|---|---|
| OWASP LLM08 | Orientação sobre Fraquezas de Vetores e Embeddings | https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/ |
| sentence-transformers | Geração de embeddings para teste | https://www.sbert.net/ |
| Cliente Qdrant | Armazenamento de vetores + busca filtrada | https://qdrant.tech/documentation/ |
| Chroma / Weaviate / Pinecone | Armazenamentos de vetores alternativos | https://docs.trychroma.com/ |
| vec2text | Linha de base de pesquisa de inversão de embeddings | https://github.com/jxmorris12/vec2text |
| MITRE ATLAS | AML.T0024 Exfiltração via API de Inferência ML | https://atlas.mitre.org/ |
Critérios de Validação
- Pipeline RAG inventariado (modelo de embedding, armazenamento, inquilinato, métrica, top_k, filtros).
- Exposição à inversão de embeddings medida e classificada.
- Delta de inferência de associação calculado para itens no corpus vs. controle.
- Isolamento multi-inquilino testado tanto com quanto sem filtros de cliente (confirmação de aplicação no lado do servidor).
- Domínio de envenenamento medido apenas em uma coleção de teste.
- Fragmentos recuperados varridos em busca de conteúdo de injeção indireta.
- Descobertas relatadas com remediação para cada classe de fraqueza.
- Nenhum corpus de produção modificado durante a avaliação.