Projects / Veil -- Containment & Escape Harness

Veil -- Containment & Escape Harness

wipGo

Security research harness in Go that runs an AI agent or a human inside a hardened, believable Docker "world", observes everything from the host side, and scores their attempts to break out.

STATUSwip
LANGUAGEGo
STACKGo · Docker Engine API · gopacket · iptables · DNS · Server-Sent Events · Claude API · Linux
OVERVIEW

Veil is built around one rule: outside-in, never inside-out. From inside the world, a subject should not be able to see past the veil or tell it is contained. From the host side, every action and reaction is captured and streamed to a live dashboard. That makes it a CTF-style cyber-range and an agent-evaluation harness at once, because escape attempts are a first-class, measured signal. It is early (0.x): the skeleton runs end to end, but the isolation substrate (containers vs. gVisor vs. Firecracker) is still undecided and there are no escape-rate results yet.

THE WORLD
  • The subject's box is a hardened container: read-only rootfs, all capabilities dropped, no-new-privileges, non-root user, memory/PID/CPU caps, masked /proc paths and a digest-pinned base image. The container backend fails closed if Docker is unreachable.
  • It sits on an internal, no-egress veil-net network, so successful network egress is impossible by construction and the attempt is the signal.
  • A synthetic DNS resolver answers world names such as web.veil.local and sinkholes unknown names to a transparent honeypot rather than failing, which keeps the world believable.
  • Resident containers act as a small-office LAN of web, mail and git servers, an SSH bastion and workstations. They answer HTTP/SMTP/IMAP probes, present an SSH banner, and generate ambient DNS/HTTP/SMTP traffic. The resident binary is stdlib-only, and a test enforces that.
  • A subject is driven by a replayed command script, a deterministic offline brain, or an opt-in Claude brain called host-side. The brain sees only its own command output, never the observer feed.
ESCAPE DETECTION & SCORING
  • Three host-side signals: external-dns (resolving a non-world name, seen by the resolver), egress-attempt (sending to an off-network IP, seen by the packet tap) and honeypot-hit (connecting to a sinkholed name, captured by the honeypot).
  • A read-only scorer weights the vectors (recon < direct egress < completed sinkhole connection), adds a bonus for cloud-metadata targets, and maps them to four stages: none, recon, active-egress and breach-path.
  • GET /report returns the score, stage, per-vector counts and a timeline that ties each escape to the command that caused it.
IMPLEMENTATION
  • About 8.4k lines of Go, of which about 4.1k are tests. gopacket is the only direct third-party dependency.
  • Hand-written Docker Engine API client over the Unix socket, with hijacked exec-attach streams and a bounded demuxer for Docker's multiplexed stdout/stderr frames.
  • The honeypot uses iptables REDIRECT plus SO_ORIGINAL_DST to recover the subject's intended port. Packet capture and the honeypot are Linux-only; elsewhere they degrade to a stub tap or turn off, and detection keeps running.
  • Events flow through an in-memory pub/sub bus to an SSE-fed web dashboard.