DAEDALUS — A Security Research Control Plane for Apple’s Container Runtime
Jul 28, 2026 Security ResearchTools SecuritymacOSContainerizationMalware AnalysisApple SiliconMCPVirtualizationWhat 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.
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:
| Page | What it shows |
|---|---|
| Dashboard | System health, container counts, disk usage, capabilities |
| Containers | Live table (3s polling), search/filter, create form with advanced options (kernel, CPU, memory, DNS, mounts) |
| Terminal | Interactive xterm.js — line-buffered input, ↑ command history |
| Images | Browse, pull, inspect, delete container images |
| Logs | Container selector, boot/stdout toggle, live log feed |
| Audit | Tamper-evident operation trail with filters |
| Profiles | Security profile cards with configured settings |
Creating a Container#
- Go to Containers → + New
- Choose
alpine:latest(fast) ordebian:latest(proxy/network tools) and select a profile - Keep Detach checked — the container stays running automatically
- 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:
| Tool | Operation |
|---|---|
daedalus_health | Probe host capabilities |
daedalus_run | Create + start container |
daedalus_list | List containers |
daedalus_inspect | Inspect a container |
daedalus_exec | Execute command inside |
daedalus_logs | Fetch logs |
daedalus_stop | Stop container |
daedalus_destroy | Destroy (confirm-gated) |
daedalus_image_pull | Pull image |
daedalus_image_list | List images |
daedalus_profiles | List 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):
| Surface | Tests | Result |
|---|---|---|
| Unit Tests | 126 | All pass |
| CLI Regression | 15 | All pass |
| API Regression | 21 | All pass |
| MCP Regression | 16 | All pass |
| UI Build | 8 checks | All pass |
| Integration Tests | 11 | All pass |
| Security Profiles | 6 | All pass |
| mypy strict | 23 source files | Zero 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#
| Profile | DNS Blocked | Network Isolated | Controlled Resolver | tmpfs | Status |
|---|---|---|---|---|---|
| detonation | Yes (verified) | Yes (verified) | N/A | /tmp, /var/tmp | Pass |
| isolated | Yes (verified) | Yes (verified) | N/A | None | Pass |
| deception | No | No | 10.0.0.53 (verified) | None | Pass |
| general | No | No | N/A | None | Pass |
| bench | No | No | N/A | None | Pass |
| fuzz | No | No | N/A | None | Pass |
| proxy | No | No | 192.168.64.1:8083 (via –proxy) | Mounted via exec | Pass |
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