Sandboxing and Tool Execution Security for AI Agents

When an agent can run code, every tool call is a potential attack surface. Designing sandbox environments that contain blast radius without hobbling capability.

AgentixForce Team··12 min read

The moment you give an AI agent the ability to execute code, you've changed the threat model for your entire system. You're no longer just worried about the agent saying wrong things. You're worried about it doing wrong things with real computational resources. Code execution tools are the most powerful and the most dangerous capability you can give an agent, and the security architecture around them cannot be an afterthought.

But code execution isn't the only dangerous tool. File system access, shell commands, HTTP requests to arbitrary URLs, and database write access all carry real risk. This post treats all tool execution as a security-critical surface and covers the architecture that contains the damage when something goes wrong.

Why Tool Execution Is Dangerous by Default

When an agent calls a tool, the model is making a judgment about what to do. That judgment is based on its training, the system prompt, and the current context. All three can be manipulated. A model that has been jailbroken through prompt injection may make tool calls that a correctly-operating agent would never make. A model that received misleading context may make tool calls that are technically correct given what it was told but harmful in reality.

The sandbox is the architectural answer to this. If a compromised agent calls a shell execution tool, the sandbox ensures that shell is running in an isolated environment with no access to the host filesystem, no network access to internal services, and hard resource limits that prevent resource exhaustion. The agent is compromised. The sandbox limits what that compromise can actually do.

The Threat Model for Agent Tool Calls

Before designing sandbox architecture, you need to be explicit about what you're defending against. The threat model for agent tool calls includes four distinct attacker scenarios.

  • Prompt injection: an attacker embeds malicious instructions in data the agent processes. The injected instructions direct the agent to make harmful tool calls it would not otherwise make.
  • Model errors: the model makes an incorrect tool call due to reasoning errors, hallucination, or misinterpretation of instructions. Not malicious, but the effect can be just as harmful.
  • Malicious tool implementations: a compromised or malicious tool implementation that returns harmful data or takes unauthorized side effects when called.
  • Resource exhaustion: an agent, whether compromised or simply running a large task, consuming excessive CPU, memory, network bandwidth, or making too many API calls.

Sandbox Technologies Compared

There are several technologies for sandboxing code execution in agent systems, each with different tradeoffs in security strength, performance overhead, and operational complexity.

  • Docker containers with seccomp profiles: widely available, reasonable performance overhead, but container isolation is process-level not kernel-level. A kernel vulnerability can escape. Good for most use cases.
  • gVisor: Google's container sandbox that interposes on system calls at the user-space level. Provides much stronger isolation than standard Docker. Some performance overhead on syscall-heavy workloads.
  • Firecracker microVMs: AWS's lightweight virtual machine technology. True VM-level isolation with near-container boot times. Used by AWS Lambda and Fly.io. Strong isolation, very fast startup.
  • WebAssembly (WASM) sandboxes: language-level sandboxing for supported execution environments. Excellent for running agent-generated code in the browser or in WASM-compatible runtimes. Limited to WASM-compilable languages.
  • E2B sandboxes: managed sandboxed environments specifically designed for AI code execution. Handles the operational complexity of running isolated execution environments at scale.
python
# Using E2B for managed sandbox execution
from e2b_code_interpreter import Sandbox

async def execute_agent_code(code: str, timeout: int = 30) -> dict:
    """
    Execute agent-generated code in an E2B sandbox.
    The sandbox is isolated from the host and auto-destroyed after execution.
    """
    async with Sandbox(timeout=timeout) as sandbox:
        # Each sandbox is a fresh isolated environment
        # - no access to host filesystem
        # - no access to internal network services
        # - auto-destroyed after timeout or context exit
        result = await sandbox.run_code(code)

        return {
            "stdout": result.logs.stdout,
            "stderr": result.logs.stderr,
            "results": [str(r) for r in result.results],
            "error": str(result.error) if result.error else None,
        }
        # Sandbox auto-destroys here, no cleanup needed
        # Any files written inside are gone

# Firecracker alternative for on-premise deployments
class FirecrackerSandbox:
    def __init__(self, vm_config: dict):
        self.config = vm_config

    async def __aenter__(self):
        # Boot a fresh microVM in ~125ms
        self.vm = await self.start_vm(self.config)
        return self

    async def __aexit__(self, *args):
        await self.vm.destroy()  # full VM teardown

    async def execute(self, code: str) -> dict:
        return await self.vm.run(code)

Layered Sandbox Architecture

A production sandbox isn't a single technology. It's a layered defense. Each layer handles a different aspect of isolation, and they're designed so that a failure or bypass in one layer gets caught by the next.

Layered Sandbox Architecture for Tool ExecutionHOST SYSTEMNETWORK BOUNDARY — egress restricted to approved domains onlyAGENT PROCESSorchestrates executiontool_router.call(name, args)Allowed Toolsweb_search()read_url()write_output()Blockedexec_shell() — blockedwrite_fs() — blockedSANDBOXgVisor / Firecracker microVMExecution EnvironmentCPU: limited (0.5 cores)RAM: capped (256MB)FS: ephemeral onlynet: no outboundsyscalls: filteredpid_ns: isolatedtimeout: 30s maxexecresultPOLICY ENGINEweb_search(query)ALLOWread_url(url)CHECK DOMAINexec_shell(cmd)DENYwrite_db(query)DENYEXECUTION AUDITtool: web_search exit: 0 ms: 842tool: read_url exit: 0 ms: 214tool: exec_shell BLOCKED by policytool: write_output exit: 0 ms: 12Fig 1 — Sandbox layers: the host enforces network egress; the container caps resources and isolates the filesystem; the policy engine blocks disallowed tool calls before they reach the sandbox.
Figure 1, Four concentric defense layers: the host enforces network egress rules; the sandbox container caps resources and isolates the filesystem; the policy engine checks each tool call before execution; the audit log records every execution for post-incident analysis.

The outermost layer is network-level isolation. Egress from agent processes should be restricted to a known allowlist of external domains. Internal services like databases and message queues should be behind service mesh policies that require specific service account identities, not just network adjacency.

Resource Limits and Timeouts

Every tool execution needs explicit resource limits. Without them, a single misbehaving agent can exhaust the resources of the host and impact all other agents running on the same infrastructure. This isn't a theoretical concern. It happens.

python
import asyncio
import resource
from typing import Callable, Any

class ResourceLimitedExecutor:
    """Execute tool calls with explicit resource constraints."""

    def __init__(
        self,
        max_cpu_seconds: float = 10.0,
        max_memory_mb: int = 256,
        max_wall_clock_seconds: float = 30.0,
        max_file_size_mb: int = 50,
    ):
        self.cpu_limit = max_cpu_seconds
        self.memory_limit = max_memory_mb * 1024 * 1024  # bytes
        self.timeout = max_wall_clock_seconds
        self.file_limit = max_file_size_mb * 1024 * 1024

    def _set_limits(self):
        """Called in subprocess before execution begins."""
        # CPU time limit, raises SIGXCPU if exceeded
        resource.setrlimit(resource.RLIMIT_CPU, (self.cpu_limit, self.cpu_limit))
        # Address space limit, prevents memory overallocation
        resource.setrlimit(resource.RLIMIT_AS, (self.memory_limit, self.memory_limit))
        # File size limit, prevents disk exhaustion
        resource.setrlimit(resource.RLIMIT_FSIZE, (self.file_limit, self.file_limit))
        # Open file descriptors, prevents fd exhaustion attacks
        resource.setrlimit(resource.RLIMIT_NOFILE, (64, 64))

    async def execute(self, tool_fn: Callable, **kwargs) -> Any:
        try:
            return await asyncio.wait_for(
                tool_fn(**kwargs),
                timeout=self.timeout,
            )
        except asyncio.TimeoutError:
            raise TimeoutError(
                f"Tool execution exceeded {self.timeout}s limit"
            )
        except MemoryError:
            raise ResourceError("Tool execution exceeded memory limit")

Network Isolation for Tool Execution

Network isolation is one of the most important sandbox controls for agents that process external content. An agent that can make HTTP requests to arbitrary URLs can be directed by a prompt injection attack to exfiltrate data to an attacker-controlled server. Network allowlisting is the direct mitigation. Without it, you're relying on the model to refuse exfiltration requests. Don't rely on the model for this.

python
from urllib.parse import urlparse
import ipaddress

class NetworkPolicyEnforcer:
    def __init__(self, allowed_domains: list[str], block_private: bool = True):
        self.allowed_domains = set(d.lower() for d in allowed_domains)
        self.block_private = block_private

    def is_allowed(self, url: str) -> tuple[bool, str]:
        """Returns (is_allowed, reason)."""
        parsed = urlparse(url)
        hostname = parsed.hostname or ""

        # Block private/internal IP ranges
        if self.block_private:
            try:
                ip = ipaddress.ip_address(hostname)
                if ip.is_private or ip.is_loopback or ip.is_link_local:
                    return False, f"Blocked: private IP {hostname}"
            except ValueError:
                pass  # hostname, not IP, continue

        # Block metadata service endpoints (cloud SSRF)
        if hostname in ("169.254.169.254", "metadata.google.internal"):
            return False, f"Blocked: cloud metadata endpoint"

        # Check domain allowlist
        domain = hostname.lower()
        if domain in self.allowed_domains:
            return True, "allowed"
        # Check subdomain match
        for allowed in self.allowed_domains:
            if domain.endswith(f".{allowed}"):
                return True, "allowed subdomain"

        return False, f"Blocked: {domain} not in allowlist"

    async def safe_request(self, url: str, **kwargs) -> dict:
        allowed, reason = self.is_allowed(url)
        if not allowed:
            raise PermissionError(f"Network request blocked: {reason}")
        return await httpx.get(url, **kwargs)

The Tool Call Security Pipeline

Individual sandbox controls are most effective when applied as a coherent pipeline through which every tool call must pass. The pipeline ensures no tool call reaches execution without passing through all required checks.

Tool Call Security PipelineLLMREQUESTagent wantsto call toolSCHEMAVALIDargs matchexpected typesPOLICYCHECKis this toolallowed here?PARAMSANITIZEstrip injectionsbound inputsEXEC INSANDBOXisolated envcapped resourcesRESULTAUDITlog outputcheck anomaliesinvalid args → error to LLMdenied action → alert + loginjection detected → abortSAFE RESULTreturned to agent contextlogged with trace_idFig 2 — Every tool call passes through six security stages. Failures at validation, policy, or sanitization abort execution and log the incident before anything executes.
Figure 2, Six-stage security pipeline for every tool call. Failures at schema validation, policy check, or parameter sanitization abort the call and log the incident. Only calls that pass all stages reach sandboxed execution.

Testing Your Sandbox

Sandbox security requires adversarial testing, not just functional testing. A sandbox that works correctly for expected inputs may fail for unexpected ones. Your sandbox test suite needs to include active attempts to escape the sandbox from within the execution environment.

  • Attempt to read files outside the sandbox directory. Verify access is denied.
  • Attempt to make HTTP requests to private IP ranges and internal services. Verify they are blocked.
  • Run a CPU spin loop and verify the timeout kills the process within the configured limit.
  • Allocate memory in a loop and verify the memory cap is enforced.
  • Attempt to fork a large number of child processes. Verify process count limits apply.
  • Attempt to write a large file. Verify disk quota enforcement.
  • Attempt to install packages that would provide additional capabilities. Verify this is blocked.

Conclusion

Tool execution security is not a configuration checkbox. It's a defense-in-depth system that needs to be designed, tested, and maintained with the same rigor as any other security-critical component. The cost of getting it wrong isn't just a security incident. It's the kind of incident that ends products and careers.

The layered approach covered here, network isolation at the host level, resource limits and filesystem isolation in the sandbox, a policy engine before execution, and comprehensive audit logging, provides multiple independent lines of defense. When one layer fails, the others contain the damage. That's the property you need to ship agentic systems involving code execution into production safely.

Frequently Asked Questions

Build secure agentic systems with AgentixForce

We help companies design, build, and secure production-grade AI agent systems. From architecture reviews to full implementation, our team brings deep expertise to every engagement.

More from AI Security

All articles
USERINPUTignore prev instructAGENT LLM⚠ injection detected!EXFILTRATEsend_email()delete_db()PROMPT INJECTION ATTACK VECTOR
AI Security

Prompt Injection Attacks and Defenses in Agentic AI Systems

Prompt injection is the most dangerous vulnerability in agentic AI today. Learn how attackers exploit it through direct inputs, web content, and tool outputs, and build layered defenses that actually hold.

14 min read
May 15, 2026
AGENT CONTEXTAPI_KEY: [VAULT]DB: [VAULT]SECRETS MGRHashiCorp VaultAWS SecretsSECRETS VAULT ARCHITECTURE
AI Security

Secrets Management in Agent Runtimes: Keeping Credentials Out of the Context Window

Agents need API keys, tokens, and passwords to do their work. Here is how to provide those credentials without ever letting them appear in a prompt or log.

10 min read
May 12, 2026
AGENTsvc-acct-7JWT TOKENsub: agent-007scope: read,callexp: 1hsig: ✓ validRESOURCEvalidate scope✓ read: allowed✗ delete: deniedAGENT IDENTITY AND AUTH FLOW
AI Security

Agent Identity and Authentication: Who Can an Agent Act As?

As agents take actions on behalf of users, identity becomes a first-class concern. OAuth scopes, service accounts, and least-privilege auth patterns for agentic systems.

11 min read
May 6, 2026
PERMISSION MATRIXreadwritedeleteadminresearch-botwriter-botmanager-botOVER-PERMD⚠ over-permissioned agent expands blast radius on compromiseLEAST-PRIVILEGE ACCESS CONTROL
AI Security

Least-Privilege Principles for Agent Tool Access

Giving your agent exactly the permissions it needs and nothing more. Why over-permissioned agents are a liability and how to scope tool access tightly without breaking workflows.

8 min read
May 4, 2026
Sandboxing and Tool Execution Security for AI Agents