fix: Prevent shell/Python injection in Codespaces, Render, and FluidStack (#252)

GitHub Codespaces scripts embedded API keys directly into heredocs sent
over SSH, allowing single-quote breakout for command injection. Fixed by
adding upload_file/run_server/inject_env_vars helpers to Codespaces lib
and using safe temp-file-upload pattern (matching Railway/Render).

Render claude.sh and openclaw.sh built JSON config via unescaped heredocs.
Fixed by using shared setup_claude_code_config/setup_openclaw_config
helpers which properly json_escape values.

FluidStack had triple-quote injection in SSH key registration (pub_key
embedded in Python triple-quotes) and missing single-quote validation in
create_server env var checks. Fixed by reading values via stdin/argv
instead of string interpolation, and added single-quote to validation.

Agent: security-auditor

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-10 14:14:41 -08:00 committed by GitHub
parent 281ea2a74f
commit f39ffd6e24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 108 additions and 159 deletions

View file

@ -31,12 +31,15 @@ fi
log_info "Codespace created: $CODESPACE"
# Set CODESPACE_NAME for upload_file/run_server/inject_env_vars helpers
CODESPACE_NAME="$CODESPACE"
# 3. Wait for codespace to be ready
wait_for_codespace "$CODESPACE"
# 4. Install Aider
log_warn "Installing Aider..."
run_in_codespace "$CODESPACE" "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
run_server "pip install aider-chat 2>/dev/null || pip3 install aider-chat"
log_info "Aider installed"
# 5. Get OpenRouter API key
@ -50,16 +53,9 @@ fi
# 6. Get model preference
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "Aider") || exit 1
# 7. Inject environment variables into ~/.bashrc
log_warn "Setting up environment variables..."
ENV_VARS="
export OPENROUTER_API_KEY='${OPENROUTER_API_KEY}'
"
run_in_codespace "$CODESPACE" "cat >> ~/.bashrc << 'ENVEOF'
$ENV_VARS
ENVEOF"
# 7. Inject environment variables via safe temp file upload
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "GitHub Codespace setup completed successfully!"
@ -73,4 +69,4 @@ echo ""
sleep 1
# Launch Aider with model
run_in_codespace "$CODESPACE" "source ~/.bashrc && aider --model openrouter/${MODEL_ID}"
run_server "source ~/.bashrc && aider --model openrouter/${MODEL_ID}"

View file

@ -31,15 +31,18 @@ fi
log_info "Codespace created: $CODESPACE"
# Set CODESPACE_NAME for upload_file/run_server/inject_env_vars helpers
CODESPACE_NAME="$CODESPACE"
# 3. Wait for codespace to be ready
wait_for_codespace "$CODESPACE"
# 4. Install Claude Code
log_warn "Installing Claude Code..."
run_in_codespace "$CODESPACE" "curl -fsSL https://claude.ai/install.sh | bash"
run_server "curl -fsSL https://claude.ai/install.sh | bash"
# Verify installation
if ! run_in_codespace "$CODESPACE" "command -v claude" &>/dev/null; then
if ! run_server "command -v claude" &>/dev/null; then
log_error "Claude Code installation failed"
delete_codespace "$CODESPACE"
exit 1
@ -54,59 +57,18 @@ else
OPENROUTER_API_KEY=$(get_openrouter_api_key_oauth 5180)
fi
# 6. Inject environment variables into ~/.bashrc
log_warn "Setting up environment variables..."
# 6. Inject environment variables via safe temp file upload
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}" \
"ANTHROPIC_BASE_URL=https://openrouter.ai/api" \
"ANTHROPIC_AUTH_TOKEN=${OPENROUTER_API_KEY}" \
"ANTHROPIC_API_KEY=" \
"CLAUDE_CODE_SKIP_ONBOARDING=1" \
"CLAUDE_CODE_ENABLE_TELEMETRY=0" \
"PATH=\$HOME/.claude/local/bin:\$HOME/.bun/bin:\$PATH"
ENV_VARS="
export OPENROUTER_API_KEY='${OPENROUTER_API_KEY}'
export ANTHROPIC_BASE_URL='https://openrouter.ai/api'
export ANTHROPIC_AUTH_TOKEN='${OPENROUTER_API_KEY}'
export ANTHROPIC_API_KEY=''
export CLAUDE_CODE_SKIP_ONBOARDING='1'
export CLAUDE_CODE_ENABLE_TELEMETRY='0'
export PATH=\"\$HOME/.claude/local/bin:\$HOME/.bun/bin:\$PATH\"
"
run_in_codespace "$CODESPACE" "cat >> ~/.bashrc << 'ENVEOF'
$ENV_VARS
ENVEOF"
# 7. Configure Claude Code settings
log_warn "Configuring Claude Code..."
run_in_codespace "$CODESPACE" "mkdir -p ~/.claude"
# Create settings.json
SETTINGS_JSON="{
\"theme\": \"dark\",
\"editor\": \"vim\",
\"env\": {
\"CLAUDE_CODE_ENABLE_TELEMETRY\": \"0\",
\"ANTHROPIC_BASE_URL\": \"https://openrouter.ai/api\",
\"ANTHROPIC_AUTH_TOKEN\": \"${OPENROUTER_API_KEY}\"
},
\"permissions\": {
\"defaultMode\": \"bypassPermissions\",
\"dangerouslySkipPermissions\": true
}
}"
run_in_codespace "$CODESPACE" "cat > ~/.claude/settings.json << 'SETTINGSEOF'
$SETTINGS_JSON
SETTINGSEOF"
# Create global state file
GLOBAL_STATE="{
\"hasCompletedOnboarding\": true,
\"bypassPermissionsModeAccepted\": true
}"
run_in_codespace "$CODESPACE" "cat > ~/.claude.json << 'STATEEOF'
$GLOBAL_STATE
STATEEOF"
# Create empty CLAUDE.md
run_in_codespace "$CODESPACE" "touch ~/.claude/CLAUDE.md"
# 7. Configure Claude Code settings via shared helper
setup_claude_code_config "$OPENROUTER_API_KEY" "upload_file" "run_server"
echo ""
log_info "Setup complete. Opening interactive session..."

View file

@ -31,12 +31,15 @@ fi
log_info "Codespace created: $CODESPACE"
# Set CODESPACE_NAME for upload_file/run_server/inject_env_vars helpers
CODESPACE_NAME="$CODESPACE"
# 3. Wait for codespace to be ready
wait_for_codespace "$CODESPACE"
# 4. Install gptme
log_warn "Installing gptme..."
run_in_codespace "$CODESPACE" "pip install gptme 2>/dev/null || pip3 install gptme"
run_server "pip install gptme 2>/dev/null || pip3 install gptme"
log_info "gptme installed"
# 5. Get OpenRouter API key
@ -50,16 +53,9 @@ fi
# 6. Get model preference
MODEL_ID=$(get_model_id_interactive "openrouter/auto" "gptme") || exit 1
# 7. Inject environment variables into ~/.bashrc
log_warn "Setting up environment variables..."
ENV_VARS="
export OPENROUTER_API_KEY='${OPENROUTER_API_KEY}'
"
run_in_codespace "$CODESPACE" "cat >> ~/.bashrc << 'ENVEOF'
$ENV_VARS
ENVEOF"
# 7. Inject environment variables via safe temp file upload
inject_env_vars \
"OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"
echo ""
log_info "GitHub Codespace setup completed successfully!"
@ -73,4 +69,4 @@ echo ""
sleep 1
# Launch gptme with model
run_in_codespace "$CODESPACE" "source ~/.bashrc && gptme -m openrouter/${MODEL_ID}"
run_server "source ~/.bashrc && gptme -m openrouter/${MODEL_ID}"

View file

@ -174,6 +174,57 @@ delete_codespace() {
}
}
# Upload a file to codespace via gh codespace cp
# Args: $1 = local path
# $2 = remote path
upload_file() {
local local_path="$1"
local remote_path="$2"
if [[ ! -f "$local_path" ]]; then
log_error "Local file not found: $local_path"
return 1
fi
if [[ -z "${CODESPACE_NAME:-}" ]]; then
log_error "CODESPACE_NAME not set. Call create_codespace first."
return 1
fi
gh codespace cp "$local_path" "${CODESPACE_NAME}:${remote_path}"
}
# Run a command on the codespace (wrapper matching other providers' interface)
run_server() {
local cmd="$1"
if [[ -z "${CODESPACE_NAME:-}" ]]; then
log_error "CODESPACE_NAME not set. Call create_codespace first."
return 1
fi
gh codespace ssh --codespace "$CODESPACE_NAME" -- bash -c "$cmd"
}
# Inject environment variables into shell config
# Writes to a temp file and uploads to avoid shell interpolation of values
inject_env_vars() {
log_warn "Injecting environment variables..."
local env_temp
env_temp=$(mktemp)
chmod 600 "${env_temp}"
track_temp_file "${env_temp}"
generate_env_config "$@" > "${env_temp}"
# Upload and append to .bashrc
upload_file "${env_temp}" "/tmp/env_config"
run_server "cat /tmp/env_config >> ~/.bashrc && rm /tmp/env_config"
log_info "Environment variables configured"
}
# Get codespace info
# Args: $1 = codespace name
get_codespace_info() {