assessing-vector-and-embedding-weaknesses について
RAG システムのベクトルと埋め込みの弱点を調べるセキュリティ評価用スキルです。反転、メンバーシップ推論、テナント分離、ポイズニング、間接プロンプトインジェクションの検査と対策を扱います。
主な機能
- 01埋め込み反転リスクの測定
- 02コーパスへの登録有無の推定
- 03テナント分離のテスト
- 04ポイズニング文書の検索優位性測定
- 05取得チャンクのインジェクション検査
- 06結果と緩和策の報告
活用シーン
- 01許可を得た RAG アプリの評価
- 02ベクトルストアのテナント境界の検証
- 03ユーザー文書の取り込み処理のテスト
- 04公開前の検索フィルター確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill assessing-vector-and-embedding-weaknessesインストール後、Claude Code で/assessing-vector-and-embedding-weaknessesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdstandards.mdscripts/agent.py詳細
ベクトルと埋め込みの脆弱性の評価
許可された用途に限ります: これらのテストは、お客様が所有する、または評価を許可されている RAG システムのベクトルストアおよび埋め込みモデルに対して実施します。管理権限のないシステムに対する埋め込みの逆変換やテナント間の探索は、第三者のデータを漏えいさせるおそれがあり、許可なく実施することは禁止されています。
概要
検索拡張生成(RAG)システムは、ドキュメントをベクトルデータベース(Pinecone、Qdrant、Weaviate、Chroma、pgvector、FAISS)に保存される埋め込みベクトルに変換し、LLM の応答の根拠とするために最も近いベクトルを検索します。OWASP の LLM08:2025 Vector and Embedding Weaknesses は、このレイヤーに固有のセキュリティリスクを対象としています。
- 埋め込みの逆変換 — 埋め込みは一方向ではありません。学習済みの逆変換モデル(またはブラックボックスによる再構成攻撃)は、ベクトルから元のテキストのかなりの部分を復元し、ソースドキュメントを漏えいさせる可能性があります(MITRE ATLAS AML.T0024.001 Invert ML Model に対応)。
- メンバーシップ推論 — 特定のレコードがコーパスに含まれていたかどうかを問い合わせます(AML.T0024.000)。
- テナント間/マルチテナントの漏えい — 1 つの名前空間/コレクションが共有されている場合や、フィルターによる分離が行われていない場合、あるテナントが別のテナントのチャンクを取得します。
- ナレッジベースのポイズニング — コーパスへの書き込みが可能な攻撃者が、検索を支配するように細工したチャンク(想定されるクエリとのコサイン類似度が高いもの)を挿入し、間接的なプロンプトインジェクションのペイロードを仕込みます。
- 検索の操作 — 多数の無関係なクエリで検索されるよう調整された敵対的なドキュメントです(「検索の乗っ取り」)。
親となる手法は AML.T0024 — Exfiltration via ML Inference API です。攻撃者は正規の推論/クエリアクセスを利用してデータを持ち出します(逆変換、メンバーシップ推論、モデル抽出によるソーステキストの持ち出し)。このスキルでは、5 種類すべての脆弱性クラスを再現可能な方法で評価します。
使用する場面
- RAG/ベクトル検索アプリケーションのセキュリティ評価時(OWASP LLM08 のカバレッジ)。
- ベクトルストアがマルチテナントで、名前空間/メタデータの分離を検証する必要がある場合。
- コーパスがユーザー提供またはサードパーティのドキュメントを受け入れる場合(ポイズニングの攻撃対象領域)。
- 埋め込みエンドポイントに外部からアクセスできる場合(逆変換/メンバーシップ推論の攻撃対象領域)。
- 本番稼働前に検索フィルタリングの制御を検証する場合。
前提条件
- 対象の埋め込みエンドポイントおよびベクトルストアを含む、承認と評価範囲。
- Python 3.10 以降。
- テスト用コレクションへの読み取りアクセス(ポイズニングテストでは書き込みアクセスも必要です)。本番コーパスは決して使用しないでください。
# Vector DB clients + embeddings + similarity tooling
python -m pip install numpy scikit-learn sentence-transformers
python -m pip install qdrant-client chromadb pinecone-client weaviate-client
# (optional) text-embedding inversion research baseline
python -m pip install vec2text
目標
- 対象の埋め込みモデルにおける埋め込み逆変換の露出度を測定します。
- コーパスに対してメンバーシップ推論プローブを実行します。
- テナント間の漏えいがないか、マルチテナント分離(名前空間、メタデータフィルター、RBAC)をテストします。
- テスト用コレクションに無害なポイズニング済みチャンクを挿入し、検索を支配する度合いを測定します。
- 検索されたチャンクに間接的なプロンプトインジェクションのコンテンツが残っているかを検出します。
- テナントごとのフィルター、コンテンツ検証、埋め込みアクセス制限などの制御を推奨します。
MITRE ATT&CK との対応
| ID | 戦術 | 正式な手法名 | このスキルでの役割 |
|---|---|---|---|
| AML.T0024 | ATLAS:データの持ち出し | Exfiltration via ML Inference API | クエリ/埋め込みアクセスを使用したソースデータの持ち出し |
| AML.T0024.000 | ATLAS:データの持ち出し | Infer Training Data Membership | コーパスに対するメンバーシップ推論プローブ |
| AML.T0024.001 | ATLAS:データの持ち出し | Invert ML Model | 埋め込みの逆変換によるソーステキストの再構成 |
| AML.T0020 | ATLAS:リソース開発 | Poison Training Data | コーパスのナレッジベースポイズニング |
| AML.T0051.001 | ATLAS:初期アクセス | LLM Prompt Injection: Indirect | 検索されたチャンクに埋め込まれたインジェクションペイロード |
ワークフロー
ステップ 1:RAG パイプラインの棚卸し
埋め込みモデルと次元数、ベクトルストアとそのテナンシーモデル、チャンク分割の戦略、検索時の top_k と類似度指標(コサイン/ドット積/L2)、クエリ実行時に適用されるメタデータフィルターを記録します。
# Example: inspect a Qdrant collection
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
info = client.get_collection("docs")
print(info.config.params.vectors) # size + distance metric
print(client.count("docs")) # corpus size
ステップ 2:埋め込み反転のリスクをテストする
類似したテキストの埋め込みは互いに近くなります。埋め込みエンドポイントにアクセスできる攻撃者は、ターゲットベクトルと一致する埋め込みを持つテキストを反復的に再構築できます。候補となる再構築テキストとターゲット間のコサイン類似度を使って、埋め込み空間内の最近傍検索でどの程度の情報が復元されるかを測定します。
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, diagnosed with hypertension."
target_vec = model.encode([secret])
# Attacker has only target_vec and the embedding endpoint. Hill-climb candidate text.
candidates = [
"Patient name and medical record number with a diagnosis.",
"John Doe medical record hypertension diagnosis",
"Patient John Doe MRN diagnosed 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}")
# High similarity for a near-verbatim guess => inversion risk is real for this model.
研究レベルの再構築ベースラインには、互換性のある埋め込みモデルに対して vec2text を使用し、全文が復元されることを実証できます。
ステップ 3:メンバーシップ推論
特定のドキュメントがコーパスに含まれているかどうかを、完全一致の引用文を使ったクエリに対するトップ 1 の検索類似度で判断します。コーパス内のアイテムは、コーパス外の対照アイテムよりも最大類似度が顕著に高くなります。
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, "<exact quote from a known chunk>")
control = membership_score(client, "docs", model.encode, "An unrelated random sentence.")
print(f"in-corpus={in_corpus:.3f} control={control:.3f} delta={in_corpus-control:.3f}")
# A large positive delta indicates the item is in the corpus (membership leak).
ステップ 4:マルチテナントの分離をテストする
テナント B がテナント A のチャンクを検索できないことを確認します。フィルターが適用されるはずのクエリを、テナント B の認証情報で実行し、結果にテナント A の tenant_id が含まれないことを検証します。
# Query as tenant B; expect ONLY tenant_id == "B" results.
from qdrant_client.models import Filter, FieldCondition, MatchValue
vec = model.encode(["confidential salary information"])[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("CROSS-TENANT LEAK" if leaked else "isolation OK", "->", len(leaked), "foreign rows")
# Critical test: repeat WITHOUT the filter to confirm the server, not the client,
# enforces isolation. If unfiltered queries return tenant A data, isolation is client-side only.
hits_nofilter = client.search(collection_name="docs", query_vector=vec, limit=10)
print("server-side isolation FAILS" if any(h.payload.get("tenant_id") != "B" for h in hits_nofilter) else "OK")
ステップ 5:ナレッジベースのポイズニング(テスト用コレクションのみ)
無関係な多くのクエリで検索されるように作成した無害なポイズニング済みチャンクを挿入し、それが top_k に何回含まれるかを測定します。
from qdrant_client.models import PointStruct
# Benign marker payload (no real injection) to measure retrieval dominance.
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"{'POISON in top5' if dominated else 'clean'}: {q}")
ステップ 6:検索されたチャンク内の間接的なプロンプトインジェクションを検出する
取得したチャンクのテキストをプロンプトに連結する前に、インジェクションのマーカーをスキャンします。
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(["help"])[0].tolist(), limit=10):
flags = chunk_is_injection(hit.payload.get("text", ""))
if flags:
print("INDIRECT INJECTION in chunk", hit.id, flags)
ステップ 7:報告と修正
- 反転/メンバーシップ: 埋め込みエンドポイントにレート制限と認証を適用し、生の類似度スコアを返さないようにして、埋め込みにクエリできる利用者を制限します。
- テナント間のアクセス: テナントフィルターをサーバー側で強制します(可能であればテナントごとにコレクション/名前空間を分離します)。クライアントから提供されるフィルターに依存してはいけません。
- ポイズニング: 取り込むすべてのチャンクを検証してプロベナンスのタグを付け、入力をスキャンしてインジェクションを検出し、単一のソースが検索結果に占める割合を制限します。
- 間接的なインジェクション: 検索されたチャンクをサニタイズし、出力ガードレールを適用します(
defending-llms-with-guardrailsを参照)。
ツールとリソース
| ツール | 目的 | 主な情報源 |
|---|---|---|
| OWASP LLM08 | ベクトルと埋め込みの脆弱性に関するガイダンス | https://genai.owasp.org/llmrisk/llm082025-vector-and-embedding-weaknesses/ |
| sentence-transformers | テスト用の埋め込み生成 | https://www.sbert.net/ |
| Qdrant client | ベクトルストアとフィルター付き検索 | https://qdrant.tech/documentation/ |
| Chroma / Weaviate / Pinecone | 代替のベクトルストア | https://docs.trychroma.com/ |
| vec2text | 埋め込み反転の研究用ベースライン | https://github.com/jxmorris12/vec2text |
| MITRE ATLAS | AML.T0024 ML 推論 API 経由の流出 | https://atlas.mitre.org/ |
検証基準
- RAG パイプラインを棚卸しした(埋め込みモデル、ストア、テナンシー、指標、top_k、フィルター)。
- 埋め込み反転のリスクを測定し、評価した。
- コーパス内のアイテムと対照アイテムについて、メンバーシップ推論の差分を算出した。
- クライアントフィルターの有無の両方でマルチテナントの分離をテストした(サーバー側で強制されることを確認した)。
- テスト用コレクションのみで、ポイズニングの優勢度を測定した。
- 検索されたチャンクをスキャンし、間接的なインジェクションの内容を検出した。
- 脆弱性の各分類について、調査結果と修正策を報告した。
- 評価中に本番コーパスを変更していない。