2 releases
Uses new Rust 2024
| new 0.0.1-pre2 | Mar 7, 2026 |
|---|
#1266 in Network programming
220KB
5.5K
SLoC
devcontainer-guard
[!WARNING] This is experimental and may have bugs. Use at your own risk.
Network and MCP access control for devcontainers. Runs as a sidecar that enforces:
- Domain allowlist - only approved domains are reachable from the container
- MCP tool allowlist - only approved MCP tools are callable by AI agents
- CLI proxy - tools like
ghandawsare proxied through the sidecar with command allowlists
Credentials (GH_TOKEN, OAuth tokens, API keys) live on the sidecar and never enter the app container.
Quick start
cargo install devcontainer-guard --version 0.0.1-pre1
cd path/to/your/project
devg init
$EDITOR .devcontainer/devg.toml # review allowed domains + MCP servers
devcontainer up
Domain allowlist
The config is a flat domain allowlist in devg.toml. devg init generates a starting list with safe defaults for common ecosystems (GitHub, npm, PyPI, RubyGems, crates.io, Maven, CocoaPods, Go, APT, and AI providers).
[proxy.network]
allow = [
"github.com",
"*.github.com",
"*.githubusercontent.com",
"crates.io",
"*.crates.io",
"*.ubuntu.com",
]
# deny overrides allow:
deny = ["gist.github.com"]
Wildcards (*.github.com) match subdomains but not the bare domain. Deny rules always win.
MCP proxy
The MCP proxy sits between the agent and remote MCP servers. The agent connects to the proxy over unauthenticated HTTP on the internal network. The proxy injects credentials when forwarding to the upstream server. The app container has no access to OAuth tokens, API keys, or any other secrets.
Registering servers
Register MCP servers on the host:
# OAuth (opens browser)
devg mcp add linear https://mcp.linear.app
# API key via headers
devg mcp add context7 https://mcp.context7.com/mcp --header "CONTEXT7_API_KEY=sk-..."
devg mcp list # see registered servers
devg mcp get linear # show details + tools list
After registering, add each server to devg.toml with an allow_tools list (see below). The agent connects to http://172.28.0.3:3129/<name> instead of the real server. devg handles auth and tool filtering.
Tool allowlist
Each server needs a [[mcp.servers]] entry in devg.toml with an explicit allow_tools list. Same model as the domain allowlist - only what's listed is permitted:
[mcp]
# Allow all tools
[[mcp.servers]]
name = "context7"
allow_tools = ["*"]
# Allow only read/search operations
[[mcp.servers]]
name = "github"
allow_tools = ["get_*", "list_*", "search_*"]
Wildcards work the same as domain patterns (get_* matches get_issue, get_user, etc.).
CLI proxy
The CLI proxy lets the app container run tools like gh or aws without direct access to credentials. Commands are forwarded to the sidecar which validates them against an allowlist and executes with the configured env vars.
[cli]
[[cli.tools]]
name = "gh"
allow = ["pr *", "issue *", "repo *", "search *", "auth status"]
deny = ["auth token", "auth login", "auth logout", "api"]
env = ["GH_TOKEN"]
[[cli.tools]]
name = "aws"
allow = ["s3 ls *", "s3 cp *", "sts get-caller-identity"]
deny = ["s3 rm *", "s3 rb *"]
env = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
deny overrides allow. Tools must be installed on the sidecar (gh is included by default; add others via [compose] build).
Compose overlay
By default, the devg sidecar pulls from ghcr.io/6/devcontainer-guard:latest. To build from source or use a different image, add a [compose] section to devg.toml:
# Pull from a registry (default if [compose] is omitted)
[compose]
image = "ghcr.io/6/devcontainer-guard:latest"
# Or build from source
[compose]
build = { context = "..", dockerfile = ".devcontainer/Dockerfile", target = "proxy" }
The overlay is regenerated by devg init-env on every devcontainer up. Don't edit docker-compose.devg.yml directly.
Architecture
┌──────────────────────────────────────────────────┐
│ Internal network │
│ │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ App container │ │ Proxy sidecar │ │
│ │ (isolated) │ │ │ │
│ │ HTTP_PROXY ──┼───►│ domain proxy │──► Internet
│ │ │ │ :3128 │ │
│ │ DNS ─────────┼───►│ DNS forwarder │──► Upstream DNS
│ │ │ │ :53 │ │
│ │ MCP servers ─┼───►│ MCP proxy │──► MCP servers
│ │ via http:// │ │ :3129 │ │
│ │ proxy:3129 │ │ │ │
│ │ gh/aws/etc ──┼───►│ CLI proxy │──► APIs
│ │ (shims) │ │ :3130 │ │
│ │ │ │ │ │
│ │ (no tokens) │ │ (credentials) │ │
│ └──────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────┘
- The app container has no external network route. All traffic goes through the proxy sidecar.
- DNS queries only resolve allowed domains. Disallowed domains get NXDOMAIN.
- Blocked domain requests get a 403. Blocked MCP tool calls get a JSON-RPC error.
- Credentials never enter the app container. OAuth tokens, API keys, and GH_TOKEN live on the proxy sidecar only. The proxy injects auth when forwarding upstream.
Security model
Network isolation is kernel-enforced, not proxy-based. The Docker internal: true network has no default gateway, so the app container has no IP route to the outside world. Unsetting HTTP_PROXY or making direct TCP connections doesn't bypass it. Packets have nowhere to go. The only reachable host is the proxy sidecar on the internal network.
MCP server domains are intentionally not in the domain allowlist. The agent can only reach them through devg's MCP proxy, which enforces tool filtering. Connecting directly would be blocked by the network.
Known limitations:
- Domain fronting: a CONNECT request to an allowed CDN domain could route to an attacker's backend via SNI/Host manipulation. devg sees the CONNECT domain, not the backend.
- Container escape: a kernel exploit that breaks out of the container bypasses all isolation. Not specific to devg. Running Docker inside a VM (e.g., Docker Desktop, Firecracker) adds defense-in-depth.
- No TLS inspection: for HTTPS, devg sees
CONNECT domain:443but cannot inspect request paths, headers, or bodies inside the tunnel. A process with valid credentials for an allowed domain can do anything that domain permits.
Commands
| Command | Purpose |
|---|---|
devg init |
Scaffold devg into a project |
devg status |
Check if everything is wired correctly |
devg why-denied |
Show denied requests from the proxy log |
devg mcp add <name> <url> [--header] |
Register an MCP server (OAuth or API key) |
devg mcp get <name> |
Show server details and tools list |
devg mcp list |
List registered servers |
Development
cargo check # fast compile check
cargo build # full build
cargo test # run all tests
cargo clippy # lint
This repo includes a .devcontainer/ that dogfoods devg itself. It builds from source, runs with GitHub/Rust/APT/AI domains allowed, and proxies Context7 as a sample MCP server. Set CONTEXT7_API_KEY in your environment to try it (free key from context7.com/dashboard). Open in VS Code or run:
devcontainer up
devcontainer exec .devcontainer/smoke-test.sh
Dependencies
~11–19MB
~316K SLoC