# Claude Focus Sandbox Auth
The `cf --sandbox` workflow creates a Firecracker microVM via slicer, provisions it with
Claude Code, and launches Claude inside it. **By default, Claude Code has no credentials
inside the VM.** This document explains how to configure a long-lived API token so Claude
launches authenticated automatically on every provisioned VM.
> [!info] One-time setup, automatic after that
> Run `cfauth setup` once to store a token in macOS Keychain. Every subsequent
> `cf --sandbox` session will inject that token during provisioning — no manual
> login needed inside the VM.
## Why Auth Forwarding Is Needed
`cf --sandbox` targets a Linux Firecracker VM. The host machine uses corporate auth
(`api_key_helper`, `ANTHROPIC_BASE_URL`, macOS Keychain entry `Claude Code-credentials`).
None of these mechanisms are portable to a Linux VM. The solution is a **long-lived
personal API token** stored once on the host and injected automatically during
provisioning.
> [!tip] Native slicer alternative
> If token management becomes cumbersome, the entire auth path can be replaced with
> slicer's built-in agent support:
> ```bash
> slicer claude . --credentials-file ~/.claude/.credentials.json
> ```
> This uses slicer's native auth forwarding and eliminates the need for manual token
> management. See [slicer docs](https://docs.slicervm.com/mac/coding-agents/).
## One-Time Setup
### Step 1: Generate a long-lived token
```bash
claude setup-token
```
Follow the interactive flow. Claude Code will guide you through generating a token
at claude.ai and storing it locally.
### Step 2: Store the token for `cf` to read
```bash
cfauth setup
```
This prompts you to paste the token and stores it in macOS Keychain under
`service=cf-claude-token, account=$USER`. The token is never written to disk
in plaintext.
## Token Source Lookup Order
When provisioning a slicer VM, `cf` tries these sources in order:
| Priority | Source | Details |
|----------|--------|---------|
| 1 | `CF_CLAUDE_API_KEY` env var | Explicit override — highest priority |
| 2 | macOS Keychain | `security find-generic-password -s "cf-claude-token"` |
| 3 | Token file | `~/.config/cf/claude-token` — must be mode **600** |
| 4 | Not found | Warning printed; provisioning continues without auth |
If no token is found, Claude launches unauthenticated inside the VM and will prompt
for login interactively on first use. This is a **soft failure** — it does not abort
the session.
## Per-VM Provisioning Flow
Auth injection is phase 3d of the provisioning pipeline:
```
3a. Bootstrap (uid 0) — install node, tmux, claude
3b. Dotfiles (uid 1000) — nvim, stow, gh, dotfiles
3c. gh auth (uid 1000) — gh auth login --with-token
3d. Claude auth (uid 1000) — NEW: inject ANTHROPIC_API_KEY into ~/.bashrc ← this doc
4. Launch (uid 1000) — slicer vm shell ... claude
```
The token is injected into the VM's `~/.bashrc` inside a marker block:
```bash
# cf-claude-auth
export ANTHROPIC_API_KEY='...'
export ANTHROPIC_BASE_URL='...' # only if set on host
export ANTHROPIC_CUSTOM_HEADERS='...' # only if set on host
# end-cf-claude-auth
```
Injection is **idempotent**: re-provisioning the same VM replaces the existing block
rather than appending a duplicate.
The token is passed via stdin pipe (not env vars or command arguments) to prevent
it from appearing in `ps aux` output.
## `cfauth` Subcommand Reference
```
cfauth [setup|reroll|status|clear]
```
| Command | Action |
|---------|--------|
| `cfauth setup` | Prompt for token, store in macOS Keychain |
| `cfauth reroll` | Revoke old token (prompts), then prompt for new one |
| `cfauth status` | Show which source is currently active |
| `cfauth clear` | Remove token from macOS Keychain |
### Examples
```bash
# Check current auth source
cfauth status
# → cf auth: source=keychain:cf-claude-token
# Rotate a token (revoke old one at claude.ai/settings first)
cfauth reroll
# Remove the stored token
cfauth clear
# → cf auth: keychain token cleared
```
> [!warning] Revoking before rerolling
> Run `cfauth reroll` (not `cfauth clear` + `cfauth setup`) to rotate a token.
> The `reroll` command reminds you to revoke the old token at claude.ai/settings
> before storing the new one, preventing stale token confusion.
## What Gets Forwarded
| Variable | Condition |
|----------|-----------|
| `ANTHROPIC_API_KEY` | Always forwarded (from token lookup) |
| `ANTHROPIC_BASE_URL` | Forwarded if set on host (corporate proxy) |
| `ANTHROPIC_CUSTOM_HEADERS` | Forwarded if set on host (corporate auth headers) |
This means the VM uses the same API endpoint and auth headers as the host machine —
important on corporate networks with a custom proxy.
## Troubleshooting
### `cf auth: no Claude API token found`
No token is configured. Run `cfauth setup` to store one.
### Claude prompts for login inside the VM
Auth injection failed silently during provisioning, or the token has expired.
Check `cfauth status` on the host, then re-inject manually:
```bash
# Inspect what's in the VM's .bashrc
slicer vm exec <vm-hostname> --uid 1000 -- grep -A5 'cf-claude-auth' ~/.bashrc
# Re-inject manually (slicer vm exec reads from stdin)
cfauth status # confirm token exists on host first
```
### Token file has unsafe permissions
```
cf: token file ~/.config/cf/claude-token has unsafe permissions (644) — expected 600, skipping
```
Fix with:
```bash
chmod 600 ~/.config/cf/claude-token
```
### ANTHROPIC_BASE_URL not forwarded to VM
The variable must be set in the **host shell** at the time `cf --sandbox` is run.
If using a shell config file (e.g. `~/.zshrc.local`), ensure it exports the var
before launching `cf`.
### Auth works locally but not in remote slicer VMs
Remote VM auth (`--remote` with a slicer backend) uses the same injection path
via SSH. Confirm the remote host can reach the Claude API endpoint:
```bash
ssh <remote-host> 'curl -s https://api.anthropic.com/healthz'
```
If you're behind a corporate proxy, `ANTHROPIC_BASE_URL` must be set on the host so
it gets forwarded during provisioning.