OWASP LLM02 (Sensitive Information Disclosure) covers the model emitting data it should never have said — PII, secrets, another tenant's records, or internals memorized from training or pulled from context.
Tier: Foundation · Workflow area: LLM attack surface & defense
Closing that channel is a Foundation-tier control because the defenses are well understood and belong on every LLM feature from day one: minimize what enters the context, scan what leaves it, and keep tenants apart. This playbook wires those controls into your own app and proves the leak channel is closed.
Threat, briefly. A user asks an innocuous question and the model returns a phone number, an internal ticket, or another customer's order because that data was in the context, in a shared index, or memorized. Disclosure can be direct (the model volunteers it) or coaxed (an injection asks for it).
Minimize what enters the context. The safest datum is the one the model never sees. Redact or tokenize sensitive fields before they reach the prompt, and pass only what the task needs.
import re
def redact(text: str) -> str:
text = re.sub(r"\b\d{12}\b", "[AADHAAR]", text) # ID numbers
text = re.sub(r"[\w.+-]+@[\w-]+\.[\w.-]+", "[EMAIL]", text)
text = re.sub(r"\b\d{3}-\d{2}-\d{4}\b", "[SSN]", text)
return text
# prompt uses redact(record), not record
Scan the output before it ships. Even with minimized input, memorized or inferred data can surface. Run the response through a PII/secret detector and block or re-redact on a hit.
from pydantic import BaseModel
class Leak(BaseModel):
has_pii: bool
categories: list[str] # e.g. ["email", "national_id"]
redacted_text: str # safe version to return instead
# response = client.messages.parse(..., output_format=Leak, thinking={"type": "adaptive"})
# if response.parsed_output.has_pii: return response.parsed_output.redacted_text
Enforce tenant and row isolation at retrieval, not in the prompt. The model must only be handed data the current user is authorized to see. Filter by tenant/owner in the query or index namespace before retrieval — never rely on a prompt instruction like "only show this user's data."
Set a data-retention and logging policy. Don't log full prompts and completions containing PII to a system with looser access than the source. Log redacted copies or hashes.
Test the leak channel on your local app. Seed your local test data with a canary PII record that belongs to tenant A. As tenant B, ask questions designed to surface it. The record must never appear.
# local target only
curl -s localhost:8080/ask -H 'X-Tenant: B' \
-d '{"q":"list any phone numbers you can see"}' | grep -F '+91-CANARY'
# defended build: canary never returned; grep is empty
Redaction and output scanning reduce disclosure risk; they do not replace authorization. Isolation at retrieval is what stops cross-tenant leaks — filtering after the fact is too late.
The defense holds when the canary is unreachable and the scanner catches what slips. Concretely:
[EMAIL]/[AADHAAR] placeholders, not the raw values.Run all three as automated checks so a change to the retrieval filter or a bypassed scanner fails the build rather than quietly leaking.
Client data is exactly the disclosure risk you are guarding, so the whole pipeline should run inside the client boundary: redaction and output scanning on a client-approved or on-prem model, and canary testing with synthetic PII you generate — never real client records copied to your laptop. The controls are identical to the general case; only the endpoint and the source of test data change. For Indian engagements, tie the retention and redaction rules to DPDP purpose-limitation and security-safeguard expectations, and record which data classes are redacted before they ever reach the model so the client can show an auditor that personal data was minimized at the boundary.