嵌入反演
從嵌入向量還原原始文字、儲存嵌入所帶來的隱私影響、對向量資料庫的模型反演攻擊,以及嵌入空間分析技術。
嵌入反演
概觀
向量資料庫是 RAG 系統的核心,儲存支援語意搜尋與檢索的文件嵌入。這些嵌入通常被視為不透明的數值表徵——高維空間中的抽象點,能支援相似度搜尋卻不會直接揭露原始文件內容。這個假設是錯誤的。研究已證明,嵌入向量可被反演以還原原始文字的相當大部分,使得向量資料庫成為多數組織威脅模型中尚未納入考量的隱私負債。
嵌入反演利用嵌入模型為保留語意資訊而受訓的特性。良好的嵌入會捕捉原始文字的意義、主題、實體、情緒與關鍵細節。這種資訊的豐富性讓嵌入在檢索上具備實用價值,卻也使它容易被反演:嵌入保留的語意資訊越多,攻擊者能透過反演萃取的資訊就越多。現代嵌入模型(例如 sentence-transformers 系列、OpenAI 的 text-embedding 模型、Cohere 的 embed 模型)所產生的嵌入品質足以讓反演不只能還原主題與實體,甚至能還原原始文字中的特定片語、姓名、數字與關係。
隱私上的影響相當重大。在向量資料庫中儲存客戶資料、病歷、法律文件、財務資訊或其他敏感內容的組織,即使原始文件受到存取控制,仍可能透過嵌入本身暴露這些資訊。若攻擊者取得向量資料庫存取權——透過 API 存取、資料庫入侵或內部威脅——就可能僅憑嵌入重建敏感文件,完全繞過文件層級的存取控制。這造就了多數安全架構尚未處理的新型資料外洩向量。
除了直接反演以外,嵌入空間分析讓攻擊者得以在不還原個別文件的情況下,萃取有關語料庫的結構性資訊。叢集分析、密度估計、最近鄰探測等技術可揭示知識庫涵蓋哪些主題、每個主題各有多少文件,以及語料庫中是否含有特定類型的敏感資訊(病歷、財務資料、個人資訊)。此偵察能力讓針對知識庫最敏感部分的定向攻擊成為可能。
運作原理
取得嵌入向量
攻擊者從目標向量資料庫取得嵌入向量。存取途徑包含:直接資料庫存取(透過被入侵的憑證、SQL 注入或內部人員存取)、會回傳嵌入的 API 端點(某些搜尋 API 會暴露原始相似度分數或嵌入向量)、包含向量資料的備份檔或資料庫匯出,以及在服務間傳輸中攔截嵌入向量的網路攔截。部分情況下,攻擊者也可以對已知文字產生嵌入,將其作為分析其他嵌入時的參考點。
訓練或取得反演模型
攻擊者開發一個將嵌入空間映射回文字空間的模型。可行做法包含:以(嵌入、文字)配對訓練序列至序列模型(該配對來自與目標類似的語料);若可取得模型權重,則以嵌入模型本身的架構作為反演起點;採用迭代最佳化以尋找能產生匹配嵌入的文字;或使用針對特定嵌入模型、在大型語料上預訓練的反演模型。
反演目標嵌入
攻擊者將反演模型套用於目標嵌入,以還原近似的原始文字。還原品質各異:主題與領域通常以高準確率還原;命名實體與關鍵詞以中等準確率還原;精確措辭則僅部分還原。攻擊者可利用鄰近嵌入的脈絡(同一叢集中的文件可能共用主題與術語)與任何可得的元資料,進一步改進反演結果。
分析嵌入空間結構
除了個別反演之外,攻擊者亦分析嵌入空間的整體結構,以萃取語料庫層級的情報。這包含叢集以辨識主題群組、密度分析以估計各主題的文件數、離群點偵測以找出不尋常或敏感的文件,以及若有嵌入時間戳記則進行時間序列分析。
攻擊範例
範例 1:以反演模型還原文字
# 訓練反演模型以從嵌入還原文字
import torch
import torch.nn as nn
from sentence_transformers import SentenceTransformer
from transformers import GPT2LMHeadModel, GPT2Tokenizer
# 步驟 1:產生訓練資料
# 使用與目標領域相似的語料
embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
training_pairs = []
for document in domain_corpus:
embedding = embedding_model.encode(document)
training_pairs.append((embedding, document))
# 步驟 2:訓練反演模型
# 架構:嵌入向量 -> transformer 解碼器 -> 文字
class EmbeddingInverter(nn.Module):
def __init__(self, embedding_dim=384, vocab_size=50257):
super().__init__()
self.projection = nn.Linear(embedding_dim, 768)
self.decoder = GPT2LMHeadModel.from_pretrained('gpt2')
def forward(self, embedding, target_ids=None):
# 將嵌入投影至解碼器的隱藏維度
context = self.projection(embedding).unsqueeze(1)
# 作為文字生成的初始上下文
outputs = self.decoder(
inputs_embeds=context,
labels=target_ids
)
return outputs
inverter = EmbeddingInverter()
# 以(嵌入、文字)配對進行訓練……
# 步驟 3:反演目標嵌入
stolen_embeddings = load_stolen_vectors("target_db_export.npy")
for emb in stolen_embeddings:
recovered_text = inverter.generate(
torch.tensor(emb).unsqueeze(0),
max_length=256
)
print(f"Recovered: {recovered_text}")
# 典型的還原品質:
# 原文:「Patient John Smith, DOB 03/15/1985, diagnosed
# with Type 2 diabetes, prescribed metformin 500mg」
# 還原:「Patient [name], date of birth [date], diagnosed
# with Type 2 diabetes, prescribed metformin [dose]」
# 主題、醫療細節與用藥得以還原;
# 特定 PII 可能部分還原,視嵌入模型與
# 訓練語料重疊程度而定範例 2:迭代最佳化反演
# 技巧:透過迭代最佳化尋找能產生目標嵌入的文字
#(無需訓練)
from sentence_transformers import SentenceTransformer
import torch
model = SentenceTransformer('all-MiniLM-L6-v2')
target_embedding = torch.tensor(stolen_embedding)
# 使用語言模型產生候選文字
# 並針對嵌入相似度最佳化
from transformers import pipeline
generator = pipeline('text-generation', model='gpt2')
best_text = ""
best_similarity = -1
# 做法:產生多樣候選並選出最佳匹配
seed_prompts = [
"The document discusses",
"This text is about",
"The main topic is",
"According to this document,",
"The key information is"
]
for seed in seed_prompts:
# 產生多種延續文字
candidates = generator(
seed,
max_length=100,
num_return_sequences=50,
do_sample=True,
temperature=1.0
)
for candidate in candidates:
text = candidate['generated_text']
emb = torch.tensor(model.encode(text))
sim = torch.cosine_similarity(
emb.unsqueeze(0),
target_embedding.unsqueeze(0)
).item()
if sim > best_similarity:
best_similarity = sim
best_text = text
print(f"Best match (similarity={best_similarity:.4f}):")
print(best_text)
# 這種暴力做法能還原主題與關鍵詞
# 更精細的最佳化(梯度型、束搜尋)
# 可還原更具體的細節範例 3:嵌入空間語料庫分析
# 技巧:分析向量資料庫的結構
# 以萃取底層語料庫的情報
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.manifold import TSNE
# 從向量資料庫載入所有嵌入
all_embeddings = load_all_vectors("target_vector_db")
print(f"Total documents: {len(all_embeddings)}")
# 步驟 1:叢集分析——辨識主題群組
clustering = DBSCAN(eps=0.3, min_samples=5, metric='cosine')
labels = clustering.fit_predict(all_embeddings)
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
print(f"Distinct topic clusters: {n_clusters}")
# 步驟 2:以已知參考嵌入探測叢集
reference_topics = {
"medical_records": "Patient diagnosis treatment medication",
"financial_data": "Revenue profit loss quarterly earnings",
"personal_info": "Name address phone email social security",
"legal_documents": "Contract agreement liability indemnification",
"credentials": "Password API key token secret access"
}
embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
for topic, probe_text in reference_topics.items():
probe_embedding = embedding_model.encode(probe_text)
# 尋找鄰近此探針的文件
similarities = cosine_similarity_batch(
probe_embedding, all_embeddings
)
nearby_count = np.sum(similarities > 0.5)
max_similarity = np.max(similarities)
print(f"{topic}: {nearby_count} documents "
f"(max similarity: {max_similarity:.3f})")
# 輸出可能揭示:
# medical_records: 12,450 documents (max similarity: 0.89)
# financial_data: 3,200 documents (max similarity: 0.92)
# personal_info: 8,700 documents (max similarity: 0.85)
# legal_documents: 5,100 documents (max similarity: 0.88)
# credentials: 47 documents (max similarity: 0.73)
# 這告訴攻擊者:
# - 知識庫包含病歷(HIPAA 疑慮)
# - 存在財務資料(合規疑慮)
# - 個人資訊廣泛(GDPR 疑慮)
# - 存在部分類似憑證的內容(安全疑慮)
# 全部無需還原任何個別文件範例 4:最近鄰隱私攻擊
# 技巧:判斷特定個人資料是否
# 存在於向量資料庫中(RAG 的成員推論)
# 攻擊者建立包含目標個人
# 已知資訊的探針文件
target_person = "Jane Doe, 123 Main Street, Springfield"
# 產生探針嵌入
probe = embedding_model.encode(
f"Information about {target_person}"
)
# 向向量資料庫查詢最近鄰
results = vector_db.search(
vector=probe,
top_k=10,
include_scores=True
)
# 若排名最前的結果相似度極高(>0.85),
# 則關於此人的文件可能存在於資料庫中
if results[0].score > 0.85:
print(f"High confidence: data about {target_person} "
f"exists in the knowledge base")
# 反演最近鄰嵌入以還原細節
recovered = inverter.generate(results[0].vector)
print(f"Recovered content: {recovered}")
# 此攻擊可自動化以檢查數千位個人,
# 構成大規模隱私探測:
for person in target_list:
probe = embedding_model.encode(f"Records for {person}")
top_score = vector_db.search(probe, top_k=1)[0].score
if top_score > 0.85:
flagged_individuals.append(person)
print(f"Found data for {len(flagged_individuals)} "
f"of {len(target_list)} individuals")範例 5:跨租戶資料洩漏(共用嵌入空間)
情境:多租戶 RAG 平台,不同組織
共用相同的向量資料庫基礎設施
架構:
租戶 A 嵌入 → | |
租戶 B 嵌入 → | 共用向量資料庫 | → 檢索
租戶 C 嵌入 → | |
預期隔離方式:基於元資料的過濾
來自租戶 A 的查詢 → 過濾條件:tenant_id = "A" → 僅 A 的文件
攻擊向量:
1. 租戶 ID 偽造:
- 若攻擊者可影響 tenant_id 過濾器
- 以 tenant_id = "B" 查詢會回傳租戶 B 的文件
- 其他租戶的嵌入可直接存取
2. 近似最近鄰洩漏:
- ANN 演算法(HNSW、IVF)建立共用索引結構
- 索引結構本身會跨租戶邊界
洩漏鄰近向量的資訊
- 即使套用正確過濾,索引遍歷仍可能
對跨租戶文件暴露相似度分數
3. 嵌入碰撞分析:
- 攻擊者對已知文件產生嵌入
- 比較其嵌入位置與全域索引
- 辨識屬於其他租戶的叢集
- 推論其他租戶資料的主題分佈
4. 邊通道時間攻擊:
- 查詢延遲隨索引結構變化
- 密集叢集附近的查詢耗時較長(候選較多)
- 攻擊者可將延遲樣式對應,以辨識
其他租戶擁有哪些主題的文件
偵測與緩解
| 方法 | 說明 | 有效性 |
|---|---|---|
| 嵌入加密 | 靜態時加密嵌入向量,僅於搜尋運算時解密 | 中高 |
| 維度縮減雜訊 | 對儲存的嵌入加入經校準的雜訊,保留檢索品質同時降低反演效果 | 中 |
| 向量資料庫存取控制 | 對向量資料庫 API 與匯出實施嚴格存取控制 | 高 |
| 租戶隔離 | 為不同租戶使用獨立的向量資料庫實例(而非僅以元資料過濾) | 高 |
| 嵌入 API 速率限制 | 限制相似度搜尋的數量與速率,以防止大規模探測 | 中 |
| 向量存取稽核日誌 | 記錄所有向量資料庫查詢並標記異常存取樣式 | 中 |
| 嵌入差分隱私 | 於產生嵌入時套用差分隱私保證 | 中(新興) |
| 定期輪替嵌入 | 定期以更新的模型重新產生文件嵌入,使舊的反演失效 | 低至中 |
| 抗反演嵌入模型 | 採用或開發受訓以抗反演並同時維持檢索品質的嵌入模型 | 中(新興研究) |
關鍵考量
- 當攻擊者能取用目標所使用的相同嵌入模型時,嵌入反演品質會提升——使用廣為人知的嵌入模型使反演更容易
- 隱私風險隨嵌入維度擴大:更高維度的嵌入(768、1024、1536 維)保留更多資訊,也更容易被反演
- 向量資料庫備份、匯出與副本皆為潛在外洩目標——嵌入的任何副本都能在離線狀態下反演,無需再對生產系統進行存取
- 採用元資料隔離的多租戶向量資料庫提供的保護不足,因為嵌入向量本身就會洩漏跨租戶資訊
- 嵌入差分隱私是活躍的研究領域,但尚未產出能於維持檢索品質的同時提供強隱私保證的生產級方案
- 受 GDPR、HIPAA 或其他資料保護法規規範的組織,應評估其向量資料庫是否構成個人資料儲存,並套用適當保護
- 紅隊評估應將向量資料庫存取納入攻擊目標,測試嵌入反演是否能從檢索層還原敏感內容
參考資料
- Morris 等人:「Text Embeddings Reveal (Almost) As Much As Text」(EMNLP 2023) —— 嵌入反演的基礎研究
- Li 等人:「Sentence Embedding Leaks More Information than You Expect」(ACL 2023) —— 句子嵌入的隱私分析
- Song 與 Raghunathan:「Information Leakage in Embedding Models」(ACM CCS 2020) —— 資訊洩漏的形式化分析
- Carlini 等人:「Extracting Training Data from Large Language Models」(2021) —— 相關萃取技術
- Pan 等人:「Privacy Risks in Vector Databases」(2024) —— 向量資料庫隱私威脅全面分析