Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security

Forage provides isolation for AI agents, but it’s important to understand the threat model and limitations.

Threat Model

Trusted

  • Host system administrator
  • Nix store contents (from nixpkgs/trusted sources)
  • Forage module configuration

Untrusted

  • AI agent behavior
  • Code being worked on in workspace
  • Packages installed by agents at runtime

Security Layers

Container Isolation

Sandboxes use systemd-nspawn containers:

  • Separate PID namespace
  • Separate network namespace
  • Separate mount namespace
  • Resource limits (cgroups)
  • Ephemeral root filesystem

Filesystem Isolation

PathAccessNotes
/Read-writeEphemeral (tmpfs), lost on restart
/nix/storeRead-onlyShared from host
/workspaceRead-writeBind-mounted from host
/run/secretsRead-onlyAPI keys and credentials

Agents can only persistently modify files in /workspace.

Network Isolation

ModeDescription
fullUnrestricted internet access
restrictedAllowlist of specific hosts
noneNo network access

Even with network = "none", containers can communicate with the nix daemon socket.

Credential Isolation

API key mode: keys are stored in host-side files, copied into a per-sandbox secrets directory, and bind-mounted read-only. Wrapper scripts read the file and export the env var only for the agent process.

OAuth mode: tokens are injected via CLAUDE_CODE_OAUTH_TOKEN environment variable. The token is either a long-lived token from the forage-ctl token store (<stateDir>/tokens/claude-oauth.json, mode 0600) or a short-lived token extracted from the host keychain at sandbox creation time. Neither the host keychain nor the token store file is mounted into the container.

In both cases, a determined agent could discover the credential by inspecting its own environment or process memory. The isolation prevents accidental leakage, not intentional exfiltration. See Authentication for the full workflow.

Mitigations

ThreatMitigation
Agent exfiltrates API keysAPI proxy (keeps secrets on host); obfuscation via wrappers (UX convenience, not a security boundary)
Agent accesses host filesystemContainer isolation, explicit bind mounts only
Agent makes unwanted network callsNetwork isolation modes
Agent runs dangerous commandsPermission rules (allow/deny) via managed settings
Agent corrupts system stateEphemeral root, easy reset
Agent fills diskEphemeral tmpfs has size limits
Agent escapes containersystemd-nspawn security features

Limitations

Auth Obfuscation Is Not Foolproof

A determined agent could:

  • Read files in /run/secrets/ directly
  • Inspect its own process memory
  • Intercept API calls

Wrappers provide obfuscation, not security. They stop casual discovery, not intentional exfiltration.

Container Escape Vulnerabilities

systemd-nspawn is not a security boundary like a VM. Kernel vulnerabilities could allow container escape. For high-security scenarios, consider:

  • Running sandboxes in VMs
  • Additional seccomp filtering
  • SELinux/AppArmor policies

DNS Resolution Timing

In restricted mode, allowed host IPs are resolved at sandbox creation time and baked into nftables rules. If a host’s IPs change (e.g., CDN rotation), the rules become stale and connectivity may break until the sandbox is reconfigured with forage-ctl network.

Network Exfiltration

Even with network = "none", agents could potentially:

  • Encode data in DNS queries (if DNS is available)
  • Use timing side channels
  • Embed data in legitimate API calls

Workspace Access

Agents have full read-write access to /workspace. They could:

  • Modify or delete project files
  • Read sensitive files in the project
  • Create files that execute on the host

Best Practices

Secret Management

# Use proper secret management (sops-nix, agenix)
secrets = {
  anthropic = config.sops.secrets.anthropic-api-key.path;
};

# Don't hardcode secrets
# BAD: secrets = { anthropic = "/home/user/.secrets/key"; };

Template Design

# Minimize installed packages
extraPackages = with pkgs; [ ripgrep fd ];
# Don't include: curl, wget, netcat, etc. unless needed

# Use network isolation when possible
network = "none";  # For tasks that don't need network

# Use granular permissions instead of skipAll when possible
agents.claude.permissions = {
  allow = [ "Read" "Glob" "Grep" "Edit(src/**)" ];
  deny = [ "Bash(rm -rf *)" ];
};

Agent Permissions

Use the most restrictive permissions that still allow the agent to do its job:

  • Prefer granular allow/deny over skipAll
  • Use deny rules to block dangerous patterns even when allowing broad tool access
  • skipAll is convenient for trusted development workflows but grants full tool access

Workspace Hygiene

  • Don’t put sensitive files (SSH keys, credentials) in project directories
  • Use .gitignore / .jjignore to exclude sensitive patterns
  • Review agent-created files before committing

Regular Resets

# Reset sandbox periodically to clear accumulated state
forage-ctl reset myproject

Monitor Agent Activity

  • Review files modified by agents
  • Check git/jj history for unexpected changes
  • Monitor network traffic if concerned

Additional Security Features

API Proxy

The forage-ctl proxy command starts an HTTP proxy that:

  • Keeps secrets on the host, never in containers
  • Injects API keys into requests at runtime
  • Can log all API calls for audit
  • Enables rate limiting and request filtering

Future Security Enhancements

Syscall Filtering

Additional seccomp profiles to restrict:

  • Dangerous syscalls
  • Network operations
  • File operations outside allowed paths

Read-Only Workspace Mode

For review tasks where the agent shouldn’t modify files:

templates.review = {
  readOnlyWorkspace = true;
  # ...
};

This is implemented and enforces filesystem-level read-only mounting of /workspace.

Reporting Security Issues

If you discover a security vulnerability in Forage, please report it responsibly:

  1. Do not open a public issue
  2. Email security concerns to the maintainers
  3. Allow time for a fix before public disclosure

See the project repository for contact information.