Detonate¶
detonate is a separate binary (cmd/detonate, built to bin/detonate) for Tier-A local behavioral malware analysis — actually running a live sample in a disposable VM to observe what it does. It is deliberately not a berth subcommand: berth's entire value is safe-by-default agent execution against untrusted code and repos, and bolting live-malware detonation onto that binary would blur the exact blast-radius boundary berth exists to protect. detonate is a distinct, higher-privilege, explicitly-invoked operation.
If you only need to inspect a suspicious file without running it, you don't need detonate — use berth spawn --forensic or --evidence for static triage instead.
What it is not¶
detonate does not ship a sandbox. It ships an enforced state machine around a sandbox you build and prove is isolated. There is no golden VM image, no INetSim gateway, and no bundled malware — you provision all of it yourself, per the detonation strategy doc.
Operator prerequisites — it FAILS CLOSED without these¶
detonate refuses to run rather than fake success. Before run will do anything, you need:
- An immutable golden VM image — a pre-built, read-only, hashed analysis VM (instrumentation installed, network pre-pinned to the isolated segment, powered off clean). Every run clones it; the golden itself is never booted.
- An isolated, no-uplink network — a segment with no NAT, no bridge, and no route to the host, berth's network, or your LAN. Isolation is code-enforced: the Tart backend boots the guest with
tart run --net-softnet --net-softnet-allow=<cidr>, never--net-bridged(a bridge can reach the host LAN/internet). The--gatewayyou pass is that softnet allow-list CIDR, anddetonaterejects any allow-list that isn't a single private range —0.0.0.0/0,::/0, or any public/routable CIDR fails closed before boot. - Passwordless sudo for Softnet — Tart's softnet needs root, and it invokes its helper via
sudo. Without a passwordless-sudo entry,tart run --net-softnetdies immediately with "root privileges are required … Softnet process terminated prematurely." Configure it once (scoped to the softnet binary only), e.g.: - An INetSim (or equivalent) fakenet gateway VM on that private segment (e.g.
10.0.0.0/24), running out-of-band capture (tcpdump/tshark) and simulated DNS/HTTP/SMTP, so the sample gets a plausible network to talk to without ever reaching the real internet. The softnet allow-list pins the guest to exactly this gateway's private CIDR. - A sacrificial or segmented detonation host — never the analyst's daily Mac. A full VM escape should land on a throwaway box, not your primary machine.
The Tart backend now runs a real detonation: inject builds a read-only ISO of the sample (hdiutil makehybrid -iso — Tart attaches raw images and ISOs but rejects an Apple UDIF/UDRO .dmg) and run attaches it read-only via tart run --disk=<image>:ro, so the sample disk can never be written back to. Provisioning the golden image and the fakenet gateway VM is still operator work — detonate enforces the containment around them, it does not build them for you.
Known hardening trade-off: artifacts leave the guest through a writable host share (tart run --dir=out:<dir>), which is an escape surface a compromised guest could abuse. collect is already symlink-safe (it never dereferences guest-planted symlinks), but the stronger fix — collecting artifacts from an offline disk after poweroff — is deferred, since it can't be implemented or verified without a real Tart guest.
Preflight: detonate check¶
Run detonate check before a real detonation. It validates that the environment is present and provably isolated without running, cloning, or booting anything — a read-only safety net so nobody fires a live sample into a misconfigured, non-isolated setup. It prints a ✓/✗ checklist and exits non-zero if any hard check fails (fail closed — an unverified environment is never "ready").
detonate check # tart, hdiutil, softnet sudo, state dir
detonate check --golden remnux-golden # + verify the golden exists
detonate check --gateway 10.0.0.0/24 # + verify the CIDR is isolated
detonate check --golden remnux-golden --gateway 10.0.0.0/24
What it checks:
- tart installed —
tartonPATH; hard-fails if missing (detonation is impossible without it). - hdiutil available — needed to build the read-only sample image (macOS); hard-fails if missing.
- softnet passwordless sudo — verifies a
NOPASSWDsudoers rule exists for the softnet binary, the #1 detonation gotcha (tart run --net-softnetdies without it). Read-only: it parsessudo -n -l's policy listing and never executes softnet or prompts for a password. Hard-fails if no rule is found (if you just added one, runsudo -vand re-check). See the operator prerequisites. - state dir writable — the detonate state dir (
~/.berth/detonate, orDETONATE_STATE_DIR) exists or can be created and written; hard-fails otherwise. - golden image (only with
--golden) — verifies the named golden exists via a read-onlytart list; hard-fails if named but missing. - isolation gateway (only with
--gateway) — runs the same softnet isolation validator therunpath enforces. A ✓ here meansrunwould accept this CIDR as isolated; a public/0.0.0.0/0/non-private range hard-fails.
check never runs tart run/clone or touches run state — its only side effects are a read-only tart list (for --golden) and a read-only sudo -n -l policy query (for the softnet check). A green result means the environment is ready, not that any sample is safe: benign behavior in the sandbox is not proof of safety, and you must still ensure the named gateway is a genuinely no-uplink fakenet segment.
Verb lifecycle¶
detonate route <static.json> # pick Tier A/B/C, or refuse
detonate create <run> --golden <name> # CoW-clone the immutable golden
detonate inject <run> <sample> # offline disk-mount; hash into custody
detonate run <run> --gateway <private-cidr> --timeout 180s [--yes]
# boot on softnet pinned to <private-cidr>; hard timeout
detonate collect <run> --out <dir> # offline RO-mount; pull + hash artifacts
detonate destroy <run> # delete the clone; golden stays untouched
Example sequence:
detonate route static-findings.json
detonate create run-042 --golden remnux-golden
detonate inject run-042 ./sample.bin
detonate run run-042 --gateway 10.0.0.0/24 --timeout 300s
detonate collect run-042 --out ~/detonate-out/run-042
detonate destroy run-042
route reads berth's static-triage JSON (arch/format) and decides Tier A (local), Tier B/C (self-hosted or commercial cloud — not orchestrated by this tool), or refuses. The decision to detonate is always a separate, explicit, human act — never automatic.
Enforced hard boundaries¶
These are enforced in code, not left to operator discipline:
- Isolated-network-only.
runcallsConfigureIsolatedNet(which rejects any allow-list that isn't a single private CIDR), validates the result before boot, and re-validates the captured CIDR again immediately before invokingtart run; anything that isn't isolated fails closed andRunis never invoked. The guest is booted with--net-softnet, never--net-bridged. - Offline sample injection, read-only.
injectbuilds a read-only disk image of the sample;runrefuses to boot a run with no injected sample and attaches the image via--disk=<image>:ro. There is no live network or shared-folder path for the sample to enter the guest, and the sample disk is never writable. - PoweredOff-gated collect.
collectchecksPoweredOfffirst and refuses to pull artifacts from a running VM. - Typed confirmation.
runprints a warning and requires you to typedetonate <run-name>back exactly, or pass--yesand setDETONATE_I_UNDERSTAND=1— a bare--yesin a script is not enough. - Auto-destroy on failure. If
Runerrors,detonatedestroys the clone automatically before returning the error. - No reuse. Every run's lifecycle state (
Created→Injected→Detonated→Collected, plus the terminalDestroyed) is persisted to a per-run JSON file (~/.berth/detonate/<run>.json, overridable viaDETONATE_STATE_DIR) and checked before every verb:createrefuses if a run by that name already exists,injectrequiresCreated,runrequiresInjectedand refuses outright — "run<run>already detonated" — if the state is alreadyDetonated/Collected, andcollectrequiresDetonated. This holds across separate invocations, not just within one process.runmarks stateDetonatedbefore booting, not after success, so a boot that errors and then fails to auto-destroy still leaves the run blocked from reuse. Each gated verb also takes an exclusive per-run advisory lock (<run>.lock) for its whole check-then-act section, so two concurrent invocations against the same run name can't both pass the state check before either persists the result.destroyis the sole escape hatch: it's always allowed, from any state, and clears both the state and lock files so the run name can be created fresh.
Chain of custody¶
Every verb appends an entry to berth's existing append-only audit log: inject records the sample's sha256 before any VM work happens (so the attempt is logged even if the injection fails closed); collect hashes every extracted artifact. Review the trail with:
Benign in the sandbox is NOT proof of safety¶
Read this before trusting a quiet run. A sample that shows no malicious behavior during detonation has not been proven safe — it may be evasive, environment-aware, waiting for a trigger the sandbox didn't provide, or simply sleeping past the timeout. Only positive findings (observed C2, drops, injection, persistence) are trustworthy. Never green-light a sample as safe based on a quiet detonation, and say so loudly in every report.
See the detonation strategy doc for the full threat model, isolation substrate rationale, and the complete list of inviolable boundaries.