Detonate manual

Detonate is a behavioral detonation platform for software supply chain security. It takes the artifacts you depend on — npm packages, Python libraries, Java JARs, binaries and Docker images — and runs them inside completely isolated micro-virtual machines to observe what they actually do.

Every file the artifact reads, every network connection it makes, every process it spawns is captured and classified. If it tries to steal your SSH keys, exfiltrate credentials, or phone home to a command-and-control server — you'll know before it ever touches production.

Not a scanner. Scanners check code and signatures. Detonate runs the code in a realistic environment and watches what happens. Static analysis tells you what code looks like. Detonate tells you what it does.

How does it work?

When you submit an artifact (e.g. npm install some-package), Detonate:

  1. Spins up a micro-virtual machine in ~125 milliseconds using Firecracker — the same technology AWS uses for Lambda and Fargate
  2. Plants honeypot files inside the VM — fake SSH keys, AWS credentials, API tokens, .env files. If the artifact touches any of them, that's an instant red flag
  3. Installs and runs your artifact inside the VM, just like it would run on a real server
  4. Monitors everything — file access, network connections, DNS queries, process execution, credential hunting
  5. Generates a verdict: benign, suspicious, or policy-violating — with a full event timeline explaining why

The artifact sees a real Linux machine with real files, real network, and realistic credentials to steal. It has no way to detect that it's being observed.

Why Firecracker?

Most sandboxing tools use Docker containers or ptrace-based tracing (like strace). Sophisticated malware can detect both — by checking /.dockerenv, inspecting /proc/1/cgroup, or measuring the timing overhead of syscall interception.

Detonate uses Firecracker microVMs instead. Firecracker is an open-source virtual machine monitor created by Amazon for running AWS Lambda. Each detonation runs inside a real virtual machine with its own Linux kernel, backed by hardware-level KVM isolation. The artifact sees:

Firecracker boots in ~125ms, uses minimal resources (~2MB memory overhead), and provides the same isolation guarantees as a full virtual machine. If malware can escape a Firecracker VM, that's a KVM kernel bug — not a misconfigured Docker socket.

Requirements

ComponentRequirement
OSLinux (x86_64) with KVM support
CPUIntel VT-x or AMD-V (check with egrep -c '(vmx|svm)' /proc/cpuinfo)
KVM/dev/kvm must exist and be accessible
DockerDocker 24+ (for containerized deployment)
RAM4GB minimum (each microVM uses 2GB by default)
Disk~2GB for Firecracker + kernel + rootfs images
DatabaseSQLite (default) or PostgreSQL 14+

Quick start

Everything is baked into the Docker image — Firecracker, kernel, rootfs images and the monitoring agent. One command, 20 seconds:

# That's it. Everything is included.
docker run -d \
  --name detonate \
  -p 3000:3000 \
  --device /dev/kvm:/dev/kvm \
  finsys/detonate:latest

Then open http://your-host:3000, create your admin account, and submit your first artifact.

Batteries included. The Docker image contains Firecracker, a Linux kernel, rootfs images for all supported ecosystems (npm, PyPI, Maven, binary) and the in-VM monitoring agent. No additional downloads or setup required.
KVM required. The --device /dev/kvm flag passes your CPU's hardware virtualization to the container. Your host must be a Linux machine (bare metal or VM with nested virtualization enabled) with an Intel VT-x or AMD-V capable CPU.

Custom Firecracker installation

Skip this section for most deployments. The Docker image already includes Firecracker, the kernel, and all rootfs images. You only need this if you want to use a different Firecracker version, a custom kernel, or custom rootfs images on a bare-metal installation.

Firecracker is a single static binary (~3MB) with zero dependencies. No package manager, no runtime, no configuration files — just one executable that talks directly to your CPU's virtualization hardware.

# Create directories
sudo mkdir -p /opt/detonate/{rootfs,data}
sudo chown -R $(whoami):$(whoami) /opt/detonate

# Download Firecracker v1.11.0 (latest stable)
ARCH=$(uname -m)  # x86_64 or aarch64
VERSION="v1.11.0"
curl -fsSL "https://github.com/firecracker-microvm/firecracker/releases/download/${VERSION}/firecracker-${VERSION}-${ARCH}.tgz" \
  -o /tmp/firecracker.tgz

# Extract and install
tar xzf /tmp/firecracker.tgz -C /tmp
cp /tmp/release-${VERSION}-${ARCH}/firecracker-${VERSION}-${ARCH} /opt/detonate/firecracker
chmod +x /opt/detonate/firecracker

# Verify
/opt/detonate/firecracker --version

# Clean up
rm -rf /tmp/firecracker.tgz /tmp/release-${VERSION}-${ARCH}

Kernel image

Every virtual machine needs a Linux kernel to boot. Detonate uses a minimal kernel image (~21MB) that's optimized for fast startup. This is not your host's kernel — it runs only inside the microVM.

# Download the recommended kernel
curl -fsSL "https://s3.amazonaws.com/spec.ccfc.min/img/quickstart_guide/x86_64/kernels/vmlinux.bin" \
  -o /opt/detonate/vmlinux

# Verify
ls -lh /opt/detonate/vmlinux   # should be ~21MB
Custom kernels. You can compile your own kernel with CONFIG_VIRTIO_* and CONFIG_VSOCK enabled for smaller images (~5MB). See the Firecracker docs.

Rootfs images

When Firecracker boots a microVM, it needs a filesystem to use as the VM's "hard drive". These are called rootfs images — each one is a self-contained Linux environment tailored to a specific artifact type.

Think of them like Docker images, but for virtual machines. Each rootfs contains:

ImageRuntimeSizeUsed for
rootfs-npm.ext4Node.js 20~500MBnpm packages
rootfs-pypi.ext4Python 3.12~400MBPython packages (pip, wheels)
rootfs-maven.ext4Java 21 (JRE)~400MBJava JARs and Maven artifacts
rootfs-binary.ext4Ubuntu 24.04~200MBBinary executables (ELF, scripts)

Building rootfs images

Detonate ships with a build system that creates rootfs images from standard Docker base images. You need Go 1.21+, Docker, and e2fsprogs (for mkfs.ext4).

# Install build dependencies
sudo apt install -y golang-go e2fsprogs docker.io

# From the detonate repository
cd sandbox/

# Build everything at once
make build-all

# Or build individually
make build-agent          # compile the monitoring agent (~5MB)
make build-rootfs-npm     # Node.js environment (~500MB)
make build-rootfs-pypi    # Python environment (~400MB)
make build-rootfs-maven   # Java environment (~400MB)
make build-rootfs-binary  # Ubuntu base (~200MB)

# Install to /opt/detonate/rootfs/
make install
Build once, reuse forever. Rootfs images only need to be rebuilt when you want to update the base runtime version (e.g. Node 20 → 22) or when Detonate releases a new version of the monitoring agent. They do not change between detonations.

Custom rootfs images

You can create rootfs images from any Docker image. This is useful if your artifacts need specific runtimes, libraries, or tools that aren't in the default images.

# Export any Docker image as a filesystem
docker create --name temp your-custom-image:latest
docker export temp | gzip > custom-rootfs.tar.gz
docker rm temp

# Create an ext4 filesystem image
dd if=/dev/zero of=rootfs-custom.ext4 bs=1M count=1024
mkfs.ext4 rootfs-custom.ext4

# Mount and populate
sudo mkdir -p /mnt/rootfs
sudo mount rootfs-custom.ext4 /mnt/rootfs
sudo tar xzf custom-rootfs.tar.gz -C /mnt/rootfs

# Add the Detonate monitoring agent
sudo cp detonate-agent /mnt/rootfs/usr/local/bin/
sudo mkdir -p /mnt/rootfs/etc/detonate

# Finalize
sudo umount /mnt/rootfs
mv rootfs-custom.ext4 /opt/detonate/rootfs/
Disk space. The ext4 images use sparse files — a 500MB rootfs only uses ~500MB on disk despite being allocated as 1GB. Total disk for all four default images is approximately 1.5GB.

Verify installation

Check that everything is in place:

ls -lh /opt/detonate/
# Expected:
# firecracker     2.6M   (the binary)
# vmlinux          21M   (kernel image)
# rootfs/          1.5G  (directory with ext4 images)
#   rootfs-npm.ext4
#   rootfs-pypi.ext4
#   rootfs-maven.ext4
#   rootfs-binary.ext4

# Quick smoke test — start and immediately stop a VM
/opt/detonate/firecracker --api-sock /tmp/fc-test.sock &
FC_PID=$!
sleep 1
kill $FC_PID
rm -f /tmp/fc-test.sock
echo "Firecracker works!"

Docker deployment

The recommended way to run Detonate. The Docker image includes everything — Firecracker, kernel, rootfs images, monitoring agent. Just add KVM.

# Minimal — uses SQLite, everything self-contained
docker run -d \
  --name detonate \
  --restart unless-stopped \
  -p 3000:3000 \
  --device /dev/kvm:/dev/kvm \
  finsys/detonate:latest

# With PostgreSQL and persistent data
docker run -d \
  --name detonate \
  --restart unless-stopped \
  -p 3000:3000 \
  --device /dev/kvm:/dev/kvm \
  -v detonate-data:/app/data \
  -e DATABASE_URL=postgres://user:pass@db-host:5432/detonate \
  finsys/detonate:latest

Environment variables

VariableDefaultDescription
PORT3000HTTP port
DATABASE_URLnone (SQLite)PostgreSQL connection string. Omit to use SQLite.
DATA_DIR/app/dataSQLite database and upload storage
Advanced. Firecracker paths (FIRECRACKER_BIN, FIRECRACKER_KERNEL, FIRECRACKER_ROOTFS_DIR) are pre-configured inside the image. Override them only if you want to use custom binaries or rootfs images mounted from the host.

Bare metal deployment

# Install bun
curl -fsSL https://bun.sh/install | bash

# Clone and build
git clone https://gitea.bor6.pl/jarek/detonate.git
cd detonate
bun install
bun run build

# Run
DATABASE_URL=postgres://user:pass@localhost:5432/detonate \
  node build/index.js

Reverse proxy

Traefik (file provider)

http:
  routers:
    detonate:
      rule: "Host(`detonate.example.com`)"
      entryPoints:
        - websecure
      service: detonate
      tls:
        certResolver: letsencrypt
  services:
    detonate:
      loadBalancer:
        servers:
          - url: "http://192.168.1.113:3000"

Caddy

detonate.example.com {
    reverse_proxy localhost:3000
}

Nginx

server {
    listen 443 ssl;
    server_name detonate.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Database setup

Detonate supports both SQLite and PostgreSQL. Migrations run automatically on startup.

SQLite (default)

No configuration needed. The database is created at $DATA_DIR/detonate.db on first start.

PostgreSQL

# Create the database
psql -U postgres -c "CREATE DATABASE detonate;"

# Set the connection string
export DATABASE_URL=postgres://user:pass@host:5432/detonate

# Tables are created automatically on first start

First login

On first visit, Detonate enters setup mode. Create your admin account with a username and password (min 8 characters). After that, you'll be redirected to the dashboard.

Submitting artifacts

Go to Submit in the sidebar. Choose the artifact type, enter the source (package name, URL, or upload a file), select quarantine duration and policy profile, then hit Detonate in cage.

TypeSource formatExample
npmPackage name with versionlodash@4.17.21
PyPIPackage namerequests==2.31.0
MavenGroup:artifact:versionorg.apache.commons:commons-lang3:3.14.0
BinaryFile uploadAny ELF/executable
DockerImage referencenginx:latest

Deception packs

Detonate plants fake credentials inside every cage. If the artifact reads any of these honeypot files, it's flagged as a deception hit with critical severity.

PackWhat's planted
SSH keysFake id_rsa, id_ed25519, known_hosts, SSH config
AWS credentialsFake ~/.aws/credentials and config
npm/yarn tokensFake .npmrc, .yarnrc.yml with auth tokens
PyPI tokensFake .pypirc with upload credentials
Git credentialsFake .git-credentials, .gitconfig
Cloud metadataFake GCP service account, Azure profile
Docker configFake ~/.docker/config.json with registry auth
KubernetesFake kubeconfig with cluster endpoints
Environment filesFake .env with DB URLs, API keys, Stripe keys

Policies and whitelists

Policies define what's allowed during detonation. Create whitelist rules for file paths, IP addresses, DNS domains and ports. Group rules into reusable policy profiles.

Rule types:

Understanding verdicts

VerdictMeaningTypical triggers
BenignNo suspicious behavior detectedNormal file access, expected network activity
SuspiciousSome anomalous behavior, needs reviewAccessing sensitive paths, unexpected outbound connections, anti-evasion signals
Policy-violatingClear malicious intentDeception file access, credential exfiltration, C2 communication

Reports and exports

From any cage detail page, click the Report dropdown to download:

API tokens

Go to API tokens in the sidebar. Create a token with the permissions you need (submit, read, admin). The token is shown once at creation — copy it immediately.

# Token format
dt_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789ab

Submitting via API

curl -X POST https://detonate.example.com/api/v1/submit \
  -H "Authorization: Bearer dt_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "artifactType": "npm",
    "source": "suspicious-package@1.0.0",
    "quarantineDuration": 600
  }'

# Response: { "id": 42, "status": "pending", "message": "..." }

Polling verdicts

# Check status
curl https://detonate.example.com/api/v1/status/42 \
  -H "Authorization: Bearer dt_YOUR_TOKEN"

# Get verdict (for pipeline pass/fail)
curl https://detonate.example.com/api/v1/verdict/42 \
  -H "Authorization: Bearer dt_YOUR_TOKEN"

# Response: { "id": 42, "verdict": "benign", "pass": true, "completed": true }

# Download full report
curl https://detonate.example.com/api/v1/report/42?format=json \
  -H "Authorization: Bearer dt_YOUR_TOKEN"

GitHub Actions

- name: Detonate in Detonate
  run: |
    CAGE_ID=$(curl -s -X POST \
      $DETONATE_URL/api/v1/submit \
      -H "Authorization: Bearer $TC_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"artifactType":"npm","source":"$PACKAGE","quarantineDuration":600}' \
      | jq -r '.id')

    # Poll until complete
    while true; do
      RESULT=$(curl -s $DETONATE_URL/api/v1/verdict/$CAGE_ID \
        -H "Authorization: Bearer $TC_TOKEN")
      if [ "$(echo $RESULT | jq -r '.completed')" = "true" ]; then
        if [ "$(echo $RESULT | jq -r '.pass')" != "true" ]; then
          echo "BLOCKED: $(echo $RESULT | jq -r '.verdict')"
          exit 1
        fi
        echo "PASSED: benign"
        break
      fi
      sleep 30
    done
  env:
    DETONATE_URL: ${{ secrets.DETONATE_URL }}
    TC_TOKEN: ${{ secrets.TC_TOKEN }}

GitLab CI

detonate-scan:
  stage: test
  image: curlimages/curl:latest
  script:
    - CAGE_ID=$(curl -s -X POST
        "$DETONATE_URL/api/v1/submit"
        -H "Authorization: Bearer $TC_TOKEN"
        -H "Content-Type: application/json"
        -d "{\"artifactType\":\"npm\",\"source\":\"$PACKAGE\"}"
        | jq -r '.id')
    - |
      while true; do
        VERDICT=$(curl -s "$DETONATE_URL/api/v1/verdict/$CAGE_ID"
          -H "Authorization: Bearer $TC_TOKEN")
        COMPLETED=$(echo "$VERDICT" | jq -r '.completed')
        [ "$COMPLETED" = "true" ] && break
        sleep 30
      done
    - PASS=$(echo "$VERDICT" | jq -r '.pass')
    - '[ "$PASS" = "true" ] || exit 1'

How it works

Detonate controller (SvelteKit + Node.js)
  │
  ├── Firecracker API (REST on Unix socket)
  │     → microVM boots in ~125ms
  │       ├── Custom init agent (Go binary)
  │       ├── fanotify → file access monitoring
  │       ├── iptables LOG → network monitoring
  │       ├── /proc polling → process monitoring
  │       ├── Deception honeypots planted
  │       └── Artifact executed per type
  │
  ├── virtio-vsock ← event stream (JSONL)
  │     → Collector parses, classifies, batches to DB
  │
  ├── Timeout → kill VM → generate verdict
  │
  └── SSE → real-time events to UI

Why Firecracker

We chose Firecracker microVMs over Docker containers or gVisor for one reason: stealth.

In-VM agent

A small Go binary (~5MB) runs as PID 1 inside each microVM. It:

  1. Mounts /proc, /sys, /dev, /tmp
  2. Disguises its own process name (see below)
  3. Creates a realistic environment (hostname, users, /etc/hosts)
  4. Plants deception files from the cage config
  5. Starts file monitoring via fanotify
  6. Sets up network logging via iptables -j LOG
  7. Connects to the host via virtio-vsock
  8. Executes the artifact (npm install, pip install, java -jar, etc.)
  9. Streams all events as JSONL to the host until killed

Agent process disguise

Sophisticated malware may inspect /proc/1/comm, /proc/1/cmdline, or run ps to identify monitoring agents. To counter this, Detonate disguises the agent on every detonation:

This means every detonation has a different-looking PID 1, and there are no files named "detonate" visible anywhere inside the VM.

Anti-evasion detection

Detonate detects artifacts that try to fingerprint the sandbox:

TechniqueWhat we detect
Long sleepsleep/nanosleep calls > 60 seconds (delayed payload)
Environment fingerprintingReading /proc/cpuinfo, /sys/class/dmi, VM indicators
Sandbox detectionChecking for /.dockerenv, container cgroup markers
Delayed callbacksOutbound connections > 5 minutes after start
Timing attacksRapid repeated timing-sensitive syscalls

Since we use Firecracker (not Docker), most sandbox detection techniques fail — the artifact sees a real Linux machine.