DEV Community

Jordan Huang
Jordan Huang

Posted on

Giving an AI Coding Agent a Job Without Giving It Your Credentials

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:

  1. 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.
  2. Network egress — the agent downloads or uploads something you never approved.
  3. 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
  "
Enter fullscreen mode Exit fullscreen mode

Notes:

  • env -i is the single highest-value line. Most leaks I've seen discussed are just inherited environment variables.
  • unshare --net removes 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
Enter fullscreen mode Exit fullscreen mode

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 (1)

Collapse
 
bobleer profile image
Bob Lee

Running the canary before the agent is the bit people skip. I’d rotate the fake token per job too, so an alert tells you which execution leaked instead of only proving that something leaked eventually.