Code Execution Policy
The code-execution policy category governs what generated code agents can execute. It controls allowed languages, blocked commands, restricted filesystem paths, package installation, sandbox requirements, and optional human review before execution.
Use it when your agents generate and run code -- whether in a cloud sandbox (E2B) or locally via subprocess.
Rules
| Rule | Type | Default | Description |
|---|---|---|---|
allowed_languages | string[] | ["python", "javascript", "sql"] | Languages agents are allowed to execute |
blocked_languages | string[] | [] | Languages explicitly denied (takes precedence over allowed) |
blocked_commands | string[] | ["rm -rf", "chmod", "chown"] | Command strings that are forbidden in code. Uses substring matching against the full command |
blocked_paths | string[] | ["/etc", "/var", "~/.ssh", "~/.aws"] | Filesystem paths that code cannot access. Matches using path prefix |
allowed_paths | string[] | ["/workspace", "/tmp"] | If set, code can only access these paths (allowlist). Leave empty to allow all paths not in blocked_paths |
allow_package_install | boolean | false | Whether the agent can install packages at runtime |
allowed_packages | string[] | [] | If allow_package_install is true, restrict to these packages |
max_execution_time_seconds | integer | 30 | Maximum allowed execution duration per command |
max_output_size_kb | integer | 1024 | Maximum output size per execution |
require_review | boolean | false | Require human approval before every code execution |
sandbox_required | boolean | true | Whether code must run in a sandbox (E2B). Set to false for local subprocess execution |
action_on_violation | string | "block" | "block" to prevent execution, "warn" to log and continue |
How Matching Works
Understanding the difference between blocked commands and blocked paths is critical for configuring policies correctly.
Blocked Commands -- Substring Match
Blocked commands use case-insensitive substring matching against the full command string. This means the blocked command text must appear somewhere in the generated command.
| Blocked Command | Generated Code | Match? | Why |
|---|---|---|---|
rm -rf | rm -rf /etc/* | Yes | "rm -rf" is in "rm -rf /etc/*" |
wget | wget https://example.com/file.sh | Yes | "wget" is in the command |
curl | curl -X POST https://api.example.com | Yes | "curl" is in the command |
ls | ls -la | Yes | "ls" is in "ls -la" |
rm -rf | rm file.txt | No | "rm -rf" is not in "rm file.txt" |
Putting a command like ls in blocked_paths instead of blocked_commands won't work. blocked_paths checks filesystem paths (like /etc or ~/.ssh), not command names. If you want to block a command, add it to blocked_commands.
Blocked Paths -- Prefix Match
Blocked paths use prefix matching against the filesystem paths extracted from the command. The SDK extracts paths starting with / or ~/ from the command string before evaluation.
| Blocked Path | Path in Command | Match? | Why |
|---|---|---|---|
/etc | /etc/shadow | Yes | /etc/shadow starts with /etc |
/etc | /etc/passwd | Yes | /etc/passwd starts with /etc |
~/.ssh | ~/.ssh/id_rsa | Yes | ~/.ssh/id_rsa starts with ~/.ssh |
/var | /var/log/syslog | Yes | /var/log/syslog starts with /var |
/etc | /tmp/etc-backup | No | /tmp/etc-backup does not start with /etc |
Allowed Paths -- Allowlist Mode
If allowed_paths is set (non-empty), only those path prefixes are permitted. Any path not matching an allowed prefix is blocked. If allowed_paths is empty, all paths not in blocked_paths are allowed.
| Configuration | Behavior |
|---|---|
blocked_paths: ["/etc"], allowed_paths: [] | Block /etc/*, allow everything else |
blocked_paths: [], allowed_paths: ["/workspace", "/tmp"] | Only allow /workspace/* and /tmp/*, block all others |
blocked_paths: ["/etc"], allowed_paths: ["/workspace"] | Both apply -- /etc blocked, only /workspace allowed |
Sandbox vs Local Execution
Code execution policies support two modes depending on how your agent runs code:
Sandboxed Execution (E2B)
For agents that execute code in a cloud sandbox (E2B). The sandbox provides an isolated environment, so the policy adds defense-in-depth.
{
"name": "Sandbox Code Governance",
"category": "code-execution",
"rules": {
"allowed_languages": ["python", "javascript"],
"blocked_commands": ["rm -rf", "chmod", "chown"],
"blocked_paths": ["/etc", "~/.ssh", "~/.aws"],
"allowed_paths": ["/workspace", "/tmp"],
"allow_package_install": false,
"max_execution_time_seconds": 30,
"max_output_size_kb": 1024,
"sandbox_required": true,
"action_on_violation": "block"
}
}
Key settings:
sandbox_required: true-- warns if no sandbox is availableallowed_pathsset -- restricts execution to specific directories inside the sandbox
Local Execution (Subprocess)
For agents that generate shell commands and run them on the host machine via subprocess. More dangerous than sandboxed execution, so policies are your primary safety net.
{
"name": "Local Code Execution Governance",
"category": "code-execution",
"rules": {
"allowed_languages": ["python", "shell"],
"blocked_commands": ["rm -rf", "chmod", "chown", "wget", "curl"],
"blocked_paths": ["/etc", "~/.ssh", "~/.aws", "/var"],
"allowed_paths": [],
"allow_package_install": false,
"max_execution_time_seconds": 30,
"max_output_size_kb": 1024,
"sandbox_required": false,
"require_review": false,
"action_on_violation": "block"
}
}
Key differences from sandbox mode:
sandbox_required: false-- no cloud sandbox, executing locallyallowed_languagesincludes"shell"-- the agent generates shell commands, not just Pythonblocked_commandsincludes"wget","curl"-- prevent data exfiltration from the hostallowed_pathsis empty -- we block specific dangerous paths instead of allowlisting
For local execution, block wget and curl to prevent data exfiltration. In a sandbox, these are less dangerous since the sandbox is isolated.
Enforcement
Code execution policies are evaluated at mid-execution time. When the SDK records a code execution event, the policy is checked before the code runs.
Enforcement Flow
Agent generates code
│
▼
SDK calls record_code_execution(language, code, paths)
│
▼
Controlplane evaluates code-execution policies
│
├── Check language restrictions
├── Check blocked commands (substring match)
├── Check blocked paths (prefix match)
├── Check allowed paths (if set)
├── Check package restrictions
└── Check execution time / output size limits
│
├── ALLOW → Code executes
├── WARN → Code executes, warning logged in trace
└── BLOCK → PolicyViolationError raised, code never runs
In Agent Code
import waxell_observe as waxell
from waxell_observe import WaxellContext
from waxell_observe.errors import PolicyViolationError
waxell.init()
try:
async with WaxellContext(
agent_name="code-agent",
workflow_name="code-exec",
enforce_policy=True,
mid_execution_governance=True,
) as ctx:
# Record the code execution -- governance checks happen here
ctx.record_code_execution(
language="shell",
code="ls -la /tmp",
paths=["/tmp"],
)
# If we reach here, the policy allowed it
result = subprocess.run("ls -la /tmp", shell=True, capture_output=True)
except PolicyViolationError as e:
print(f"Blocked: {e}")
# The code never executed
With the Subprocess Instrumentor
For local execution agents, the subprocess instrumentor automatically intercepts subprocess.run() calls and enforces governance without manual record_code_execution() calls:
import waxell_observe as waxell
from waxell_observe.instrumentors import instrument_all
waxell.init()
instrument_all(libraries=["subprocess"]) # Opt-in: auto-intercept subprocess
async with WaxellContext(
agent_name="code-agent",
enforce_policy=True,
mid_execution_governance=True,
) as ctx:
# The instrumentor automatically:
# 1. Extracts the command from subprocess.run()
# 2. Calls record_code_execution() with language="shell"
# 3. Checks governance -- raises PolicyViolationError if blocked
# 4. Only executes the subprocess if governance allows it
result = subprocess.run("ls -la /tmp", shell=True, capture_output=True)
The subprocess instrumentor is opt-in only. It patches subprocess.run(), subprocess.Popen(), and os.system(). It only activates inside a WaxellContext -- outside of one, subprocess calls work normally with zero overhead.
Human Review
Set require_review: true to gate every code execution on human approval, regardless of whether the command itself would be allowed by other rules.
When review is enabled:
- Agent generates code
- SDK presents the code to the approval handler (e.g., terminal prompt, Slack message)
- If approved -- governance rules are still evaluated. A dangerous command is blocked even if approved.
- If denied --
PolicyViolationErrorraised immediately
from waxell_observe.approval import prompt_approval
waxell.init(on_policy_block=prompt_approval)
async with WaxellContext(
agent_name="code-agent",
enforce_policy=True,
mid_execution_governance=True,
) as ctx:
ctx.record_code_execution(language="shell", code="uname -a")
# Terminal prompt: "Approve this operation? (y/n):"
# If approved AND policy allows → executes
# If approved BUT policy blocks → still blocked
# If denied → PolicyViolationError
Human review is an additional gate, not a bypass. Even if a reviewer approves a command, policy rules (blocked commands, blocked paths) are still enforced. This prevents accidental approval of dangerous commands.
See Approval Workflows for custom handlers (Slack, webhooks, etc).
Creating a Policy
Via Dashboard
- Navigate to Governance > Policies
- Click Create Policy
- Select category: Code Execution
- Configure rules:
| Field | Sandbox Mode | Local Mode |
|---|---|---|
| Allowed Languages | python, javascript | python, shell |
| Blocked Commands | rm -rf, chmod, chown | rm -rf, chmod, chown, wget, curl |
| Blocked Paths | /etc, ~/.ssh, ~/.aws | /etc, ~/.ssh, ~/.aws, /var |
| Allowed Paths | /workspace, /tmp | (leave empty) |
| Allow Package Install | OFF | OFF |
| Max Execution Time | 30 | 30 |
| Max Output Size | 1024 | 1024 |
| Require Review | OFF | OFF |
| Sandbox Required | ON | OFF |
| Action on Violation | Block | Block |
- Set Scope to target specific agents, or leave empty for all agents
- Save
Via API
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "Local Code Execution Governance",
"category": "code-execution",
"rules": {
"allowed_languages": ["python", "shell"],
"blocked_commands": ["rm -rf", "chmod", "chown", "wget", "curl"],
"blocked_paths": ["/etc", "~/.ssh", "~/.aws", "/var"],
"allowed_paths": [],
"allow_package_install": false,
"max_execution_time_seconds": 30,
"max_output_size_kb": 1024,
"require_review": false,
"sandbox_required": false,
"action_on_violation": "block"
},
"scope": {
"agents": ["local-code-agent"]
}
}'
Observability
Code execution governance events appear in the execution detail:
Trace Tab
code_exec:shellspan -- the governance check. Present for everyrecord_code_execution()call. This span does not mean the code executed -- it means the code was evaluated for governance.subprocess.runspan -- the actual execution. Only present if governance allowed the command to run. If you seecode_exec:shellbut nosubprocess.run, the command was blocked before execution.
Governance Tab
- Pre-execution: Shows
code_execution_rulesmetadata with the full policy configuration - Mid-execution: Per-command evaluations showing ALLOW, WARN, or BLOCK with the specific reason (e.g., "Blocked command 'rm -rf' found in code" or "Code accessed blocked path '/etc/shadow'")
Incidents
Policy violations create incidents visible in Governance > Incidents. Each incident includes:
- The policy that triggered it
- The blocked command or path
- The agent name and execution ID
- Whether it was a mid-execution block (prevented execution) or post-execution audit finding
Warn vs Block
Switch between enforcement modes using action_on_violation:
| Mode | Behavior | Use Case |
|---|---|---|
"block" | PolicyViolationError raised, code never executes | Production -- prevent dangerous actions |
"warn" | Warning logged, code still executes | Monitoring -- discover what agents are doing before enforcing |
Start with "warn" to audit your agents' behavior, then switch to "block" once you've tuned the rules.
Combining with Other Policies
Code execution policies work alongside other categories:
- Safety policies limit total steps and tool calls; code execution governs the content of those calls
- Content policies scan for PII or credentials in inputs/outputs; code execution scans for dangerous commands
- Approval policies gate high-stakes actions;
require_reviewspecifically gates code execution - Network policies control outbound access;
blocked_commandswithwget/curlprevents downloads at the command level
Common Gotchas
-
Blocked commands vs blocked paths:
blocked_commandsdoes substring matching on the command string ("rm -rf"matches"rm -rf /tmp").blocked_pathsdoes prefix matching on extracted filesystem paths ("/etc"matches the path/etc/shadow). Don't mix them up. -
Allowed paths empty = allow all: When
allowed_pathsis empty, all paths not inblocked_pathsare permitted. Setallowed_pathsto restrict to a specific allowlist. -
Multiple code execution policies: If you have both a sandbox policy and a local policy, scope them to different agents. Otherwise both evaluate for every agent, and the sandbox policy's
sandbox_required: truewill warn for agents running locally. -
Language must match: The
allowed_languagescheck requires the language string to match exactly. For local subprocess agents, use"shell"-- not"bash"or"sh". -
Substring matching is broad: Blocking
"rm"would also block"format"because"rm"appears in"format". Be specific: use"rm -rf"instead of"rm".