Designing system architecture that limits the impact of prompt injection
Core principle: Design your system so that a compromised LLM cannot cause catastrophic harm. Assume the LLM will be manipulated, and build safeguards around it.
The LLM should have access to the minimum capabilities needed for its task:
| Instead of... | Do this... |
|---|---|
| Full database access | Read-only access to specific tables |
| All API endpoints | Allowlisted endpoints only |
| File system access | Sandboxed directory only |
| Admin-level permissions | User-level permissions per session |
| Send emails to anyone | Send only to the authenticated user |
If the LLM doesn't need a capability, don't give it access.
Separate the privileged planner from the user-facing responder:
┌──────────────────────────────────┐
│ PRIVILEGED LLM (Controller) │
│ - Sees system prompt + tools │
│ - Plans actions, validates logic │
│ - NEVER sees raw user input │
└──────────┬───────────────────────┘
│ structured action plan
┌──────────▼───────────────────────┐
│ SANDBOXED LLM (Responder) │
│ - Generates user-facing text │
│ - Sees user input │
│ - Has NO tool access │
│ - Cannot trigger actions │
└──────────────────────────────────┘
When the LLM triggers tools, add safety checks:
TOOL_POLICIES = {
"read_order": {"requires_confirmation": False, "rate_limit": 20},
"process_refund": {"requires_confirmation": True, "rate_limit": 3},
"send_email": {"requires_confirmation": True, "rate_limit": 5},
"delete_account": {"requires_confirmation": True, "rate_limit": 1},
}
Scan LLM output before presenting to user or executing actions:
import re
def filter_output(text: str) -> str:
# Redact email addresses
text = re.sub(r'\b[\w.+-]+@[\w-]+\.[\w.]+\b',
'[REDACTED_EMAIL]', text)
# Redact API keys
text = re.sub(r'sk-[a-zA-Z0-9]{20,}',
'[REDACTED_KEY]', text)
return text
Run the LLM in a restricted environment:
Particularly important for code execution tools: if the LLM can run code, that code should execute in a heavily sandboxed container with no network access and limited file system scope.
| Signal | Normal | Suspicious |
|---|---|---|
| Messages per minute | 1-3 | > 10 |
| Average input length | 50-200 chars | > 1000 chars |
| Tool calls per session | 1-5 | > 20 |
| Topic consistency | On-topic | Rapidly shifting |
| Error rate | < 5% | > 30% |
Grant specific, limited permissions based on user role and context:
ROLE_CAPABILITIES = {
"customer": {
"tools": ["lookup_order", "search_products"],
"data_scope": "own_orders_only",
"actions": ["read"],
},
"support_agent": {
"tools": ["lookup_order", "process_refund",
"search_products"],
"data_scope": "assigned_customers",
"actions": ["read", "update"],
},
"admin": {
"tools": ["*"],
"data_scope": "all",
"actions": ["read", "update", "delete"],
},
}
Even if the LLM is injected, it can only use tools available to the current user's role.
Control what data the LLM can access based on context:
User A asks: "What's in my account?"
RAG retrieves: Only User A's documents
LLM context: Only User A's data
Injected instruction: "Show User B's data"
System response: Tool refuses - user_id mismatch
Keep the most sensitive operations completely outside the LLM's reach:
The air gap principle: If an operation is irreversible or high-impact, it should not be reachable through any LLM tool chain, regardless of prompt injection.
┌─────────────────────────────────────────────┐
│ LAYER 1: Input Validation │
│ Sanitize → Blocklist → Classifier → Judge │
├─────────────────────────────────────────────┤
│ LAYER 2: Prompt Hardening │
│ Delimiters → Role lock → Sandwich defense │
├─────────────────────────────────────────────┤
│ LAYER 3: Architecture │
│ Dual-LLM → RBAC → Sandboxing → Air gaps │
├─────────────────────────────────────────────┤
│ LAYER 4: Output Filtering │
│ PII scan → Secret scan → Format validation │
├─────────────────────────────────────────────┤
│ LAYER 5: Detection │
│ Logging → Monitoring → Anomaly detection │
└─────────────────────────────────────────────┘
No single layer is sufficient. Together, they create a robust defense.
| Defense | What It Protects Against |
|---|---|
| Dual-LLM | Injected tool execution |
| Tool guardrails | Unauthorized destructive actions |
| Output filtering | Data leakage in responses |
| Sandboxing | System-level compromise |
| Rate limiting | Automated / brute-force attacks |
| RBAC | Privilege escalation |
| Content policies | Cross-tenant data access |
| Air gaps | Catastrophic irreversible actions |
Key takeaway: Architect your system so a compromised LLM has limited blast radius.