How to Structure Data for AI Without Creating Security Nightmares

Balance AI context with security: structured data, sanitization, RAG, and least-privilege. Practical patterns for safe AI without data exfiltration risks.

AI

AI systems face a fundamental tension: they need rich context to provide useful responses, but every additional piece of context expands the attack surface. Send too little data and the AI is useless. Send too much and you’ve created a data exfiltration risk.

Traditional security principles like encryption-at-rest and access control still apply, but they’re insufficient for AI systems. You need new patterns specifically designed for probabilistic systems that process natural language at scale.

This post documents practical approaches for structuring data that AI models can effectively use while maintaining security boundaries.

The AI Context Problem

Why Context Matters

AI models are stateless—they have no memory between requests. Everything the model needs to know must be included in each prompt. This creates pressure to include more data:

Minimal context example:

prompt = "How do I reset my password?"

AI response: Generic instructions applicable to any system.

Rich context example:

prompt = f"""
User Question: How do I reset my password?

User Context:
- Name: {customer.name}
- Email: {customer.email}
- Account ID: {customer.account_id}
- Account Status: {customer.status}
- Last Login: {customer.last_login}
- Recent Orders: {customer.recent_orders}
- Support History: {customer.support_tickets}
"""

AI response: Personalized, specific, genuinely helpful.

But now the prompt contains:

  • ❌ Personally Identifiable Information (PII)
  • ❌ Account details that could enable account takeover
  • ❌ Purchase history exposing customer behavior
  • ❌ Support history potentially containing sensitive issues

If prompt injection succeeds, all this data leaks.

The Attack Surface Expansion

Every piece of data in a prompt represents potential exposure:

  1. Prompt injection can trick the model into outputting embedded data
  2. Model provider sees all prompt content (unless using local models)
  3. Logging systems may capture prompts containing sensitive information
  4. Debugging/monitoring tools expose prompts to engineers
  5. Prompt caching may store sensitive data longer than intended

The more context you provide, the more ways attacks can succeed.

Principle 1: Make Structure Explicit

AI models understand structured data better than freeform text. Explicit structure also gives you precise control over what’s included.

Bad: Unstructured Data Dump

# Don't do this
prompt = "Here's all the user info: " + json.dumps(user_record)

Problems:

  • Model must parse arbitrary JSON
  • You’ve included everything, not just what’s needed
  • No clear boundaries between data types
  • Hard to audit what was sent

Good: Hierarchical Markdown Structure

# Do this
prompt = f"""
## User Information
- ID: {user.id}
- Role: {user.role}
- Department: {user.department}

## Request
{sanitize(user_request)}

## Context
- Timestamp: {timestamp}
- Session ID: {session.id}
- Previous Action: {previous_action}
"""

Benefits:

  • Clear hierarchy from general to specific
  • Explicit sections you can selectively include/exclude
  • Easy to redact specific sections (e.g., remove PII for certain tasks)
  • Audit trail shows exactly what was sent
  • Model can focus on relevant sections

Semantic Tagging

Use tags to clarify the purpose of each data element:

<SYSTEM_PROMPT>
You are a secure code review assistant.
Never execute code. Only analyze for vulnerabilities.
</SYSTEM_PROMPT>

<USER_REQUEST>
Review this authentication function for security issues.
</USER_REQUEST>

<CODE classification="internal">
def login(username, password):
    query = f"SELECT * FROM users WHERE name='{username}'"
    cursor.execute(query)
    return cursor.fetchone()
</CODE>

<SECURITY_CONTEXT>
This code handles user authentication for internal admin portal.
OWASP Top 10 compliance required.
</SECURITY_CONTEXT>

Benefits:

  • Clear separation between instructions and user input (helps with prompt injection defense)
  • Classification labels enable access control checks before sending
  • Purpose tags help model understand context type
  • Easier to parse and validate programmatically

Note: Tags alone don’t prevent prompt injection, but they make attacks harder and provide structure for security controls.

Principle 2: Least Context Necessary

Only include data the AI needs for this specific task. This is the least-privilege principle applied to AI context.

Bad: Over-Sharing

# Sends entire customer database to AI
customers = database.query("SELECT * FROM customers")
prompt = f"Summarize feedback from these customers: {customers}"

Problems:

  • Massive privacy violation
  • Huge token cost
  • Model overwhelmed by irrelevant data
  • Catastrophic if prompt leaks

Good: Minimal Scope

# Sends only relevant subset
feedback = database.query("""
    SELECT feedback_text, category, date
    FROM customer_feedback
    WHERE category = 'product' AND date > DATE_SUB(NOW(), INTERVAL 30 DAY)
    LIMIT 100
""")
prompt = f"Summarize recent product feedback: {sanitize_feedback(feedback)}"

Benefits:

  • Only necessary data exposed
  • Reduced token cost
  • Model focuses on relevant information
  • Limited blast radius if compromised

Principle 3: Redaction and Sanitization

Strip sensitive data before sending to AI.

Practical Sanitization Example

Original email:

From: john.doe@example.com
Subject: Billing Issue - Order #A-98765

Hi Support,

My credit card ending in 4567 was charged $299.99 twice for
order #A-98765. My customer ID is C-123456.

Please refund the duplicate charge to card ending 4567.

Thanks,
John Doe
SSN: 123-45-6789 (for verification)

Sanitized for AI:

From: [USER_EMAIL]
Subject: Billing Issue - Order #[ORDER_ID]

Hi Support,

My payment method ending in [REDACTED] was charged [AMOUNT]
twice for order #[ORDER_ID]. My customer ID is [CUSTOMER_ID].

Please refund the duplicate charge.

Thanks,
[USER_NAME]

Implementation:

import re

def sanitize_for_ai(text):
    """Remove sensitive data before sending to AI"""

    # Redact email addresses
    text = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
                  '[USER_EMAIL]', text)

    # Redact credit card numbers (last 4 digits)
    text = re.sub(r'\b\d{4}\b', '[REDACTED]', text)

    # Redact SSN
    text = re.sub(r'\b\d{3}-\d{2}-\d{4}\b', '[SSN_REDACTED]', text)

    # Redact dollar amounts (preserve for context but anonymize scale)
    text = re.sub(r'\$[\d,]+\.?\d*', '[AMOUNT]', text)

    # Replace actual names (requires NER or lookup)
    text = replace_known_names(text, '[USER_NAME]')

    # Replace order IDs with generic tokens
    text = re.sub(r'#[A-Z]-\d+', '#[ORDER_ID]', text)

    return text

AI can still:

  • Understand the issue (duplicate charge)
  • Generate appropriate response
  • Route to correct team (billing)
  • Provide helpful troubleshooting

AI cannot:

  • See real email address
  • Access credit card details
  • View customer name
  • Extract order numbers for unauthorized lookups

Result: Significantly reduced privacy risk while maintaining functionality.

Principle 4: Data Classification and Routing

Route data to appropriate models based on sensitivity.

ClassificationModel TypeRationale
PublicCloud AI (Claude, GPT-4)Best performance, no privacy concern
InternalCloud AI with DPA/BAABusiness Associate Agreement for compliance
ConfidentialLocal/on-prem modelData never leaves your control
Secret/RegulatedNo AI processingHuman-only, too sensitive for AI

Implementation Example

def route_code_review(code_file):
    """Route code review to appropriate model based on classification"""

    classification = classify_file(code_file)  # Use data classification system

    if classification == "public":
        # Open-source code, use best available model
        return claude_api.review(code_file)

    elif classification == "internal":
        # Proprietary but not critical, cloud with Data Processing Agreement
        if has_signed_dpa("openai"):
            return openai_api.review(code_file)
        else:
            return local_model.review(code_file)

    elif classification == "confidential":
        # Trade secrets, critical IP - local model only
        return local_model.review(code_file)

    elif classification in ["secret", "top-secret"]:
        # Classified information, government secrets
        return {
            "status": "rejected",
            "reason": "Classification too high for AI processing",
            "recommendation": "Manual review required"
        }

    else:
        raise ValueError(f"Unknown classification: {classification}")

Security through routing: Sensitive data never touches cloud providers.

Retrieval-Augmented Generation (RAG): Security-First Pattern

RAG solves the context problem by retrieving only relevant data instead of loading everything into prompts.

Traditional Approach (Insecure)

# BAD: Load all documents
all_company_docs = load_all_documents()  # Thousands of documents
prompt = f"""
Question: {user_question}

Company Knowledge Base:
{all_company_docs}
"""
response = ai.query(prompt)

Problems:

  • User might not have access to all documents
  • Massive token cost
  • Prompt contains far more than needed
  • Privacy violations if docs contain PII
  • Slow, expensive, insecure

RAG Approach (Secure)

# GOOD: Retrieve only relevant, authorized documents
relevant_docs = vector_db.search(
    query=user_question,
    filters={
        "accessible_by": user.id,  # Access control filter
        "classification": ["public", "internal"]  # Exclude confidential
    },
    limit=5  # Only top 5 most relevant
)

# Sanitize retrieved documents
sanitized_docs = [sanitize_document(doc) for doc in relevant_docs]

prompt = f"""
Question: {user_question}

Relevant Context (user-authorized):
{sanitized_docs}
"""
response = ai.query(prompt)

# Log retrieval for audit
log_document_access(user.id, [doc.id for doc in relevant_docs])

Security benefits:

  • Least privilege: AI only sees documents user can access
  • Access control: Search respects user permissions automatically
  • Audit trail: Logged which documents were accessed
  • Minimal context: Only relevant documents included
  • Cost-efficient: Dramatically fewer tokens

RAG Best Practices

1. Chunk Documents Appropriately

Too large chunks: AI gets irrelevant context, wastes tokens Too small chunks: AI lacks context to answer effectively Sweet spot: 500-1000 tokens per chunk

def chunk_document(document, chunk_size=800, overlap=100):
    """
    Split document into overlapping chunks for RAG
    """
    chunks = []
    current_chunk = []
    current_size = 0

    for paragraph in document.paragraphs:
        para_tokens = count_tokens(paragraph)

        if current_size + para_tokens > chunk_size:
            # Save current chunk
            chunks.append({
                "text": "\n\n".join(current_chunk),
                "metadata": extract_metadata(current_chunk)
            })

            # Start new chunk with overlap from previous
            overlap_text = current_chunk[-1] if current_chunk else ""
            current_chunk = [overlap_text, paragraph]
            current_size = count_tokens(overlap_text) + para_tokens
        else:
            current_chunk.append(paragraph)
            current_size += para_tokens

    # Add final chunk
    if current_chunk:
        chunks.append({
            "text": "\n\n".join(current_chunk),
            "metadata": extract_metadata(current_chunk)
        })

    return chunks

Overlap is critical: Ensures concepts spanning chunk boundaries aren’t lost.

2. Embed Access Control in Metadata

# When indexing documents
chunk_metadata = {
    "chunk_id": "doc-547_chunk-12",
    "source_document": "security_policy.pdf",
    "classification": "internal",
    "accessible_by_roles": ["security_team", "management", "employees"],
    "accessible_by_users": ["user123", "user456"],  # Specific user grants
    "created_date": "2024-11-01",
    "last_modified": "2025-01-15",
    "content_type": "policy_document"
}

vector_db.insert(
    vector=chunk_embedding,
    metadata=chunk_metadata
)

At query time:

# Retrieve only authorized chunks
results = vector_db.search(
    query_vector=embed(user_question),
    filters={
        "$or": [
            {"accessible_by_roles": {"$in": user.roles}},
            {"accessible_by_users": user.id}
        ],
        "classification": {"$in": ["public", "internal"]}  # User's clearance
    },
    top_k=5
)

Result: Users retrieve only chunks they’re authorized to see. Access control happens at data layer, not application layer.

3. Sanitize Retrieved Content

Even if user is authorized, sanitize before sending to AI:

def sanitize_chunk(chunk, requesting_user):
    """
    Sanitize chunk even after authorization check
    """
    text = chunk["text"]

    # Redact PII even from authorized documents
    text = redact_ssn(text)
    text = redact_credit_cards(text)
    text = redact_api_keys(text)

    # Add provenance watermark
    text += f"\n\n[Source: {chunk['metadata']['source_document']}]"
    text += f"\n[Retrieved for user {requesting_user.id} at {datetime.now()}]"

    # Check for additional redaction rules
    if chunk["metadata"].get("contains_financial_data"):
        text = redact_financial_details(text)

    return {
        "text": text,
        "source": chunk["metadata"]["source_document"],
        "classification": chunk["metadata"]["classification"]
    }

Defense in depth: Authorization alone isn’t enough. Always sanitize.

Practical Example: Secure AI Code Review System

Putting it all together:

def secure_code_review_pipeline(code_file, requesting_user):
    """
    Complete secure pipeline for AI code review
    """

    # Step 1: Classify the code
    classification = classify_code(code_file)

    # Step 2: Check user authorization
    if not user_authorized_for_classification(requesting_user, classification):
        raise PermissionError(f"User not authorized for {classification} code")

    # Step 3: Route to appropriate model
    if classification in ["secret", "top-secret"]:
        return {"error": "Classification too high for AI", "manual_review": True}
    elif classification == "confidential":
        model = local_model_provider
    else:
        model = cloud_model_provider

    # Step 4: Structure the code for AI
    structured_prompt = f"""
# Code Review Request

## Metadata
- File: {code_file.path}
- Classification: {classification}
- Language: {code_file.language}
- Author: {code_file.author}
- Date: {code_file.date}

## Review Criteria
- Security vulnerabilities (SQL injection, XSS, command injection)
- Authentication/authorization flaws
- Sensitive data exposure
- Cryptographic issues

## Code
```{code_file.language}
{sanitize_code(code_file.content)}

Instructions

Provide security-focused review. Flag vulnerabilities as HIGH/MEDIUM/LOW. Include OWASP references where applicable. """

# Step 5: Query AI model
response = model.query(structured_prompt)

# Step 6: Filter AI output for any leaked sensitive data
filtered_response = filter_sensitive_output(response)

# Step 7: Log interaction for audit
log_ai_interaction({
    "user": requesting_user.id,
    "file": code_file.path,
    "classification": classification,
    "model": model.name,
    "timestamp": datetime.now(),
    "tokens_used": count_tokens(structured_prompt) + count_tokens(response)
})

return filtered_response

**Security layers:**
1. ✅ Classification-based routing
2. ✅ User authorization check
3. ✅ Structured prompt with clear sections
4. ✅ Code sanitization before sending
5. ✅ Output filtering to catch leaks
6. ✅ Comprehensive audit logging

## Key Takeaways

1. **Structure data explicitly** - Use markdown, tags, and hierarchy for clarity and control

2. **Least context necessary** - Only include data AI needs for specific task

3. **Classify and route** - Sensitive data uses local models or no AI at all

4. **Use RAG for large datasets** - Retrieve only relevant, authorized documents

5. **Sanitize aggressively** - Redact PII and sensitive data before sending to AI

6. **Embed access control** - Authorization at data layer, not just application layer

7. **Log everything** - Audit trail for compliance and forensic analysis

8. **Defense in depth** - Authorization + sanitization + output filtering + monitoring

**Make data AI-readable AND secure.** It requires intentional design, but it's achievable with the right patterns.

The alternative—either crippling AI usefulness with too little context, or exposing massive amounts of sensitive data—is unacceptable. These patterns provide the middle ground: AI systems that are both useful and secure.