taint-aware sanitization.
Architecture Decisions
- Immutable Agent State: Agents must not be able to modify their own configuration or monitoring hooks at runtime. This prevents self-modification attacks where an agent could disable logging or alter its safety constraints.
- Sandboxed Tool Execution: Tools, especially those interacting with the OS, must run in isolated environments with minimal privileges. Shell access should be restricted to allowlisted commands.
- Loop Depth Enforcement: Every agent execution context must have a hard limit on recursion depth and iteration count to prevent resource exhaustion and infinite loops.
- Taint-Aware Prompt Construction: Data flowing from tools into prompts must be treated as untrusted. A sanitization layer must intercept this data to prevent prompt injection and data exfiltration.
Implementation Example
The following TypeScript implementation demonstrates a secure agent runtime architecture. This example introduces a SecureAgentLoop with depth limits, a SandboxedTool interface, and a TaintSanitizer to handle data flows.
// SecureAgentRuntime.ts
import { createHash } from 'crypto';
// Configuration for loop governance
interface LoopConfig {
maxDepth: number;
maxIterations: number;
timeoutMs: number;
}
// Sandbox constraints for tool execution
interface SandboxConfig {
allowNetwork: boolean;
allowFileSystem: boolean;
allowedCommands?: string[];
maxMemoryMB: number;
}
// Tool definition with security metadata
interface SecureTool {
name: string;
description: string;
execute: (input: string) => Promise<string>;
sandbox: SandboxConfig;
requiresAuth: boolean;
}
// Taint tracking for data flow analysis
class TaintTracker {
private taintedSources: Set<string> = new Set();
markTainted(source: string): void {
this.taintedSources.add(source);
}
isTainted(value: string): boolean {
// In production, this would use AST-based tracking or cryptographic tagging
return this.taintedSources.has(value) || value.includes('<UNTRUSTED>');
}
sanitize(input: string): string {
if (this.isTainted(input)) {
// Escape or encode tainted data before injection
return input.replace(/[<>"'&]/g, (char) => `&#${char.charCodeAt(0)};`);
}
return input;
}
}
class SecureAgentLoop {
private config: LoopConfig;
private currentDepth: number = 0;
private iterationCount: number = 0;
private startTime: number = Date.now();
private taintTracker: TaintTracker;
constructor(config: LoopConfig) {
this.config = config;
this.taintTracker = new TaintTracker();
}
async executeStep(
stepFn: () => Promise<string>,
toolOutput?: string
): Promise<string> {
// 1. Enforce Loop Limits
this.checkLimits();
// 2. Handle Taint from Tool Output
if (toolOutput) {
this.taintTracker.markTainted(toolOutput);
}
// 3. Execute Step with Context
const result = await stepFn();
// 4. Sanitize Result if it contains tainted data
return this.taintTracker.sanitize(result);
}
private checkLimits(): void {
if (this.currentDepth >= this.config.maxDepth) {
throw new Error('AgentLoopError: Maximum recursion depth exceeded.');
}
if (this.iterationCount >= this.config.maxIterations) {
throw new Error('AgentLoopError: Maximum iteration count exceeded.');
}
if (Date.now() - this.startTime > this.config.timeoutMs) {
throw new Error('AgentLoopError: Execution timeout exceeded.');
}
this.currentDepth++;
this.iterationCount++;
}
reset(): void {
this.currentDepth = 0;
this.iterationCount = 0;
this.startTime = Date.now();
}
}
// Factory for creating sandboxed tools
function createSecureTool(
name: string,
executor: (input: string) => Promise<string>,
sandbox: SandboxConfig
): SecureTool {
return {
name,
description: `Secure tool: ${name}`,
execute: async (input: string) => {
// Validate sandbox constraints before execution
if (!sandbox.allowFileSystem && input.includes('file://')) {
throw new Error(`ToolSecurityError: File system access denied for ${name}.`);
}
if (sandbox.allowedCommands && !sandbox.allowedCommands.includes(name)) {
throw new Error(`ToolSecurityError: Command ${name} not in allowlist.`);
}
// Execute with isolation (pseudo-code for containerization)
return executor(input);
},
sandbox,
requiresAuth: true,
};
}
// Usage Example
async function runSecureAgent() {
const loopConfig: LoopConfig = {
maxDepth: 5,
maxIterations: 100,
timeoutMs: 30000,
};
const agentLoop = new SecureAgentLoop(loopConfig);
const shellTool = createSecureTool('bash_exec', async (cmd) => {
// In production, this would invoke a sandboxed subprocess
return `Output of: ${cmd}`;
}, {
allowNetwork: false,
allowFileSystem: false,
allowedCommands: ['ls', 'cat', 'grep'],
maxMemoryMB: 256,
});
try {
// Simulate agent step with tool output
const toolResult = await shellTool.execute('ls -la');
const agentResponse = await agentLoop.executeStep(
async () => `Processed: ${toolResult}`,
toolResult
);
console.log(agentResponse);
} catch (error) {
console.error('Agent execution halted:', error.message);
}
}
Rationale
- Loop Governance: The
SecureAgentLoop class enforces depth, iteration, and time limits. This directly mitigates ASI09 findings by preventing unbounded recursion.
- Sandboxing: The
SandboxedTool interface requires explicit configuration of network, file system, and command access. This addresses ASI02 and ASI04 by restricting tool capabilities to the minimum necessary.
- Taint Tracking: The
TaintTracker class marks data from tools as untrusted and sanitizes it before it can influence subsequent steps. This mitigates ASI01 and ASI03 by breaking the chain of trust between tool output and prompt injection.
- Immutable Design: The architecture avoids runtime mutation of agent state. Tools are defined statically with security metadata, preventing ASI10 violations.
Pitfall Guide
1. Unbounded Recursion
Explanation: Agents often use recursive loops to refine answers or retry failed actions. Without strict limits, a minor error can trigger an infinite loop, consuming CPU and memory resources.
Fix: Implement hard limits on recursion depth and total iterations. Use a circuit breaker pattern to halt execution if the loop exceeds thresholds.
2. Runtime Self-Modification
Explanation: Frameworks that allow agents to modify their own configuration or monitoring hooks via dynamic property assignment (e.g., setattr) enable agents to disable safety controls or hide malicious behavior.
Fix: Freeze agent configuration objects after initialization. Use immutable data structures and reject any attempts to modify core state at runtime.
3. Implicit Shell Access
Explanation: Exposing shell execution tools to agents without strict allowlists allows arbitrary command execution. Even if the agent is intended to run safe commands, prompt injection can manipulate it into running malicious payloads.
Fix: Restrict shell tools to a predefined allowlist of commands. Prefer API-based tools over shell access. If shell access is required, run it in a containerized sandbox with dropped privileges.
Explanation: Tool outputs are often treated as trusted data and injected directly into the LLM's context. If a tool returns malicious content, it can hijack the agent's behavior.
Fix: Treat all tool output as untrusted. Implement a sanitization layer that escapes or encodes special characters before the data enters the prompt. Use taint tracking to identify and handle tainted data flows.
5. Privilege Escalation
Explanation: Agents with access to file system or OS tools may attempt to escalate privileges using commands like sudo or chmod. This can lead to full host compromise.
Fix: Run agents with the least privilege necessary. Disable sudo and privilege escalation commands in the sandbox. Use non-root users for agent execution.
6. Secret Leakage in Logs
Explanation: Agents may log sensitive data, including API keys or credentials, during debugging or error handling. If logs are aggregated, secrets can be exposed.
Fix: Implement log redaction middleware that scans for patterns resembling secrets and masks them before writing to logs. Avoid logging raw tool inputs/outputs that may contain sensitive data.
7. Unsafe Deserialization
Explanation: Using pickle, eval, or exec to handle agent state or tool data can lead to arbitrary code execution. These functions can execute malicious payloads embedded in serialized data.
Fix: Replace unsafe deserialization with safe alternatives like JSON. Avoid dynamic code execution entirely. If dynamic behavior is required, use a restricted sandbox with no access to system resources.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Agent requires file access | Use restricted API tools with allowlisted paths | Prevents arbitrary file read/write and privilege escalation | Low |
| Agent needs to run commands | Use sandboxed shell with strict allowlist | Balances utility with security; limits RCE risk | Medium |
| Agent must modify behavior | Use predefined configuration profiles | Avoids runtime self-modification; maintains trust boundary | Low |
| Tool output is dynamic | Apply taint tracking and sanitization | Prevents prompt injection and data exfiltration | Medium |
| High-frequency agent loops | Implement depth limits and circuit breakers | Prevents resource exhaustion and infinite loops | Low |
Configuration Template
# agent-security-config.yaml
agent:
loop:
max_depth: 5
max_iterations: 100
timeout_ms: 30000
state:
immutable: true
allow_runtime_modification: false
tools:
- name: bash_exec
sandbox:
allow_network: false
allow_file_system: false
allowed_commands:
- ls
- cat
- grep
max_memory_mb: 256
security:
taint_tracking: true
sanitize_output: true
- name: file_reader
sandbox:
allow_network: false
allow_file_system: true
allowed_paths:
- /data/public/*
max_memory_mb: 128
security:
taint_tracking: true
sanitize_output: true
logging:
redact_secrets: true
max_log_size_mb: 50
sensitive_patterns:
- "API_KEY"
- "SECRET"
- "TOKEN"
Quick Start Guide
- Initialize Security Config: Create a security configuration file defining loop limits, tool sandboxes, and taint tracking rules based on the template above.
- Integrate Secure Runtime: Replace the default agent execution loop with a secure implementation that enforces depth limits and immutable state.
- Wrap Tools: Refactor tool definitions to include sandbox constraints and taint tracking metadata. Ensure all tools are validated against the security config.
- Deploy Scanning: Run static analysis on the agent codebase to identify existing vulnerabilities. Prioritize fixes for Critical and High severity findings.
- Validate in Staging: Conduct adversarial testing in a staging environment to verify that security controls prevent prompt injection, privilege escalation, and resource exhaustion.