Summary
devc-remote.sh currently requires secrets (CLAUDE_CODE_OAUTH_TOKEN, TS_CLIENT_ID, TS_CLIENT_SECRET) as plain environment variables. This is insecure (visible in shell history, process lists, .zshenv) and requires manual claude setup-token + copy-paste.
The script should resolve secrets automatically via a fallback chain with secure storage backends.
Problem
- No auto-extraction — user must manually run
claude setup-token, copy the OAT, and set an env var
- No secure storage — tokens sit in plain text env vars or dotfiles
- Applies to all secrets — Claude Code OAuth, Tailscale client ID/secret all have the same problem
Proposed Design
resolve_secret() fallback chain
resolve_secret() {
local name="$1"
# 1. Env var (explicit override, backwards-compatible)
# 2. OS keychain:
# - macOS: security find-generic-password (triggers Touch ID)
# - Linux desktop: secret-tool lookup (GNOME Keyring / KDE Wallet via libsecret)
# 3. Bitwarden CLI (bw get password) — prompts for master password
# 4. age-encrypted file (~/.config/devc-remote/secrets.age)
# - Uses SSH keys as identity (no extra key management)
# - Best fallback for headless Linux / SSH servers
# 5. Fail with actionable error message
}
Backend detection (auto)
| Platform |
Primary |
Fallback 1 |
Fallback 2 |
| macOS |
security (Keychain, Touch ID) |
bw CLI |
age file |
| Linux (desktop) |
secret-tool (GNOME Keyring / KDE Wallet) |
bw CLI |
age file |
| Linux (headless) |
bw CLI |
age file |
— |
| Any |
env var override |
always works |
— |
Secret storage
After first claude setup-token, offer to store the token in the best available backend:
- macOS keychain —
security add-generic-password -s "devc-remote" -a "<name>" -w
- Triggers Touch ID / biometric on read
- Linux
secret-tool — secret-tool store --label "devc-remote/<name>" service devc-remote name <name>
- Integrates with GNOME Keyring / KDE Wallet (unlocked with login session)
- Bitwarden CLI —
bw create item in a "devc-remote" folder
bw unlock prompts for master password
age-encrypted file — age -r <pubkey> -o ~/.config/devc-remote/secrets.age
- Uses SSH keys as identity (
age -d -i ~/.ssh/id_ed25519)
- No daemon, no GUI, works over SSH — ideal for headless servers
- Available in nixpkgs (
pkgs.age)
Secrets covered
| Secret |
Current |
Proposed |
CLAUDE_CODE_OAUTH_TOKEN |
env var |
keychain / secret-tool / bw / age / env var |
TS_CLIENT_ID |
env var |
keychain / secret-tool / bw / age / env var |
TS_CLIENT_SECRET |
env var |
keychain / secret-tool / bw / age / env var |
GHCR_TOKEN |
gh auth token |
already OK (gh handles its own auth) |
UX flow (first time)
$ ./scripts/devc-remote.sh --open ssh myserver
ℹ Claude Code OAuth token not found.
ℹ Run 'claude setup-token' to generate one, then re-run this command.
The token will be stored in your macOS keychain (Touch ID protected).
UX flow (subsequent runs, macOS)
$ ./scripts/devc-remote.sh --open ssh myserver
🔐 [Touch ID prompt]
✓ Claude Code auth forwarded
✓ Tailscale key injected
UX flow (headless Linux with age)
$ ./scripts/devc-remote.sh --open ssh myserver
🔐 Decrypting secrets with ~/.ssh/id_ed25519...
✓ Claude Code auth forwarded
✓ Tailscale key injected
Acceptance Criteria
Related
Summary
devc-remote.shcurrently requires secrets (CLAUDE_CODE_OAUTH_TOKEN,TS_CLIENT_ID,TS_CLIENT_SECRET) as plain environment variables. This is insecure (visible in shell history, process lists,.zshenv) and requires manualclaude setup-token+ copy-paste.The script should resolve secrets automatically via a fallback chain with secure storage backends.
Problem
claude setup-token, copy the OAT, and set an env varProposed Design
resolve_secret()fallback chainBackend detection (auto)
security(Keychain, Touch ID)bwCLIagefilesecret-tool(GNOME Keyring / KDE Wallet)bwCLIagefilebwCLIagefileSecret storage
After first
claude setup-token, offer to store the token in the best available backend:security add-generic-password -s "devc-remote" -a "<name>" -wsecret-tool—secret-tool store --label "devc-remote/<name>" service devc-remote name <name>bw create itemin a "devc-remote" folderbw unlockprompts for master passwordage-encrypted file —age -r <pubkey> -o ~/.config/devc-remote/secrets.ageage -d -i ~/.ssh/id_ed25519)pkgs.age)Secrets covered
CLAUDE_CODE_OAUTH_TOKENTS_CLIENT_IDTS_CLIENT_SECRETGHCR_TOKENgh auth tokenUX flow (first time)
UX flow (subsequent runs, macOS)
UX flow (headless Linux with age)
Acceptance Criteria
resolve_secret()function with env → keychain/secret-tool → bw → age fallback chain--store-secret <name>subcommand to save a secret to the preferred backendresolve_secret()agefallback for headless environments using SSH keys as identityRelated
setup-claude.sh— consumesCLAUDE_CODE_OAUTH_TOKENin containersetup-tailscale.sh— consumesTAILSCALE_AUTHKEYin container