Fix critical security vulnerabilities from audit

- Rustup: Download script to temp file with shebang/size validation
  before execution, matching mise/ollama pattern (line 1119)

- SKIP_EXPORTS: Refactor from embedded shell commands to base64-encoded
  list decoded safely in VM, eliminating injection risk (line 478)

- Playwright symlink: Validate path is executable and within expected
  cache directory before creating system symlinks (line 1053)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
guessthepw 2026-01-25 09:34:24 -05:00
parent cbc379c0cc
commit 26501daa4e
2 changed files with 57 additions and 12 deletions

View file

@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.9.0] - 2025-01-25
### Security
- Fix rustup pipe-to-shell vulnerability: now downloads to temp file with validation before execution
- Fix SKIP_EXPORTS command injection risk: refactored to use base64-encoded list instead of shell command string
- Fix Playwright symlink path validation: validates executable and path prefix before creating symlinks
## [0.8.0] - 2025-01-25
### Added

View file

@ -390,12 +390,12 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
echo ""
# Build SKIP env var exports for unselected components
SKIP_EXPORTS=""
# Build list of skipped component IDs (space-separated, safe characters only)
# Security: Only hardcoded component IDs from COMP_IDS are used, no user input
SKIP_LIST=""
for ((i=0; i<COMP_COUNT; i++)); do
if [ "${COMP_SELECTED[$i]}" = "0" ]; then
UPPER_ID=$(echo "${COMP_IDS[$i]}" | tr '[:lower:]' '[:upper:]')
SKIP_EXPORTS="${SKIP_EXPORTS}export SKIP_${UPPER_ID}=1; "
SKIP_LIST="${SKIP_LIST}${COMP_IDS[$i]} "
fi
done
@ -463,19 +463,31 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
orb push -m "$VM_NAME" "$SCRIPT_DIR/setup_env.sh" /tmp/setup_env.sh
echo ""
if [ -n "$SKIP_EXPORTS" ]; then
echo -e "${YELLOW}Skipping:${NC} $SKIP_EXPORTS"
if [ -n "$SKIP_LIST" ]; then
echo -e "${YELLOW}Skipping:${NC} $SKIP_LIST"
fi
echo -e "${YELLOW}Running provisioning inside VM (this will take a while)...${NC}"
echo ""
# Security: Use base64 encoding to safely pass values to VM (Issue #1)
# Security: Use base64 encoding to safely pass all values to VM
# This prevents any shell injection through special characters
GIT_NAME_B64=$(printf '%s' "$GIT_NAME" | base64)
GIT_EMAIL_B64=$(printf '%s' "$GIT_EMAIL" | base64)
VNC_PASSWORD_B64=$(printf '%s' "$VNC_PASSWORD" | base64)
SKIP_LIST_B64=$(printf '%s' "$SKIP_LIST" | base64)
orb run -m "$VM_NAME" bash -c "${SKIP_EXPORTS}export GIT_NAME=\$(echo '$GIT_NAME_B64' | base64 -d); export GIT_EMAIL=\$(echo '$GIT_EMAIL_B64' | base64 -d); export VNC_PASSWORD=\$(echo '$VNC_PASSWORD_B64' | base64 -d); bash /tmp/setup_env.sh --non-interactive"
# Security: All user-controlled values are base64-encoded before passing to VM
# The decode script sets SKIP_* env vars from the safe SKIP_LIST
orb run -m "$VM_NAME" bash -c "
export GIT_NAME=\$(echo '$GIT_NAME_B64' | base64 -d)
export GIT_EMAIL=\$(echo '$GIT_EMAIL_B64' | base64 -d)
export VNC_PASSWORD=\$(echo '$VNC_PASSWORD_B64' | base64 -d)
for comp in \$(echo '$SKIP_LIST_B64' | base64 -d); do
upper=\$(echo \"\$comp\" | tr '[:lower:]' '[:upper:]')
export \"SKIP_\${upper}=1\"
done
bash /tmp/setup_env.sh --non-interactive
"
echo ""
echo -e "${GREEN}============================================================================${NC}"
@ -1050,8 +1062,13 @@ install_playwright() {
# Symlink Playwright's Chromium if no system one (check for existing files)
if ! command_exists chromium-browser && ! command_exists chromium; then
local pw_chrome=$(find "$HOME/.cache/ms-playwright" -name "chrome" -type f 2>/dev/null | head -1)
if [ -n "$pw_chrome" ]; then
local pw_chrome
pw_chrome=$(find "$HOME/.cache/ms-playwright" -name "chrome" -type f 2>/dev/null | head -1)
# Security: Validate the found path before creating symlinks
# - Must not be empty
# - Must be an executable file
# - Must be within the expected playwright cache directory
if [ -n "$pw_chrome" ] && [ -x "$pw_chrome" ] && [[ "$pw_chrome" == "$HOME/.cache/ms-playwright"* ]]; then
[ ! -e /usr/local/bin/chromium ] && sudo ln -sf "$pw_chrome" /usr/local/bin/chromium
[ ! -e /usr/local/bin/google-chrome ] && sudo ln -sf "$pw_chrome" /usr/local/bin/google-chrome
echo "Using Playwright's bundled Chromium"
@ -1115,8 +1132,29 @@ install_base_deps() {
if command_exists cargo; then
cargo install watchexec-cli
else
# Fallback: install cargo first, then watchexec
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# Security: Download rustup script first, validate, then execute (Issue #3)
# Same pattern as mise/ollama - never pipe directly to shell
local rustup_script
rustup_script=$(mktemp)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o "$rustup_script"
# Verify it's a shell script
if ! head -1 "$rustup_script" | grep -qE '^#!\s*/(bin|usr/bin)/(env\s+)?(ba)?sh'; then
log_error "Downloaded rustup script doesn't have a valid shell shebang"
rm -f "$rustup_script"
return 1
fi
# Check file size is reasonable (not empty, not huge)
local script_size
script_size=$(wc -c < "$rustup_script")
if [ "$script_size" -lt 100 ] || [ "$script_size" -gt 2000000 ]; then
log_error "Downloaded rustup script has suspicious size: $script_size bytes"
rm -f "$rustup_script"
return 1
fi
sh "$rustup_script" -y
rm -f "$rustup_script"
source "$HOME/.cargo/env"
cargo install watchexec-cli
fi