# Sandboxing: example scenario

Move the ticket-service agent into a devcontainer with only the repo mounted, an egress allowlist and a scoped token, and keep the agent's own sandbox on inside.

Source: https://ai-sw-factory.mellicci.dev/fundamentals/sandboxing/example-scenario

## Scenario

The `ticket-service` team wants agents to run with fewer approval prompts, and later unattended in CI. Today the agent runs on each laptop as the developer. Its commands can read `~/.ssh` and cloud credentials, and they can reach the open internet. Removing the prompts on that setup lets one bad command, or one injected instruction in an issue, reach all of it.

This page shows one way to contain it: a devcontainer with Node 22, pnpm and Postgres, where the agent can reach the repository and three named hosts, and nothing else.

## Before → after

| | Before | After |
|---|---|---|
| Files | Home directory, SSH keys and other repos are readable | Only the repo is mounted; none of the rest exists in the container |
| Network | Any host is reachable | npm registry, GitHub and the model provider's API only |
| Credentials | The developer's own | A task-scoped token passed as an environment variable |
| Mistakes | Damage lands on the laptop | Damage stays in a container you rebuild |

## Design

**Diagram:** The devcontainer holds the agent, its built-in sandbox and Postgres; only allowlisted hosts are reachable, and the host's home directory and Docker socket are not mounted.

- Devcontainer · disposable (repo only, scoped token):
  - Agent — model + harness
  - Built-in sandbox — limits each command it runs
  - Postgres — test database
- Host · not mounted:
  - Home directory — keys, tokens, dotfiles
  - Docker socket — control of the host
- → allowlist only
- Allowed hosts — registry · GitHub · model API

Two layers stack. The container limits what the whole agent can reach; the agent's built-in sandbox stays on inside it and limits each command. Files live in `.devcontainer/`: `devcontainer.json`, `docker-compose.yml` (the `app` and `db` services, with only the repo mounted as the workspace) and a firewall script that applies the allowlist at start. Generic excerpt, where `<your agent's CLI>` is a placeholder:

```json
{
  "name": "ticket-service",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  "postCreateCommand": "corepack enable && pnpm install && npm install -g <your agent's CLI>",
  "postStartCommand": "sudo /usr/local/bin/init-firewall.sh",
  "remoteEnv": {
    "GH_TOKEN": "${localEnv:TICKET_AGENT_GH_TOKEN}",
    "DATABASE_URL": "postgres://ticket:ticket@db:5432/ticket"
  }
}
```

## What happens at runtime

| Step | What happens |
|---|---|
| 1. Rebuild | The editor or CI builds `app` and `db` from the files; the agent CLI is installed and the repo is mounted. |
| 2. Firewall applies | `postStartCommand` runs the script: output is dropped except the three hosts and `db`. |
| 3. Agent runs | The agent works as a non-root user, with its built-in sandbox on; `pnpm install` and `git push` succeed. |
| 4. Blocked request | A command runs `curl https://example.com`; the firewall drops it and the command fails. |
| 5. Damage stays inside | A bad `rm -rf` or an injected instruction can only touch the workspace and the container. |
| 6. Rebuild | You discard the container and rebuild: a clean slate, with the same rules. |

## What can go wrong

| Failure | How you notice | What to do |
|---|---|---|
| Home directory or Docker socket mounted "for convenience": a container escape | `ls ~` inside shows your real files, or `docker ps` works | Mount the repo only; pass a scoped token instead of mounting credentials |
| Allowlist too broad, such as all of `github.com` | Data can leave through a public gist or another repo | List the fewest hosts; use a filtering proxy where the stakes justify it |
| Long-lived credentials inside the box | A token with broad scope or no expiry sits in the environment | Issue a token per task, limited to `ticket-service`, with an expiry |
| Built-in sandbox turned off "because the container is enough" | Commands can read and write anything in the container, including the token | Keep both layers on; see the [security model](https://ai-sw-factory.mellicci.dev/fundamentals/security-model) |
| Container config drifts from CI | The same task passes on a laptop and fails in CI, or the reverse | Build CI from the same `.devcontainer/` files, not a copy |
