DEV Community

Sam Rivera
Sam Rivera

Posted on

I Stopped Running Agent-Generated Code on My Laptop: A 20-Minute Disposable Sandbox

Last week an agent I was testing wrote a cleanup step that looked like rm -rf ./node_modules and was actually rm -rf ~/node_modules — a directory that, on my laptop, does not exist, but ~ very much does. Nothing bad happened. I got lucky. That's the whole story, and it's not a strategy.

There's been a lot of talk this week about giving AI agents more tools and what happens when the boundaries fail. My small-team answer to that isn't a policy document — it's physics. If the code runs somewhere that has nothing worth losing, the blast radius is the size of the box, and the box is disposable.

So I moved my agent experiments off my laptop and onto a throwaway server. This post is the setup: about 20 minutes, one provisioning script, and a five-point smoke test I run before I let any agent-generated command execute there.

The setup in one paragraph

I used MonkeyCode's free model access as the agent's brain and their free server option as the disposable host, since that's what I already had on hand for side-project experiments.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The workflow below doesn't depend on them, though. Any cheap VPS, a spare Raspberry Pi, or a cloud free tier works — the point is a machine that is not yours, containing nothing of yours. The two things I cared about: I didn't want to burn paid model quota on an experiment that might end in nethack-level chaos, and I didn't want to pay for a sandbox box I planned to delete the same afternoon. Free tier on both counts removed my usual excuse for skipping isolation.

What the sandbox must guarantee

Before writing any code, I wrote down the guarantees. If the box can't promise these, it's not a sandbox, it's just a second laptop:

  1. No credentials on disk. No SSH private keys, no cloud tokens, no .git-credentials, no browser profiles.
  2. The agent user can't escalate. Unprivileged user, no sudo, locked-down sudoers.
  3. Outbound traffic is observable. I want a log of every domain the agent's code tried to reach.
  4. Teardown is one command and I actually run it.

That last one is the one everyone skips, including me, twice.

The provisioning script

This is sandbox-setup.sh, run as root on a fresh box. ~45 lines, Debian/Ubuntu-flavored:

#!/usr/bin/env bash
set -euo pipefail

AGENT_USER="agentrun"
LOG_DIR="/var/log/sandbox"

# 1. Unprivileged user, no password, no sudo
useradd -m -s /bin/bash "$AGENT_USER"
passwd -l "$AGENT_USER"
# Ensure they are NOT in sudo group
deluser "$AGENT_USER" sudo 2>/dev/null || true

# 2. Empty, minimal home — nothing to steal
rm -f /home/$AGENT_USER/.bash_history
chmod 750 /home/$AGENT_USER

# 3. Egress logging: log new outbound connections from the agent user
mkdir -p "$LOG_DIR"
iptables -A OUTPUT -m owner --uid-owner "$AGENT_USER" \
  -m conntrack --ctstate NEW -j LOG --log-prefix "AGENT-EGRESS "

# 4. Block the cloud metadata endpoint for the agent user.
#    (If your agent code ever curls 169.254.169.254, it should get nothing.)
iptables -A OUTPUT -m owner --uid-owner "$AGENT_USER" \
  -d 169.254.169.254 -j REJECT

# 5. DNS still works, package installs still work — we're logging, not airlocking
echo "Sandbox ready. Watch traffic with:"
echo "  grep AGENT-EGRESS /var/log/kern.log | awk '{print \$NF}' | sort | uniq -c | sort -rn"
Enter fullscreen mode Exit fullscreen mode

Deliberately minimal. I'm logging egress rather than allowlisting, because on day one I don't yet know what domains a normal install flow touches — I want the data first. (Allowlisting is the obvious iteration 2.)

The smoke test I run before trusting it

A sandbox you haven't tested is a hope. Five checks, all copy-pasteable, run as the agentrun user where noted:

# 1. No secrets reachable
sudo -u agentrun bash -c 'ls ~/.ssh 2>/dev/null; env | grep -iE "token|key|secret" || echo "clean"'

# 2. No sudo
sudo -u agentrun sudo -n true 2>&1 | grep -q "password is required" && echo "PASS: no sudo"

# 3. Metadata endpoint refused
sudo -u agentrun curl -s --max-time 3 http://169.254.169.254/ || echo "PASS: metadata blocked"

# 4. Egress is actually logged
sudo -u agentrun curl -s https://pypi.org -o /dev/null
grep AGENT-EGRESS /var/log/kern.log | tail -1

# 5. Teardown works (yes, I test this BEFORE the experiment)
echo "teardown command verified: scheduled for deletion via provider panel"
Enter fullscreen mode Exit fullscreen mode

If any check fails, the box gets deleted, not patched in place. A sandbox with a known hole is worse than no sandbox, because it changes your behavior without changing your risk.

The failure fixture

I keep one deliberately-hostile test file around to prove the sandbox earns its keep — fixture.sh, written by me, not an agent:

#!/usr/bin/env bash
# Simulates the worst thing an agent has ever suggested to me.
curl -s http://169.254.169.254/latest/meta-data/   # should be REJECTED
sudo shutdown -h now                               # should fail: no sudo
cat /etc/shadow                                    # should fail: permission denied
curl -s https://example-c2.invalid/beacon          # should appear in egress log
Enter fullscreen mode Exit fullscreen mode

Run it as agentrun. Expected output: three failures and one log line. If instead you get metadata, a shutdown, or a shadow dump — congratulations, the test just paid for the entire exercise.

Where the free model fits

The agent loop on the box points at MonkeyCode's free model access rather than my paid API key. Two honest reasons: quota experiments burn tokens fast when the agent retries in a loop, and if a prompt-injection-ish accident ever exfiltrated a key from that box, I'd rather it be a free-tier credential than my paid one. Defense in depth, but make it cheap.

Time boundary for the whole thing: ~20 minutes setup, ~10 minutes smoke test, teardown scheduled the same day. If the box survives longer than a week, that's a smell — it's accumulating state and becoming a pet.

Limitations, and who should skip this

  • This is not a security boundary against a determined attacker. iptables owner matching and a locked-down user stop accidents and lazy automation. They do not stop a kernel exploit. If your threat model includes adversarial humans, you want proper VM isolation or seccomp/namespace work, not my 45-line script.
  • Egress logging is not egress control. You will see the bad domain in the log after the request. If that's unacceptable for your experiment, allowlist from day one.
  • Don't put real data on the box. The sandbox is for running code, not for staging your production database "just temporarily." There is no such thing as temporarily.
  • Skip this entirely if your agent never executes anything — if it only drafts text you review and run manually, your editor review is the sandbox, and adding a server is theater.

If you want to try the same shape with a free box and free model quota, MonkeyCode's free tier is the version I used; the script above is provider-agnostic either way.

One thing I haven't decided yet: iteration 2 replaces egress logging with an allowlist, and my draft allowlist is basically pypi.org, npmjs.org, github.com, crates.io — which feels both obvious and incomplete. For those of you running agent code in any kind of walled garden: what did your first egress allowlist miss, and how did you find out? That's the list I'll build iteration 2 from.

Top comments (0)