This document describes the threat model, security findings, and recommendations for the jailed opencode sandbox implemented in flake.nix.
- Host filesystem - Prevent unauthorized read/write access outside the project directory
- Host network - Prevent direct network access; only allow whitelisted hosts via proxy
- Host processes & namespaces - Prevent interference with host processes or namespace escape
- Host secrets - Prevent access to credentials, tokens, or sensitive data outside permitted paths
- Future run integrity - Prevent the jailed process from influencing or breaking future sandbox runs
| Component | Source | Trust Level |
|---|---|---|
jail-nix |
sourcehut:~alexdavid/jail-nix | High - core sandbox primitive |
nixpkgs |
github:NixOS/nixpkgs/nixos-unstable | High - standard Nix packages |
ip2unix |
github:nixcloud/ip2unix | Medium - traffic redirection |
mitmproxy |
nixpkgs | Medium - runs on host, enforces allowlist |
opencode |
github:numtide/llm-agents.nix | Low - the agent being jailed |
The adversary (jailed opencode process) can:
- Execute arbitrary commands inside the jail
- Read/write all mounted paths
- Attempt symlink attacks, namespace escapes, kernel exploits
- Try to influence environment variables or wrapper behavior
- Attempt to corrupt state that affects future runs
The adversary cannot (by design):
- Access the host network directly (network namespace unshared)
- Access
/nix/storewriteable (read-only mount) - Create new sessions (
no-new-session) - Access paths not explicitly bind-mounted
Severity: Medium (by design, but worth documenting)
Location: flake.nix:57-59
commonJailOptionsNoNet = with jail.combinators; [
time-zone
no-new-session
mount-cwd # <-- Host's current working directory is mounted
];Impact:
- The agent has full read/write access to the entire project directory
- Any file in CWD can be read, modified, or deleted
- If CWD is
/home/user/or similar, large portions of home directory are exposed
Recommendation:
- Mount only specific subdirectories instead of full CWD
- Consider read-only mounts for directories that shouldn't be modified
- Document that CWD must be a trusted project directory
Severity: Medium (by design for functionality)
Location: flake.nix:92-97
opencodeConfigPaths = [
"~/.config/opencode"
"~/.local/share/opencode"
"~/.local/state/opencode"
];Impact:
- Agent can read API keys, tokens, and credentials stored in opencode config
- Agent can modify opencode configuration, affecting future runs
- Agent can inject malicious config or state that persists across jail sessions
Recommendation:
- Mount config directories as read-only if opencode doesn't need to write them
- Audit what credentials are stored in these paths
- Consider separate config profiles for jailed vs. host opencode
Severity: Low-Medium (DoS vector, limited path manipulation)
Location: flake.nix:296-305, 371-375
Details:
- Host wrapper creates
/tmp/jailed-agentsfor Unix socket + mitmproxy config - Jail has read/write access to this directory and
mitmproxy-confsubdirectory - Host cleanup uses
rm -rf "$SOCK_DIR"on exit
Attack Scenarios:
- Symlink attack on
mitmproxy-conf: Agent createsmitmproxy-confas symlink to another directory. Due tomkdir -pandset -euo pipefail, mitmproxy startup will fail (DoS to next run). - Socket manipulation: Agent could potentially delete or rename
proxy.sockduring execution. - Hosts file manipulation: Agent can modify
$SOCK_DIR/hostsbut this is overwritten at startup.
Why Path Traversal is Limited:
rm -rfon a symlink only removes the symlink, not the targetmkdir -p "$MITM_CONF_DIR"fails if final component is a symlinkset -euo pipefailensures failures abort the wrapper
Recommendation:
- Use per-run random temp directories:
mktemp -d /tmp/jailed-agents.XXXXXX - Pass
SOCK_DIRdynamically toadd-runtimeinstead of hardcoding - Validate/clear
$SOCK_DIRcontents before starting mitmproxy
Severity: Medium (footgun if misconfigured)
Location: flake.nix:431-439
if [ "${OPENCODE_DISABLE_JAIL:-0}" = "1" ]; then
# Runs opencode on HOST, only wrapped with ip2unix
exec ip2unix ... -- opencode "$@"
else
# Runs jailed opencode
${jailedOpencodeViaUnixSocket { }}/bin/jailed-opencode-ip2unix "$@"
fiImpact:
- If
OPENCODE_DISABLE_JAIL=1is set in host environment, jail is disabled - Agent cannot toggle this from inside jail (child cannot change parent env)
- Dangerous if set in shell profile, systemd unit, or config by accident
Recommendation:
- Rename to
OPENCODE_UNSAFE_DISABLE_JAILto signal danger - Add warning log output when this is enabled
- Never set in production or automated environments
Severity: Low (documented behavior, but easy to misuse)
Location: flake.nix:467-476
devShells.default = pkgs.mkShell {
packages = [
pkgs.nixd
pkgs.nixfmt
pkgs.statix
llm-agents.packages.${system}.opencode # <-- Unsandboxed opencode
ip2unixPkg
mitmproxyPkg
];
};Impact:
- Running
nix developthenopencodebypasses all jail protections - User may accidentally use unsandboxed opencode thinking it's jailed
Recommendation:
- Document that
nix run .#opencode-with-mitmis the only jailed entrypoint - Consider removing opencode from dev shell or renaming it (e.g.,
opencode-unsafe)
Severity: Low (architectural assumption)
Details:
- Jail uses
jail-nixto unshare network namespace - No direct verification that network namespace is truly isolated
- If
jail-nixhas a bug, network isolation could fail
Mitigation:
- Startup assertions test that direct network fails (
curl www.example.com) - Even if namespace escapes, mitmproxy allowlist still enforces host whitelist
- Multiple layers of defense (jail + proxy + allowlist)
Recommendation:
- Keep startup assertions enabled in production
- Periodically audit
jail-nixfor namespace handling bugs
Severity: Low
Location: flake.nix:367-375
SOCK_DIR="/tmp/jailed-agents" # Fixed pathImpact:
- Attacker-controlled files in
/tmp/jailed-agentspersist across runs - Could be used for DoS or influence behavior of next run
/tmpis typically world-writable, enabling cross-user attacks on multi-user systems
Recommendation:
- Use per-run random directories
- Set restrictive permissions on
SOCK_DIR(chmod 700) - Consider using
$XDG_RUNTIME_DIRinstead of/tmp
| Attack Vector | Likelihood | Impact | Mitigation Status |
|---|---|---|---|
| Direct network access | Low | High | ✅ Namespace unsharing + startup assertions |
| Network via non-allowed host | Low | Medium | ✅ mitmproxy --allow-hosts |
| Host filesystem escape | Low | High | ✅ jail-nix bind mounts only |
Symlink attack on /tmp |
Medium | Low | |
| Config manipulation | Medium | Medium | |
| CWD data exfiltration | High | Medium | |
| Jail disable via env var | Low | High | |
| Dev shell bypass | Medium | High | |
| jail-nix vulnerability | Low | Critical |
- Use per-run temp directories instead of fixed
/tmp/jailed-agents - Remove or restrict
mount-cwdfor the ip2unix jail variant if full project access isn't needed - Audit opencode config for sensitive data; consider read-only mounts where possible
- Rename
OPENCODE_DISABLE_JAILto signal danger (OPENCODE_UNSAFE_DISABLE_JAIL) - Add validation of
$SOCK_DIRcontents before mitmproxy startup - Set restrictive permissions on temp directories (
chmod 700)
- Document allowed entrypoints clearly (only
nix run .#opencode-with-mitm) - Consider removing opencode from dev shell or renaming it
- Add telemetry/logging when jail is disabled
The sandbox provides reasonable isolation against network access and host filesystem escape, with defense-in-depth via:
- Namespace unsharing (jail-nix)
- Unix socket-based proxy (ip2unix)
- Host allowlist enforcement (mitmproxy)
- Startup assertions
The main risks are:
- By-design host access (CWD, opencode config) - acceptable if understood
- Temp directory manipulation - can cause DoS, limited path traversal
- Configuration footguns (
OPENCODE_DISABLE_JAIL, dev shell)
The jail is not suitable for:
- Running untrusted code that may exploit kernel vulnerabilities
- Protecting against a determined adversary with root access
- Multi-tenant environments without additional hardening
For typical use (running AI agents on local projects with network allowlisting), the current design provides appropriate isolation with acceptable tradeoffs for usability.