Skip to main content
Risoluto’s HTTP surface is designed for local, trusted environments by default. This guide covers the controls available when you need to expose it or harden the sandbox.

Hardening Checklist

1

Set a Write Token

When binding to a non-loopback address, always set a write token:
export RISOLUTO_BIND="0.0.0.0"
export RISOLUTO_WRITE_TOKEN="$(openssl rand -hex 32)"
All mutating requests (POST, PUT, PATCH, DELETE) then require:
Authorization: Bearer <your-token>
2

Enable Sandbox Security Defaults

Verify these are enabled (they are by default):
codex:
  sandbox:
    security:
      noNewPrivileges: true     # --security-opt=no-new-privileges
      dropCapabilities: true    # Drop ALL Linux capabilities
3

Restrict Egress

Lock down outbound network access from worker containers:
codex:
  sandbox:
    egressAllowlist:
      - "api.openai.com"
      - "api.github.com"
      - "registry.npmjs.org"
4

Enable gVisor (Optional)

For defense-in-depth container isolation:
codex:
  sandbox:
    security:
      gvisor: true
Requires runsc installed on the Docker host.
5

Back Up the Master Key

The MASTER_KEY protects all encrypted secrets. Store it in a password manager or secrets vault — if lost, all credentials become unrecoverable.

Write Guard

Never bind to 0.0.0.0 without also setting RISOLUTO_WRITE_TOKEN. Without it, anyone on your network can mutate configuration and secrets.
All mutating API requests are protected by a write guard middleware:
ScenarioBehavior
Request from loopback (127.0.0.1, ::1)Allowed — no token required
Non-loopback, no RISOLUTO_WRITE_TOKEN set403 write_forbidden
RISOLUTO_WRITE_TOKEN set, valid bearer tokenAllowed from any address
RISOLUTO_WRITE_TOKEN set, missing/invalid token401 write_unauthorized
Read-only methods (GET, HEAD, OPTIONS) are exempt from write guard checks and always allowed from any address. Webhook routes (/webhooks/*) bypass IP/token checks — they use their own HMAC signature verification.

Credential Encryption

Risoluto encrypts all stored credentials using AES-256-GCM with a key derived from MASTER_KEY via SHA-256:
PropertyValue
AlgorithmAES-256-GCM
IV length12 bytes (random per write)
Auth tag16 bytes (GCM authentication)
Key derivationSHA-256 of MASTER_KEY
Storagesecrets.enc (file-backed) or encrypted_secrets table (SQLite)
Audit trailsecrets.audit.log records every set/delete operation
The DB-backed store (DbSecretsStore) encrypts each secret individually with its own IV and auth tag, stored as separate rows. Key names are plaintext; values are encrypted.
Worker containers run on the risoluto-internal Docker bridge network by default. The data plane (when using control/data plane split) is not exposed to the host — it only listens on this private network.For additional isolation, use a custom Docker network:
codex:
  sandbox:
    network: my-isolated-network
When codex.sandbox.security.dropCapabilities is true (default), worker containers start with --cap-drop=ALL. Combined with noNewPrivileges: true, this ensures containers cannot escalate privileges.The resulting docker run flags:
--cap-drop=ALL
--security-opt=no-new-privileges
gVisor (runsc) provides a user-space kernel that intercepts all syscalls from the container, adding a strong isolation boundary between the agent and the host kernel.
codex:
  sandbox:
    security:
      gvisor: true
This passes --runtime=runsc to Docker. Install gVisor first:
# Debian/Ubuntu
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt-get update && sudo apt-get install -y runsc
sudo systemctl restart docker
The egressAllowlist restricts which domains worker containers can reach. When the list is non-empty, only listed domains are reachable:
codex:
  sandbox:
    egressAllowlist:
      - "api.openai.com"
      - "api.github.com"
      - "registry.npmjs.org"
      - "pypi.org"
This is enforced via the Codex sandbox policy. An empty list (default) allows all outbound traffic.
For fine-grained syscall filtering, specify a custom seccomp profile:
codex:
  sandbox:
    security:
      seccompProfile: /etc/docker/seccomp-risoluto.json
This passes --security-opt=seccomp=/etc/docker/seccomp-risoluto.json to Docker.

Sandbox Policy Summary

PolicyDefaultEffect
noNewPrivilegestruePrevents privilege escalation via setuid/setgid binaries
dropCapabilitiestrueDrops ALL Linux capabilities (--cap-drop=ALL)
gvisorfalseUser-space kernel for syscall interception
seccompProfile""Custom seccomp profile (empty = Docker default)
egressAllowlist[]Domain-level egress filtering (empty = allow all)
threadSandbox"workspace-write"Agent can only write to its workspace directory
turnSandboxPolicy{type: "workspaceWrite"}Per-turn sandbox: workspace write + no network access

Rate Limiting

All /api/* and /metrics endpoints are rate-limited to 300 requests per 60 seconds per client. Webhook endpoints (/webhooks/linear) have a separate limit of 600 requests per 60 seconds. Exceeding the limit returns HTTP 429 Too Many Requests.

Filesystem Paths

Host-Side

PathPurposeSafe to delete?
.risoluto/ (or $DATA_DIR)SQLite DB, config overlay, encrypted secretsNo — lose all history
../risoluto-workspaces/Per-issue workspace directoriesYes — re-created on dispatch
~/.codex/Codex CLI auth credentialsPartial — need codex login again

Inside Worker Containers

PathPurpose
/home/agent/.codex-runtimeEphemeral per-attempt CODEX_HOME
/home/agentContainer HOME with build caches
/workspaceBind-mounted issue workspace
Directories like ~/.risoluto-codex or ~/.risoluto-codex-home are not created by Risoluto. If found on your host, they are leftover Codex CLI data and can be safely deleted.

Environment Variables

VariableDefaultDescription
RISOLUTO_BIND127.0.0.1HTTP bind address
RISOLUTO_WRITE_TOKENBearer token for remote write access
MASTER_KEYEncryption key for the secrets store

What’s Next

Trust Model

Understand Risoluto’s trust boundaries and threat model.

Custom Sandbox

Build a custom sandbox image with your project’s toolchain.
Last modified on March 31, 2026