enclv
v0.1.0
Published
A lightweight containment environment for AI agents
Downloads
101
Maintainers
Readme
enclv
A lightweight containment environment for AI agents.
enclv (enclave) is a tool for developers who want to run AI-generated code, scrapers, or autonomous scripts locally without giving them the keys to the kingdom. It provides sensible guardrails against accidental damage and credential leakage: agents can only talk to the specific network endpoints you allow, and they are restricted to writing files in an isolated /work/output directory.
To be clear: this isn't a hypervisor-level sandbox for analyzing actual malware or zero-day exploits. It's simply a vastly safer alternative to running python agent.py directly on your laptop.
It works by running the agent inside a disposable Docker container with strict filesystem isolation and secure secret injection. All outbound network traffic is forced through an egress proxy that drops anything not on your allowlist. Secrets are kept out of standard environment variables, and all files written to /work/output are captured and hashed when the run completes.
Requirements
- Docker (Docker Desktop on macOS, Docker Engine or Desktop on Linux)
- Node.js >= 18
Quick Start
npm install
npm run build
# Create a config file in your agent directory
npx enclv init
# Run an agent
npx enclv run --dir ./my-agentExamples
Run an AI coding agent with API access
# enclv.yaml
cmd: python agent.py
allow:
- api.anthropic.com
secrets:
- ANTHROPIC_API_KEY
timeout: 10m
memory: 2g$ enclv run --dir ./my-coding-agent
✓ Loaded enclv.yaml
✓ Command: python agent.py
✓ Network: blocked except [api.anthropic.com:443]
✓ Timeout: 10m | Memory: 2G | CPU: 2
─── Run Started ──────────────────────────────────────
[14:32:01] start │ Container started
[14:32:02] net ✓ │ CONNECT api.anthropic.com:443 → 2.1KB sent, 15.3KB recv
[14:32:05] stdout │ Generated app.py (142 lines)
[14:32:05] stdout │ Generated requirements.txt
[14:32:05] stop │ Container exited (code 0)
─── Run Complete ─────────────────────────────────────
Exit code: 0
Duration: 4.2s
Network:
✓ api.anthropic.com:443 1 request 2.1KB sent 15.3KB recv
Files written:
app.py 3.8KB sha256:a1b2c3d4...
requirements.txt 42B sha256:e5f6a7b8...
Output saved to: enclv-output/run-2026-02-25-14-32-00The agent could reach the Anthropic API but nothing else. Output files are captured with SHA256 hashes.
Scrape a website and analyze the results
# enclv.yaml
cmd: python scrape.py
allow:
- news.ycombinator.com
- api.openai.com
secrets:
- OPENAI_API_KEY
timeout: 5m$ enclv run --dir ./hn-scraperThe agent can reach Hacker News and OpenAI, but cannot exfiltrate scraped data to any other domain.
Run untrusted code with zero network access
# enclv.yaml
cmd: python process.py
timeout: 2m
memory: 1g$ enclv run --dir ./data-pipelineNo allow list means all outbound traffic is blocked. The agent can only read its input files and write to /work/output.
Run from a GitHub repo
$ enclv run --repo https://github.com/user/agent.gitClones the repo, reads its enclv.yaml, and runs it in a contained environment. The cloned directory is cleaned up after the run.
How It Works
┌─────────────────────────────────────────────────────────┐
│ Host (macOS / Linux) │
│ │
│ ┌──────────────────────┐ ┌─────────────────────────┐ │
│ │ Internal Network │ │ External Network │ │
│ │ (no internet) │ │ (has internet) │ │
│ │ │ │ │ │
│ │ ┌────────────────┐ │ │ │ │
│ │ │ Sandbox │ │ │ │ │
│ │ │ Container │──┼──│──X (no direct route) │ │
│ │ │ (agent code) │ │ │ │ │
│ │ └───────┬────────┘ │ │ │ │
│ │ │ DNS:53 │ │ │ │
│ │ │ HTTP:3128 │ │ │ │
│ │ ┌───────┴────────┐ │ │ │ │
│ │ │ Proxy ├──┼──┤ (allowed traffic │ │
│ │ │ Container │ │ │ exits here) │ │
│ │ │ (DNS + HTTP) │ │ │ │ │
│ │ └────────────────┘ │ │ │ │
│ └──────────────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────┘The Docker internal network and proxy container together form the enforcement boundary. The proxy is the only bridge to external networks.
The sandbox container sits on an internal-only Docker network with no route to the internet. All traffic must go through the proxy container, which:
- Resolves DNS only for allowed domains (NXDOMAIN for everything else)
- Allows HTTP CONNECT only to allowed host:port pairs
- Blocks raw IP connections (no domain-bypass via hardcoded IPs)
- Logs every request with timestamps, bytes, and allow/block status
All outbound HTTP and HTTPS traffic from the sandbox must traverse the proxy.
Supported Languages
enclv auto-detects the runtime from files in your agent directory:
| File found | Runtime | Base image | Install step |
|---|---|---|---|
| Dockerfile | Any (you control it) | Your FROM image | Your build steps |
| requirements.txt | Python | python:3.12-slim | pip install -r requirements.txt |
| pyproject.toml | Python | python:3.12-slim | pip install . |
| package.json | Node.js | node:20-slim | npm install --production |
| (none of the above) | Python (default) | python:3.12-slim | (none) |
Detection is checked in the order above -- the first match wins. If your project has both a Dockerfile and a requirements.txt, the Dockerfile takes priority.
Using other languages: Supply your own Dockerfile in the agent directory and enclv will build from it. Go, Rust, Java, Ruby, Deno -- anything that runs in Docker works. enclv injects its entrypoint wrapper and applies all runtime hardening regardless of the base image.
# Node.js example
cmd: node index.js
# Python example
cmd: python main.py
# Any language with a Dockerfile
cmd: ./my-binary --flagConfiguration
Create an enclv.yaml in your agent directory:
cmd: python main.py
allow:
- api.openai.com
- "*.googleapis.com"
- mcp.internal.corp:9090
secrets:
- OPENAI_API_KEY
- INTERNAL_TOKEN
timeout: 30m
memory: 2g
cpu: 2Config Options
| Field | Default | Description |
|-------|---------|-------------|
| cmd | required | Entry command to run |
| allow | [] | Domains the agent can reach (default port 443) |
| secrets | [] | Env vars to inject from host (mounted on tmpfs) |
| timeout | 30m | Max run duration (30s, 5m, 1h) |
| memory | 2g | Memory limit (512m, 2g) |
| cpu | 2 | CPU core limit |
| dns_upstream | 127.0.0.11 | Upstream DNS resolver for the proxy (Docker embedded DNS) |
CLI Usage
# Run from a local directory
enclv run --dir ./my-agent
# Run from a git repo
enclv run --repo https://github.com/user/agent.git
# Run a Docker image directly
enclv run --image my-agent:latest --cmd "python main.py"
# Override config from CLI
enclv run --dir ./my-agent --allow "api.openai.com,httpbin.org" --timeout 1h --verbose
# Inject secrets
enclv run --dir ./my-agent --secret "OPENAI_API_KEY,DB_TOKEN"
# Create starter config
enclv initFlags
| Flag | Description |
|------|-------------|
| --dir <path> | Agent source directory |
| --repo <url> | Git repo to clone |
| --image <ref> | Docker image to run |
| --cmd <command> | Entry command |
| --allow <domains> | Comma-separated allowed domains |
| --secret <names> | Comma-separated secret env var names |
| --timeout <dur> | Max duration |
| --memory <limit> | Memory limit |
| --cpu <n> | CPU limit |
| --verbose | Show Docker build output and debug info |
Security Model
What enclv Is (and Isn't)
enclv is designed for developers who run AI coding agents, scrapers, and automation scripts and want to prevent those agents from accidentally (or intentionally) accessing unauthorized services, modifying the host filesystem, or leaking credentials. It provides meaningful, layered containment that is vastly better than running agents directly on your machine.
enclv is not designed for:
- Running malware samples or analyzing zero-day exploits
- Multi-tenant isolation between mutually distrustful parties
- Containing adversaries with kernel-level exploitation capabilities
For those use cases, consider hypervisor-level isolation (Firecracker) or specialized user-space kernels (gVisor).
Threat Model
enclv protects against:
- Network exfiltration -- agent code cannot reach domains outside the allowlist
- Unauthorized outbound access -- DNS and HTTP CONNECT are filtered at the proxy; everything else is unreachable
- Raw socket bypass attempts -- direct TCP/UDP to external IPs, direct DNS to public resolvers, and IPv6 are all blocked at the network level
- Filesystem damage -- the root filesystem is read-only; the agent can only write to
/work/outputand/tmp - Secret leakage via container inspection -- secrets are mounted on tmpfs, not set as container environment variables
- In-container privilege escalation -- non-root user,
no-new-privileges, all capabilities dropped
enclv does not protect against:
- Kernel exploits / container escape -- Docker containers share the host kernel. enclv applies defense-in-depth (dropped capabilities, read-only rootfs, seccomp defaults), but a kernel-level exploit could bypass container boundaries entirely. This is inherent to any container-based isolation and is why hypervisor-level tools like Firecracker exist.
- Application-layer exfiltration via allowed domains -- enclv operates at the network layer, filtering by DNS and HTTP CONNECT hostnames. Because it does not perform TLS termination, it cannot inspect the encrypted payload of requests sent to allowed domains. A malicious agent could exfiltrate injected secrets by embedding them inside a legitimate request to an allowed endpoint (e.g., hiding a secret in a prompt sent to
api.openai.com). However, enclv logs all connection metadata -- destination, timestamps, and bytes transferred -- providing a forensic audit trail for post-incident analysis. - Malicious Dockerfiles -- when the agent directory supplies its own
Dockerfile, it controls the base image and build steps. enclv still applies runtime hardening (read-only rootfs, dropped capabilities, non-root user), but a maliciousFROMdirective can pull an attacker-controlled image. If you do not trust the agent code, do not let it supply its own Dockerfile. - Host disk exhaustion -- the
/work/outputDocker volume has no size quota. A malicious agent could write unlimited data to fill the host disk./tmpis capped at 100MB via tmpfs, but the output volume is not currently limited. (A--disk-limitoption is planned -- see issue #1.) - Side-channel attacks -- timing, cache, or other microarchitectural side channels are out of scope
- Host-level compromise -- if the host is already compromised, container isolation provides no additional protection
Enforcement Layers
enclv enforces isolation at three layers:
- Network namespace isolation -- Docker
--internalnetwork prevents any direct route to external hosts - Egress policy enforcement -- proxy allowlist controls which domains and ports are reachable; all other traffic is blocked
- Container hardening -- read-only root filesystem, all capabilities dropped, no privilege escalation, PID limits, IPv6 disabled, sensitive procfs/sysfs paths masked
Network Isolation
- Sandbox runs on a Docker
--internalnetwork (no external route) - Only the proxy container bridges internal and external networks
- The sandbox container is not attached to the external network
- DNS queries to external resolvers are unreachable from the sandbox network
host.docker.internalandgateway.docker.internalare poisoned to 0.0.0.0- Raw TCP/UDP to external IPs fails from the sandbox network (no route / unreachable)
- HTTP CONNECT to non-allowed domains returns 403
- Raw IP addresses in CONNECT requests are blocked (no DNS bypass)
Container Hardening
Docker containers share the host kernel and are not equivalent to VM-level isolation. enclv applies the following hardening to reduce the attack surface within that constraint:
- Read-only root filesystem (
ReadonlyRootfs: true) - All capabilities dropped (
CapDrop: ALL) - No new privileges (
no-new-privileges:true) - PID limit (256 processes)
- Memory + CPU limits enforced via cgroups
- User 1000 (non-root)
- Sensitive paths masked (
/proc/kcore,/proc/keys,/sys/firmware, etc.) - /tmp on tmpfs (writable, noexec, not persisted, 100MB cap)
- /work/output on Docker volume (writable, extracted after run)
For kernel-level isolation, consider running Docker with a gVisor runtime (--runtime=runsc) or using Firecracker microVMs.
Secret Injection
Secrets are read from host environment variables and mounted as individual files on a tmpfs at /run/secrets/. The entrypoint script reads them into env vars before executing the agent command. Secrets never touch the root filesystem or the container's environment metadata.
Note: secrets are protected from leakage via docker inspect and filesystem persistence, but they are available to the running agent process. If the agent has access to an allowed network domain, it could include secrets in outbound request payloads (see "Application-layer exfiltration" above).
Platform Notes
On macOS, Docker Desktop runs containers inside a Linux VM. enclv enforces isolation within the Docker network namespace. While networking semantics differ from native Linux hosts, the internal-only network and proxy boundary are validated via integration tests on both macOS and Ubuntu (CI).
The proxy's DNS resolver defaults to Docker's embedded DNS (127.0.0.11), which inherits the host's DNS configuration. This works in corporate environments with DNS interception or VPNs where public resolvers like 8.8.8.8 may be blocked. Override with dns_upstream in enclv.yaml if needed.
Output
Each run produces a bundle in enclv-output/run-<timestamp>/:
run-2026-02-25-03-08-15/
├── enclv.yaml # Config used for this run
├── summary.json # Run summary with exit code, timing, network stats
├── files.json # File manifest with SHA256 hashes
├── network.json # Network destinations log
├── logs/
│ ├── stdout.log # Agent stdout
│ └── stderr.log # Agent stderr
└── output/ # Files the agent wrote to /work/output
├── result.json
└── ...summary.json
{
"run_id": "run-2026-02-25-03-08-15",
"exit_code": 0,
"duration_ms": 2900,
"runtime": "python:3.12-slim",
"network": {
"allowlist": ["httpbin.org"],
"total_requests": 3,
"allowed_requests": 2,
"blocked_requests": 1,
"destinations": [
{ "host": "httpbin.org", "port": 443, "requests": 2, "allowed": true },
{ "host": "api.github.com", "port": 443, "requests": 1, "allowed": false }
]
},
"filesystem": {
"files_written": 1,
"files": [
{ "path": "result.json", "size": 439, "sha256": "c63114d2..." }
]
}
}Runtime Detection
enclv auto-detects the runtime:
| File Found | Runtime |
|------------|---------|
| requirements.txt | python:3.12-slim |
| package.json | node:20-slim |
| Dockerfile | User-defined |
| (none) | python:3.12-slim (default) |
Testing
npm testIntegration tests require Docker running. They exercise the full security model:
- simple-ok: Basic agent runs and produces output
- writes-files: Multiple files + nested directories extracted correctly
- tries-exfil: HTTP/HTTPS to blocked domains are blocked
- raw-socket-bypass: Raw TCP/UDP to external IPs are blocked
- direct-dns-bypass: Direct DNS to 8.8.8.8/1.1.1.1 is blocked
- allowed-domain: Allowed domains succeed, blocked domains fail
- tries-shell: Read-only rootfs enforced, subprocesses monitored
Development
npm run build # Compile TypeScript
npm test # Run integration tests
npm run dev # Watch mode (tsc --watch)License
MIT
