AI coding agents are safest when you assume they will eventually run the wrong command, install the wrong package, or follow a malicious instruction hidden in code or docs. The practical response is to contain them by default with disposable workspaces, least-privilege credentials, package and network controls, and approval checkpoints before anything sensitive happens.
This guide shows how to set up those controls in real developer workflows so an agent can still edit code and run tests without inheriting access to your laptop, cloud admin account, or production systems. The goal is not to make agents useless; it is to make mistakes cheap and reversible.
Start by running every agent in a disposable sandbox
The safest default is to run each AI coding task in a fresh, disposable workspace with a hard filesystem and process boundary.
If an agent runs directly on your host machine, it can often see far more than the repo you intended: SSH keys, cloud config, browser sessions, shell history, local Docker state, and cached credentials. A disposable sandbox sharply reduces that exposure and gives you a clean teardown path after each task.
Use a fresh copy of the repo for every task
Instead of pointing the agent at your main checkout, create a temporary working directory:
WORKDIR="$(mktemp -d)"
rsync -a --delete ./repo/ "$WORKDIR/repo/"
cd "$WORKDIR/repo"
When the run ends, delete it:
rm -rf "$WORKDIR"
That simple pattern gives you:
- Isolation from unrelated local files
- Easy cleanup after a bad run
- A reviewable artifact set
- A natural place to enforce policy
Mount only the project, not your whole machine
At minimum, the agent should only be able to read and write:
- The project directory
- A temporary scratch area
- A minimal cache if your build requires it
Do not expose by default:
~/.ssh~/.aws,~/.config/gcloud, or other cloud auth dirs~/.npmrc,.pypirc, or registry tokens- Browser profiles and desktop keychains
- Docker socket
- Your full home directory
A practical container baseline:
docker run --rm -it \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=512m \
-v "$PWD:/workspace:rw" \
-w /workspace \
--network none \
--cap-drop=ALL \
--security-opt no-new-privileges \
my-agent-image
This does not make the environment perfect, but it is much safer than letting an agent run with your normal user context.
Choose the isolation level you can actually operate
Use the strongest isolation you can manage reliably, and prefer a setup that is easy to reset between runs.
| Option | Isolation strength | Good fit | Main tradeoff |
|---|---|---|---|
| Host process with wrappers | Low to medium | Quick experiments | Shares host state |
| Rootless container | Medium | Local dev and CI | Still shares host kernel |
| Hardened container sandbox | Medium to high | Team workflows | Requires policy tuning |
| MicroVM-style sandbox | High | Untrusted execution | More operational setup |
| Remote isolated runner | High | Centralized team control | Less local visibility |
If you are comparing developer-facing agent CLIs, it also helps to look at how they expose shell execution, approvals, and file access. This comparison of Claude Code vs Codex vs Gemini CLI vs OpenCode is useful background before you standardize on one workflow.
Use least-privilege credentials through a broker, not mounted secrets
The right way to give an agent access is to mint short-lived, scoped credentials on demand instead of mounting long-lived secrets.
This is the control that prevents “the agent made a poor decision” from becoming “the agent had permanent access to everything.” In practice, most dangerous setups come from convenience shortcuts: reusing your personal cloud profile, exposing a repo token with broad write access, or putting credentials directly in environment variables for the whole session.
Give the agent task-scoped access only
A safe credential for an agent should be:
- Short-lived
- Scoped to one resource or repo
- Limited to specific actions
- Bound to a task or environment
- Logged and revocable
Examples of safer scopes:
- Read one Git repository
- Create a branch and open a PR, but not merge
- Upload artifacts to one bucket path
- Read logs from one staging namespace
- Query a staging database in read-only mode
Unsafe patterns to avoid:
- Mounting
~/.awsor~/.kube - Reusing a human admin token
- Giving org-wide Git provider access
- Letting an agent inherit CI secrets meant for deploy jobs
Broker credentials instead of passing them directly
A credential broker lets the agent ask for access and receive a temporary token only if policy allows it.
Workflow:
- Agent requests a capability, like “create a PR” or “read staging logs”
- Policy checks the task type, repo, environment, and requested scope
- Broker mints a short-lived credential
- Action is logged
- Credential expires automatically
A policy file can be simple:
agent_policies:
coding_task_default:
repo:
actions: [read, create_branch, open_pr]
artifacts:
actions: [read, write]
cloud:
actions: []
staging_debug_task:
kubernetes:
namespace: app-staging
actions: [get, list]
resources: [pods, logs]
production:
actions: []
Separate identities by function
Split repo, package, cloud, and deployment access into separate identities so one leaked token does not unlock everything.
A practical separation looks like this:
agent-repo-writer: create branch, push branch, open PRagent-package-reader: read approved internal registryagent-artifact-writer: upload test outputagent-staging-debug: read logs in one namespace- No production identity at all for normal coding tasks
That approach also makes approvals easier because you can approve a specific scope rather than a vague “let the agent proceed.”
Enforce package, command, and network controls by default
Agents should be allowed to edit code and run low-risk commands, but installs, network access, and privileged tooling should be policy-controlled.
Most real incidents do not begin with the model “thinking dangerously.” They begin when the agent reaches for a shell command, downloads a dependency, or follows instructions embedded in untrusted input.
Put command execution behind an allowlist
A practical way to start is to classify commands into three groups:
Allow by default
git diffgit statusgrep,sed,awkpytest,go test,cargo test,npm test- formatter and linter commands
- file edits inside
/workspace
Require approval
curl,wgetnpm install,pip install,cargo addgit pushdocker buildkubectl,terraform,helm- commands that write outside
/workspace
Deny by default
- Access to Docker socket
- Direct SSH to remote hosts
- Writing to home directories
sudo- Production deploy commands in local agent sessions
A wrapper can block or escalate commands before execution:
#!/usr/bin/env bash
set -euo pipefail
cmd="$*"
case "$cmd" in
git\ diff*|git\ status*|pytest*|npm\ test*|go\ test*|cargo\ test*)
exec "$@"
;;
curl*|wget*|npm\ install*|pip\ install*|kubectl*|terraform*|git\ push*)
echo "APPROVAL REQUIRED: $cmd" >&2
exit 42
;;
sudo*|ssh* )
echo "DENIED: $cmd" >&2
exit 43
;;
*)
echo "UNCLASSIFIED COMMAND, REVIEW REQUIRED: $cmd" >&2
exit 44
;;
esac
Your agent runner can interpret exit 42 as “pause and request human approval.”
Restrict package installs and install scripts
Package managers are one of the easiest paths across trust boundaries, so treat them as controlled operations.
Use these defaults where possible:
npm ci --ignore-scripts
pnpm install --frozen-lockfile --ignore-scripts
pip install --require-hashes -r requirements.txt
Good package controls include:
- Require a lockfile
- Prefer internal mirrors or approved registries
- Disable lifecycle scripts unless explicitly approved
- Separate dependency resolution from code execution
- Run all installs inside the sandbox only
If an agent truly needs a new package, make that a review event: show the package name, source registry, lockfile diff, and resulting command before approval.
Default to no network, then allowlist specific destinations
Network egress should start closed and open only for explicit, reviewable destinations.
In practice, that means:
--network nonefor local runs unless required- If network is needed, allow only:
- your Git provider API
- approved package registry
- internal artifact store
- Disable git credential helpers
- Block ad hoc external URLs
A useful compromise for developer workflows is a task profile:
| Task profile | Network policy | Credential policy |
|---|---|---|
| Refactor local code | None | None |
| Run tests with cached deps | None or registry-only | Package read only |
| Open PR | Git provider only | Repo branch/PR scope |
| Debug staging issue | Internal endpoints only | Staging read-only |
| Deploy | Separate pipeline, not agent shell | CI-managed only |
This gives teams a way to automate common tasks without silently expanding access.
Add explicit approval checkpoints for risky actions
Approval checkpoints should stop the agent before installs, external access, credential elevation, branch pushes, or environment changes.
Approvals work best when they are tied to concrete actions, not vague trust levels. A developer should see exactly what the agent wants to do and what new risk boundary it is trying to cross.
Trigger approvals on policy events, not intuition
Good approval triggers include:
- First outbound network request
- Any new package install
- Any command outside the allowlist
- Request for cloud or Kubernetes credentials
git pushto a remote- Changes to CI config, Dockerfiles, or infra code
- Database migrations
- Anything touching staging or production
This is easy to implement in CI and local runs by treating blocked commands as policy events.
Example flow:
- Agent requests
npm install some-package - Policy wrapper returns “approval required”
- Runner captures:
- exact command
- working directory
- lockfile diff if already generated
- package source
- Developer approves or denies
- If approved, runner issues a one-time exception token and re-runs the command
- Exception expires after the task
Make approval prompts reviewable and auditable
A good approval payload should include:
- Command to run
- Why the agent claims it is needed
- Files changed so far
- Diff preview
- Requested credential scope
- Target system
- Expiry for the approval
Example JSON event:
{
"event": "approval_required",
"task_id": "task-123",
"action": "git push origin agent/task-123",
"reason": "Open a draft PR for review",
"requested_scope": "repo:branch-write",
"expires_in": "15m"
}
That event can be routed to a terminal prompt, chatops bot, or CI approval job.
Use branch protections and CI as the final checkpoint
Approvals inside the sandbox are helpful, but the strongest guardrail is to keep the agent out of direct production paths.
Recommended pattern:
- Agent creates a branch or patch
- Human reviews the diff
- CI runs tests and policy checks
- Protected branch rules enforce review
- Deployment happens through your normal release pipeline
That way, even if the agent produces a bad patch, it still has to pass the same controls as human-written changes.
Build a real developer workflow around policy enforcement
The most practical setup is an agent runner that starts a fresh sandbox, enforces policy with wrappers, and promotes output only through PRs or CI artifacts.
Principles matter, but teams need something they can operationalize this week.
A workable local runner
A local wrapper script can handle the basics:
#!/usr/bin/env bash
set -euo pipefail
IMAGE="my-agent-image"
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
rsync -a --delete ./ "$WORKDIR/repo/"
docker run --rm \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=512m \
--mount type=bind,src="$WORKDIR/repo",dst=/workspace,rw \
--workdir /workspace \
--network none \
--cap-drop=ALL \
--security-opt no-new-privileges \
-e PATH="/policy-bin:/usr/local/bin:/usr/bin:/bin" \
"$IMAGE"
Inside /policy-bin, place wrappers for git, npm, pip, curl, and other sensitive commands. The wrapper either:
- allows the command,
- emits an approval event, or
- denies the action.
A workable CI runner
In CI, the same pattern becomes even easier to enforce:
- Start an ephemeral job container or VM
- Inject only repo-scoped or task-scoped credentials
- Run the agent against a fresh checkout
- Upload patch, logs, and test results as artifacts
- Require human approval before:
- pushing a branch
- opening a PR
- invoking deployment workflows
This makes the CI system your policy boundary instead of the developer laptop.
Keep output narrow and disposable
The agent should produce only a small set of outputs:
- a patch or branch
- test and lint logs
- a machine-readable action log
- optional draft PR text
It should not directly mutate shared infrastructure, secrets stores, or protected branches.
If you are evaluating platforms for this kind of workflow, browse AI agent tools and runtimes with an eye toward sandbox support, command approvals, and credential isolation. For broader background, this AI agent guide is a useful companion to the workflow patterns here.
Implement these four controls first for the biggest risk reduction
If you want the fastest practical improvement, start with disposable workspaces, zero mounted personal secrets, short-lived credentials, and mandatory approvals for installs and network access.
You do not need a perfect platform on day one. You do need to remove the most dangerous defaults.
First-week implementation checklist
- Run the agent in a fresh container or isolated runner
- Copy the repo into a temporary workspace
- Mount only the project directory
- Do not expose local cloud, SSH, or browser credentials
- Set no network by default
- Wrap high-risk commands with allow/approve/deny logic
- Use a broker for temporary repo or cloud access
- Require approval for package installs, outbound fetches, and
git push - Route all changes through PRs and branch protections
- Destroy the environment after the task
Red flags that mean your setup is still too open
Tighten controls if the agent can:
- read your home directory
- use your personal cloud profile
- install arbitrary packages from the public internet without review
- access the Docker socket
- push directly to protected branches
- run deploy commands from the same session used for coding
A good sandbox does not need to be fancy. It needs to be disposable, narrowly scoped, and unable to quietly cross trust boundaries.
Frequently Asked Questions
Is a container enough for sandboxing an AI coding agent?
A container is a strong baseline and much safer than running directly on your host. For higher-risk tasks, teams often add stricter filesystem, network, and credential controls or use a more isolated runner.
Should an AI coding agent ever get production access?
Usually no. If production interaction is unavoidable, gate it behind a separate workflow with explicit approvals, narrow scopes, and short-lived credentials issued only for that one action.
How do I enforce approvals without constantly interrupting developers?
Group tasks into profiles and auto-allow low-risk actions like file edits and test runs. Only interrupt for trust-boundary crossings such as installs, network access, credential requests, branch pushes, and environment changes.
What is the best first step for a small team?
Start by moving the agent into a disposable workspace with no mounted personal secrets. That single change removes many of the most common paths to secret leakage and accidental system access.