Sandbox access

By default a seat's sandbox reaches only the platform: no internet, no cluster services, no credentials. Access profiles grant practical access: binaries, hosts, IP ranges, GitHub repositories and a browser. Each part of a profile is a plugin with its own enforcement, and a seat gets the union of the profiles named on the seat and on its teams.

access_profiles = {
  github_engineer = {
    tools  = { binaries = ["git", "gh"] }
    egress = { hosts = ["pypi.org", "files.pythonhosted.org"] }
    github = { connection = "github", repos = ["acme/sandbox"],
               permissions = { contents = "write", pull_requests = "write" }, delivery = "sandbox" }
  }
  internal_db = { network = { rules = [{ cidr = "10.0.5.0/24", ports = [5432] }] } }
  web         = { browser = { session = { connection = "github_web" } } }
}
seats = {
  engineer = { …, access_profiles = ["github_engineer", "internal_db"] }
}
teams = {
  engineering = { …, access_profiles = ["web"] }   # every member
}

Plugins

PluginWhat it grantsHow it is enforcedChanges apply
toolsBinaries the seat needs, e.g. git, gh, curl.The seat runner checks they are on the image's PATH before the harness starts. A missing binary stops the seat with a reason; nothing is installed at run time.New Pod
egressHosts: example.com, *.example.com (subdomains only), or host:port. Without a port, 443 and 80.The egress gateway matches the CONNECT host and port, and requires the TLS server name to equal the host. Plain HTTP is matched on its host.Live: within seconds, open connections included
networkDirect connections to IP ranges, optionally limited to ports and a protocol (tcp, udp, sctp).NetworkPolicy ipBlock rules, which every NetworkPolicy implementation enforces. Host names cannot be expressed this way; use egress.New Pod
githubRepositories and permissions (contents, pull_requests, issues, metadata: read or write) through a github connection.See GitHub.Repositories and permissions live; switching delivery needs a new Pod
browserA headless Chromium as tools (browser_navigate, browser_click, …), optionally signed in.Its traffic goes through the egress gateway, so the seat's egress hosts decide what it can open. The browser closes when the grant goes.New Pod

Readiness never claims access that is not in effect. A seat's AccessApplied condition is true only when its Pod runs the configuration revision that contains its tools, image, NetworkPolicy and proxy settings. Until then it says what it is waiting for, and if the seat cannot start, for example because a binary is missing, it says why.

The egress gateway

Hostname egress needs the gateway. It is off by default; enable it in the platform stage with enable_egress = true. A seat whose profiles need it stays blocked, with that reason, while it is off.

GitHub

Declare a connection with adapter github. Its secret holds either a GitHub App (app_id, installation_id, private_key; recommended) or a fine-grained personal access token (token). endpoint_ref points at GitHub Enterprise's API, for example https://ghe.example.com/api/v3.

Platform delivery (default)

The seat calls repository operations with connections.invoke: repo.read, pull_request.read, pull_request.create, issue.read and issue.comment, as the permissions allow and only on the granted repositories. They go through the operation ledger like any connector operation, and the credential never enters the sandbox.

Sandbox delivery

With delivery = "sandbox", git and gh in the sandbox work with GitHub directly. GitHub's hosts are allowed through the egress gateway, or the connection's own endpoint for GitHub Enterprise. The seat runner sets git's credential helper to steadmesh-tools credential git, and gh runs through a wrapper that fetches a token for each invocation. Every fetch is recorded as a credential_issued event, with the repositories and permissions but never the token.

The credential boundary. A credential delivered to the sandbox can be read by the agent and by any process in the seat's Pod. Until it expires or is revoked, it can be used against any destination the seat's egress allows. Steadmesh keeps credentials out of Terraform plans and state, CRDs, manifests, Pod specs, images, instruction text and logs, and records every delivery. Prefer platform delivery when the seat does not need git or gh itself.

Revocation

Browser

The browser plugin needs a seat image with Chromium and Playwright MCP: the -browser variants. Releases publish ghcr.io/darcys22/steadmesh/seat-pi-browser (the platform module's seat_images["pi-browser"]); build one locally with build/build.sh seat-pi-browser. The seat's harness gets a browser MCP server, steadmesh-tools browser-mcp, which runs Playwright MCP headless and isolated through the egress gateway.

Signed-in sessions. A personal access token cannot create a browser session. To browse as an account, declare a connection with adapter browser_session whose secret key storage_state holds a Playwright storage state (cookies and local storage) for a dedicated test account, and name it in browser.session. Produce the state with a manual login, for example npx playwright codegen --save-storage=state.json https://github.com/login. Then store state.json in the Secret or Vault entry. It is delivered to the sandbox, with the boundary stated above.

Revocation. When the grant goes, the browser is killed within a few seconds and a tool call in progress fails. The gateway also closes its connections. Cookies already delivered stay valid at the website until you log the account out or replace the session. Rotating the secret is picked up without Terraform; the browser loads the new state when it next starts.

Seat images

ImageIncludes
seat-claudecode, seat-codex, seat-piThe harness; git 2.39, gh 2.23 (with the credential wrapper) and curl 7.88 from Debian bookworm
seat-pi-browser (any <seat image>-browser)The above, plus Chromium and Playwright MCP 0.0.83
seat-fakeThe deterministic test harness only; no tools

Adding a sandbox plugin

  1. Add the plugin's configuration to spec.AccessProfile and to the provider schema; run make generate and make provider-docs.
  2. Implement access.Plugin in pkg/access. Check validates the configuration with exact paths; Apply adds the plugin's contribution to the seat's SeatAccess. Decide which parts need a new Pod: they belong in PodPart, so they change the configuration revision. Live parts must be enforced where the platform or gateway reads them at request time.
  3. Enforce it where it acts: the Pod or NetworkPolicy (runtime/kube), the seat runner (cmd/seat-runner/access.go), the platform (/v1/access, /v1/credentials) or the gateway. If it cannot be enforced on a cluster, fail validation with a clear reason rather than claim it.
  4. Record denials and deliveries as events, document the revocation behaviour and credential boundary, and add unit and e2e tests.