Docs / Security / Sandbox escape

sandbox_escape

When an agent has a code-execution tool (Python REPL, shell wrapper, Jupyter kernel, sandboxed JavaScript runtime), sandbox_escape scans the tool's arguments + return values for patterns indicating the agent is trying to break out: importing OS modules, evaluating dynamic code, opening raw network sockets, reading cloud instance metadata, walking /proc or /sys, escalating privileges, or reading host secret files.

What this detector catches

Twelve built-in patterns covering Python and JavaScript escape vectors:

  • python_os_import — agent imports os from the sandboxed Python runtime (matches import os and from os import).
  • python_subprocess_import — imports subprocess (matches import subprocess and from subprocess import).
  • shell_invocation os.system, os.popen, subprocess.run / call / Popen / check_output, child_process.exec / spawn.
  • dynamic_code_eval eval(...) or exec(...) called with arguments.
  • js_vm_module — Node.js vm.runInNewContext, vm.runInContext, vm.createContext. The vm module is itself a sandbox primitive; reaching for it from inside a code-exec sandbox is a classic nested-context escape vector.
  • js_dynamic_require require(...) with a non-literal argument (variable, expression, function call). The safe form require('fs') with a string literal is NOT flagged.
  • js_function_constructor (new) Function('...') constructing a runnable function from a string at runtime; identical attack surface to eval.
  • raw_socket_open import socket / socket.socket / create_connection / gethostbyname.
  • instance_metadata_access — AWS / GCP / Azure link-local IMDS endpoints ( 169.254.169.254, metadata.google.internal, metadata.azure.com).
  • proc_sys_access /proc/self, /proc/1, /sys/kernel, /sys/class.
  • privilege_escalation chmod <numeric>, chown root, setuid(0) patterns. Does NOT match sudo or su - directly; add a custom pattern if you need those flagged.
  • host_secret_read .aws/credentials, .ssh/id_rsa, .ssh/id_ed25519, .ssh/known_hosts, /etc/passwd, /etc/shadow.

The detector matches against tool-call arguments and return values via RE2 (no ReDoS risk). Built-in patterns run first, then custom patterns. The production path emits ALL distinct matches per execution (capped at 20), so multi-vector attacks surface as multiple failure_groups rather than collapsing into the first match. Pattern compilation panics at process startup if any built-in rule fails to compile — a fail-fast posture that prevents shipping a silent-dark detector to production.

What it does NOT catch

  • Path-traversal sequences themselves ( ../../../-style prefixes). The detector matches the destination (e.g. /etc/passwd, .ssh/id_rsa), not the traversal pattern. This is deliberate: false-positive cost on legitimate ../ usage in code-exec tools would be too high.
  • Dynamic import() in JavaScript — the current JS pattern set covers vm.*, dynamic require(), and the Function constructor, but does NOT cover dynamic import() expressions. Add a custom pattern via the custom-patterns primitive if your stack uses ES-module dynamic imports.
  • Obfuscated escape attempts — base64-encoded payloads, hex-encoded strings, multi-line concatenation. The pattern set is open-source-bound; sophisticated attackers can bypass with relatively cheap obfuscation. Defense-in-depth practices (real OS-level sandboxing, syscall restriction, capability-based confinement) are the load-bearing layer; this detector is the telemetry layer above them.

What to do when it fires

Open the failure-group detail page. The Playbook walks through three diagnostics: identify which pattern_id fired (the signature carries the pattern name + a redacted snippet of the matched substring); trace which tool call produced the hit (intent matters — a Python REPL agent legitimately importing os is qualitatively different from a code-search agent suddenly invoking subprocess); decide between revoking the tool from the agent vs tightening the OS-level sandbox vs adding a custom allowlist for the legitimate use case.

Custom rules

Per-project custom sandbox-escape patterns ship via the same primitive that backs prompt_injection + data_leakage. See Custom security patterns for the editor + REST surface. Use custom patterns to extend coverage to vectors the built-ins miss (e.g. sudo / su - if your stack treats those as dangerous, dynamic ES-module import(), Ruby Kernel.system, obfuscated base64 payloads decoded to dangerous shapes) or to lock down argument shapes specific to your tools.

Related docs