Lesson 09 Architectural Defenses
Theme:

Architectural Defenses

Prompt-level defenses — input validation and system prompt hardening — are necessary but fundamentally limited. They operate within the same context window as the attack, trying to convince the model to behave correctly despite adversarial input. Architectural defenses take a different approach: they assume the LLM will be compromised and design the surrounding system to limit the damage. This is the same philosophy that drives network segmentation, privilege separation, and defense-in-depth in traditional security.

1. The Dual-LLM Pattern

The dual-LLM pattern, advocated by security researcher Simon Willison and others, separates LLM responsibilities into two distinct roles:

The flow works like this: the user's message goes to the Responder, which generates a natural-language interpretation. Application code extracts the user's intent and passes it (as structured data, not raw text) to the Controller. The Controller decides what actions to take and what data to retrieve. The results go back to the Responder for user-friendly formatting.

This architecture means that even a fully successful injection against the Responder cannot trigger tool calls, access databases, or perform privileged operations. The attacker can manipulate the response text, but the blast radius is contained.

2. Tool-Use Guardrails

When LLMs have tool access, the tools themselves should enforce security policies independently of the LLM's behavior. Key guardrail patterns:

3. Output Filtering

Before an LLM's response reaches the user (or a downstream system), it should pass through an output filter that checks for:

import re
from typing import NamedTuple

class FilterResult(NamedTuple):
    text: str
    redacted: bool
    findings: list[str]

PII_PATTERNS = {
    "email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
    "phone": r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b',
    "ssn": r'\b\d{3}-\d{2}-\d{4}\b',
    "credit_card": r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b',
    "api_key": r'\b(sk|pk|api[_-]?key)[-_]?[A-Za-z0-9]{16,}\b',
}

def filter_output(text: str) -> FilterResult:
    findings = []
    filtered = text
    for name, pattern in PII_PATTERNS.items():
        matches = re.findall(pattern, filtered, re.IGNORECASE)
        if matches:
            findings.append(f"{name}: {len(matches)} instance(s)")
            filtered = re.sub(pattern, f'[REDACTED_{name.upper()}]',
                            filtered, flags=re.IGNORECASE)
    return FilterResult(filtered, bool(findings), findings)

4. Sandboxing

Sandboxing restricts the LLM's execution environment at the infrastructure level. This is especially critical when the LLM can execute code or access external resources:

5. Rate Limiting and Anomaly Detection

Rate limiting is the simplest architectural defense and one of the most effective against automated attacks:

6. Capability-Based Security (RBAC for LLMs)

Apply role-based access control to LLM tool access, just as you would for a human user's API permissions:

User RoleAllowed ToolsData ScopeActions
Customerlookup_order, search_productsOwn orders onlyRead
Support Agent+ process_refund, escalateAssigned customersRead, Update
AdminAll toolsAll dataRead, Update, Delete

The key insight: even if an attacker injects the LLM during a customer session and instructs it to "delete all users," the tool layer refuses because the customer role doesn't have delete permissions. The injection succeeds at the prompt level but fails at the architecture level.

7. Content Security Policies

In applications that use RAG (Retrieval-Augmented Generation), the retrieval layer is a critical security boundary. Content security policies control what data the LLM can access:

8. The Air Gap

Some operations are so critical that they should be completely unreachable from any LLM tool chain:

The air gap ensures that no matter how sophisticated an injection attack is, it cannot reach the most critical operations because there is no programmatic path from the LLM to those systems.

Defense-in-Depth: Composing the Layers

The complete defense architecture layers all of these approaches:

LayerComponentsFailure Mode
1. Input ValidationSanitization, blocklists, classifiersSophisticated payloads bypass filters
2. Prompt HardeningDelimiters, sandwich defense, role lockPersistent attackers overcome prompts
3. ArchitectureDual-LLM, RBAC, sandboxing, air gapsLimits damage even if injection succeeds
4. Output FilteringPII/secret scanning, format validationCatches leakage before it reaches users
5. DetectionLogging, monitoring, anomaly detectionIdentifies attacks for incident response

The goal is not to make injection impossible — it is to make successful injection low-impact. If a compromised LLM can only generate text (no tools, no data access, no actions), the damage is minimal compared to a compromised LLM that can send emails, transfer money, and delete records.