Provides a shareable mise task, ai:sbx, that runs an AI coding agent in a Docker Sandbox scoped to a single GitHub repository and a set of read-only AWS roles. The repository is derived from origin rather than configured, so the sandbox identity cannot drift from the checkout in use. GitHub access is a repository-scoped fine-grained PAT held in the sbx secret store and injected by its host-side proxy, so the token is never exposed to the agent. The host ~/.aws directory and SSO token cache are never mounted; instead the host exports short-lived credentials for approved read-only profiles and only those land in the sandbox. Host profiles are commonly suffixed to mark the grant (api-portal-readonly) while Terraform references the account name (api-portal), so a trailing -readonly is stripped when the profile is written into the sandbox. Two host profiles that collapse to the same sandbox name are rejected during setup, before any credentials are exported, since a silent overwrite would hand Terraform the wrong identity under a plausible-looking name. All state lives under ~/.config/ai-sbx; repositories supply nothing and need no mise.toml.
ai-sandbox
A shareable mise task that runs an AI coding agent inside a Docker Sandbox, scoped to exactly one GitHub repository and one set of read-only AWS roles.
Description
Giving an agent your everyday credentials gives it your everyday blast radius. A
long-lived PAT reaches every repository you can reach; ~/.aws reaches every role your
SSO session can assume, admin included. This task narrows both, and puts the boundary
somewhere the agent cannot edit.
It provides a single command, ai:sbx, which:
- Derives the repository from
origin. No repository name is typed or configured, so the sandbox identity cannot drift from the checkout you are standing in. The sandbox name isai-<owner>-<repo>-<digest>, stable across runs. - Scopes GitHub access to one repository. A fine-grained PAT restricted to that
repository is stored with
sbx secret set. Docker's host-side proxy injects it into outbound requests; the token is never placed inGH_TOKEN, never written into the repository, and is not readable by the agent. - Keeps your AWS admin profiles out of the sandbox entirely. Your
~/.awsdirectory and your SSO token cache are never mounted or copied. Instead, the host runsaws configure export-credentialsagainst named read-only profiles you approve once, and only the resulting short-lived role credentials are written into the sandbox. The agent cannot use your SSO session to discover or request other roles. - Renames profiles for Terraform. Host profiles are commonly suffixed to mark the
grant —
api-portal-readonly— while Terraform code references the account name,api-portal. A trailing-readonlyis stripped when the profile is written into the sandbox, so unmodified Terraform resolves the read-only credentials. - Refreshes credentials on every launch, since exported SSO credentials are short-lived.
- Requires nothing from the repository. All state lives under
~/.config/ai-sbx/. Repositories that want first-class support can opt in with three lines ofmise.toml; repositories that do not are unaffected, and developers who do not use mise never notice.
What the sandbox receives
| Receives | Does not receive |
|---|---|
| Temporary read-only role credentials | Your SSO access or refresh token |
Profile names, minus the -readonly suffix |
Your host ~/.aws/config or credentials |
| Proxy-injected GitHub auth for one repository | A readable GitHub token |
| The repository working tree | Admin profile names or role assignments |
Installation
Dependencies
Install these on the host — none of them are needed inside the sandbox.
| Tool | Purpose | Install |
|---|---|---|
| mise | Runs the task and distributes it | Getting started |
Docker Sandboxes (sbx) |
Sandbox, secret store, credential proxy | Ships with Docker Desktop |
| AWS CLI v2 | aws configure export-credentials |
Required only when using --aws-profile |
jq |
Parses exported credentials | Required only when using --aws-profile |
git, sha256sum |
Repository identity | Already present on most systems |
Verify:
mise --version
sbx version
aws --version
jq --version
User-level install (recommended)
Adding the task to your personal mise config makes ai:sbx available in every Git
repository on the machine, without touching a single repository.
Add to ~/.config/mise/config.toml:
[task_config]
includes = [
"git::https://git.mroberts.dev/mroberts/ai-sandbox.git//tasks?ref=v1.0.0",
]
Pin ref to a tag or, better, a commit SHA. The task executes on your host with your
credentials — a moving ref means an unreviewed change runs the next time the cache
expires.
The repository is public, so https needs no credentials and works on a fresh machine
with no SSH agent. For a private fork, use the SSH form instead, which requires a key
registered with the forge:
includes = [
"git::ssh://[email protected]/mroberts/ai-sandbox.git//tasks?ref=v1.0.0",
]
Optional personal defaults:
[env]
AI_SBX_AGENT = "codex"
AI_SBX_MODE = "clone"
AI_SBX_BRANCH = "ai-sbx"
Confirm it loaded:
mise tasks ls # expect: ai:sbx
mise caches the clone under $MISE_CACHE_DIR/remote-git-tasks-cache. Force a refetch
with MISE_TASK_REMOTE_NO_CACHE=true.
Repository-level install (optional)
A repository whose team has adopted the workflow can add the same include to its
mise.toml:
[task_config]
includes = [
"git::https://git.mroberts.dev/mroberts/ai-sandbox.git//tasks?ref=v1.0.0",
]
Developers with mise get ai:sbx; developers without mise are unaffected. Do not put
AWS profile names or tokens in a repository config — the approved profile list is
per-user state, and no secret belongs in a repository.
Both installs can coexist. Nothing about the workflow requires the repository-level one.
Usage
1. Authenticate your read-only AWS profiles on the host
aws sso login --profile api-portal-readonly
aws sso login --profile prod-readonly
Setup fails fast with the exact aws sso login command if a profile is missing or its
session has expired.
2. Set up a repository, once
cd ~/src/api-portal
mise run ai:sbx -- setup \
--aws-profile api-portal-readonly \
--aws-profile prod-readonly
This derives the repository from origin, creates the sandbox, then prompts for a
fine-grained GitHub PAT. Create it at
github.com/settings/personal-access-tokens
scoped to that one repository:
Resource owner: your user or organization
Repository access: Only select repositories
Selected repository: owner/api-portal
Permissions:
Metadata: Read
Contents: Read and write
Pull requests: Read and write
Actions: Read, if required
Issues: Only if required
Workflows: No access unless explicitly required
Expiration: the shortest period you will tolerate
Paste it at the prompt. It is not written to shell history.
3. Run the agent
mise run ai:sbx -- run
Refreshes AWS credentials, then attaches. Pass agent arguments after a second --:
mise run ai:sbx -- run -- "Review the Terraform plan for the staging workspace"
4. Inside the sandbox
gh is already authenticated through the proxy, for that repository only:
gh pr list
gh pr create --fill
AWS named profiles work as Terraform expects:
aws sts get-caller-identity --profile api-portal
AWS_PROFILE=api-portal terraform init
AWS_PROFILE=api-portal terraform plan
provider "aws" {
profile = "api-portal"
region = "us-east-1"
}
Commands
| Command | Effect |
|---|---|
setup [options] |
Configure the repository, create the sandbox, store the GitHub token, install AWS profiles |
run [-- args...] |
Refresh AWS credentials and attach to the agent |
refresh |
Refresh AWS credentials without attaching |
status |
Show repository, sandbox, agent, mode, profile mapping, stored secrets |
remove |
Remove the sandbox and this repository's local configuration |
setup options
| Option | Default | Effect |
|---|---|---|
--aws-profile NAME |
none | Host profile to expose. Repeatable. Trailing -readonly stripped inside the sandbox |
--agent NAME |
codex |
Sandbox agent. See sbx create --help for the list |
--clone |
on | Give the agent a Git worktree on its own branch |
--direct |
off | Mount the host working tree read-write |
--branch NAME |
ai-sbx |
Branch used by --clone |
--replace |
off | Destroy and recreate an existing sandbox |
Environment defaults
| Variable | Default | Overrides |
|---|---|---|
AI_SBX_AGENT |
codex |
--agent |
AI_SBX_MODE |
clone |
--clone / --direct |
AI_SBX_BRANCH |
ai-sbx |
--branch |
AWS profile naming
Only a trailing -readonly is removed. Everything else passes through:
| Host profile | Sandbox profile |
|---|---|
api-portal-readonly |
api-portal |
prod-readonly |
prod |
dev |
dev |
readonly-first |
readonly-first |
team-readonly-readonly |
team-readonly |
Two host profiles that collapse to the same sandbox name — dev-readonly and dev —
are rejected at setup, before any credentials are exported. Silently letting one
overwrite the other would hand Terraform the wrong identity under a name that looks
right.
Variants such as _readonly, -ro, and -read-only are not stripped.
Where state lives
~/.config/ai-sbx/repos/<digest>/config mode 600, no secrets
Holds repository identity, sandbox name, agent, mode, branch, and the approved host
profile names. Tokens live in the sbx secret store; AWS credentials exist only inside
the sandbox and only until they expire.
Inspect the current repository's state with mise run ai:sbx -- status.
Security notes
- The repository is not the boundary. A repository-controlled file such as
.envrcormise.tomlcould otherwise choose which credentials get loaded. Profile approval lives in your user-owned config; the repository only supplies its own identity, which is cross-checked againstoriginon every run. - Fine-grained PATs, one per repository, with an expiration. A classic PAT reaches every repository you can reach; that is the thing this design exists to prevent.
- Read-only AWS roles. The sandbox boundary limits reach, not intent. Grant roles
that cannot cause damage if the agent misbehaves. Terraform
planneeds read access;applyshould stay outside the sandbox. - Rotation is manual. Revoke a PAT at GitHub and re-run
setupto replace it. --directweakens isolation. The agent writes directly to your working tree. Prefer the default--clone.
Development
bash tests/profile-mapping.test.sh
shellcheck -x tasks/ai/sbx tests/profile-mapping.test.sh
Task names come from directory nesting, not from colons in filenames: tasks/ai/sbx
registers as ai:sbx, whereas a file literally named tasks/ai:sbx registers as
ai_sbx. Task files must be executable.