Aller au contenu principal

Model Inversion & Knowledge Base Extraction Defence

This page covers an important distinction: classical model inversion is low risk for this stack, but two closely related attacks are real and require active controls.


Classical Model Inversion β€” Why It's Low Risk Here​

Classical model inversion attacks query a model repeatedly to reconstruct its training data from its weights β€” extracting memorised private text that the model saw during training. This is well-documented against large language models where researchers extracted verbatim training samples via specific prompting.

This is low risk for this specific deployment because:

llama3.2:1b and llama3.2:3b are pre-trained base models from Meta, trained on public web data. HDI Seguros policy documents, claim records, underwriting criteria, and financial data are not in that training set β€” they are confidential internal documents that never reached the public internet. There is nothing to extract from the model weights that relates to HDI Seguros business.

The assessment changes completely if models are ever fine-tuned on internal documents. See the Fine-Tuning Gate section β€” this is a hard prerequisite before any fine-tuning phase.


The Two Real Attacks for RAG Systems​

Attack 1 β€” Knowledge Base Enumeration​

An attacker with a valid virtual key systematically queries the system to reconstruct the entire Qdrant knowledge base without ever accessing Qdrant directly:

"What is the franchise for water damage?" β†’ extracts €150
"What is the franchise for fire damage?" β†’ extracts €500
"What is the indemnity limit for property damage?" β†’ extracts €500,000
"What are the exclusions for clause 1?" β†’ extracts clause text
"What are the exclusions for clause 2?" β†’ extracts clause text
...200 queries later: full policy wording reconstructed

Each individual query is legitimate. Only the systematic pattern across many distinct topics reveals the attack. This requires no technical exploit β€” just a valid API key and a structured list of questions.

Risk in insurance: entire policy wordings, pricing structures, underwriting criteria, and claims procedures could be exfiltrated by a competitor, a disgruntled employee, or a compromised broker account. All of this is confidential commercial and regulatory information.

Attack 2 β€” Embedding Reconstruction​

nomic-embed-text embeddings (768-dimensional vectors) are stored in Qdrant alongside the original chunk text. Research (Vec2Text, 2023) has demonstrated that embedding vectors can be inverted to reconstruct the original text with meaningful accuracy for sentence-length inputs (~40–60% fidelity).

If Qdrant storage is compromised, an attacker with the raw embedding vectors could partially recover policy document text without having the documents themselves.

Risk level in this stack: lower than enumeration, because Qdrant is internal-only β€” it has no Ingress, is accessible only via Tailscale and Cilium NetworkPolicy, and requires a network-level compromise first. Mitigated by Longhorn LUKS encryption at rest.


What's Already In Your Stack​

ControlWhat it covers
Virtual key routing (issue #34)Department-scoped read access β€” IT key cannot query claims-docs
Per-key query rate limits (issue #34)Slows enumeration β€” limited requests per minute
DataSovereigntyHook (issue #42)Responses never leave your infrastructure
Tailscale + UFWQdrant unreachable from internet
Cilium NetworkPolicyPod-level network isolation within cluster

Virtual key routing already gives partial enumeration resistance β€” a compromised direction-it key can only enumerate the IT collection, not claims or finance. The blast radius of any single compromised key is scoped to one department's document set.


Defence Architecture​

PRE-CALL (EnumerationDetectionHook)
└── Track unique query topic fingerprints per API key per hour
β†’ warning at 60 distinct topics
β†’ block + CRITICAL event at 80 distinct topics

POST-CALL (verbatim reproduction check)
└── SequenceMatcher: response vs retrieved chunks
β†’ > 60% verbatim overlap β†’ abstraction_compliance = 0 + WARNING

SYSTEM PROMPT (high-stakes departments)
└── Abstraction constraint appended to claims/finance/legal/underwriting prompts
"Ne jamais reproduire textuellement plus de deux phrases consΓ©cutives"

STORAGE (Qdrant PVC)
└── Longhorn LUKS encryption at rest
β†’ embedding vectors + chunk text encrypted on disk
β†’ key managed by Vault (issue #15)

FINE-TUNING GATE
└── GitHub Actions workflow blocks fine-tuning unless safety checklist closed

Layer 1 β€” Enumeration Detection​

How it works​

Each query is fingerprinted by its first six normalised words. Redis HyperLogLog tracks the cardinality of unique fingerprints per API key per hour β€” the count of distinct topics, not the count of queries.

Fingerprint A"what is the franchise for water"
Fingerprint B"what is the franchise for fire"
Fingerprint C"what is the indemnity limit for"

Three distinct topics. The same question asked 80 times counts as one topic β€” normal repeated clarifications do not trigger the detector.

Thresholds​

ThresholdAction
60 unique topics / hourenumeration-warning WARNING event in Langfuse β€” monitoring escalated, request not blocked
80 unique topics / hourenumeration-detected ERROR event + HTTP 429 β€” request blocked, Grafana alert fires

Counter resets after 1 hour. A power user who legitimately asks many different questions will approach 60 but is unlikely to exceed 80 in a single hour of normal work β€” the threshold is calibrated to systematic machine-speed querying, not human browsing.

Reviewing an enumeration alert​

Langfuse β†’ Events β†’ filter: name = "enumeration-detected"
β†’ metadata.key_hash identifies the API key
β†’ metadata.unique_topics count at time of block
β†’ metadata.department which collection was being enumerated

Cross-reference with Langfuse Traces to see the full query sequence for that key hash:

Langfuse β†’ Traces β†’ filter by time window β†’ sort by created_at
β†’ look for short factual questions with systematic topic progression

If enumeration is confirmed, revoke the key:

curl -X DELETE http://litellm.ai.svc:4000/key/delete \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-d '{"keys": ["<virtual-key>"]}'

Layer 2 β€” Response Abstraction​

System prompt constraint​

All high-stakes department system prompts (claims, finance, legal, underwriting, audit) include:

RÈGLE SUPPLΓ‰MENTAIRE β€” REPRODUCTION TEXTUELLE:
Ne jamais reproduire textuellement plus de deux phrases consΓ©cutives d'un document source.
SynthΓ©tise, reformule et explique β€” ne copie pas le texte brut du document.
Si l'utilisateur demande explicitement une citation textuelle, indique:
"Je ne peux pas reproduire le texte intΓ©gral. RΓ©fΓ©rez-vous au document [source] page [X]."

This makes enumeration harder β€” the attacker gets a paraphrase, not the exact policy wording. Combined with the citation requirement from issue #44, the attacker knows which document to request but cannot reconstruct its text from model responses alone.

Post-call verbatim detection​

After every response, the abstraction_compliance score is computed using SequenceMatcher between the response text and each retrieved chunk:

ScoreMeaning
1Response is a genuine synthesis β€” no verbatim copying detected
0Response is > 60% verbatim overlap with a source chunk β€” flagged

A score of 0 fires a verbatim-reproduction-detected WARNING in Langfuse. This is non-blocking β€” it surfaces cases where the model ignored the abstraction constraint for human review and system prompt tuning.


Layer 3 β€” Qdrant PVC Encryption at Rest​

Why​

Qdrant stores two sensitive artefacts on disk: the original chunk text (policy document excerpts) and the nomic-embed-text embedding vectors. Without encryption, a compromised node, a stolen disk, or a Longhorn snapshot exfiltration exposes both in plaintext. The embedding vectors can be partially inverted to recover text even without the chunk payload.

Implementation β€” Longhorn LUKS​

Longhorn's built-in encryption support uses LUKS (Linux Unified Key Setup) at the volume level. The encryption key is stored in a Kubernetes Secret populated by the External Secrets Operator from Vault (issue #15) β€” never hardcoded.

# StorageClass: longhorn-encrypted
parameters:
encrypted: "true"
CRYPTO_KEY_CIPHER: "aes-xts-plain64"
CRYPTO_KEY_HASH: "sha256"
CRYPTO_KEY_SIZE: "256"
CRYPTO_PBKDF: "argon2i"

Qdrant's PVC uses storageClassName: longhorn-encrypted. The encryption is transparent to Qdrant β€” no application changes required.

Verifying encryption is active​

# On the node hosting the Qdrant PVC (check which node via kubectl)
ssh set-hog "lsblk -o NAME,TYPE,FSTYPE | grep crypto"
# β†’ shows a crypto_LUKS device backing the volume

# From Mac via controller:
kubectl get pvc -n ai
# β†’ qdrant-storage Bound longhorn-encrypted 10Gi

Migration from unencrypted PVC​

# 1. Scale down Qdrant
kubectl scale deployment qdrant -n ai --replicas=0

# 2. Snapshot existing PVC via Longhorn UI
# β†’ Longhorn UI β†’ Volumes β†’ qdrant-storage β†’ Create Snapshot

# 3. Restore snapshot into a new PVC using longhorn-encrypted StorageClass
# β†’ Longhorn UI β†’ Snapshots β†’ Restore β†’ StorageClass: longhorn-encrypted

# 4. Update Qdrant Helm values
ssh controller "/home/ktayl/.local/bin/helm upgrade qdrant qdrant/qdrant \
--namespace ai \
--values /home/ktayl/minicloud-ktaylorganisation/ansible/helm-values/qdrant-values.yaml \
--wait"

# 5. Scale back up and verify
kubectl scale deployment qdrant -n ai --replicas=1
kubectl rollout status deployment/qdrant -n ai

# 6. Verify collection counts match pre-migration
kubectl exec -n ai deployment/qdrant -- \
curl -s http://localhost:6333/collections | jq '.result.collections[].name'

Fine-Tuning Gate​

If fine-tuning of llama3.2 on internal HDI Seguros documents is ever considered, the following safety checklist must be completed and documented in a closed GitHub issue before the fine-tuning workflow can run:

ItemWhy
Differential privacy training (DP-SGD, Ξ΅ ≀ 8)Limits how much any single training example can influence model weights β€” caps memorisation
Training data PII scan + removal (Presidio batch)No names, IBANs, policy numbers, or national IDs in training set
Membership inference attack testVerify the fine-tuned model cannot confirm whether a specific document was in training data
Model inversion attack test (Carlini et al. protocol)Verify the model does not reproduce verbatim training text under systematic prompting
Output filter blocking verbatim training data reproductionLast-resort post-call filter if the above controls are insufficient
Legal reviewFine-tuning on policyholder documents may create GDPR Art. 22 obligations or IP issues

A GitHub Actions workflow (finetuning-gate.yml) enforces this β€” the fine-tuning run fails unless the checklist issue is closed. This gate cannot be bypassed without modifying the workflow, which itself requires a PR review.

Current status: fine-tuning is not planned. Base models (llama3.2:1b/3b) are used as-is. The gate exists to prevent the checklist being skipped when fine-tuning is eventually evaluated.


Grafana Alerting​

AlertConditionSeverity
KnowledgeBaseEnumerationDetectedAPI key reaches 80 unique topics / hourCritical, fires immediately
EnumerationWarningAPI key reaches 60 unique topics / hourWarning, fires immediately
VerbatimReproductionSpike> 15% of responses have abstraction_compliance = 0 over 1 hourWarning, fires after 15 min

Operational Checks​

# Check enumeration counters in Redis (any key near threshold)
kubectl exec -n ai deployment/redis -- \
redis-cli KEYS "enum_topics:*" | xargs -I{} sh -c \
'echo -n "{} topics: "; redis-cli PFCOUNT {}'

# Check verbatim reproduction rate (last 24h)
# β†’ Langfuse β†’ Scores β†’ abstraction_compliance β†’ filter value = 0

# Check enumeration events today
# β†’ Langfuse β†’ Events β†’ filter: name = "enumeration-detected" OR "enumeration-warning"

# Verify Qdrant PVC is on encrypted StorageClass
kubectl get pvc -n ai -o custom-columns=\
'NAME:.metadata.name,STORAGECLASS:.spec.storageClassName,STATUS:.status.phase'

# Confirm LUKS device active on Qdrant node
kubectl get pod -n ai -l app=qdrant -o wide # find node
ssh <qdrant-node> "lsblk -o NAME,TYPE,FSTYPE | grep -i luks"

What These Controls Do Not Cover​

GapReasonPath
Adversarial embedding queries (Vec2Text-style attack on live Qdrant API)Qdrant API is internal-only; requires cluster access firstCovered by Tailscale + Cilium NetworkPolicy
Enumeration below 80 topics/hourA patient attacker queries 79 topics/hour indefinitelyLonger-window detection (24h sliding window) as v2 enhancement
Fine-tuned model memorisationNot applicable today β€” no fine-tuning plannedFine-tuning gate blocks this path until safety checklist is complete
Model extraction / surrogate model trainingRequires thousands of API calls with outputs; rate limits slow this significantlyExisting per-key rate limits from issue #34