Add Your Own CLI Tool to the Sandbox
This guide walks you from a plain command-line tool to a credentialed one baked into the agent sandbox. By the end you will have authored a devcontainer Feature that installs your tool, published it to your own container registry, and (for tools that talk to a third-party API) declared the credential story that lets Aileron seal the tool’s token at the network boundary so the agent never holds it.
This is the task page. The authoritative reference for the composition model is ADR-0017, the credential contract is ADR-0026, and the full aileron sandbox CLI workflow lives in Sandbox Composition. This guide links out to those rather than restating them.
Orientation: devcontainers and Features
A devcontainer ↗ is a standard, tool-agnostic way to describe the container a project develops in. The description lives in a .devcontainer/devcontainer.json file: it names a base image and lists the tools to layer on top. The standard is owned by the wider ecosystem, not by Aileron, so the same file works with VS Code, GitHub Codespaces, and any other devcontainer-aware tool.
A devcontainer Feature is one self-contained install unit. It is a small directory holding a manifest (devcontainer-feature.json) and an install script (install.sh) that runs on top of any base image. A Feature installs one thing, the GitHub CLI for example, and a project composes several Features together to assemble its full toolchain. Features publish to a container registry, so you reference one by its registry coordinate the same way you reference an image.
Aileron composes the agent sandbox from exactly these pieces. Aileron owns a minimal, harness-free base image and you extend it by listing Features in devcontainer.json. Aileron reads the standard devcontainer fields and stores its own settings under a customizations.aileron block, which is the standard’s escape hatch for tool-specific configuration. Because composition is standard devcontainer Features, “Aileron’s base plus an agent plus your own tools” is expressible as three independent Features on one base, with no image-lineage merging required. That model is the subject of ADR-0017.
Compose existing Features
Before authoring anything, you can compose Features that already exist. aileron sandbox init scaffolds a starter .devcontainer/devcontainer.json that lists Aileron’s base image and an agent Feature:
aileron sandbox initThe scaffold is the Tier 1 customization tier. It writes one file: the base image, an agent Feature, and a commented slot for your own tooling Feature.
{
"name": "Aileron sandbox",
"image": "ghcr.io/alrubinger/aileron-sandbox-base:<version>",
"features": {
// The agent Feature installs the agent CLI onto the base image.
"ghcr.io/alrubinger/aileron-features/claude:0": {}
// Add your own tooling as its own Feature alongside the agent Feature:
// "ghcr.io/acme/internal-tools:1": {}
},
"customizations": {
"aileron": {
"mediation": "default",
"approval_surface": "both"
}
}
}Each entry under features is a registry coordinate plus an options object. To add a tool that already ships as a Feature, drop its coordinate in alongside the agent Feature and rebuild. The rest of this guide is about the case where the tool you need does not yet have a Feature, so you author one.
Author a Feature for a plain CLI
A Feature for a plain CLI is a directory with two files: the manifest and the install script. Mirror the layout of the worked gh Feature at images/sandbox-features/gh/ ↗.
sandbox-features/
└── acme-cli/
├── devcontainer-feature.json
└── install.sh
The manifest declares the Feature’s id, version, and human-readable name. For a plain tool with no credential, that is all it needs:
{
"id": "acme-cli",
"version": "0.0.1",
"name": "Acme CLI",
"description": "Installs the Acme CLI onto the Aileron sandbox base so it is on PATH for the non-root agent user.",
"documentationURL": "https://example.com/acme-cli"
}The install script runs as root during the image build and lands the binary on PATH for the non-root agent user. The Aileron base image is Alpine, so an apk recipe is the common case. Keep it idempotent and image-layer-clean:
#!/bin/sh
set -eu
apk add --no-cache acme-cliIf your tool ships only as a release binary or through another package manager, fetch and install it here instead. The install script is ordinary shell; Aileron does not constrain how the tool gets installed, only that it ends up on PATH.
Publish the Feature
A Feature is consumed from a container registry, so publish it before you reference it. The official devcontainer CLI packages and pushes a directory of Features:
devcontainer features publish \
./sandbox-features \
--namespace your-org/your-repo \
--registry ghcr.ioThis is the same command Aileron’s own .github/workflows/sandbox-features.yml workflow runs over images/sandbox-features/** to publish the in-house Features. Publishing a manifest at version 0.0.1 emits the tag set 0.0.1, 0.0, 0, and latest, so a coordinate pinned to the broadest major tag (:0) keeps resolving across patch bumps without re-editing your devcontainer.json.
The first publish of a new package needs a one-time manual step: a freshly pushed GHCR package starts private, and the sandbox build pulls Features anonymously, so flip the package to public in its GHCR settings after the first publish. This is per-package, not per-build.
Once published, reference it from your devcontainer.json alongside the agent Feature:
{
"features": {
"ghcr.io/alrubinger/aileron-features/claude:0": {},
"ghcr.io/your-org/your-repo/acme-cli:0": {}
}
}Then build and verify with the aileron sandbox workflow:
aileron sandbox plan
aileron sandbox buildplan reports the composition tier Aileron infers, and build composes the Features onto the base image through the official @devcontainers/cli build engine. The full plan/build/check/launch workflow is documented in Sandbox Composition.
Advanced: a credentialed CLI
A CLI that talks to a third-party API needs a credential. The defining property of the Aileron sandbox is that the agent never holds that credential. Aileron seals it at the network boundary: the tool’s outbound HTTPS request leaves the sandbox carrying a placeholder, and the Aileron proxy swaps in the real token at egress. The token lives in the Aileron vault, never in the container.
You declare this credential story as one CLI-capability unit under customizations.aileron.cli on the tool’s Feature. The unit is one tool’s complete credential story in one place: how the token is acquired, and how each outbound host is sealed. This is the subject of ADR-0026, which is the authoritative contract for every field below.
The gh Feature is the worked example. Its manifest carries this customizations.aileron.cli block:
{
"customizations": {
"aileron": {
"cli": {
"name": "gh",
"key": "user/github",
"presence": {
"builtin": "base"
},
"acquisition": {
"mode": "device-flow",
"container_name": "aileron-auth-github",
"login_cmd": ["gh", "auth", "login", "--hostname", "github.com", "--git-protocol", "https", "--web"],
"token_cmd": ["gh", "auth", "token", "--hostname", "github.com"],
"browser_shim": "echo"
},
"sealing": [
{
"host": "github.com",
"scheme": "basic",
"emit_mechanism": "inject",
"username": "x-access-token"
},
{
"host": "api.github.com",
"scheme": "bearer",
"emit_mechanism": "sentinel-swap",
"sentinel": {
"value": "ghp_AILERONSENTINELAAAAAAAAAAAAAAAAAAAAA",
"env": "GH_TOKEN"
}
}
]
}
}
}
}Read the unit top to bottom:
keyis the tool’s credential identity, declared once in<kind>/<service>form (user/githubhere). It is the single source of truth. Aileron derives the acquisition’s storage location, the credential kind (user, the first path segment), and every sealing entry’s credential reference from this one field. The unit must not re-declare any derived field, because that would let the tool’s identity drift; re-declaring one is a load error.acquisitionis the one-time login that mints the token. Indevice-flowmode Aileron runslogin_cmdinteractively in a throwaway container, you complete the device login in your browser, and Aileron reads the token back out withtoken_cmdand stores it in the vault.sealingis a list, one entry per outbound host. Each entry says which credential scheme that host expects and how Aileron emits it at the proxy. Thegithub.comentry seals git-over-HTTPS with HTTP basic auth and injects unconditionally. Theapi.github.comentry seals theghAPI call with a bearer token by planting a non-secret sentinel value that the proxy swaps for the real token at egress. The sentinel is a committable placeholder with no authority.presencerecords which built-in image tier already carries the tool. It is accepted and validated but drives no behavior yet.
The host reads this unit from the resolved sandbox image’s devcontainer.metadata label at launch and projects it into the same credential-capture and proxy-binding machinery the rest of Aileron uses. You author one unit on one Feature, and no central file in Aileron changes. To adapt the example for your own tool, change the name, set key to your tool’s <kind>/<service> identity, point acquisition at your tool’s login and token commands, and add one sealing entry per host your tool calls. See ADR-0026 for the full field-by-field descriptor contract.
What this does not do yet
A few honest limitations apply today.
- Device-flow acquisition only. The shipped acquisition mode is the interactive
device-flowlogin above. Thedirect-tokenmode, for a tool you hand a token to directly rather than logging in, is reserved and rejected: a unit naming it fails validation with an explicit not-yet-implemented error (ADR-0026).- A newly installed tool needs an MCP restart to surface. Live in-session discovery refresh is a follow-on runtime layer, tracked in #897 ↗. Rebuild and relaunch the sandbox after adding a Feature.
- Custom Features publish to your own registry with no Aileron co-sign. The
devcontainer features publishflow pushes to your GHCR namespace under your own trust. Aileron does not co-sign or vet third-party Features, and the trust tiers that would gate an untrusted unit are forward-declared, not shipped (ADR-0026).
Where to go next
- Sandbox Composition for the full
aileron sandbox init/plan/build/check/launchworkflow and the composition tiers. - Sandbox Agent Images for the Feature directory structure and GHCR publishing path in depth.
- ADR-0017 for the composition model decision.
- ADR-0026 for the CLI-capability unit contract.