refactor: secure temp files with chmod 600 before writing credentials

Added chmod 600 to all temporary files that contain sensitive data (API keys, tokens, configs):
- ENV_TEMP: 35 files (all agent scripts across 5 clouds)
- OPENCLAW_CONFIG_TEMP: 5 files (already done in previous commit)
- SETTINGS_TEMP: 5 files (Claude Code settings)
- GLOBAL_STATE_TEMP: 5 files (Claude Code global state)
- DOTENV_TEMP: 5 files (NanoClaw .env files)

Total: 55 temp files secured

This prevents race conditions where sensitive data could be read by other users
between mktemp creation (mode 600 by default) and data being written.

Security hardening for task #23.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Sprite 2026-02-07 20:03:55 +00:00
parent da7724da68
commit 7162f9c236
36 changed files with 87 additions and 0 deletions

View file

@ -348,3 +348,40 @@ OPENROUTER_API_KEY=sk-or-v1-xxxxx \
- `OPENROUTER_API_KEY` - Skip OAuth and use this API key directly
- `LINODE_TYPE` - Instance type (default: `g6-standard-1`)
- `LINODE_REGION` - Datacenter region (default: `us-east`)
---
## Security
### API Token Storage
Spawn stores cloud provider API tokens and OpenRouter API keys locally in JSON files at `~/.config/spawn/`:
- `hetzner.json` - Hetzner Cloud API token
- `digitalocean.json` - DigitalOcean API token
- `vultr.json` - Vultr API key
- `linode.json` - Linode API token
- OpenRouter API keys stored in shell config files (`~/.bashrc`, `~/.zshrc`)
**Security Posture:**
- All token files are created with `chmod 600` (user read/write only)
- Tokens are stored in **plaintext** - not encrypted at rest
- Security relies on filesystem permissions and OS user isolation
**Recommendations:**
1. **Protect your user account** - Use strong passwords, disk encryption, and secure your SSH keys
2. **Use dedicated API tokens** - Create tokens specifically for Spawn with minimal required permissions
3. **Rotate tokens regularly** - Revoke and regenerate API tokens periodically
4. **Multi-user systems** - On shared machines, be aware that root users can read these files
5. **Backup security** - Ensure backups of `~/.config/` are encrypted
**Why plaintext?**
- Simplicity and compatibility across all Unix-like systems
- File permissions (`600`) provide adequate protection for single-user machines
- Encryption at rest would require key management, adding complexity without significant security benefit for typical use cases
- Cloud providers recommend similar approaches for CLI tools (AWS CLI, gcloud, etc.)
**Alternative approaches:**
- For higher security requirements, consider using environment variables instead of saved tokens
- Pass `OPENROUTER_API_KEY`, `HCLOUD_TOKEN`, etc. as environment variables on each run
- Use OS credential stores (Keychain on macOS, Secret Service on Linux) - requires additional dependencies

View file

@ -51,6 +51,7 @@ if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -46,6 +46,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -67,6 +68,7 @@ log_warn "Configuring Claude Code..."
run_server "$DO_SERVER_IP" "mkdir -p ~/.claude"
SETTINGS_TEMP=$(mktemp)
chmod 600 "$SETTINGS_TEMP"
cat > "$SETTINGS_TEMP" << EOF
{
"theme": "dark",
@ -87,6 +89,7 @@ upload_file "$DO_SERVER_IP" "$SETTINGS_TEMP" "/root/.claude/settings.json"
rm "$SETTINGS_TEMP"
GLOBAL_STATE_TEMP=$(mktemp)
chmod 600 "$GLOBAL_STATE_TEMP"
cat > "$GLOBAL_STATE_TEMP" << EOF
{
"hasCompletedOnboarding": true,

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -43,6 +43,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -46,6 +46,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -62,6 +63,7 @@ rm "$ENV_TEMP"
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
chmod 600 "$DOTENV_TEMP"
cat > "$DOTENV_TEMP" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF

View file

@ -51,6 +51,7 @@ if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -51,6 +51,7 @@ if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -46,6 +46,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -68,6 +69,7 @@ run_server "$HETZNER_SERVER_IP" "mkdir -p ~/.claude"
# Upload settings.json
SETTINGS_TEMP=$(mktemp)
chmod 600 "$SETTINGS_TEMP"
cat > "$SETTINGS_TEMP" << EOF
{
"theme": "dark",
@ -89,6 +91,7 @@ rm "$SETTINGS_TEMP"
# Upload ~/.claude.json global state
GLOBAL_STATE_TEMP=$(mktemp)
chmod 600 "$GLOBAL_STATE_TEMP"
cat > "$GLOBAL_STATE_TEMP" << EOF
{
"hasCompletedOnboarding": true,

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -43,6 +43,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -46,6 +46,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -62,6 +63,7 @@ rm "$ENV_TEMP"
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
chmod 600 "$DOTENV_TEMP"
cat > "$DOTENV_TEMP" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF

View file

@ -51,6 +51,7 @@ if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -24,6 +24,7 @@ MODEL_ID="${MODEL_ID:-openrouter/auto}"
if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model ID"; exit 1; fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -22,6 +22,7 @@ if [[ -n "$OPENROUTER_API_KEY" ]]; then log_info "Using OpenRouter API key from
else OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180); fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -38,6 +39,7 @@ rm "$ENV_TEMP"
log_warn "Configuring Claude Code..."
run_server "$LINODE_SERVER_IP" "mkdir -p ~/.claude"
SETTINGS_TEMP=$(mktemp)
chmod 600 "$SETTINGS_TEMP"
cat > "$SETTINGS_TEMP" << EOF
{
"theme": "dark",
@ -56,6 +58,7 @@ EOF
upload_file "$LINODE_SERVER_IP" "$SETTINGS_TEMP" "/root/.claude/settings.json"
rm "$SETTINGS_TEMP"
GLOBAL_STATE_TEMP=$(mktemp)
chmod 600 "$GLOBAL_STATE_TEMP"
cat > "$GLOBAL_STATE_TEMP" << EOF
{
"hasCompletedOnboarding": true,

View file

@ -19,6 +19,7 @@ if [[ -n "$OPENROUTER_API_KEY" ]]; then log_info "Using OpenRouter API key from
else OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180); fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -19,6 +19,7 @@ if [[ -n "$OPENROUTER_API_KEY" ]]; then log_info "Using OpenRouter API key from
else OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180); fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -19,6 +19,7 @@ if [[ -n "$OPENROUTER_API_KEY" ]]; then log_info "Using OpenRouter API key from
else OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180); fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -21,6 +21,7 @@ if [[ -n "$OPENROUTER_API_KEY" ]]; then log_info "Using OpenRouter API key from
else OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180); fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -33,6 +34,7 @@ run_server "$LINODE_SERVER_IP" "cat /tmp/env_config >> ~/.zshrc && rm /tmp/env_c
rm "$ENV_TEMP"
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
chmod 600 "$DOTENV_TEMP"
cat > "$DOTENV_TEMP" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF

View file

@ -24,6 +24,7 @@ MODEL_ID="${MODEL_ID:-openrouter/auto}"
if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model ID"; exit 1; fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -54,6 +54,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -38,6 +38,7 @@ log_warn "Setting up environment variables..."
# Create temp file with env config
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -60,6 +61,7 @@ run_sprite "$SPRITE_NAME" "mkdir -p ~/.claude"
# Create Claude settings.json via file upload
SETTINGS_TEMP=$(mktemp)
chmod 600 "$SETTINGS_TEMP"
cat > "$SETTINGS_TEMP" << EOF
{
"theme": "dark",
@ -81,6 +83,7 @@ rm "$SETTINGS_TEMP"
# Create ~/.claude.json global state to skip onboarding and trust dialogs
GLOBAL_STATE_TEMP=$(mktemp)
chmod 600 "$GLOBAL_STATE_TEMP"
cat > "$GLOBAL_STATE_TEMP" << EOF
{
"hasCompletedOnboarding": true,

View file

@ -33,6 +33,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -41,6 +41,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -33,6 +33,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -41,6 +41,7 @@ OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -56,6 +57,7 @@ rm "$ENV_TEMP"
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
chmod 600 "$DOTENV_TEMP"
cat > "$DOTENV_TEMP" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF

View file

@ -50,6 +50,7 @@ log_warn "Setting up environment variables..."
# Create temp file with env config
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -38,6 +38,7 @@ if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -35,6 +35,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -53,6 +54,7 @@ log_warn "Configuring Claude Code..."
run_server "$VULTR_SERVER_IP" "mkdir -p ~/.claude"
SETTINGS_TEMP=$(mktemp)
chmod 600 "$SETTINGS_TEMP"
cat > "$SETTINGS_TEMP" << EOF
{
"theme": "dark",
@ -72,6 +74,7 @@ upload_file "$VULTR_SERVER_IP" "$SETTINGS_TEMP" "/root/.claude/settings.json"
rm "$SETTINGS_TEMP"
GLOBAL_STATE_TEMP=$(mktemp)
chmod 600 "$GLOBAL_STATE_TEMP"
cat > "$GLOBAL_STATE_TEMP" << EOF
{
"hasCompletedOnboarding": true,

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -32,6 +32,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]

View file

@ -34,6 +34,7 @@ fi
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]
@ -47,6 +48,7 @@ rm "$ENV_TEMP"
log_warn "Configuring nanoclaw..."
DOTENV_TEMP=$(mktemp)
chmod 600 "$DOTENV_TEMP"
cat > "$DOTENV_TEMP" << EOF
ANTHROPIC_API_KEY=${OPENROUTER_API_KEY}
EOF

View file

@ -38,6 +38,7 @@ if ! validate_model_id "$MODEL_ID"; then log_error "Exiting due to invalid model
log_warn "Setting up environment variables..."
ENV_TEMP=$(mktemp)
chmod 600 "$ENV_TEMP"
cat > "$ENV_TEMP" << EOF
# [spawn:env]