Offense runs two kinds of work in a sandbox: a live assessment through the runner, and a source scan of an uploaded archive. Both use the same saved worker snapshot and the same isolation rules.

The snapshots

Sandbox images are versioned saved snapshots. They are release artifacts with recorded metadata, not a mutable "latest" that changes under a running job.

Snapshot Adds
offense-worker-v0-1-0 Base workstation: Node.js, Python, Docker, git, curl, jq
offense-worker-v0-2-0 Per-job model gateway, egress proxy and UID firewall, evidence manifest and redaction, separate verifier
offense-worker-v0-3-0 Nuclei 3.11.1 (13,899 templates), testssl.sh, ffuf, Trivy, jwt_tool, plus a partial OWASP Top 10 coverage map
offense-worker-v0-4-0 Semgrep, Schemathesis, Arjun, Corsy, Interactsh client — the current default

The runner creates sandboxes from offense-worker-v0-4-0 with noEnv: true and a finite TTL, so a job inherits no owner environment. Job-scoped secrets are injected at runtime and revoked on terminal status.

Known limits the release notes record plainly: container egress mediation still uses host networking rather than full mediation, and not every added CLI has been run end-to-end in production. The v0.4 tools were validated as starting in a fresh sandbox.

Engines

An external runner accepts engine: "strix" or engine: "shannon".

  • Strix receives the model in its own dialect (openrouter/<id>).
  • Shannon receives openrouter:<id> (SHANNON_AI_MODEL) and runs its nested Docker Compose stack — Temporal plus an ephemeral pentest worker. It runs in no-source mode: an empty repository is mounted, and only findings that are live-validated (SARIF status: exploited) are reported.

Both reach the model only through the per-job gateway, which holds the provider key. The investigator gets a short-lived bearer token, a request cap and a model-spend cap. The gateway requests usage accounting, records token counts and cost, enforces its own caps, and writes model-usage.json into the evidence bundle.

Network boundary

  • Egress is allowlist-only and exact: host or host:port, port defaulting to
    1. Wildcards, private and local addresses, and database or mail ports are refused, and the list is capped at 20 entries.
  • Only the target's host and port are allowlisted for a job. The login host is added only when it differs.
  • Destination scope is enforced outside the model: redirects and IPv4/IPv6 resolution are validated, and metadata and private/control-plane endpoints are blocked.

Model routing

  • Platform-funded (PLATFORM_OPENROUTER_KEY): self-service workspaces launch without connecting a key; model usage bills at provider cost + 20%.
  • Workspace BYOK: an OpenRouter key stored at /app/settings, encrypted with AES-GCM under CREDENTIALS_KEY (base64, 32 bytes). Only the last four characters are stored in clear. Model usage records billedMicros = $0, and a separate MODEL_BUDGET_MICROS cap (default $1) applies.
  • A workspace key does not override a Boundless selection.

The catalog exposed by GET /api/v1/models includes boundless/deepseek-v4.1-flash (at cost), deepseek-v4.1-flash-abliterated (at cost, requires basic KYC self-attestation) and deepseek/deepseek-v4.1-flash-1017 (provider cost + 20%, or direct with a workspace key). ASSESSMENT_MODEL_ID overrides the default; the provider-specific model ID is saved on each assessment so routing and metering agree.

Runner deployment shape

The runner is a Cloudflare Container Worker (offense-runner) in the same account, reached over the RUNNER service binding. It is a control plane, not the isolation boundary — Boat remains the provider for each job.

Jobs are sharded across RUNNER_SHARDS containers by assessment id, each running RUNNER_CONCURRENCY jobs, so a sandbox always lands on the container holding its job.

Sandbox templates

There is no user-editable template editor. What you can vary per launch:

Lever Where
Budget budgetMicros on launch
Egress egress on launch, or POST /sandboxes/:id/egress
Instruction instruction in the body, or /sandboxes/:id/config
Model model in the body, or the workspace default
Lifetime maxSeconds (clamped by MAX_SANDBOX_SECONDS, default 14400)
Engine engine when dispatching through the runner

For repeatable multi-step work, the product's template concept is a recipe — an orchestration contract rather than a Boat snapshot. Recipes such as critical-dependency-remediation describe the profile (agent+verifier, ci+assessment, …), the prompts, the tools and the ordered steps; the coding agent does the editing and Offense supplies disposable runtime validation.