Compare commits

..

No commits in common. "main" and "v0.7.0" have entirely different histories.
main ... v0.7.0

6 changed files with 45 additions and 1729 deletions

View file

@ -5,108 +5,6 @@ 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.14.1] - 2025-01-25
### Changed
- Updated README.md with Windows skip parameters and VNC password info
- Updated CLAUDE.md with Windows script documentation and security patterns
- Fixed VNC password minimum documentation (6 → 8 chars)
## [0.14.0] - 2025-01-25
### Security
- Add SHA256 checksum verification for Ubuntu cloud image downloads (prevents MITM attacks)
- Add strict input validation for git name, email, VM password, and VNC password
- Validate loaded config.env values to detect tampering
- VNC password minimum increased from 6 to 8 characters
- Block shell metacharacters in all user inputs to prevent injection
- Config file created with restricted ACL from the start (no race condition window)
- Add security reminder to remove cloud-init ISO after first boot (contains passwords)
### Added
- ARM64 architecture detection for Windows on ARM devices
- Log file creation at `$env:TEMP\setup_env_windows_<timestamp>.log`
- Cleanup on failure: automatically removes partial VM, disk, and ISO on error
- Hosts file backup before modification (removed on success, kept on failure)
- Input validation functions: `Test-GitName`, `Test-GitEmail`, `Test-VMPassword`, `Test-VNCPassword`
- Checksum caching to avoid re-downloading verification data
### Changed
- Ubuntu image URL now uses detected architecture instead of hardcoded amd64
- All major operations now log to file for troubleshooting
- VM creation wrapped in try/catch with automatic cleanup on failure
## [0.13.0] - 2025-01-25
### Changed
- Rewrote `setup_env_windows.ps1` to fully implement WINDOWS_PLAN.md
- Password handling uses cloud-init `chpasswd` with plaintext (type: text) instead of broken hash generation
- Multiple ISO creation methods with fallback chain: oscdimg → WSL genisoimage → IMAPI2 COM
- Downloads use BITS transfer for reliability with progress reporting
- SSH readiness checking with timeout before displaying connection info
### Added
- Component skip parameters: `-SkipVNC`, `-SkipPostgreSQL`, `-SkipOllama`, `-SkipPlaywright`
- VNC password support via base64 encoding in cloud-init
- Automatic hosts file cleanup when VM is deleted with `-Force`
- Proper prerequisite checking for Hyper-V, Windows edition, and admin privileges
### Fixed
- Cloud-init password configuration (was using bash syntax in PowerShell)
- ISO creation now works without Windows ADK by using WSL or IMAPI2 fallbacks
- Hosts file handling with proper admin privilege elevation
## [0.12.0] - 2025-01-25
### Added
- Python runtime management via mise (alongside Node.js, Erlang, Elixir)
- `WINDOWS_PLAN.md` documenting Hyper-V implementation strategy and security rationale
### Fixed
- Tidewave CLI download URL (now uses correct `tidewave_app` repo with musl binaries)
### Changed
- Python is now a selectable component managed by mise instead of system apt
## [0.11.0] - 2025-01-25
### Added
- Windows support via Hyper-V for maximum security isolation
- `setup_env_windows.ps1` PowerShell script with full VM provisioning
- Ubuntu cloud image support with cloud-init automation
- SSH key generation for passwordless VM access on Windows
- Hosts file integration for easy `<vmname>.local` access
### Security
- Hyper-V provides stronger isolation than WSL2 (separate kernel, network, filesystem)
- No host integration services enabled by default
## [0.10.0] - 2025-01-25
### Added
- OpenCode: Open-source AI coding assistant with multi-provider support
- Tidewave CLI: Elixir/Phoenix MCP server for AI-powered development
- New component selection options for OpenCode and Tidewave
## [0.9.1] - 2025-01-25
### Fixed
- Add error checking for base64 decode operations in VM provisioning
- Add `set -e` to VM bootstrap script for early failure detection
## [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
- Project memory system using CLAUDE.md, CHANGELOG.md, and README.md
- Versioning rules and documentation update guidelines in CLAUDE.md
## [0.7.0] - 2025-01-25
### Added

View file

@ -1,42 +1,17 @@
# CLAUDE.md
## Project Memory
This project uses three documentation files as persistent memory. **You must keep these files up to date when making changes:**
| File | Purpose | Update When |
|------|---------|-------------|
| `CLAUDE.md` | Technical context for Claude Code sessions | Adding new patterns, conventions, or implementation details |
| `CHANGELOG.md` | Version history following Keep a Changelog format | Every commit (add entry, bump version) |
| `README.md` | User-facing documentation | Changing user-visible behavior, adding features, or modifying usage |
### Versioning Rules
- Use semantic versioning (MAJOR.MINOR.PATCH)
- Increment PATCH for bug fixes and minor updates
- Increment MINOR for new features and enhancements
- Increment MAJOR for breaking changes
- Tag every commit with its version: `git tag -a vX.Y.Z -m "Description"`
## Project Overview
This repository provides scripts for creating isolated development sandboxes for Claude Code on macOS (OrbStack) and Windows (Hyper-V). Both platforms offer full VM isolation for safely running AI coding assistants with elevated permissions.
**Supported platforms:**
- **macOS**: OrbStack VMs (ARM64 Apple Silicon)
- **Windows**: Hyper-V VMs (maximum security, stronger than WSL2)
This repository contains a dual-mode setup script for creating OrbStack-based development sandboxes tailored for Claude Code with Elixir/Erlang, browser automation, and PostgreSQL.
## Repository Structure
```
setup_env.sh - macOS script (OrbStack host mode + Linux VM provisioning)
setup_env_windows.ps1 - Windows script (Hyper-V with cloud-init)
WINDOWS_PLAN.md - Windows implementation plan and security rationale
setup_env.sh - Main script (macOS host mode + Linux VM provisioning mode)
config.env.example - Example credentials file
config.env - User credentials (gitignored, created on first run)
.gitignore - Ignores config.env
README.md - User-facing documentation
CHANGELOG.md - Version history (Keep a Changelog format)
CLAUDE.md - This file (context for Claude Code sessions)
```
@ -86,7 +61,7 @@ This means `./setup_env.sh my-vm` on macOS does everything end-to-end.
- `--yes`/`-y` accepts all components without prompting but still allows interactive credential entry
- `MISE_GLOBAL_CONFIG_FILE` and `MISE_CONFIG_DIR` are set to prevent OrbStack host-mount config pollution
### Security Patterns (macOS/Linux)
### Security Patterns
- **Input validation**: Use `validate_vm_name()`, `validate_vnc_password()`, `validate_safe_input()` for all user inputs
- **Safe config loading**: Use `load_config_safely()` which parses key=value pairs without shell execution (never `source`)
@ -96,30 +71,9 @@ This means `./setup_env.sh my-vm` on macOS does everything end-to-end.
- **Symlinks**: Check with `[ ! -e path ]` (not `[ ! -L path ]`) to avoid overwriting existing files
- **Architecture detection**: Use `detect_architecture()` for portable binary downloads
### Editing setup_env_windows.ps1
- PowerShell script for Windows Hyper-V VM creation
- Uses Ubuntu cloud images with cloud-init for automated provisioning
- Supports ARM64 architecture detection for Windows on ARM
- Skip parameters: `-SkipVNC`, `-SkipPostgreSQL`, `-SkipOllama`, `-SkipPlaywright`
- ISO creation fallback chain: oscdimg → WSL genisoimage → IMAPI2 COM
- Log file created at `$env:TEMP\setup_env_windows_<timestamp>.log`
### Security Patterns (Windows)
- **Input validation**: Use `Test-GitName`, `Test-GitEmail`, `Test-VMPassword`, `Test-VNCPassword` for all user inputs
- **Config file security**: ACL set to owner-only before writing content (no race condition)
- **Config validation**: Loaded config.env values are re-validated to detect tampering
- **Checksum verification**: Ubuntu image verified against SHA256 checksums from Canonical
- **Cleanup on failure**: `Invoke-Cleanup` removes partial VM, disk, and ISO on errors
- **Hosts file backup**: Created before modification, removed on success, kept on failure
- **Cloud-init ISO**: Contains passwords in plaintext; user reminded to remove after first boot
### Testing
There are no automated tests. To test changes:
**macOS:**
1. Remove an existing test VM: `orb delete test-sandbox`
2. Run: `./setup_env.sh test-sandbox`
3. Verify provisioning completes
@ -127,21 +81,13 @@ There are no automated tests. To test changes:
5. Test VNC: `ssh test-sandbox@orb -- vnc-start`, then `open vnc://test-sandbox.orb.local:5901`
6. Clean up: `orb delete test-sandbox`
**Windows:**
1. Remove existing test VM: `Stop-VM -Name test-sandbox -Force; Remove-VM -Name test-sandbox -Force`
2. Run: `.\setup_env_windows.ps1 -VMName test-sandbox`
3. Verify provisioning completes (check `/var/log/provision.log` in VM)
4. Test SSH: `ssh -i $env:USERPROFILE\.ssh\id_ed25519_test-sandbox dev@test-sandbox.local`
5. Clean up: `Stop-VM -Name test-sandbox -Force; Remove-VM -Name test-sandbox -Force`
### Security Considerations
- `config.env` is chmod 600 / owner-only ACL and gitignored (stores git name/email only, never passwords)
- `config.env` is chmod 600 and gitignored (stores git name/email only, never VNC password)
- PostgreSQL uses scram-sha-256 for all TCP connections (peer auth for local socket)
- The script refuses to run as root inside the VM
- VNC password is required (min 8 chars), validated to block shell metacharacters, never stored
- VNC binds to all interfaces (`-localhost no`) to allow connections from the host — this is intentional and documented
- VNC password is required (min 6 chars), validated to block shell metacharacters, never stored
- VNC binds to all interfaces (`-localhost no`) to allow connections from the macOS host — this is intentional for the OrbStack use case and documented in the vnc-start script
- All user inputs are validated before use to prevent command injection
- External scripts (mise, ollama) are downloaded to temp files and validated before execution
- Host filesystem access is disabled inside the VM for isolation
- Windows: Ubuntu image downloads are verified against SHA256 checksums

View file

@ -1,27 +1,20 @@
# Secure AI Coding Sandboxes
# OrbStack Development Sandbox
Disposable, isolated Linux VMs for running Claude Code with `--dangerously-skip-permissions`. One command creates a fully provisioned environment. Blow it away and recreate it in minutes.
sDisposable, isolated Linux VMs for running Claude Code with `--dangerously-skip-permissions`. One command creates a fully provisioned environment. Blow it away and recreate it in minutes.
The VM is a real Linux machine with its own filesystem, network, and process space — complete isolation from your host with easy access for development.
| Platform | Script | Isolation Level |
|----------|--------|-----------------|
| **macOS** | `setup_env.sh` (OrbStack) | Full VM isolation |
| **Windows** | `setup_env_windows.ps1` (Hyper-V) | Full VM isolation |
The VM is a real Linux machine with its own filesystem, network, and process space — but you edit files from your Mac, access services on `*.orb.local`, and SSH in without any key setup. All the isolation of a container with none of the friction.
## Why This Exists
Running `claude --dangerously-skip-permissions` on your host machine means Claude can execute arbitrary commands, install packages, modify system files, and access everything on your disk. That's powerful for autonomous coding but risky on a machine with your SSH keys, credentials, and personal files.
These scripts create throwaway VMs where Claude can run unrestricted:
This script creates throwaway VMs where Claude can run unrestricted:
- **Isolated filesystem** — Claude can't touch your host files, keys, or configs
- **Isolated filesystem** — Claude can't touch your macOS files, keys, or configs
- **Isolated network** — services run on their own IP, no port conflicts with your host
- **Disposable**delete and recreate in one command
- **Disposable**`orb delete my-sandbox` wipes everything; recreate in one command
- **Multiple VMs** — run separate sandboxes per project with shared git credentials
- **Full access from host** — edit files in your editor, browse databases, view running apps
## Quick Start (macOS)
- **Full access from Mac** — edit files in your editor, browse databases, view running apps
```bash
# Create a sandbox, SSH in, run Claude unrestricted
@ -32,7 +25,7 @@ claude --dangerously-skip-permissions
If anything goes wrong: `orb delete my-project && ./setup_env.sh my-project`
### macOS Setup
## Quick Start
```bash
# Create and provision a VM (one command from macOS)
@ -58,65 +51,11 @@ When run manually inside a VM, you're prompted for each component individually:
./setup_env.sh --yes
```
### macOS Requirements
## Requirements
- macOS with Apple Silicon (ARM64)
- [OrbStack](https://orbstack.dev) installed (`brew install orbstack`)
## Quick Start (Windows)
```powershell
# Run as Administrator
.\setup_env_windows.ps1 -VMName my-project
# Connect via SSH (after provisioning)
ssh -i $env:USERPROFILE\.ssh\id_ed25519_my-project dev@my-project.local
# Run Claude unrestricted
claude --dangerously-skip-permissions
```
If anything goes wrong: `Remove-VM -Name my-project -Force` then run the script again.
### Windows Setup
```powershell
# Create and provision a VM (run as Administrator)
.\setup_env_windows.ps1 -VMName my-sandbox
# Customize resources
.\setup_env_windows.ps1 -VMName my-sandbox -MemoryGB 16 -DiskGB 100 -CPUs 8
# Skip optional components
.\setup_env_windows.ps1 -VMName my-sandbox -SkipVNC -SkipOllama
```
On first run, you'll be prompted for:
- Git commit author name and email (saved to `config.env`)
- VM user password (min 8 chars, not stored)
- VNC password if VNC is enabled (min 8 chars, not stored)
### Windows Requirements
- Windows 10/11 Pro, Enterprise, or Education (Hyper-V not available on Home)
- Hyper-V enabled: `Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All`
- Administrator privileges
- One of: Windows ADK, WSL with genisoimage, or Windows 10+ (IMAPI2 fallback)
### Why Hyper-V (not WSL2)?
WSL2 is convenient but provides weaker isolation:
- Shares kernel with all WSL2 instances
- Host filesystem mounted by default
- Can launch Windows executables from Linux
- Network traffic bypasses Windows firewall
Hyper-V provides **maximum security**:
- Separate kernel per VM
- Complete filesystem isolation
- Own network stack
- No Windows integration by default
## What Gets Installed
All components are optional — deselect what you don't need in the interactive picker.
@ -131,9 +70,7 @@ All components are optional — deselect what you don't need in the interactive
| Playwright | latest | Browser testing framework |
| PostgreSQL | system default | Database |
| Ollama | latest | Local LLM inference |
| Claude Code | latest | AI coding assistant (Anthropic) |
| OpenCode | latest | Open-source AI coding assistant (multi-provider) |
| Tidewave | latest | Elixir/Phoenix MCP server for AI tools |
| Claude Code | latest | AI coding assistant |
| yq | latest | YAML processor |
| watchexec | latest | File watcher (via cargo) |
| TigerVNC + XFCE | system | VNC access for browser login flows |

View file

@ -1,257 +0,0 @@
# Windows Hyper-V Implementation Plan
This document describes the plan for implementing secure, isolated development sandboxes on Windows using Hyper-V instead of WSL2.
## Why Hyper-V Over WSL2
### WSL2 Security Limitations
| Issue | Impact |
|-------|--------|
| **Shared kernel** | All WSL2 instances share Microsoft's Linux kernel |
| **Host filesystem access** | `/mnt/c` mounted by default, can access Windows files |
| **Bidirectional execution** | Linux can launch Windows executables |
| **Firewall bypass** | WSL2 outbound traffic bypasses Windows firewall rules |
| **Process visibility** | Windows has limited visibility into WSL2 processes |
| **Credential exposure** | Linux processes can potentially access Windows credentials |
### CVE History
- **CVE-2024-20681**: Local privilege escalation to SYSTEM via WSL
- **CVE-2025-9074**: Container escape via WSL2 filesystem sharing
- **CVE-2025-53788**: Undisclosed flaw in WSL/host communication
### Hyper-V Isolation Model
| Feature | Benefit |
|---------|---------|
| **Separate kernel** | Each VM runs its own Linux kernel |
| **Complete filesystem isolation** | No access to Windows files by default |
| **Own network stack** | Separate IP, no firewall bypass |
| **No Windows integration** | Cannot launch Windows programs |
| **Snapshot support** | Can checkpoint and restore VM state |
| **Hardware isolation** | Uses VT-x/AMD-V virtualization |
## Implementation Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Windows Host │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ setup_env_windows.ps1│ │ Hyper-V Manager │ │
│ │ │ │ │ │
│ │ • Check prereqs │───▶│ ┌──────────────────────────┐ │ │
│ │ • Download image │ │ │ Ubuntu VM │ │ │
│ │ • Create cloud-init │ │ │ │ │ │
│ │ • Provision VM │ │ │ • Isolated filesystem │ │ │
│ │ • Configure network │ │ │ • Own network (NAT) │ │ │
│ └─────────────────────┘ │ │ • Claude Code + tools │ │ │
│ │ │ • No Windows access │ │ │
│ │ └──────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## Script Workflow
### Phase 1: Prerequisites Check
```powershell
# Verify environment
1. Check Administrator privileges
2. Check Windows edition (Pro/Enterprise/Education required)
3. Check Hyper-V enabled
4. Check for existing VM (handle -Force flag)
5. Find suitable network switch
```
### Phase 2: Configuration
```powershell
# Collect credentials
1. Load config.env if exists (git name/email)
2. Prompt for missing values
3. Prompt for VM password (not stored)
4. Generate SSH key pair for VM access
```
### Phase 3: Image Preparation
```powershell
# Prepare Ubuntu cloud image
1. Download Ubuntu cloud image (VHDX format)
2. Cache in $env:ProgramData\HyperV-DevSandbox\Images
3. Copy and resize for new VM
4. Generate cloud-init ISO with:
- User account configuration
- SSH key injection
- Package installation
- Setup script embedding
```
### Phase 4: VM Creation
```powershell
# Create Hyper-V VM
1. Create Generation 2 VM (UEFI)
2. Configure resources (CPU, RAM, disk)
3. Disable Secure Boot (for Ubuntu)
4. Attach cloud-init ISO
5. Disable integration services for isolation
6. Enable nested virtualization (for Docker)
```
### Phase 5: Provisioning
```powershell
# Start and provision
1. Start VM
2. Wait for IP assignment
3. Add to hosts file (<vmname>.local)
4. Cloud-init runs setup_env.sh internally
5. Display connection instructions
```
## Cloud-Init Configuration
```yaml
#cloud-config
hostname: <vmname>
users:
- name: dev
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- <generated-public-key>
packages:
- openssh-server
- curl
- git
write_files:
- path: /tmp/setup_env.sh
content: <base64-encoded-script>
runcmd:
- bash /tmp/setup_env.sh --non-interactive --yes
```
## Security Hardening
### VM Configuration
```powershell
# Disable integration services for maximum isolation
Disable-VMIntegrationService -Name "Guest Service Interface"
Disable-VMIntegrationService -Name "Heartbeat"
# Keep Time Synchronization and Shutdown for usability
```
### Network Isolation Options
1. **NAT (Default)**: VM can access internet, isolated from LAN
2. **Internal Only**: VM can only communicate with host
3. **Private**: Completely isolated network
### Firewall Rules (Optional)
```powershell
# Allow SSH access only
New-NetFirewallRule -Name "HyperV-SSH-$VMName" `
-Direction Inbound -LocalPort 22 -Protocol TCP `
-RemoteAddress <VM-IP> -Action Allow
```
## Requirements
### Windows Edition
- Windows 10/11 Pro
- Windows 10/11 Enterprise
- Windows 10/11 Education
- Windows Server 2016+
**Not supported**: Windows 10/11 Home (no Hyper-V)
### Hardware
- 64-bit processor with SLAT (Second Level Address Translation)
- VM Monitor Mode extensions (VT-x on Intel, AMD-V on AMD)
- Minimum 4 GB RAM (8+ GB recommended)
- BIOS/UEFI virtualization enabled
### Software
- Hyper-V enabled: `Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All`
- Windows ADK (for cloud-init ISO creation): [Download](https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install)
## Usage
### Create Sandbox
```powershell
# Basic usage (run as Administrator)
.\setup_env_windows.ps1 -VMName my-project
# With custom resources
.\setup_env_windows.ps1 -VMName my-project -MemoryGB 16 -DiskGB 100 -CPUs 8
# Replace existing VM
.\setup_env_windows.ps1 -VMName my-project -Force
```
### Connect to Sandbox
```powershell
# Via SSH (recommended)
ssh -i $env:USERPROFILE\.ssh\id_ed25519_<vmname> dev@<vmname>.local
# Via Hyper-V console
vmconnect localhost <vmname>
```
### Manage Sandbox
```powershell
# Stop VM
Stop-VM -Name <vmname>
# Start VM
Start-VM -Name <vmname>
# Get VM IP
(Get-VM -Name <vmname> | Get-VMNetworkAdapter).IPAddresses
# Delete VM completely
Stop-VM -Name <vmname> -Force
Remove-VM -Name <vmname> -Force
Remove-Item -Path "$env:ProgramData\HyperV-DevSandbox\VMs\<vmname>" -Recurse -Force
```
## Comparison: OrbStack vs Hyper-V
| Feature | OrbStack (macOS) | Hyper-V (Windows) |
|---------|------------------|-------------------|
| Host OS | macOS only | Windows only |
| Setup complexity | Low | Medium |
| Isolation | Full VM | Full VM |
| DNS | `*.orb.local` auto | Manual hosts file |
| SSH | Automatic | Key-based |
| Filesystem sharing | Configurable | Disabled by default |
| Performance | Near-native | Near-native |
| Nested virtualization | Yes | Yes |
## Known Limitations
1. **Windows ADK Required**: Cloud-init ISO creation requires `oscdimg` from Windows ADK
2. **Manual DNS**: No automatic DNS like OrbStack's `*.orb.local`
3. **Key Management**: SSH keys generated per-VM, stored in user's .ssh folder
4. **No Clipboard Sharing**: Disabled for security (can be enabled if needed)
5. **Generation 2 Only**: Uses UEFI, no legacy BIOS support
## Future Improvements
- [ ] Add option for WSL2 (for users who prefer convenience over security)
- [ ] Implement automatic DNS via Windows hosts file or local DNS server
- [ ] Add PowerShell ISO creation without Windows ADK dependency
- [ ] Support for VM templates/checkpoints
- [ ] Integration with Windows Terminal for easy access
- [ ] Optional WinRM configuration for PowerShell remoting

View file

@ -284,34 +284,30 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
# ---- Interactive component selection ----
# Component IDs and descriptions
COMP_IDS=(postgresql mise node python erlang chromium vnc ollama claude opencode playwright plugins)
COMP_IDS=(postgresql mise node erlang chromium vnc ollama claude playwright plugins)
COMP_NAMES=(
"PostgreSQL"
"mise"
"Node.js (LTS)"
"Python (latest)"
"Erlang/Elixir"
"Chromium"
"VNC + XFCE"
"Ollama"
"Claude Code"
"OpenCode"
"Playwright"
"Claude Plugins + Tidewave"
"Claude Plugins"
)
COMP_DESCS=(
"Database server for local development"
"Version manager for Node.js, Python, Erlang, Elixir"
"JavaScript runtime (required for Claude/OpenCode)"
"Python runtime managed by mise"
"Version manager for Node.js, Erlang, Elixir"
"JavaScript runtime (required for Claude Code)"
"BEAM VM + Elixir functional language"
"Browser for automation and testing"
"Remote desktop for browser-based login flows"
"Local LLM runner for offline inference"
"AI coding assistant CLI from Anthropic"
"Open-source AI coding assistant (multi-provider)"
"Browser testing and automation framework"
"Code review, Tidewave MCP, browser automation"
"Code review, feature dev, browser automation plugins"
)
# All selected by default
@ -394,12 +390,12 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
echo ""
# 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=""
# Build SKIP env var exports for unselected components
SKIP_EXPORTS=""
for ((i=0; i<COMP_COUNT; i++)); do
if [ "${COMP_SELECTED[$i]}" = "0" ]; then
SKIP_LIST="${SKIP_LIST}${COMP_IDS[$i]} "
UPPER_ID=$(echo "${COMP_IDS[$i]}" | tr '[:lower:]' '[:upper:]')
SKIP_EXPORTS="${SKIP_EXPORTS}export SKIP_${UPPER_ID}=1; "
fi
done
@ -467,32 +463,19 @@ if [[ "$(uname -s)" == "Darwin" ]]; then
orb push -m "$VM_NAME" "$SCRIPT_DIR/setup_env.sh" /tmp/setup_env.sh
echo ""
if [ -n "$SKIP_LIST" ]; then
echo -e "${YELLOW}Skipping:${NC} $SKIP_LIST"
if [ -n "$SKIP_EXPORTS" ]; then
echo -e "${YELLOW}Skipping:${NC} $SKIP_EXPORTS"
fi
echo -e "${YELLOW}Running provisioning inside VM (this will take a while)...${NC}"
echo ""
# Security: Use base64 encoding to safely pass all values to VM
# Security: Use base64 encoding to safely pass values to VM (Issue #1)
# 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)
# 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 "
set -e
export GIT_NAME=\$(echo '$GIT_NAME_B64' | base64 -d) || { echo 'Failed to decode GIT_NAME'; exit 1; }
export GIT_EMAIL=\$(echo '$GIT_EMAIL_B64' | base64 -d) || { echo 'Failed to decode GIT_EMAIL'; exit 1; }
export VNC_PASSWORD=\$(echo '$VNC_PASSWORD_B64' | base64 -d) || { echo 'Failed to decode VNC_PASSWORD'; exit 1; }
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
"
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"
echo ""
echo -e "${GREEN}============================================================================${NC}"
@ -933,15 +916,6 @@ install_node() {
export PATH="$HOME/.local/share/mise/shims:$PATH"
}
install_python() {
export PATH="$HOME/.local/bin:$PATH"
eval "$(~/.local/bin/mise activate bash)"
mise use -g python@latest
export PATH="$HOME/.local/share/mise/shims:$PATH"
# Ensure pip is available via mise python
python -m pip install --upgrade pip
}
install_erlang() {
export PATH="$HOME/.local/bin:$PATH"
eval "$(~/.local/bin/mise activate bash)"
@ -1076,13 +1050,8 @@ 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
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
local pw_chrome=$(find "$HOME/.cache/ms-playwright" -name "chrome" -type f 2>/dev/null | head -1)
if [ -n "$pw_chrome" ]; 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"
@ -1090,59 +1059,6 @@ install_playwright() {
fi
}
install_opencode() {
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
if command_exists opencode; then
echo "OpenCode already installed"
return 0
fi
npm install -g opencode-ai
}
install_tidewave() {
if command_exists tidewave; then
echo "Tidewave already installed"
return 0
fi
# Download Tidewave CLI binary from tidewave_app releases
# Binary naming: tidewave-cli-<arch>-unknown-linux-musl
local machine_arch
machine_arch=$(uname -m)
local tidewave_arch=""
case "$machine_arch" in
x86_64) tidewave_arch="x86_64" ;;
aarch64) tidewave_arch="aarch64" ;;
*)
log_error "Unsupported architecture for Tidewave: $machine_arch"
return 1
;;
esac
# Get latest release URL from GitHub (tidewave_app repo)
local release_url="https://github.com/tidewave-ai/tidewave_app/releases/latest/download/tidewave-cli-${tidewave_arch}-unknown-linux-musl"
# Security: Download to temp file first, validate, then install
local temp_file
temp_file=$(mktemp)
if curl -fsSL "$release_url" -o "$temp_file"; then
# Verify it's an executable (ELF binary)
if file "$temp_file" | grep -q "ELF"; then
sudo mv "$temp_file" /usr/local/bin/tidewave
sudo chmod +x /usr/local/bin/tidewave
echo "Tidewave CLI installed"
else
log_error "Downloaded tidewave is not a valid binary"
rm -f "$temp_file"
return 1
fi
else
log_error "Failed to download Tidewave CLI"
rm -f "$temp_file"
return 1
fi
}
install_plugins() {
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
mkdir -p ~/.config/claude
@ -1162,12 +1078,6 @@ install_plugins() {
echo "Adding MCP servers..."
claude mcp add playwright --scope user -- npx @anthropic-ai/mcp-server-playwright 2>/dev/null || true
claude mcp add superpowers-chrome --scope user -- npx github:obra/superpowers-chrome --headless 2>/dev/null || true
# Note: Tidewave MCP is configured per-project when running a Phoenix app
# Use: claude mcp add --transport http tidewave http://localhost:4000/tidewave/mcp
echo ""
echo "Tidewave MCP: Add per-project with your Phoenix app running:"
echo " claude mcp add --transport http tidewave http://localhost:PORT/tidewave/mcp"
}
install_base_deps() {
@ -1205,29 +1115,8 @@ install_base_deps() {
if command_exists cargo; then
cargo install watchexec-cli
else
# 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"
# Fallback: install cargo first, then watchexec
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
cargo install watchexec-cli
fi
@ -1312,15 +1201,12 @@ register_step "base" "System packages (git, build tools, curl)"
register_step "postgresql" "PostgreSQL database server"
register_step "mise" "mise version manager"
register_step "node" "Node.js LTS runtime"
register_step "python" "Python (mise-managed)"
register_step "erlang" "Erlang/OTP VM"
register_step "elixir" "Elixir language"
register_step "chromium" "Chromium browser"
register_step "vnc" "VNC server + XFCE desktop"
register_step "ollama" "Ollama local LLM runner"
register_step "claude" "Claude Code CLI"
register_step "opencode" "OpenCode CLI"
register_step "tidewave" "Tidewave CLI (Elixir MCP)"
register_step "playwright" "Playwright browser automation"
register_step "plugins" "Claude Code plugins"
@ -1328,29 +1214,21 @@ register_step "plugins" "Claude Code plugins"
should_skip "postgresql" && skip_step "postgresql"
should_skip "mise" && skip_step "mise"
should_skip "node" && skip_step "node"
should_skip "python" && skip_step "python"
should_skip "erlang" && skip_step "erlang"
should_skip "chromium" && skip_step "chromium"
should_skip "vnc" && skip_step "vnc"
should_skip "ollama" && skip_step "ollama"
should_skip "claude" && skip_step "claude"
should_skip "opencode" && skip_step "opencode"
should_skip "playwright" && skip_step "playwright"
should_skip "plugins" && skip_step "plugins"
# Elixir skipped if Erlang skipped
[ "${STEP_STATUS[erlang]}" = "skipped" ] && skip_step "elixir"
# Tidewave skipped if Erlang skipped (it's for Phoenix/Elixir projects)
[ "${STEP_STATUS[erlang]}" = "skipped" ] && skip_step "tidewave"
# Python skipped if mise skipped
[ "${STEP_STATUS[mise]}" = "skipped" ] && skip_step "python"
# Claude/OpenCode/Playwright skipped if Node skipped
# Claude/Playwright skipped if Node skipped
if [ "${STEP_STATUS[node]}" = "skipped" ]; then
[ "${STEP_STATUS[mise]}" = "skipped" ] || true # mise can still install
skip_step "claude"
skip_step "opencode"
skip_step "playwright"
skip_step "plugins"
fi
@ -1389,23 +1267,19 @@ run_step_sync "base" install_base_deps || true
wait_for_running_steps
# ============================================================================
# Phase 3: Node + Python + Erlang (parallel, all need mise)
# Phase 3: Node + Erlang (parallel, both need mise)
# ============================================================================
if [ "${STEP_STATUS[mise]}" = "done" ]; then
[ "${STEP_STATUS[node]}" != "skipped" ] && start_step "node" install_node
[ "${STEP_STATUS[python]}" != "skipped" ] && start_step "python" install_python
[ "${STEP_STATUS[erlang]}" != "skipped" ] && start_step "erlang" install_erlang
wait_for_running_steps
fi
# ============================================================================
# Phase 4: Elixir + Claude + OpenCode + Playwright + Tidewave
# (Elixir/Tidewave need Erlang, others need Node)
# Phase 4: Elixir + Claude + Playwright (Elixir needs Erlang, others need Node)
# ============================================================================
[ "${STEP_STATUS[erlang]}" = "done" ] && [ "${STEP_STATUS[elixir]}" != "skipped" ] && start_step "elixir" install_elixir
[ "${STEP_STATUS[erlang]}" = "done" ] && [ "${STEP_STATUS[tidewave]}" != "skipped" ] && start_step "tidewave" install_tidewave
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[claude]}" != "skipped" ] && start_step "claude" install_claude
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[opencode]}" != "skipped" ] && start_step "opencode" install_opencode
[ "${STEP_STATUS[node]}" = "done" ] && [ "${STEP_STATUS[playwright]}" != "skipped" ] && start_step "playwright" install_playwright
wait_for_running_steps
@ -1480,12 +1354,6 @@ fi
if [ "${STEP_STATUS[claude]}" = "done" ]; then
echo -e "${CYAN}Claude:${NC} claude"
fi
if [ "${STEP_STATUS[opencode]}" = "done" ]; then
echo -e "${CYAN}OpenCode:${NC} opencode"
fi
if [ "${STEP_STATUS[tidewave]}" = "done" ]; then
echo -e "${CYAN}Tidewave:${NC} tidewave (run in Phoenix project dir)"
fi
if [ "${STEP_STATUS[ollama]}" = "done" ]; then
echo -e "${CYAN}Ollama:${NC} ollama run llama3.2"
fi

File diff suppressed because it is too large Load diff