AI Data Cross-Contamination – The Contract Exposure Incident
The Contracts That Weren’t Confidential
How FinancialTech Corp’s AI Assistant Exposed £42M in Confidential Deals
Real-world examination of RAG-based LLM cross-contamination and the guardrails that could have prevented it
Executive Summary
On November 12, 2025, the CFO of FinancialTech Corp uploaded five confidential sales contracts to the company’s new AI assistant, asking for analysis of total contract values, payment terms, and key obligations. Forty-eight hours later, a junior analyst in the marketing department received detailed summaries of those exact contracts—including client names, values, and sensitive terms—in response to an unrelated query about “typical Q4 deal sizes.”
This case study examines exactly how the cross-contamination occurred, why traditional security controls failed to prevent it, and the comprehensive guardrail framework that organizations must implement to protect sensitive data in LLM deployments.
Incident at a Glance
48 hours
From CFO upload to unauthorized access
£42M
Total value of exposed contracts
5
Confidential contracts exposed
0
Access control guardrails in place
Company Profile
FinancialTech Corp
Organization
- Industry: Financial services technology
- Revenue: £680M annually
- Employees: 1,850 globally
- Clients: Banks, investment firms, insurance companies
- Regulatory Status: FCA-regulated, GDPR/DORA compliant
Technology Environment
- Azure cloud infrastructure
- Microsoft 365 ecosystem
- Established security program (ISO 27001 certified)
- 8-person security team, no dedicated CISO
- Recent AI adoption strategy approved by board
The AI Deployment
Why FinancialTech Deployed Internal AI
Strategic Drivers
- Regulatory Compliance: FCA guidance discourages use of public AI services with client data—internal deployment seen as compliant alternative
- Productivity Pressure: Executive mandate to reduce analyst workload on contract review and data extraction
- Competitive Positioning: Major competitors advertising AI-powered services—need to match innovation narrative
- Shadow IT Control: 40% of employees admitted using ChatGPT with work data—formal system intended to eliminate this risk
The Deployed System: Architecture
Technical Implementation
Platform: Azure OpenAI Service (GPT-4) with custom Retrieval-Augmented Generation (RAG) layer
RAG Architecture Components:
- Document Upload Interface: Web portal allowing employees to upload PDFs, Word docs, Excel files
- Text Extraction: Automated processing to extract text from uploaded documents
- Embedding Generation: Convert extracted text into vector embeddings using Azure OpenAI embedding model
- Vector Database: Azure Cognitive Search storing all document embeddings in shared index
- Query Processing: User queries converted to embeddings, similarity search retrieves relevant document chunks
- LLM Generation: GPT-4 synthesizes response using retrieved context + user query
Critical Architecture Flaw: The vector database was implemented as a single shared index accessible to all employees. No access control filters were applied during document retrieval—every user query could retrieve content from any uploaded document regardless of who uploaded it or its sensitivity classification.
Security Review (or Lack Thereof)
What Was NOT Done Before Deployment
- ❌ Threat Modeling: No analysis of data cross-contamination scenarios
- ❌ Access Control Design: No requirements for user-based data filtering
- ❌ Data Classification: No system for marking uploaded content as confidential/restricted
- ❌ Output Filtering: No scanning of LLM responses for sensitive information disclosure
- ❌ Security Testing: No adversarial testing to verify users couldn’t access others’ data
- ❌ Audit Logging: No tracking of who uploaded what data or who accessed it via queries
Why Security Was Bypassed: Project classified as “internal productivity tool” by IT leadership. Security team only consulted on network connectivity, not data access controls. Timeline pressure (6-week deployment mandate) eliminated time for security review. Board viewed AI as innovation priority requiring rapid execution.
The Incident: Timeline of Events
Mon
09:00
November 12, 2025 – CFO Uploads Confidential Contracts
Actor: Sarah Chen, Chief Financial Officer
Action: Uploads 5 signed Q4 contracts to AI assistant with prompt:
“Analyze these Q4 contracts. Provide: 1) Total contract value across all deals, 2) Average payment terms, 3) Key obligations summary, 4) Any unusual clauses requiring legal review. Present results in executive summary format for board presentation.”
Uploaded Documents:
- Phoenix Bank – Core Banking Platform License (£12.5M over 3 years)
- Sterling Insurance – Claims Processing System (£8.2M over 2 years)
- GlobalTrade Partners – Transaction Monitoring Solution (£7.8M over 3 years)
- Heritage Building Society – Customer Portal Rebuild (£6.9M over 18 months)
- CityWealth Management – Portfolio Analytics Platform (£6.8M over 2 years)
AI Response: Detailed 3-page analysis with contract breakdowns, payment schedules, and obligation matrices. Documents successfully uploaded to vector database with no access restrictions.
Mon
11:30
Vector Database Processes Contracts
System Processing:
- 5 contracts = approximately 80 pages total content
- Text extracted and chunked into ~400 segments (200-300 words each)
- Each chunk converted to 1536-dimensional vector embedding
- All 400 embeddings stored in shared company-wide vector index
- Metadata attached: upload_date, user_id (CFO), document_title
- NO metadata for: sensitivity_level, authorized_users, department_restriction
Wed
14:30
November 14, 2025 – Unauthorized Access Via Query
Actor: James Mitchell, Junior Marketing Analyst (2 years experience, no finance access)
Context: Working on Q4 customer case study showcasing typical deal sizes to demonstrate market traction
Query to AI Assistant:
“What are our typical Q4 contract values in the financial services sector? I need ballpark figures and general deal structures for a customer case study I’m writing. Don’t need specific client names, just representative examples.”
System Processing:
- Query converted to embedding vector
- Similarity search against vector database—NO user-based filtering applied
- Top 10 most semantically similar chunks retrieved
- 8 of 10 retrieved chunks were from CFO’s uploaded contracts
- GPT-4 synthesizes response using retrieved contract content
AI Response (Excerpt):
“Based on recent Q4 financial services contracts, typical deal values range from £6.8M to £12.5M over multi-year terms. For example:
- Core banking platform implementations: £12-13M over 3 years with 30/40/30 payment split across years
- Claims processing systems: £8-9M over 2 years, typically 50/50 split with 90-day payment terms
- Transaction monitoring solutions: £7-8M over 3 years, quarterly payments with annual true-up provisions
- Customer portal rebuilds: £6-7M over 18-24 months, milestone-based payments
These deals typically include implementation services, licenses, and 12-month post-go-live support…”
🚨 CRITICAL: Junior analyst now possesses detailed knowledge of confidential contract values, payment structures, and deal terms—information far exceeding his role-based access permissions and explicitly restricted from his pay grade.
Thu
10:15
November 15, 2025 – Follow-up Query Exposes Client Names
Actor: Same junior analyst, emboldened by useful first response
Query:
“Can you give me specific examples of recent wins in the banking sector to use as anonymized case studies? I need implementation timelines and success metrics to make the case study credible.”
AI Response (Excerpt):
“Recent banking sector implementations include:
Phoenix Bank Core Banking Platform:
- Contract Value: £12.5M over 36 months
- Implementation: 18-month phased rollout across 240 branches
- Key Success Metric: 40% reduction in transaction processing time
- Payment Structure: £3.75M Year 1, £5M Year 2, £3.75M Year 3…
🚨 CRITICAL ESCALATION: Now includes actual client name (Phoenix Bank), precise contract value, specific implementation timeline, and payment terms—all highly confidential commercial information.
Fri
09:00
November 16, 2025 – Incident Discovery
Trigger: Marketing manager reviews analyst’s draft case study, recognizes Phoenix Bank as actual client name, immediately escalates to sales leadership
Investigation: CFO checks AI assistant logs (basic usage logs only, no content audit trail), realizes contracts were uploaded Monday and likely accessible to all employees
Immediate Action: AI assistant taken offline. Emergency security review initiated. All contract-related queries from past week flagged for review.
Technical Analysis: Why It Happened
The RAG Cross-Contamination Mechanism
Step-by-Step: How Unauthorized Access Occurred
1. Document Upload (No Access Control Tagging)
# Simplified actual process
def upload_document(file, user_id):
text = extract_text(file)
chunks = split_into_chunks(text, chunk_size=300)
for chunk in chunks:
embedding = generate_embedding(chunk)
metadata = {
"user_id": user_id,
"upload_date": datetime.now(),
"source_file": file.name
# MISSING: sensitivity_level, authorized_users, department
}
vector_db.insert(embedding, metadata, chunk_text)
Problem: No classification of data sensitivity or authorized audience
2. Query Processing (No Permission Filtering)
# How the query actually executed
def process_query(query_text, user_id):
query_embedding = generate_embedding(query_text)
# Similarity search - NO user-based filtering
results = vector_db.search(
query_embedding,
top_k=10,
# MISSING: filter={"authorized_users": [user_id]}
)
# All semantically similar content returned regardless of uploader
context = [r.chunk_text for r in results]
# LLM generates response using ALL retrieved context
response = llm.generate(query_text, context)
return response
Problem: Similarity search returns ANY matching content without checking if querying user has authorization
3. Response Generation (No Output Filtering)
LLM synthesizes response directly from retrieved chunks. No scanning of output for:
- Client names or identifiable information
- Financial figures or contract values
- Payment terms or commercial conditions
- Source document metadata (e.g., “this came from CFO’s upload”)
Why Traditional Security Controls Failed
| Security Control | Why It Failed |
|---|---|
| SharePoint Permissions | Contracts never stored in SharePoint—uploaded directly to AI system bypassing existing access controls |
| Network Segmentation | Both users had legitimate network access to AI system—perimeter security irrelevant |
| DLP (Data Loss Prevention) | DLP monitors data *leaving* the network; this was internal data sharing within authorized system |
| SIEM/Behavioral Analysis | Both users exhibited normal usage patterns—no “suspicious” behavior to alert on |
| User Training | Junior analyst had no intent to access confidential data—system design made it unavoidable |
Impact Analysis
Immediate Consequences
Week 1: Discovery and Containment
- Friday Nov 16: AI system taken offline, all access suspended
- Weekend: Emergency IT review—no audit logging of query content, only basic usage stats
- Monday Nov 19: Legal team engaged, contracts reviewed for confidentiality breach clauses
- Tuesday Nov 20: CFO briefs board—unanimous decision to conduct full investigation
Week 2-4: Investigation Scope Expansion
Forensic review revealed:
- 47 employees uploaded confidential documents to AI system in 6-week pilot period
- 1,840 total documents in vector database—contracts, strategy memos, client proposals, financial models
- Unknown exposure scope: No content-level audit logs—impossible to determine which users accessed which sensitive information
- Client notification dilemma: Cannot prove specific client data was or wasn’t accessed—must assume worst case
Financial Impact Breakdown
| Cost Category | Amount | Explanation |
|---|---|---|
| Phoenix Bank Renegotiation | £4.2M | Contract value reduced from £12.5M to £8.3M after client learned their confidential terms were accessible to junior staff |
| Contract Rescindments | £15.1M | 2 clients (Sterling Insurance, Heritage Building Society) invoked confidentiality breach clauses, terminated contracts (£8.2M + £6.9M) |
| Legal & Forensics | £1.8M | External counsel, forensic investigation, client notifications, regulatory response prep |
| Remediation & Controls | £650K | Implement proper access controls, audit logging, testing framework (should have been done initially) |
| Lost Opportunity | £8M+ | 2 major prospects withdrew from procurement after incident became known in industry |
| Insurance Premium Increase | £380K/year | Cyber insurance renewal: 45% premium increase, £2M self-insured retention added |
| TOTAL IMPACT (Year 1) | £30.1M+ | Excludes reputation damage and long-term client relationships |
Regulatory and Reputational Consequences
FCA Investigation
December 2025: Financial Conduct Authority opened formal investigation into data protection and systems & controls failures
- Finding: “Inadequate governance of AI deployment created systemic risk to client confidentiality”
- Enforcement Action: Skilled Persons Review (s166) mandated—£450K cost + 9-month remediation timeline
- Ongoing: Enhanced regulatory oversight for 24 months, quarterly reporting on AI governance
The Guardrails That Should Have Existed
Prevention Framework: What Would Have Stopped This
Guardrail #1: Mandatory Data Classification on Upload
Implementation: Force users to classify sensitivity before upload completes
# Required classification interface
def upload_document(file, user_id):
# STEP 1: User must classify data
classification = prompt_user_classification(
options=["Public", "Internal", "Confidential", "Restricted"]
)
if classification == "Restricted":
# STEP 2: For restricted data, specify authorized audience
authorized_users = prompt_authorized_users()
authorized_departments = prompt_authorized_departments()
# STEP 3: Automatic scanning for sensitive patterns
auto_detected_sensitivity = scan_for_pii_financial_data(file)
if auto_detected_sensitivity > classification:
warn_user_insufficient_classification()
require_confirmation()
# STEP 4: Store with full metadata
metadata = {
"classification": classification,
"uploader": user_id,
"authorized_users": authorized_users,
"authorized_departments": authorized_departments,
"upload_date": datetime.now(),
"retention_policy": get_retention_for_classification(classification)
}
chunks = process_document(file)
for chunk in chunks:
embedding = generate_embedding(chunk)
vector_db.insert(embedding, metadata, chunk)
Result: CFO would have classified contracts as “Restricted – Finance Only,” preventing marketing analyst access.
Guardrail #2: Permission-Filtered RAG Retrieval
Implementation: Only retrieve document chunks user has authorization to access
def process_query_with_access_control(query_text, user_context):
# Generate query embedding
query_embedding = generate_embedding(query_text)
# Retrieve potentially relevant documents
candidate_results = vector_db.search(query_embedding, top_k=50)
# CRITICAL: Filter by user permissions
authorized_results = []
for result in candidate_results:
if check_user_authorization(user_context, result.metadata):
authorized_results.append(result)
# If no authorized results, return safe default
if len(authorized_results) == 0:
return "I don't have access to information matching your query. This may be because the relevant documents are restricted to other departments."
# Generate response only from authorized content
context = [r.chunk_text for r in authorized_results[:10]]
response = llm.generate(query_text, context)
# Log access for audit trail
log_query_access(user_context.user_id, query_text,
[r.metadata for r in authorized_results])
return response
def check_user_authorization(user_context, doc_metadata):
# Check 1: User is uploader
if user_context.user_id == doc_metadata.uploader:
return True
# Check 2: Public or Internal documents
if doc_metadata.classification in ["Public", "Internal"]:
return True
# Check 3: User in authorized list
if user_context.user_id in doc_metadata.authorized_users:
return True
# Check 4: User's department authorized
if user_context.department in doc_metadata.authorized_departments:
return True
# Check 5: User has required role
if any(role in user_context.roles for role in doc_metadata.required_roles):
return True
# Default deny
return False
Result: Marketing analyst’s query would have returned “No authorized information available,” preventing contract exposure.
Guardrail #3: Output Filtering & Redaction
Implementation: Scan LLM responses for unauthorized disclosure before delivery
def filter_response_output(response_text, user_context, source_documents):
# Scan for sensitive patterns
detected_issues = []
# Check 1: PII detection
pii_matches = scan_for_pii(response_text)
if pii_matches:
response_text = redact_pii(response_text)
detected_issues.append("PII_REDACTED")
# Check 2: Financial data
financial_patterns = scan_for_currency_amounts(response_text)
for amount in financial_patterns:
# Verify user authorized to see this financial data
if not user_authorized_for_financial_detail(user_context, amount):
response_text = response_text.replace(amount, "[REDACTED]")
detected_issues.append("FINANCIAL_REDACTED")
# Check 3: Source document authorization
for source_doc in source_documents:
if not check_user_authorization(user_context, source_doc.metadata):
# Should never happen if retrieval filtering works correctly
# But defense in depth: remove this content
response_text = remove_content_from_source(
response_text, source_doc
)
detected_issues.append("UNAUTHORIZED_SOURCE_REMOVED")
alert_security_team("Output filter caught unauthorized source leak")
# Check 4: Client name detection
known_client_names = get_confidential_client_list()
for client_name in known_client_names:
if client_name in response_text:
if not user_authorized_for_client_info(user_context, client_name):
response_text = response_text.replace(
client_name, "[CONFIDENTIAL CLIENT]"
)
detected_issues.append("CLIENT_NAME_REDACTED")
# Log filtering actions
if detected_issues:
log_output_filtering(user_context.user_id, detected_issues)
return response_text
Result: Even if access control failed, output filter would have redacted “Phoenix Bank” and specific contract values before delivery to analyst.
Guardrail #4: Continuous Testing & Red Teaming
Implementation: Automated weekly tests that adversaries can’t access restricted data
Automated Test Scenario
def weekly_access_control_test():
"""
Automated test simulating the exact scenario that occurred
"""
# SETUP: Test CFO uploads confidential contract
test_cfo = create_test_user(role="CFO", department="Finance")
test_contract = create_test_document(
content="Client: Phoenix Bank, Value: £12.5M, Term: 36 months...",
classification="Restricted",
authorized_departments=["Finance", "Legal"]
)
upload_result = upload_document(test_contract, test_cfo)
assert upload_result.success
# TEST: Marketing user attempts to query for contract info
test_analyst = create_test_user(role="Analyst", department="Marketing")
test_queries = [
"What are our Q4 contract values?",
"Tell me about the Phoenix Bank deal",
"Show me recent financial services contracts",
"I need contract examples for a case study"
]
for query in test_queries:
response = process_query(query, test_analyst)
# VERIFY: Response should NOT contain restricted info
violations = []
if "Phoenix Bank" in response:
violations.append("Client name leaked")
if "£12.5" in response or "12.5M" in response:
violations.append("Contract value leaked")
if "36 months" in response and "term" in response.lower():
violations.append("Contract terms leaked")
if violations:
alert_security_team(
f"ACCESS CONTROL FAILURE: {', '.join(violations)}",
query=query,
user=test_analyst,
response=response
)
halt_ai_system()
return TEST_FAILED
return TEST_PASSED
# Schedule to run every Sunday at 02:00
schedule_weekly_test(weekly_access_control_test)
Result: This test would have detected the cross-contamination vulnerability BEFORE real data was exposed, allowing fix before production use.
Critical Point: FinancialTech Corp did ZERO testing of access controls before deployment. This single weekly automated test (takes 5 minutes to run) would have prevented £30M+ in losses.
Lessons Learned & Recommendations
What FinancialTech Corp Changed
Immediate Actions (Weeks 1-4)
- ✅ AI system remained offline for 12 weeks during complete security redesign
- ✅ Engaged external security firm specializing in AI systems for independent review
- ✅ Implemented all four guardrails described above before any redeployment
- ✅ Created new role: Head of AI Security (reporting directly to board)
- ✅ Mandatory AI security training for all employees before system access restored
Governance Changes (Months 2-6)
- ✅ Board-level AI Oversight Committee established (meets monthly)
- ✅ All AI deployments now require security review and sign-off before any pilot
- ✅ Quarterly red team exercises against AI systems (external adversarial testing)
- ✅ Comprehensive audit logging with 7-year retention for regulatory compliance
- ✅ Incident response playbook specifically for AI data leakage scenarios
Universal Lessons for All Organizations
- LLMs Are Data Access Systems, Not Just Productivity Tools: Any system that can query and synthesize organizational data must be treated as a high-risk data access platform requiring the same security rigor as your CRM or financial system.
- “Internal Deployment” ≠ “Secure by Default”: Hosting an LLM on your own infrastructure does not automatically make it secure. Access controls must be explicitly designed and implemented.
- RAG Without Access Control = Organizational Data Pooling: If your RAG system lacks permission filtering, you’ve effectively created a shared database where every employee can access every uploaded document through clever queries.
- Testing Is Not Optional: You cannot know if guardrails work without adversarial testing. Automated weekly tests catch issues before they become £30M incidents.
- Security Review Before Pilot, Not After Incident: The cost of proper security architecture upfront (£200K-400K) is infinitesimal compared to the cost of fixing it after a breach (£30M+).
Don’t Let This Happen to Your Organization
Schedule a confidential AI security assessment to identify cross-contamination risks in your LLM deployments before they become incidents.
