Sandboxing AI Agent Tool Use: Filesystem, Network, and Process Isolation for Autonomous Actions
Problem
AI agents execute tool calls on real infrastructure: writing files, running shell commands, making HTTP requests, modifying databases. When an agent hallucinates a command, misinterprets context, or gets manipulated through prompt injection, it executes with the full permissions of the host process. A single rm -rf / passed through a shell tool destroys the host. A curl to an attacker-controlled URL exfiltrates environment variables. The agent does not understand the consequences of what it executes. It runs commands the same way it generates text: confidently and without hesitation. Every tool call is a potential blast radius event, and the only defense is to ensure the execution environment limits what damage any single action can cause.
Threat Model
- Adversary: (1) Agent executing hallucinated or incorrect commands at machine speed. (2) Prompt injection causing the agent to run attacker-crafted commands. (3) Supply chain compromise in tool implementations that the agent invokes.
- Blast radius: Without sandboxing: the full host filesystem, network, and all processes running under the same user. With sandboxing: limited to the sandbox boundary (a container, a microVM, or a chroot with restricted permissions).
Configuration
gVisor Sandbox for Agent Execution
gVisor interposes a user-space kernel between the agent process and the host kernel. Syscalls from the agent never reach the host kernel directly. This limits kernel exploit surface and prevents container escapes.
# gvisor-runtime-class.yaml
# Install gVisor (runsc) on nodes, then create a RuntimeClass.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
# agent-sandbox-pod.yaml
# Agent runs inside a gVisor sandbox with strict resource limits.
apiVersion: v1
kind: Pod
metadata:
name: agent-sandbox
namespace: ai-agents
labels:
app: agent-sandbox
sandbox-type: gvisor
spec:
runtimeClassName: gvisor
serviceAccountName: agent-sandbox-sa
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: agent
image: registry.example.com/agent-runner:v2.1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
volumeMounts:
- name: workspace
mountPath: /workspace
- name: tmp
mountPath: /tmp
env:
- name: AGENT_WORKSPACE
value: "/workspace"
- name: AGENT_TIMEOUT_SECONDS
value: "300"
volumes:
- name: workspace
emptyDir:
sizeLimit: 500Mi
- name: tmp
emptyDir:
sizeLimit: 100Mi
terminationGracePeriodSeconds: 10
Firecracker MicroVM for Stronger Isolation
For high-risk tool calls (shell execution, code interpretation), Firecracker microVMs provide hardware-level isolation. Each agent action runs in a fresh microVM that boots in under 200ms and is destroyed after execution.
{
"boot-source": {
"kernel_image_path": "/opt/firecracker/vmlinux",
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off"
},
"drives": [
{
"drive_id": "rootfs",
"path_on_host": "/opt/firecracker/rootfs/agent-rootfs.ext4",
"is_root_device": true,
"is_read_only": true
},
{
"drive_id": "workspace",
"path_on_host": "/tmp/agent-workspace-${SESSION_ID}.ext4",
"is_root_device": false,
"is_read_only": false
}
],
"machine-config": {
"vcpu_count": 2,
"mem_size_mib": 1024
},
"network-interfaces": []
}
The root filesystem is read-only. The workspace drive is an ephemeral disk created for this session and destroyed after. Network interfaces are omitted entirely, meaning the microVM has no network access unless explicitly configured.
Filesystem Read/Write Scoping
Restrict agent file access to a designated workspace directory. Use bind mounts with read-only flags for reference data.
# agent-filesystem-policy.yaml
# OPA policy: validate that agent pods only mount allowed paths.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sagentfilesystempolicy
spec:
crd:
spec:
names:
kind: K8sAgentFilesystemPolicy
validation:
openAPIV3Schema:
type: object
properties:
allowedMountPaths:
type: array
items:
type: string
maxVolumeSizeMi:
type: integer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sagentfilesystempolicy
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
mount := container.volumeMounts[_]
not path_allowed(mount.mountPath)
msg := sprintf(
"Agent container mount path '%v' is not in allowed list",
[mount.mountPath]
)
}
path_allowed(path) {
allowed := input.parameters.allowedMountPaths[_]
startswith(path, allowed)
}
# Apply the constraint
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAgentFilesystemPolicy
metadata:
name: agent-fs-restriction
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["ai-agents"]
parameters:
allowedMountPaths:
- "/workspace"
- "/tmp"
maxVolumeSizeMi: 500
Network Egress Restrictions
Agents should have no network access by default. Allow specific egress only when a tool requires it.
# agent-network-deny-all.yaml
# Default deny: agent pods cannot make any network connections.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-default-deny
namespace: ai-agents
spec:
podSelector:
matchLabels:
app: agent-sandbox
policyTypes:
- Egress
- Ingress
ingress: []
egress: []
---
# Selective allow: agents that need API access get a specific policy.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-api-egress
namespace: ai-agents
spec:
podSelector:
matchLabels:
app: agent-sandbox
network-profile: api-access
policyTypes:
- Egress
egress:
# DNS resolution
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
# Internal API only
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: internal-api
ports:
- protocol: TCP
port: 443
Timeout Enforcement
Every agent action must have a hard timeout. An agent stuck in a loop or waiting on a hung process must be terminated.
# timeout_executor.py
# Wraps every tool call in a timeout. Kills the process if it exceeds the limit.
import signal
import subprocess
import sys
class TimeoutError(Exception):
pass
def execute_with_timeout(
command: list[str],
timeout_seconds: int = 60,
working_dir: str = "/workspace"
) -> dict:
"""Execute a command with a hard timeout. Returns stdout, stderr, exit code."""
try:
result = subprocess.run(
command,
capture_output=True,
text=True,
timeout=timeout_seconds,
cwd=working_dir,
env={
"PATH": "/usr/bin:/bin",
"HOME": "/workspace",
"LANG": "C.UTF-8",
},
)
return {
"stdout": result.stdout[:10000], # Truncate output
"stderr": result.stderr[:5000],
"exit_code": result.returncode,
"timed_out": False,
}
except subprocess.TimeoutExpired:
return {
"stdout": "",
"stderr": f"Command timed out after {timeout_seconds} seconds",
"exit_code": -1,
"timed_out": True,
}
Rollback Mechanisms for Failed Actions
For file operations, snapshot the workspace before each action. If the action fails or produces unexpected results, restore the snapshot.
#!/bin/bash
# agent-action-wrapper.sh
# Wraps each agent action with snapshot/rollback capability.
# Usage: agent-action-wrapper.sh <timeout_seconds> <command> [args...]
set -euo pipefail
WORKSPACE="/workspace"
SNAPSHOT_DIR="/tmp/snapshots"
TIMEOUT="${1:?Timeout required}"
shift
COMMAND=("$@")
# Create snapshot
SNAPSHOT_ID=$(date +%s)-$$
mkdir -p "$SNAPSHOT_DIR"
tar cf "$SNAPSHOT_DIR/$SNAPSHOT_ID.tar" -C "$WORKSPACE" . 2>/dev/null || true
echo "[agent-wrapper] Snapshot $SNAPSHOT_ID created"
echo "[agent-wrapper] Executing: ${COMMAND[*]}"
echo "[agent-wrapper] Timeout: ${TIMEOUT}s"
# Execute with timeout
EXIT_CODE=0
timeout "$TIMEOUT" "${COMMAND[@]}" || EXIT_CODE=$?
if [ "$EXIT_CODE" -ne 0 ]; then
echo "[agent-wrapper] Command failed with exit code $EXIT_CODE"
echo "[agent-wrapper] Rolling back workspace to snapshot $SNAPSHOT_ID"
rm -rf "$WORKSPACE"/*
tar xf "$SNAPSHOT_DIR/$SNAPSHOT_ID.tar" -C "$WORKSPACE" 2>/dev/null || true
echo "[agent-wrapper] Rollback complete"
fi
# Clean up old snapshots (keep last 10)
ls -t "$SNAPSHOT_DIR"/*.tar 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true
exit "$EXIT_CODE"
Runtime Monitoring
# Prometheus alerts for sandbox violations
groups:
- name: agent-sandbox-monitoring
rules:
- alert: AgentSandboxOOMKilled
expr: >
kube_pod_container_status_last_terminated_reason{
namespace="ai-agents",
reason="OOMKilled"
} > 0
labels:
severity: warning
annotations:
summary: "Agent container {{ $labels.container }} OOM killed in pod {{ $labels.pod }}"
runbook: "Review agent memory usage. Increase limits or investigate memory leak in tool execution."
- alert: AgentSandboxTimeout
expr: >
increase(agent_tool_execution_timeouts_total{namespace="ai-agents"}[5m]) > 3
labels:
severity: warning
annotations:
summary: "Agent {{ $labels.pod }} exceeded timeout 3+ times in 5 minutes"
runbook: "Agent may be stuck in a loop. Check tool call audit logs."
- alert: AgentNetworkPolicyViolation
expr: >
increase(cilium_drop_count_total{
reason="POLICY_DENIED",
namespace="ai-agents"
}[5m]) > 0
labels:
severity: critical
annotations:
summary: "Agent pod attempted blocked network connection"
runbook: "Agent attempted egress outside its allowed destinations. Investigate tool call that triggered the request."
Expected Behaviour
- Agent tool calls execute inside gVisor sandboxes (or Firecracker microVMs for high-risk operations)
- Agent filesystem access is limited to /workspace (read-write, 500Mi max) and /tmp (100Mi max)
- Root filesystem is read-only; no privilege escalation possible
- Network access denied by default; specific egress allowed only for tools that need API access
- Every tool call has a hard timeout (default 60 seconds, configurable per tool)
- Failed actions automatically roll back the workspace to the pre-action snapshot
- OOM kills, timeouts, and network policy violations generate alerts
Trade-offs
| Control | Impact | Risk | Mitigation |
|---|---|---|---|
| gVisor runtime | 5-15% syscall overhead compared to native runc | Performance-sensitive agent tasks run slower | Use gVisor for untrusted tool calls. Run trusted, performance-critical tools with runc in a separate pod with stricter RBAC. |
| Firecracker microVMs | 150-200ms boot overhead per action | Latency adds up for agents making many sequential tool calls | Batch related actions. Keep a pool of warm microVMs for low-latency execution. |
| Read-only root filesystem | Agent cannot install packages or modify system files at runtime | Some tools expect writable system paths | Mount /tmp as writable. Pre-install all required tools in the container image. |
| Default deny networking | Agent tools that need external APIs fail silently | Agents fail without clear error messages about blocked network | Return explicit error messages when network is blocked. Document which tools require network access. |
| Workspace snapshots | Disk I/O overhead for creating tar snapshots before each action | Slow for large workspaces | Use filesystem-level snapshots (btrfs/zfs) instead of tar for workspaces over 100Mi. |
Failure Modes
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| gVisor runtime not installed on node | Pod stuck in Pending state | kubectl describe pod shows RuntimeClass not found |
Install gVisor on agent nodes. Use node affinity to schedule agent pods only on gVisor-ready nodes. |
| Snapshot rollback fails | Workspace left in corrupted state after failed action | Agent reports file errors on subsequent actions | Destroy and recreate the workspace volume. Agent re-initializes from scratch. |
| Timeout too short for legitimate operations | Agent actions killed before completion | High rate of timeout failures in audit logs | Tune per-tool timeouts based on observed execution times. Set generous limits for known slow operations. |
| Network policy not enforced | Agent makes unrestricted egress connections | Cilium/Calico logs show no policy drops; external connections succeed | Verify CNI plugin supports NetworkPolicy. Test with a curl from an agent pod to confirm policy enforcement. |
When to Consider a Managed Alternative
Building agent sandboxing infrastructure requires gVisor or Firecracker setup, Gatekeeper policies, network policies, and monitoring.
- Sysdig (#122): Runtime container security with drift detection, syscall monitoring, and automated response for sandboxed agent pods.
- Grafana Cloud (#108): Centralized monitoring dashboards for sandbox health, timeout rates, and resource consumption.
- HCP Vault (#65): Dynamic credentials for agent pods so that sandbox compromise does not expose long-lived secrets.
Premium content pack: Agent sandboxing pack. gVisor RuntimeClass configs, Firecracker microVM templates, Gatekeeper filesystem policies, NetworkPolicy templates, timeout wrapper scripts, and Prometheus alert rules for sandboxed agent deployments.