Runtime safety policyThe exact rules terminal_exec and python_rpc enforce.
1. Workspace restriction
Terminal commands run with their working directory locked to the project root (process.cwd()). If a caller passes workingDirectory, it is resolved against the project root and rejected if the resolved path escapes via .. traversal. Python RPC scripts run in a fresh os.tmpdir() directory that is removed after the run.
2. Timeout
Default timeout is 30 seconds; the per-call cap is 120 seconds. On timeout the child is sent SIGTERM; if it doesn't exit within 1s it is sent SIGKILL. The recorded exit code on timeout is 124 with a stderr note.
3. Output truncation
Stdout and stderr are each capped at 8 KB. When the cap is hit the tool sets truncated: true in its output. Raw streams are not persisted — only the truncated capture lives in the ToolCall row.
4. Hard-blocked patterns (case-insensitive)
The following intents always fail before execution:
- Destructive filesystem (rm -rf, rmdir /s, del /s, format, mkfs, dd if=)
- Power / system (shutdown, reboot, halt, poweroff)
- Encoded shell (
powershell -EncodedCommand, base64 | sh) - Pipe-to-shell (
curl | sh,wget | sh, IEX downloads) - Deployment / publish (
git push, vercel deploy, railway up, npm publish) - Auth mutation (
npm login,npm token) - Secret dumping (
printenv, env dump, cat .env*, echo $HERMES_API_KEY) - Privilege escalation (
sudo,su) - Force kill (
kill -9, Stop-Process -Force) - Bare
eval/exec
5. Approval-gated risky commands
Anything not on the allow-list (and not blocked) requires an approval before it runs. The executor creates a TOOL_CALL ApprovalItem and parks the call as BLOCKED until you decide. Multi-statement commands (&&, ||, ;, |, redirects) are forced through this path.
6. Safe allow-list (runs without approval)
The allow-list is intentionally narrow:
- git status / diff / log / branch / show
- rg (ripgrep) for codebase search
- npm run lint / typecheck / build / test / ls
- npx tsc --noEmit
- npx next lint
- npx prisma validate / generate
- ls / dir / pwd
- cat / type / Get-Content (single-file reads)
- --version probes for node, npm, npx, python, tsc, next, prisma
7. Secrets are never handed to the child
Before spawn, the runtime strips HERMES_API_KEY, every OPENROUTER_* variable, and every DATABASE_URL* from the child process's environment. So even an allow-listed command like printenv (if it were on the allow-list — it is not) could not exfiltrate them. The child also gets AGENT_FOUNDRY_RUNTIME=1 for self-identification.
8. Python-specific policy
Every python_rpc script needs an approval. The static block-list also catches:
- os.environ dump (incl HERMES_API_KEY / OPENROUTER_*)
- subprocess(..., shell=True)
- os.system
- os.remove / shutil.rmtree
- open(...env)
- exec / eval
- sockets after import
- urllib / requests / httpx / aiohttp
9. What this is NOT
This is defense in depth, not a sandbox. The runtime runs inside the host Node process — there is no per-call container, no namespace isolation, no kernel-level filtering. A determined adversary who controls a mission objective could craft inputs that slip past the policy. The proper fix is per-mission Docker / Modal / Singularity backends — see the parity board for status. Until that lands, treat the policy as a guardrail against accidents and obvious abuse, not as an adversarial boundary.


