Office Hours — What are the practical techniques for sandboxing an AI agent so it can't accidentally or maliciously delete files or access sensitive data?
A daily developer question about AI/LLMs, answered with a direct, opinionated take.
What are the practical techniques for sandboxing an AI agent so it can’t accidentally or maliciously delete files or access sensitive data?
The core problem is that agents need enough capability to be useful, but every permission you grant is a surface area for mistakes or attacks. You can’t just lock everything down—that defeats the purpose. The trick is building layered controls where each layer fails independently.
Principle: Separate Intent From Execution
The most reliable pattern is to never let the agent directly execute dangerous operations. Instead, have it generate a plan or request that a separate system approves or executes. Claude Opus 5 combined with Auto Mode achieves 0% prompt injection attack success across browser agent test scenarios using exactly this approach—human approval workflows sitting between the agent’s decisions and real actions.
This means your agent calls a function like request_file_deletion(path) instead of directly invoking os.rm(). The function logs the request, checks it against policy, and either executes it or rejects it. If the agent gets compromised or hallucinates, the gate catches it.
Filesystem Isolation: Chroot, Containers, or VMs
If your agent needs real filesystem access (writing code, reading configs), run it in a container or VM with a mounted read-only root filesystem and a single writable directory for scratch work. This is the simplest hard guarantee.
# Example: Docker container with minimal write scope
docker run \
--read-only \
--tmpfs /scratch:size=100m \
--volume /safe/read-only:/data:ro \
--volume /safe/writable:/output:rw \
agent-image
The agent can only touch /scratch and /output. It can’t touch system binaries, configuration files, or anything outside those paths. If you need it to write code, write to /output. If it tries rm -rf /, it fails silently because the root is read-only.
Capability-Based Access Control: Explicit Tool Boundaries
Don’t give agents shell access or filesystem APIs directly. Wrap everything in typed functions that enforce boundaries.
# BAD: Agent can do anything
agent_tools = {
"run_shell": lambda cmd: subprocess.run(cmd, shell=True),
}
# GOOD: Explicit safe operations
agent_tools = {
"read_file": lambda path: read_safe(path), # checks whitelist
"write_file": lambda path, content: write_safe(path, content), # checks whitelist
"list_directory": lambda path: list_safe(path), # restricted paths only
}
def read_safe(path):
# Resolve to absolute path, check it's within allowed directory
real_path = os.path.realpath(path)
allowed_dir = os.path.realpath("/safe/agent/work")
if not real_path.startswith(allowed_dir):
raise PermissionError(f"Access denied: {path}")
return open(real_path).read()
Every tool explicitly validates its inputs. No string interpolation into shell commands. No symlink traversal attacks. The agent can’t ask for operations you didn’t explicitly define.
Database Access: Read-Only Replicas and Role-Based Permissions
If your agent queries a database, connect it as a read-only database user, or use row-level security to limit what it sees. Never give it the same credentials as your application.
-- Create a restricted role for agents
CREATE ROLE agent_readonly;
GRANT SELECT ON all tables IN SCHEMA public TO agent_readonly;
REVOKE DELETE, UPDATE, INSERT ON all tables IN SCHEMA public FROM agent_readonly;
-- Agent connects with this role
-- It can only SELECT; all mutations fail at the database layer
If the agent needs to write (e.g., logging results), write to a separate audit table with triggers that prevent deletion and enforce immutability.
API Access: API Keys With Narrow Scopes and Rate Limits
Don’t give the agent your main API keys or database credentials. Issue scoped API keys with minimal permissions.
# Agent gets a key that can only:
# - Read from specific endpoints
# - Write to a single audit log endpoint
# - Make at most 100 requests per hour
agent_api_key = issueKey(
scopes=["read:public_data", "write:audit_log"],
rate_limit=100 per hour,
ip_whitelist=[agent_container_ip],
expires_in=24_hours,
)
Third-party services like OpenAI’s API keys, database credentials, and webhook secrets should live in a separate secure store (HashiCorp Vault, AWS Secrets Manager, etc.). The agent never has direct access to raw credentials.
Network Isolation: Egress Filtering and DNS Allowlisting
Run the agent in a network namespace or behind a proxy that intercepts outbound connections.
# Only allow traffic to specific endpoints
iptables -A OUTPUT -d allowed.internal.service -j ACCEPT
iptables -A OUTPUT -j DROP
Or use a corporate HTTP proxy that inspects and blocks requests to internal services, credentials, or metadata endpoints (like http://169.254.169.254 on cloud platforms).
Monitoring and Alerting: Detect Anomalies in Real Time
Even with all these guards, monitor what the agent is actually doing. Log every operation—file read/write, API call, database query—and alert on suspicious patterns.
# Log and flag anomalies
def audit_operation(operation_type, resource, agent_id, result):
log_event({
"timestamp": now(),
"agent": agent_id,
"operation": operation_type,
"resource": resource,
"result": result,
})
# Alert on anomalies
if operation_type == "file_delete" and count_deletes_last_hour > 10:
alert("Agent deleting files rapidly—possible runaway loop")
if operation_type == "api_call" and cost_last_hour > daily_budget * 0.8:
alert("Agent approaching daily budget—possible infinite loop")
This catches mistakes early. If the agent gets stuck in a loop trying to delete files, you see it in seconds, not when the production database is gone.
Code Review Before Deployment: Static Analysis of Agent Actions
Before deploying an agent update, analyze what operations it can request. Use static analysis on the agent’s reasoning traces to flag potentially dangerous patterns.
A malicious prompt injection or model misalignment that tries to delete critical data should be detectable if you’re logging the agent’s intent (the function call it’s about to make) before executing it.
Bottom line: Never give agents direct access to dangerous operations. Mediate everything through typed, validated functions; run them in isolated containers; use read-only database roles and scoped API keys; and monitor every operation for anomalies. Layer these controls so one failure doesn’t become a catastrophe.
Question via Hacker News