> ## 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.

# Secrets & Setup

> Add secrets, run setup scripts, and restart services in Boxes.

Use the Box dashboard for secrets. Open [Dashboard > Secrets](https://box.ascii.dev/box/dashboard?tab=secrets) and add the values your Boxes need.

Dashboard secrets are applied to Boxes as:

* environment variables, available to processes and shell sessions inside the Box
* secret files, written under the Box work directory using the relative paths you configure

The dashboard is the preferred path for app credentials, API keys, `.env` files, deployment tokens, and other runtime configuration. Do not pass secrets in prompts, URLs, CLI arguments that may be logged, Docker build args, or committed files.

## Add Environment Variables

1. Open [Dashboard > Secrets](https://box.ascii.dev/box/dashboard?tab=secrets).
2. Add shell-style lines in the environment variables editor, or replace the account secret setup programmatically. The API and SDK update is a full replacement, so send every variable and secret file that should remain configured:

<CodeGroup>
  ```bash CLI theme={null}
  box dashboard
  # Open Secrets, add OPENAI_API_KEY=... and DATABASE_URL=..., then save.
  ```

  ```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":"OPENAI_API_KEY=...\nDATABASE_URL=postgres://...\n","secretFiles":[]}'
  ```

  ```ts TypeScript theme={null}
  await box.updateSecrets({
    secretsUpdateRequest: {
      envContents: "OPENAI_API_KEY=...\nDATABASE_URL=postgres://...\n",
      secretFiles: [],
    },
  });
  ```

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

  box.update_secrets(SecretsUpdateRequest(
      env_contents="OPENAI_API_KEY=...\nDATABASE_URL=postgres://...\n",
      secret_files=[],
  ))
  ```
</CodeGroup>

3. Save if you used the dashboard.

New Boxes receive the variables when they start. When you save changes, Box also pushes the updated environment to existing Boxes that are still active (`provisioned`, `cloning`, `ready`, `idle`, or `running`) and have a live machine connection. Stopped, archived, deleted, or unreachable Boxes pick up the latest values the next time they are started or resumed.

## Per-Box Environment Variables

Dashboard variables apply to every Box. To give a single Box its own values, pass `env` when creating it:

<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>

Per-box values are merged over the dashboard variables, so a per-box key overrides a dashboard key with the same name. Keys must match `[A-Za-z_][A-Za-z0-9_]*` (max 128 chars), with at most 100 variables and 64KB total per Box. Reserved Box-internal names (`ASCII_TOKEN`, `BOX_ID`, and similar) are rejected. Forked Boxes inherit the source Box's per-box variables unless the fork request passes its own `env`.

## No-env Boxes

When you give Boxes to your own users, you don't want your account's secrets inside them. A no-env Box receives none of them: no environment variables, secret files, or credentials. It is confined to itself, so it can't act on your account or any other Box.

<CodeGroup>
  ```bash CLI theme={null}
  box new --no-env
  ```

  ```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":null,"noEnv":true}'
  ```

  ```ts TypeScript theme={null}
  await box.create({ createBoxRequest: { ttlSeconds: null, noEnv: true } });
  ```

  ```python Python theme={null}
  box.create(CreateBoxRequest(ttl_seconds=None, no_env=True))
  ```
</CodeGroup>

SSH, SCP, desktop, snapshots, and public URLs all work on a no-env Box. To give one a secret of its own, pass it explicitly with `env`. A no-env Box can't reach your private repositories; clone public ones, or sign in with your own credentials from inside the Box. A fork of a no-env Box is always no-env.

### What a default box receives

To know what no-env protects you from, here is what a normal (not no-env) box gets from your account:

* **Environment variables**: your dashboard 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`, and similar).
* **Credential files**: your dashboard secret files, the GitHub CLI login (`~/.config/gh/hosts.yml`), git credentials in cloned repos, Claude and Codex login files (`~/.claude/.credentials.json`, `~/.codex/auth.json`), the in-box Box CLI token (`~/.config/ascii/box/config.json`), and managed secret blocks in `~/.bashrc`.

A no-env 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 box. Before the box becomes reachable, Box scrubs every owner secret the snapshot may carry:

* `~/.bashrc` managed secret blocks
* `~/.config/gh/hosts.yml` (plus a `gh` logout) and `~/.git-credentials`
* `~/.ssh/id_*` private keys (`authorized_keys` and `known_hosts` are kept so the box stays reachable)
* `~/.codex/auth.json`, `~/.config/codex/auth.json`
* `~/.claude/.credentials.json`, `~/.config/claude/.credentials.json`
* `~/.config/ascii/box/config.json` (the in-box Box CLI token)
* every dashboard secret file you configured

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

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

Conversion is one-way: the box stays no-env afterward. While the scrub runs, SSH and desktop return a retryable `box_securing` error; retry until the box is reachable.

## Add Secret Files

Use secret files for `.env`, JSON credentials, or config files that a repo expects on disk.

1. Open [Dashboard > Secrets](https://box.ascii.dev/box/dashboard?tab=secrets).
2. Add a secret file with a relative path such as `ariana-ide-private/backend/.env`, `.env`, or `config/secrets.json`, or update the account setup programmatically. The API and SDK update is a full replacement, so include every env var and secret file that should remain:

<CodeGroup>
  ```bash CLI theme={null}
  box dashboard
  # Open Secrets, add ariana-ide-private/backend/.env as a secret file, then save.
  ```

  ```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":"OPENAI_API_KEY=...\n","secretFiles":[{"path":"ariana-ide-private/backend/.env","contents":"DATABASE_URL=postgres://...\n"}]}'
  ```

  ```ts TypeScript theme={null}
  await box.updateSecrets({
    secretsUpdateRequest: {
      envContents: "OPENAI_API_KEY=...\n",
      secretFiles: [
        { path: "ariana-ide-private/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

  box.update_secrets(SecretsUpdateRequest(
      env_contents="OPENAI_API_KEY=...\n",
      secret_files=[SecretFile(
          path="ariana-ide-private/backend/.env",
          contents="DATABASE_URL=postgres://...\n",
      )],
  ))
  ```
</CodeGroup>

3. Paste the file contents and save if you used the dashboard.

Repositories selected in [Dashboard > Repositories](https://box.ascii.dev/box/dashboard?tab=repos) are cloned into `/home/user` on the hosted Box image. Folder names use the GitHub repository name, not the owner/name pair. For example, `ariana-dot-dev/ariana-ide-private` is cloned to:

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

The secrets dashboard does not have a repository selector. Secret file paths are relative to the Box work directory, which is `/home/user` on the hosted Box image. To write a backend `.env` file into `ariana-ide-private`, include the repository folder in the path:

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

Box writes the file to:

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

Secret file paths must be relative. Unsafe absolute paths and paths that escape `/home/user` are skipped.

## Programmatic Setup

For setup scripts, configure secrets in the dashboard first, then run your setup in the Box. The script can read normal environment variables or files at the configured paths.

<CodeGroup>
  ```bash CLI theme={null}
  box_id="$(box new --json | jq -r 'select(.event == "ready") | .id')"
  box ssh "$box_id" -- bash -s < ./setup.sh
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/commands" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"command":"bash -lc ./setup.sh","cwd":"ariana-ide-private","timeoutSeconds":60}'
  ```

  ```ts TypeScript theme={null}
  await box.command({
    boxId,
    commandRequest: {
      command: "bash -lc './setup.sh'",
      cwd: "ariana-ide-private",
      timeoutSeconds: 60,
    },
  });
  ```

  ```python Python theme={null}
  from ascii_box_sdk.models.command_request import CommandRequest

  box.command(
      box_id,
      CommandRequest(command="bash -lc './setup.sh'", cwd="ariana-ide-private", timeout_seconds=60),
  )
  ```
</CodeGroup>

`box ssh <id> <command>` runs the command non-interactively and streams stdin, stdout, and stderr, so setup scripts do not need to be copied to a temporary path in the Box. The API and SDK command endpoint runs an already-present script or command inside the Box work directory.

In Windows PowerShell, prefer running this from Node, Python, WSL, Git Bash, or `cmd.exe`. Native PowerShell pipelines can keep stdin open for native executables in some environments.

Only use temporary uploaded env files for one-off local experiments. For normal usage, dashboard secrets keep the workflow auditable and avoid leaking values through logs or command history.

## Related

* [Repositories](/box/repositories)
* [Long-Running Tasks](/box/long-running-tasks)
* [SSH Access](/box/ssh-access)
* [Use in Code](/box/use-in-code)
