> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ascii.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Environments

> Decide what every new Box starts with: repositories, secrets, and which of your credentials it may use.

An **environment** is the template a new Box inherits when it starts: which GitHub repositories are cloned, which secrets are injected, and which of your credentials the Box may use. Manage it from [Dashboard > Environment](https://box.ascii.dev/box/dashboard?tab=environment) or from the `box env` commands.

Every account has one environment named `base`. You can add more, and name the one a Box should use when you create, resume, or fork it.

<Note>
  This page is about what goes **into** a Box. For running code inside one, see [Setup & Scripts](/box/setup).
</Note>

## Safe for third parties

One switch decides the entire security posture of an environment.

<CardGroup cols={2}>
  <Card title="On" icon="shield-check">
    For Boxes other people drive, such as your own end users. **Nothing of yours is passed**: no GitHub access, no secrets, no Box or Agents credentials, whatever the section toggles say. The Box is confined to itself and cannot act on your account or your other Boxes.
  </Card>

  <Card title="Off" icon="user">
    For Boxes only you drive. The four section toggles below apply, so you choose exactly what goes in.
  </Card>
</CardGroup>

<CodeGroup>
  ```bash CLI theme={null}
  box env set base --safe-for-third-parties true
  ```

  ```bash curl theme={null}
  curl -sS -X PUT "$BOX_API_BASE/environments/$ENV_ID" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"safeForThirdParties":true}'
  ```

  ```ts TypeScript theme={null}
  await box.updateEnvironment({
    environmentId: envId,
    updateBoxEnvironmentRequest: { safeForThirdParties: true },
  });
  ```

  ```python Python theme={null}
  box.update_environment(env_id, UpdateBoxEnvironmentRequest(safe_for_third_parties=True))
  ```
</CodeGroup>

The `--no-env` flag (`noEnv` in the API) is the per-Box shortcut for the same guarantee and is kept forever. `box new --no-env` behaves exactly like starting in an environment marked safe for third parties. Prefer the environment when more than the occasional Box needs it: it holds for every Box that uses it, and it survives forks and resumes without your code passing a flag.

### What a normal Box receives

To know what this protects you from, here is what a Box gets from your account when nothing is withheld.

|                       | Passed in                                                                                                                                                                                                                                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Environment variables | Your environment's variables, your GitHub token, and your model credentials if you configured agents (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `CLAUDE_CODE_OAUTH_TOKEN`, `CHATGPT_ACCOUNT_ID`), plus neutral Box-internal vars (`BOX_ID`, a machine-scoped `ASCII_TOKEN`)                                    |
| Credential files      | Your secret files, the GitHub CLI login (`~/.config/gh/hosts.yml`), git credentials in cloned repos, Claude and Codex logins (`~/.claude/.credentials.json`, `~/.codex/auth.json`), the in-box Box CLI token (`~/.config/ascii/box/config.json`), and the shell environment the box exports to SSH sessions |

A protected Box receives none of that. It keeps only the neutral Box-internal vars and whatever you pass with `env`.

### Converting an existing Box

`box resume <id> --no-env` and `box fork <id> --no-env` convert a Box whose snapshot came from a normal one. Before the Box becomes reachable, every owner secret the snapshot may carry is scrubbed: the managed `~/.bashrc` blocks, `~/.config/gh/hosts.yml` (plus a `gh` logout), `~/.git-credentials`, `~/.ssh/id_*` private keys, the Codex and Claude credential files, the in-box Box CLI token, and every secret file you configured. `authorized_keys` and `known_hosts` are kept so the Box stays reachable.

Credentials the platform never wrote are left alone: `aws`, `gcloud`, `.netrc`, `.npmrc`, and Docker logins added inside the Box all stay.

While the scrub runs, SSH and desktop return a retryable `box_securing` error. Conversion is one way: the Box stays protected afterwards.

<Warning>
  The Claude and Codex credential files are removed even when the Box's own user logged in with their personal account inside the Box. The scrub cannot tell whose they are. Back them up and restore them afterwards if they belong to the Box's user.
</Warning>

## What a Box can be given

With *Safe for third parties* off, four independent toggles decide what a Box receives. Each is a section in the dashboard; open a section to edit what is inside it.

| Section             | What the Box gets                                                     | Turning it off                                              |
| ------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------- |
| GitHub repositories | Your repositories cloned in, plus the GitHub token                    | `gh` and `git push` stop working                            |
| Secrets             | Your environment variables and secret files                           | Boxes drop them at their next start. What you typed is kept |
| Box credentials     | A scoped Box API key, so the Box can snapshot, stop and resume itself | The Box cannot manage its own lifecycle                     |
| Agents credentials  | The provider keys (Claude, Codex, and so on) from the Agents tab      | Agents in the Box have no login                             |

<CodeGroup>
  ```bash CLI theme={null}
  box env set base --github true --secrets true --box-credentials false --agents-credentials true
  ```

  ```bash curl theme={null}
  curl -sS -X PUT "$BOX_API_BASE/environments/$ENV_ID" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"passGithub":true,"passSecrets":true,"passBoxCredentials":false,"passAgentsCredentials":true}'
  ```

  ```ts TypeScript theme={null}
  await box.updateEnvironment({
    environmentId: envId,
    updateBoxEnvironmentRequest: {
      passGithub: true,
      passSecrets: true,
      passBoxCredentials: false,
      passAgentsCredentials: true,
    },
  });
  ```

  ```python Python theme={null}
  box.update_environment(env_id, UpdateBoxEnvironmentRequest(
      pass_github=True,
      pass_secrets=True,
      pass_box_credentials=False,
      pass_agents_credentials=True,
  ))
  ```
</CodeGroup>

## Versions, and when a Box moves between them

This is the part worth reading twice.

```
you save a change  ──▶  a new version is created
                            │
                            ├──▶ every Box that starts from now on gets it
                            │
                            └──▶ Boxes already running: nothing happens
                                     │
                                     └──▶ they move only when you upgrade
```

A Box takes the latest version **at the moment it starts**, and keeps that exact version for the rest of its life. Saving never reaches into a running Box. There is no automatic upgrade, no background rollout, and no scheduled window: a Box moves when you press **Upgrade** in the dashboard or run `box env upgrade`, and at no other time.

`box info` tells you which one a Box is on, as `environment` and `environmentVersion`. Compare that number against the environment's latest in `box env list`: a Box below it is still running the older configuration, which is usually the answer to "I added that secret, why does my Box not have it?"

```bash theme={null}
box info bx_f7k2q9hd     # env:  prod (v2)
box env list             # prod  latest v3   -> this Box is one upgrade behind
```

Upgrading applies the new configuration and removes any secret the new version withholds. Live Boxes are cleaned and updated immediately; stopped Boxes pick it up when they resume.

<Warning>
  Upgrading is not reversible on that Box's disk. A secret the new version withholds is deleted from the machine, not hidden. Re-pinning to the older version does not bring back a secret file the newer version dropped.
</Warning>

The Versions panel lists every version and how many Boxes sit on each, so you can see what is still running old configuration.

## Repositories

<Note>
  Repositories need a GitHub connection on your account. If you signed in with Google or an email code, open [Dashboard > Environment](https://box.ascii.dev/box/dashboard?tab=environment) and use **Connect GitHub** under GitHub repositories: it attaches GitHub to the account you already have, and you keep signing in the way you do now. Nothing else in Box requires it — you can also skip the connection entirely and use `gh` with your own token inside the Box.
</Note>

Each repository carries a base branch, an optional setup script, and optional pre-commit hooks. Box clones that branch as it is, and never creates a branch for you, forks included. Repositories stay on the base branch unless you or something inside the Box changes it.

<CodeGroup>
  ```bash CLI theme={null}
  box env add-repo base octocat/hello-world --branch develop
  box env rm-repo base octocat/hello-world
  ```

  ```bash curl theme={null}
  repo_id=$(curl -sS "$BOX_API_BASE/repos?sync=true&q=hello-world" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    | jq -r '.installations[0].repositories[0].databaseId')

  curl -sS -X POST "$BOX_API_BASE/repos" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"repositoryId\":\"$repo_id\",\"baseBranch\":\"develop\"}"
  ```

  ```ts TypeScript theme={null}
  const repos = await box.repos({ sync: true, q: "hello-world" });
  const repo = repos.installations.flatMap((i) => i.repositories)[0];

  await box.selectRepo({
    repoSelectionRequest: { repositoryId: repo.databaseId, baseBranch: "develop" },
  });
  ```

  ```python Python theme={null}
  from ascii_box_sdk.models.repo_selection_request import RepoSelectionRequest

  repos = box.repos(sync=True, q="hello-world")
  repo = repos.installations[0].repositories[0]

  box.select_repo(RepoSelectionRequest(repository_id=repo.database_id, base_branch="develop"))
  ```
</CodeGroup>

<Note>
  `selectRepo` and `updateSecrets` act on your **default** environment. To edit a named one, use the environment calls below.
</Note>

### Where repositories land

On the hosted image the SSH user is `user` and the work directory is `/home/user`. Each folder is named after the repository, not the `owner/name` pair.

| GitHub repository                   | Box path                        |
| ----------------------------------- | ------------------------------- |
| `ariana-dot-dev/ariana-ide-private` | `/home/user/ariana-ide-private` |
| `octocat/hello-world`               | `/home/user/hello-world`        |

With one repository, that folder is the project directory for agent tools. With several, `/home/user` stays the parent workspace and each repository is a sibling folder.

## Secrets

Two shapes, both injected when a Box starts.

* **Environment variables**, readable as process env vars and shell exports inside the Box.
* **Secret files**, written under `/home/user` at the relative path you give.

Use these for app credentials, API keys, `.env` files and deployment tokens. Do not pass secrets in prompts, URLs, CLI arguments that may be logged, Docker build args, or committed files.

<CodeGroup>
  ```bash CLI theme={null}
  box env set-var base STRIPE_KEY=sk_live_123
  box env rm-var base STRIPE_KEY
  box env set-file base backend/.env --from ./local.env
  cat ./local.env | box env set-file base backend/.env
  box env rm-file base backend/.env
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/secrets" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"envContents":"STRIPE_KEY=sk_live_123\n","secretFiles":[{"path":"backend/.env","contents":"DATABASE_URL=postgres://...\n"}]}'
  ```

  ```ts TypeScript theme={null}
  // default environment
  await box.updateSecrets({
    secretsUpdateRequest: {
      envContents: "STRIPE_KEY=sk_live_123\n",
      secretFiles: [
        { path: "backend/.env", contents: "DATABASE_URL=postgres://...\n" },
      ],
    },
  });

  // any named environment
  await box.updateEnvironment({
    environmentId: envId,
    updateBoxEnvironmentRequest: {
      envContents: "STRIPE_KEY=sk_live_123\n",
      secretFiles: [{ path: "backend/.env", contents: "DATABASE_URL=postgres://...\n" }],
    },
  });
  ```

  ```python Python theme={null}
  from ascii_box_sdk.models.secret_file import SecretFile
  from ascii_box_sdk.models.secrets_update_request import SecretsUpdateRequest

  # default environment
  box.update_secrets(SecretsUpdateRequest(
      env_contents="STRIPE_KEY=sk_live_123\n",
      secret_files=[SecretFile(path="backend/.env", contents="DATABASE_URL=postgres://...\n")],
  ))

  # any named environment
  box.update_environment(env_id, UpdateBoxEnvironmentRequest(
      env_contents="STRIPE_KEY=sk_live_123\n",
      secret_files=[SecretFile(path="backend/.env", contents="DATABASE_URL=postgres://...\n")],
  ))
  ```
</CodeGroup>

<Warning>
  The `/secrets` endpoint is a full **replacement**, not a merge. Send every variable and secret file that should remain, or the ones you leave out are dropped. The granular `box env set-var` and `set-file` commands change one item at a time and do not have this hazard.
</Warning>

### Secret file paths

Paths are relative to `/home/user`. There is no repository picker, so include the repository folder name to land a file inside a clone:

```text theme={null}
ariana-ide-private/backend/.env
```

writes to:

```bash theme={null}
/home/user/ariana-ide-private/backend/.env
```

Absolute paths, and paths that escape `/home/user`, are skipped.

### Per-Box variables

An environment's variables apply to every Box using it. To give one Box its own values, pass `env` when you create it. Per-Box values are merged over the environment's, so a per-Box key wins a name collision.

<CodeGroup>
  ```bash CLI theme={null}
  box new --env DATABASE_URL=postgres://... --env FEATURE_FLAG=1
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"ttlSeconds":3600,"env":{"DATABASE_URL":"postgres://...","FEATURE_FLAG":"1"}}'
  ```

  ```ts TypeScript theme={null}
  await box.create({
    createBoxRequest: {
      ttlSeconds: 3600,
      env: { DATABASE_URL: "postgres://...", FEATURE_FLAG: "1" },
    },
  });
  ```

  ```python Python theme={null}
  box.create(CreateBoxRequest(
      ttl_seconds=3600,
      env={"DATABASE_URL": "postgres://...", "FEATURE_FLAG": "1"},
  ))
  ```
</CodeGroup>

Keys must match `[A-Za-z_][A-Za-z0-9_]*` (max 128 chars), at most 100 variables and 64KB per Box. Reserved Box-internal names (`ASCII_TOKEN`, `BOX_ID`, and similar) are rejected. A forked Box inherits its source's per-Box variables unless the fork passes its own `env`.

## Named environments

There is always exactly one default, and it is what a Box uses when you do not name one.

<CodeGroup>
  ```bash CLI theme={null}
  box env list
  box env info staging
  box env new staging
  box env rename staging prod
  box env default prod
  box env rm staging
  box env upgrade prod
  ```

  ```bash curl theme={null}
  curl -sS "$BOX_API_BASE/environments" -H "Authorization: Bearer $BOX_API_KEY"

  curl -sS -X POST "$BOX_API_BASE/environments" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"name":"staging"}'

  curl -sS -X PUT "$BOX_API_BASE/environments/$ENV_ID" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"name":"prod","isDefault":true,"safeForThirdParties":true}'

  curl -sS -X POST "$BOX_API_BASE/environments/$ENV_ID/upgrade" \
    -H "Authorization: Bearer $BOX_API_KEY"

  curl -sS -X DELETE "$BOX_API_BASE/environments/$ENV_ID" \
    -H "Authorization: Bearer $BOX_API_KEY"
  ```

  ```ts TypeScript theme={null}
  const { environments } = await box.environments();
  const staging = await box.createEnvironment({
    createBoxEnvironmentRequest: { name: "staging" },
  });

  await box.updateEnvironment({
    environmentId: envId,
    updateBoxEnvironmentRequest: { name: "prod", isDefault: true, safeForThirdParties: true },
  });

  await box.upgradeEnvironment({ environmentId: envId });
  await box.deleteEnvironment({ environmentId: envId });
  ```

  ```python Python theme={null}
  from ascii_box_sdk.models.create_box_environment_request import CreateBoxEnvironmentRequest
  from ascii_box_sdk.models.update_box_environment_request import UpdateBoxEnvironmentRequest

  envs = box.environments()
  staging = box.create_environment(CreateBoxEnvironmentRequest(name="staging"))

  box.update_environment(env_id, UpdateBoxEnvironmentRequest(
      name="prod", is_default=True, safe_for_third_parties=True,
  ))

  box.upgrade_environment(env_id)
  box.delete_environment(env_id)
  ```
</CodeGroup>

Deleting is a soft delete. Boxes pinned to its versions keep running; new Boxes can no longer use it.

`upgradeEnvironment` accepts an optional list of Box ids to restrict the upgrade; omit it to move every Box of yours that is on an older version.

## Using an environment for a Box

Pass the name when you create, resume, or fork. Omit it to use your default. Unknown names are rejected outright, before anything is created or changed, so a typo costs you nothing. Environments are never created implicitly.

<CodeGroup>
  ```bash CLI theme={null}
  box new --environment staging
  box resume bx_f7k2q9hd --environment staging
  box fork bx_f7k2q9hd --environment staging
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"ttlSeconds":3600,"environment":"staging"}'
  ```

  ```ts TypeScript theme={null}
  await box.create({ createBoxRequest: { ttlSeconds: 3600, environment: "staging" } });
  await box.resume({ boxId, resumeRequest: { environment: "staging" } });
  await box.fork({ boxId, forkRequest: { environment: "staging" } });
  ```

  ```python Python theme={null}
  box.create(CreateBoxRequest(ttl_seconds=3600, environment="staging"))
  box.resume(box_id, ResumeRequest(environment="staging"))
  box.fork(box_id, ForkRequest(environment="staging"))
  ```
</CodeGroup>

<Warning>
  `--environment` and `--env` are different things. `--environment staging` picks which environment the Box uses. `--env KEY=value` sets one variable on that single Box, on top of whatever the environment gives it.
</Warning>

## Related

* [Setup & Scripts](/box/setup)
* [Platform guide](/box/platform-guide)
* [Snapshots & Copies](/box/snapshots)
* [CLI reference](/box/cli-reference)
