What is DAEDALUS?#

DAEDALUS is an open-source security-research control plane built on Apple’s container runtime. It exploits a unique property of macOS on Apple silicon — one lightweight VM per container via Virtualization.framework — to create hardware-isolated, disposable Linux sandboxes. You drive them through a CLI, a web dashboard, an HTTP API, or autonomous coding agents via MCP.

Detonation, Analysis & Experimentation — Daedalus Architecture for Linux container Units & Sandboxing.

In myth, Daedalus built the Labyrinth to contain the Minotaur — a maze nothing escapes.

GitHub · Architecture


Why This Matters#

Traditional malware analysis sandboxes (Docker, VirtualBox, QEMU) share the host kernel or run in user-mode emulation. Apple’s container runtime — built on Virtualization.framework — gives each container its own lightweight VM with its own kernel. This means:

  • Hardware-enforced isolation between samples
  • No shared kernel surface to exploit
  • Different kernels per sandbox (KASAN for fuzzing, vulnerable kernels for escape research)
  • Invisible eBPF instrumentation loaded before the OCI workload starts

No existing security tooling targets this runtime. DAEDALUS is the first.


Architecture#

DAEDALUS has three consumer surfaces over a shared engine:

CLI (Typer)  ·  HTTP API (FastAPI)  ·  MCP Server (FastMCP 11 tools)
                              │
                    ┌─────────▼─────────┐
                    │    Core Engine     │
                    │ forge  ·  icarus   │
                    │ mint   ·  talos    │
                    │ ariadne ·  minos   │
                    ├───────────────────┤
                    │ profiles · policy  │
                    │ audit   ·  store   │
                    └─────────┬─────────┘
                              │
                    ┌─────────▼─────────┐
                    │   CliBackend (L1)  │
                    │ container --format │
                    │       json         │
                    └─────────┬─────────┘
                              │
              ┌───────────────▼───────────────┐
              │  Apple container runtime       │
              │  (Virtualization.framework)    │
              │  One lightweight VM per sandbox│
              └───────────────────────────────┘

Security Profiles — Tested and Verified#

DAEDALUS ships with seven named security profiles, each tested end-to-end against a real macOS 26.5 host with container v0.1.0 — alpine:latest for speed, debian:latest for proxy/Burp/pcap analysis.

General — Normal Linux VM#

Profile: general
State: running
DNS nameservers: ['192.168.64.1']
Image: alpine:latest

No restrictions. Use this when you want a standard Linux environment — development, running services, everyday container work. Equivalent to docker run alpine.

Detonation — Maximum Lockdown for Malware Analysis#

Profile: detonation
State: running
DNS nameservers: none

The flagship profile. Drops all DNS resolution (no exfiltration), isolates the network, sets tmpfs on /tmp and /var/tmp. Every sample runs in a VM that can’t phone home. This is what you use when you detonate a suspicious binary.

Bench — Permissive Benchmarking#

Profile: bench
State: running

No restrictions whatsoever. Designed for performance benchmarking and development where isolation is not a concern.

Fuzz — Kernel Experimentation#

Profile: fuzz
State: running

For kernel fuzzing and escape research. No default kernel — pass your own KASAN or KCOV-instrumented kernel at runtime via --kernel.

Isolated — Air-Gapped#

Profile: isolated
State: running
DNS: {}

Complete network isolation. No DNS, no external network. The container can only talk to itself. Use when you need absolute certainty that a sample cannot reach the outside world.

Deception — Network Decoy#

Profile: deception
State: running
DNS nameservers: ['10.0.0.53']

Routes DNS through a controlled resolver at 10.0.0.53 — a lying DNS server you control. Combine with a network deception lab (fake C2, honeypot endpoints) to study malware behavior without letting it reach real infrastructure.


Labyrinth Control Center — The Web Dashboard#

DAEDALUS ships with a React dashboard served directly from the API server — no separate process, no Docker, no Node.js in production. Open http://127.0.0.1:8420/ui and you get:

PageWhat it shows
DashboardSystem health, container counts, disk usage, capabilities
ContainersLive table (3s polling), search/filter, create form with advanced options (kernel, CPU, memory, DNS, mounts)
TerminalInteractive xterm.js — line-buffered input, ↑ command history
ImagesBrowse, pull, inspect, delete container images
LogsContainer selector, boot/stdout toggle, live log feed
AuditTamper-evident operation trail with filters
ProfilesSecurity profile cards with configured settings

Creating a Container#

  1. Go to Containers+ New
  2. Choose alpine:latest (fast) or debian:latest (proxy/network tools) and select a profile
  3. Keep Detach checked — the container stays running automatically
  4. Click Create & Run

The container appears in the table as “running” within seconds. Click Term to open a real shell inside the VM.


MCP Integration — Autonomous Agents Welcome#

DAEDALUS exposes 11 MCP tools so coding agents can orchestrate the full lifecycle:

ToolOperation
daedalus_healthProbe host capabilities
daedalus_runCreate + start container
daedalus_listList containers
daedalus_inspectInspect a container
daedalus_execExecute command inside
daedalus_logsFetch logs
daedalus_stopStop container
daedalus_destroyDestroy (confirm-gated)
daedalus_image_pullPull image
daedalus_image_listList images
daedalus_profilesList security profiles

Destructive operations require confirm=True. Every action is audit-logged. The agent never gets silent permission to destroy.

{
  "mcpServers": {
    "daedalus": {
      "command": "uv",
      "args": ["run", "--project", "/path/to/daedalus", "python", "-m", "daedalus.mcp.server"]
    }
  }
}

Reproducibility and Audit#

Every container run is reproducible from a store manifest — image digest, profile, kernel, init-image, network topology, command, and artifacts produced (tars, pcaps, reports). The manifest is JSON and version-controllable.

Every operation flows through a tamper-evident audit log — append-only JSONL with per-entry SHA-256 checksums. You can verify the integrity of the entire forensic chain with a single method call.


Quality#

All four surfaces tested against real container v0.1.0 on macOS 26.5 (arm64):

SurfaceTestsResult
Unit Tests126All pass
CLI Regression15All pass
API Regression21All pass
MCP Regression16All pass
UI Build8 checksAll pass
Integration Tests11All pass
Security Profiles6All pass
mypy strict23 source filesZero errors

Getting Started#

git clone https://github.com/jayluxferro/daedalus
cd daedalus
uv venv && source .venv/bin/activate
uv pip install -e .

# Start the container daemon
container system start --disable-kernel-install
container system kernel set --recommended

# Pull an image
daedalus image-pull alpine:latest
daedalus image-pull debian:latest

# Run a detached container with detonation profile (auto-keepalive)
daedalus run debian:latest -p detonation -d

# Open the dashboard
python -m daedalus.api.server
open http://127.0.0.1:8420/ui


Security Verification — All Profiles Tested Live#

Every profile was tested against real container v0.1.0 on macOS 26.5 — containers were spun up and security validation commands executed inside them. Here are the results.

Detonation — DNS Completely Blocked#

$ daedalus exec test-detonation -- nslookup google.com
;; connection timed out; no servers could be reached
nslookup: write to '127.0.0.1': Connection refused

No /etc/resolv.conf file exists inside the container. All DNS resolution fails immediately. The sample cannot resolve domain names — no C2 communication, no data exfiltration via DNS tunneling.

$ daedalus exec test-detonation -- wget -T 3 http://example.com
wget: bad address 'example.com'

HTTP access fails as a cascading effect of DNS blocking. Outbound ICMP also fails (100% packet loss). The root filesystem is writable (a known limitation — container v0.1.0 doesn’t expose --read-only yet), but /tmp and /var/tmp are correctly mounted as tmpfs (in-memory only, 496 MB).

Isolated — Full Network Air-Gap#

$ daedalus exec test-isolated -- ping -c 1 -W 2 1.1.1.1
1 packets transmitted, 0 packets received, 100% packet loss

$ daedalus exec test-isolated -- wget -T 3 http://example.com  
wget: bad address 'example.com'

Ping, HTTP, DNS — all fail. No /etc/resolv.conf. The container has a valid IP on the default bridge but cannot reach anything. Complete air-gap verified.

Deception — Controlled DNS Injection#

"dns": {
  "domain": "lab.local",
  "nameservers": ["10.0.0.53"]
}
$ daedalus exec test-deception -- cat /etc/resolv.conf
nameserver 10.0.0.53
domain lab.local

DNS configuration correctly injected. The container resolves through the fake resolver at 10.0.0.53. A companion DNS listener (dnsmasq, custom server) would need to be deployed on the host for actual resolution — the profile configures the target; you run the resolver.

General, Bench, Fuzz — Normal VM Operation#

$ daedalus exec test-general -- nslookup google.com
Server:         192.168.64.1
Address:        192.168.64.1:53
Name:           google.com
Address:        142.251.47.206

$ daedalus exec test-general -- id
uid=0(root) gid=0(root)

$ daedalus exec test-general -- cat /proc/1/status
Name:   sleep
Pid:    1
Uid:    0    0    0    0
State:  S (sleeping)

DNS, TCP, process isolation, filesystem — all standard. These three profiles are unrestricted Linux VMs. Fuzz is ready for a custom KASAN kernel at runtime; pass --kernel /path/to/kasan-kernel when you create the container.

Summary#

ProfileDNS BlockedNetwork IsolatedControlled ResolvertmpfsStatus
detonationYes (verified)Yes (verified)N/A/tmp, /var/tmpPass
isolatedYes (verified)Yes (verified)N/ANonePass
deceptionNoNo10.0.0.53 (verified)NonePass
generalNoNoN/ANonePass
benchNoNoN/ANonePass
fuzzNoNoN/ANonePass
proxyNoNo192.168.64.1:8083 (via –proxy)Mounted via execPass

Advanced Analysis — Burp Suite MITM, Cert Injection, Packet Capture#

Proxy Traffic Through Burp Suite#

DAEDALUS can route all container traffic through an intercepting proxy for analysis:

daedalus run debian:latest -d -p proxy --proxy 192.168.64.1:8083 \
  --cert /Users/jay/dev/pentest/scripts/mitm.pem --name mitm-test \
  --command "tail -f /dev/null"

Critical: Use 192.168.64.1 (the host gateway IP), not 127.0.0.1. Inside the container, 127.0.0.1 is the container’s own loopback.

Verified with Burp Suite Professional running on port 8083:

$ daedalus exec mitm-test -- wget -q -O - --timeout=5 http://192.168.64.1:8083/
<html><head><title>Burp Suite</title>...    # Traffic flows through Burp!
$ daedalus exec mitm-test -- wget -q -O - --no-check-certificate \
  https://192.168.64.1:8083/
<html><head><title>Burp Suite</title>...    # HTTPS also intercepted!

Both HTTP and HTTPS requests from inside the container successfully hit the Burp proxy listener.

Proxy Environment Variables#

$ daedalus run alpine:latest -d -p proxy --proxy 192.168.64.1:8083 --name proxy-test

$ daedalus exec proxy-test -- sh -c "echo \$HTTP_PROXY \$HTTPS_PROXY"
HTTP_PROXY=http://192.168.64.1:8083 HTTPS_PROXY=http://192.168.64.1:8083

With --no-proxy for exclusions:

HTTP_PROXY=http://192.168.64.1:8083
HTTPS_PROXY=http://192.168.64.1:8083
NO_PROXY=localhost,127.0.0.1,.local

CA Certificate Injection#

$ daedalus run alpine:latest -d --cert /Users/jay/dev/pentest/scripts/mitm.pem --name cert-test

$ daedalus exec cert-test -- head -3 /usr/local/share/ca-certificates/daedalus-ca.crt
-----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIUTFem+N+1uoBwPEedXcERxBFSxkIwDQYJKoZIhvcNAQEL
BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN

The cert is injected into the container at /usr/local/share/ca-certificates/daedalus-ca.crt via exec-based file write. Container v0.1.0’s volume mounts only support directories (not individual files), so DAEDALUS works around this by piping the cert content through container exec.

Network Capture#

Alpine doesn’t ship tcpdump by default, but the container has full network visibility:

$ daedalus exec capture-test -- ip addr show eth0
2: eth0: <BROADCAST,UP,LOWER_UP>
    inet 192.168.64.2/24
    link/ether c2:de:13:4c:6d:d7

$ daedalus exec capture-test -- cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue ...

For full packet capture, use a Kali or Debian image with tcpdump pre-installed:

daedalus run kalilinux/kali-rolling:latest -d --name pcap \
  --command "tcpdump -i eth0 -w /tmp/capture.pcap"

Or install tcpdump at runtime:

daedalus exec <id> -- apt-get update && apt-get install -y tcpdump
daedalus exec <id> -- tcpdump -i eth0 -c 100 -w /tmp/capture.pcap

Getting Started#

git clone https://github.com/jayluxferro/daedalus
cd daedalus

DAEDALUS is MIT licensed. Built for security researchers, by a security researcher. GitHub