There's a conversation happening on DEV right now about what happens when AI agents get more tools and the boundaries around those tools fail. Most of the discussion is philosophical. I want to make it concrete: if you're going to let an AI coding agent run inside your CI pipeline — even on your own infrastructure — what does the actual sandbox look like, and how do you prove it holds?
This article walks through a repeatable harness: a decision table for what the agent is allowed to touch, a runnable sandbox script, and a canary test that fails loudly the moment a boundary leaks. Everything here runs on a plain Linux box.
The problem in one sentence
An agent that can read your repo and execute shell commands is, from a security standpoint, an unprivileged remote user who happens to be very fast — so treat its environment like you'd treat an untrusted contributor's laptop.
Concretely, the three failure modes I care about:
- Credential exfiltration — the agent (or a prompt injected via a file/issue it reads) prints an env var into a place that gets committed or POSTed somewhere.
- Network egress — the agent downloads or uploads something you never approved.
- Scope creep in the repo itself — the agent edits files outside the task you gave it, quietly.
Decision table: what goes where
Before any code, decide which capabilities the agent actually needs. This is the table I use as a starting point — adjust for your own tasks:
| Capability | Code-fix task | Doc-generation task | Dependency-upgrade task |
|---|---|---|---|
| Read repo files | ✅ | ✅ | ✅ |
| Write repo files | ✅ (scoped paths) | ✅ (docs/ only) | ✅ (lockfiles, manifests) |
| Execute tests/build | ✅ | ❌ | ✅ |
| Network egress | ❌ | ❌ | ✅ (package registry only) |
| Secrets in env | ❌ | ❌ | ❌ (use a short-lived token if truly needed) |
| Git push | ❌ (open MR instead) | ❌ | ❌ |
The pattern: network and secrets default to denied, and writing is always path-scoped. The agent proposes, CI disposes.
Where the agent actually runs
You need a machine to host the agent loop and a model endpoint. For experimentation, I used MonkeyCode here — it offers free access to coding models and a free server option, which made it cheap to iterate on the harness without burning a budget on my own mistakes. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Check the current product documentation for exactly which models and server limits apply, since availability details change; the sandboxing below is provider-agnostic anyway.
The important part isn't where the model lives — it's that the execution environment is locked down regardless. A generous free tier doesn't change the threat model.
The sandbox script
Below is a minimal, reproducible wrapper using only standard tooling. It runs the agent's working directory read-only-except-scratch, strips the environment, and blocks network with unshare (Linux namespaces — no Docker required for the demo, though Docker works too):
#!/usr/bin/env bash
# agent-sandbox.sh — run a command against a repo with minimal privileges.
# Usage: ./agent-sandbox.sh /path/to/repo "your-agent-command --flag"
set -euo pipefail
REPO="$(realpath "$1")"
CMD="$2"
SCRATCH="$(mktemp -d)"
trap 'rm -rf "$SCRATCH"' EXIT
# 1. Strip environment: no inherited secrets, no CI tokens.
# 2. Drop network entirely with a private net namespace.
# 3. Bind-mount the repo read-only; only $SCRATCH is writable.
env -i PATH=/usr/bin:/bin HOME="$SCRATCH" \
unshare --net --mount --map-root-user \
bash -c "
mount --bind '$SCRATCH' /tmp 2>/dev/null || true
cd '$REPO'
$CMD
"
Notes:
-
env -iis the single highest-value line. Most leaks I've seen discussed are just inherited environment variables. -
unshare --netremoves networking for the whole process tree. If your task legitimately needs a registry (the dependency-upgrade row above), replace this with an egress proxy allowlist, not open internet. - For real CI, run this inside an ephemeral job container/VM as well — defense in depth. The script is a second wall, not the only wall.
Prove it: the canary test
A sandbox you haven't attacked is a rumor. Plant canaries and assert they never escape:
#!/usr/bin/env bash
# canary-test.sh — boundary checks that must all pass before trusting the harness.
set -euo pipefail
REPO="$(mktemp -d)"
echo 'console.log("hello")' > "$REPO/app.js"
fail=0
# Test 1: a fake secret in the environment must not be readable.
export AWS_SECRET_ACCESS_KEY="CANARY-7f3d-not-a-real-key"
if ./agent-sandbox.sh "$REPO" 'env' | grep -q "CANARY-7f3d"; then
echo "FAIL: secret leaked into sandbox environment"; fail=1
else
echo "PASS: environment stripped"
fi
# Test 2: network must be unreachable.
if ./agent-sandbox.sh "$REPO" 'curl -sS --max-time 3 https://example.com' 2>/dev/null; then
echo "FAIL: network egress succeeded"; fail=1
else
echo "PASS: network blocked"
fi
# Test 3: repo must be unchanged after a hostile command.
BEFORE=$(sha256sum "$REPO/app.js" | cut -d' ' -f1)
./agent-sandbox.sh "$REPO" 'echo pwned >> app.js; git init -q . 2>/dev/null || true' || true
AFTER=$(sha256sum "$REPO/app.js" | cut -d' ' -f1)
if [ "$BEFORE" != "$AFTER" ]; then
echo "FAIL: repo was modified"; fail=1
else
echo "PASS: repo intact (modifications confined to scratch)"
fi
rm -rf "$REPO"
exit $fail
Run this in CI before any agent job. If any check fails, the agent doesn't run. That ordering matters — most setups test the agent's output but never test the cage.
One more canary worth adding once you allow limited egress for package installs: embed a unique fake token in a file the agent reads, then alert if that string ever appears in outbound requests or in the diff the agent produces. Cheap to build, catches both naive leaks and injection-driven ones.
Limitations and who shouldn't do this
- Namespace-based sandboxing is not a hard security boundary against a determined adversary with a kernel exploit. For genuinely hostile input, use a separate VM per job (most CI platforms already give you this if you don't cache runners).
- Prompt injection is not solved by sandboxing. Sandbox limits blast radius; it doesn't stop the agent from being manipulated into writing bad code within its allowed scope. Human review of the diff is still mandatory — the table above says "open MR instead of push" for exactly this reason.
- Don't use this pattern at all if your task requires the agent to touch production secrets, customer data, or signed release artifacts. Get a scoped, short-lived credential from your secrets manager and audit it, or keep that step manual.
- The free-tier setup I mentioned is fine for prototyping the harness; I haven't load-tested it against a large monorepo pipeline, and quota/availability details are documented by the provider, not guaranteed by this article.
Takeaway
The current debate about agent tool boundaries gets a lot more tractable once you write the boundaries down as a table, enforce them with a hundred lines of shell, and attack your own enforcement with canaries before trusting it. The agent platform matters less than the cage. If you want a zero-cost sandbox to try this harness yourself, MonkeyCode's free models and server are one way to get an agent loop running — then point the canary tests at it and see what holds.
Top comments (0)