Improves security and simplifies version management
Switches all tools to use latest versions by default instead of pinning specific versions, reducing maintenance overhead while still allowing customization for Erlang and Elixir. Enhances security measures by documenting input validation patterns, safe config loading practices, and credential handling procedures. Updates PostgreSQL authentication to use scram-sha-256 for all TCP connections. Clarifies that VNC passwords are never stored and must be entered each time, improving the security posture of credential management. Simplifies tool installation by removing version pinning constraints and using native package managers where appropriate.
This commit is contained in:
parent
20fa7fa3c5
commit
9ee89df424
3 changed files with 62 additions and 86 deletions
27
CLAUDE.md
27
CLAUDE.md
|
|
@ -28,11 +28,11 @@ This means `./setup_env.sh my-vm` on macOS does everything end-to-end.
|
|||
|
||||
- **Target environment**: OrbStack Ubuntu VM on macOS Apple Silicon (ARM64)
|
||||
- **Version manager**: mise (manages Node.js, Erlang, Elixir)
|
||||
- **Language versions**: Configured at the top of `setup_env.sh` as variables (`ERLANG_VERSION`, `ELIXIR_VERSION`)
|
||||
- **PostgreSQL auth**: Peer for local socket, trust for localhost TCP (127.0.0.1), scram-sha-256 for host network (192.168.0.0/16)
|
||||
- **Versions**: All tools use latest by default. Erlang/Elixir versions can be configured at the top of `setup_env.sh` (`ERLANG_VERSION`, `ELIXIR_VERSION`) - set to "latest" or pin to specific versions
|
||||
- **PostgreSQL auth**: Peer for local socket, scram-sha-256 for all TCP connections (localhost and network)
|
||||
- **Browser**: Chromium (no Chrome ARM64 Linux builds exist), symlinked to `google-chrome`
|
||||
- **VNC**: TigerVNC + XFCE on display :1 (port 5901), controlled via `vnc-start`/`vnc-stop` helpers in `~/bin`
|
||||
- **Shared credentials**: `config.env` is created once and reused for all VMs
|
||||
- **Shared credentials**: `config.env` stores git name/email only; VNC password is prompted each time (never stored)
|
||||
|
||||
## Working on This Project
|
||||
|
||||
|
|
@ -61,6 +61,16 @@ 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
|
||||
|
||||
- **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`)
|
||||
- **Credential passing**: Base64-encode values before passing to VM via `orb run` to prevent shell injection
|
||||
- **Download helper**: Use `download_verified_binary()` for binary downloads (supports optional checksum verification)
|
||||
- **Temp files**: Always use `mktemp` with restrictive permissions (`chmod 600`/`700`)
|
||||
- **Symlinks**: Check with `[ ! -e path ]` (not `[ ! -L path ]`) to avoid overwriting existing files
|
||||
- **Architecture detection**: Use `detect_architecture()` for portable binary downloads
|
||||
|
||||
### Testing
|
||||
|
||||
There are no automated tests. To test changes:
|
||||
|
|
@ -73,8 +83,11 @@ There are no automated tests. To test changes:
|
|||
|
||||
### Security Considerations
|
||||
|
||||
- `config.env` is chmod 600 and gitignored
|
||||
- PostgreSQL remote access uses scram-sha-256 (not trust)
|
||||
- `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 6 chars)
|
||||
- VNC binds to all interfaces (`-localhost no`) to allow connections from the macOS host — this is intentional for the OrbStack use case
|
||||
- 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
|
||||
|
|
|
|||
36
README.md
36
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# 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 — 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.
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ If anything goes wrong: `orb delete my-project && ./setup_env.sh my-project`
|
|||
./setup_env.sh my-sandbox
|
||||
```
|
||||
|
||||
On first run, you'll be prompted for git name, email, and a VNC password. These are saved to `config.env` and reused for all future VMs. You'll also get an interactive checklist to select which components to install.
|
||||
On first run, you'll be prompted for git commit author name and email. These are saved to `config.env` and reused for all future VMs. You'll also get an interactive checklist to select which components to install. If you select VNC, you'll be prompted for a VNC password (this is never stored and must be entered each time).
|
||||
|
||||
```bash
|
||||
# Create additional VMs — reuses config.env, shows component picker
|
||||
|
|
@ -64,13 +64,15 @@ All components are optional — deselect what you don't need in the interactive
|
|||
|------|---------|---------|
|
||||
| mise | latest | Version manager for runtimes |
|
||||
| Node.js | LTS | JavaScript runtime |
|
||||
| Erlang | 28.3.1 | BEAM VM |
|
||||
| Elixir | 1.19.5-otp-28 | Elixir language |
|
||||
| Erlang | latest | BEAM VM |
|
||||
| Elixir | latest | Elixir language |
|
||||
| Chromium | system | Browser automation target |
|
||||
| Playwright | latest | Browser testing framework |
|
||||
| PostgreSQL | system default | Database |
|
||||
| Ollama | latest | Local LLM inference |
|
||||
| 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 |
|
||||
|
||||
## Connecting from macOS
|
||||
|
|
@ -177,7 +179,7 @@ A superuser matching your Linux username and a `dev` database are created automa
|
|||
|
||||
**Auth model:**
|
||||
- Local socket (`psql dev`): peer auth (OS username must match PG role)
|
||||
- Localhost TCP (127.0.0.1): trust (passwordless)
|
||||
- Localhost TCP (127.0.0.1): scram-sha-256 (password required)
|
||||
- Host network (192.168.0.0/16, i.e., from macOS): scram-sha-256
|
||||
|
||||
### From inside the VM
|
||||
|
|
@ -246,16 +248,18 @@ The script installs these plugins at user scope:
|
|||
|
||||
## Configuration
|
||||
|
||||
Edit the version variables at the top of `setup_env.sh`:
|
||||
All tools are configured to use the latest versions by default. Erlang and Elixir versions can be customized in `setup_env.sh`:
|
||||
|
||||
```bash
|
||||
ERLANG_VERSION="28.3.1"
|
||||
ELIXIR_VERSION="1.19.5-otp-28"
|
||||
ERLANG_VERSION="latest" # or pin to specific version like "28.3.1"
|
||||
ELIXIR_VERSION="latest" # or pin to specific version like "1.19.5-otp-28"
|
||||
```
|
||||
|
||||
### Shared credentials
|
||||
|
||||
Credentials are stored in `config.env` (gitignored). To reset:
|
||||
Git credentials (name, email) are stored in `config.env` (gitignored). VNC passwords are never stored and must be entered each time you create a VM with VNC enabled.
|
||||
|
||||
To reset git credentials:
|
||||
|
||||
```bash
|
||||
rm config.env
|
||||
|
|
@ -294,4 +298,16 @@ The VM provisioning script is safe to run multiple times. It checks for existing
|
|||
|
||||
## Logs
|
||||
|
||||
Each provisioning run creates a log file at `/tmp/setup_env_<timestamp>.log` inside the VM with detailed output from package installations and any errors.
|
||||
Each provisioning run creates a log file at `/tmp/setup_env_<timestamp>.log` inside the VM with detailed output from package installations and any errors. Log files are created with mode 600 (owner-only access).
|
||||
|
||||
## Security
|
||||
|
||||
The script includes several security hardening measures:
|
||||
|
||||
- **Input validation**: VM names, VNC passwords, and git credentials are validated against strict patterns
|
||||
- **No credential storage**: VNC passwords are prompted each time and never written to disk
|
||||
- **Safe credential passing**: Values are base64-encoded when passed to the VM to prevent shell injection
|
||||
- **Config file security**: `config.env` is created with mode 600 and parsed safely (not sourced)
|
||||
- **Secure temp files**: Uses `mktemp` with restrictive permissions for all temporary files
|
||||
- **Host filesystem isolation**: macOS home directory access is disabled inside the VM
|
||||
- **PostgreSQL hardening**: Uses peer auth for local sockets, scram-sha-256 for network connections
|
||||
|
|
|
|||
85
setup_env.sh
85
setup_env.sh
|
|
@ -25,17 +25,11 @@
|
|||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Configuration - edit versions here
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
ERLANG_VERSION="28.3.1"
|
||||
ELIXIR_VERSION="1.19.5-otp-28"
|
||||
PLAYWRIGHT_VERSION="1.50.1"
|
||||
# Claude Code version - check https://www.npmjs.com/package/@anthropic-ai/claude-code for latest
|
||||
CLAUDE_CODE_VERSION="1.0.16"
|
||||
# yq version - check https://github.com/mikefarah/yq/releases for latest
|
||||
YQ_VERSION="4.45.1"
|
||||
# watchexec version - check https://github.com/watchexec/watchexec/releases for latest
|
||||
WATCHEXEC_VERSION="2.3.2"
|
||||
# Use 'latest' for mise-managed tools to always get the newest version
|
||||
ERLANG_VERSION="latest"
|
||||
ELIXIR_VERSION="latest"
|
||||
|
||||
# ============================================================================
|
||||
# Helpers
|
||||
|
|
@ -1046,14 +1040,12 @@ install_ollama() {
|
|||
|
||||
install_claude() {
|
||||
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
|
||||
# Security: Pin to specific version to prevent supply chain attacks
|
||||
npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
}
|
||||
|
||||
install_playwright() {
|
||||
export PATH="$HOME/.local/share/mise/shims:$HOME/.local/bin:$PATH"
|
||||
# Security: Install specific version globally, then use it (avoids npx auto-download)
|
||||
npm install -g "playwright@${PLAYWRIGHT_VERSION}"
|
||||
npm install -g playwright
|
||||
playwright install --with-deps chromium
|
||||
|
||||
# Symlink Playwright's Chromium if no system one (check for existing files)
|
||||
|
|
@ -1105,7 +1097,7 @@ install_base_deps() {
|
|||
sudo ln -sf "$(which fdfind)" /usr/local/bin/fd
|
||||
fi
|
||||
|
||||
# yq - with architecture detection, version pinning, and checksum verification
|
||||
# yq - with architecture detection (always latest)
|
||||
if ! command_exists yq; then
|
||||
local yq_arch
|
||||
local arch
|
||||
|
|
@ -1114,65 +1106,20 @@ install_base_deps() {
|
|||
amd64) yq_arch="amd64" ;;
|
||||
arm64) yq_arch="arm64" ;;
|
||||
esac
|
||||
local yq_binary="yq_linux_${yq_arch}"
|
||||
local yq_url="https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/${yq_binary}"
|
||||
local yq_checksum_url="https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/checksums"
|
||||
|
||||
# Fetch and verify checksum
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
curl -fsSL "$yq_checksum_url" -o "$temp_dir/checksums"
|
||||
local expected_checksum
|
||||
expected_checksum=$(grep "${yq_binary}$" "$temp_dir/checksums" | awk '{print $1}')
|
||||
|
||||
if [ -n "$expected_checksum" ]; then
|
||||
download_verified_binary "$yq_url" "/usr/local/bin/yq" "$expected_checksum"
|
||||
else
|
||||
log_error "Could not find checksum for yq, downloading without verification"
|
||||
download_verified_binary "$yq_url" "/usr/local/bin/yq"
|
||||
fi
|
||||
rm -rf "$temp_dir"
|
||||
local yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${yq_arch}"
|
||||
download_verified_binary "$yq_url" "/usr/local/bin/yq"
|
||||
fi
|
||||
|
||||
# watchexec - with architecture detection, version pinning, and checksum verification
|
||||
# watchexec - install via cargo (always latest)
|
||||
if ! command_exists watchexec; then
|
||||
local watchexec_arch
|
||||
local arch
|
||||
arch=$(detect_architecture)
|
||||
case "$arch" in
|
||||
amd64) watchexec_arch="x86_64-unknown-linux-gnu" ;;
|
||||
arm64) watchexec_arch="aarch64-unknown-linux-gnu" ;;
|
||||
esac
|
||||
local watchexec_tarball="watchexec-${WATCHEXEC_VERSION}-${watchexec_arch}.tar.xz"
|
||||
local watchexec_url="https://github.com/watchexec/watchexec/releases/download/v${WATCHEXEC_VERSION}/${watchexec_tarball}"
|
||||
local watchexec_checksum_url="https://github.com/watchexec/watchexec/releases/download/v${WATCHEXEC_VERSION}/SHA512SUMS"
|
||||
|
||||
local temp_dir
|
||||
temp_dir=$(mktemp -d)
|
||||
|
||||
# Fetch checksum file
|
||||
curl -fsSL "$watchexec_checksum_url" -o "$temp_dir/SHA512SUMS" 2>/dev/null || true
|
||||
local expected_checksum
|
||||
expected_checksum=$(grep "${watchexec_tarball}$" "$temp_dir/SHA512SUMS" 2>/dev/null | awk '{print $1}')
|
||||
|
||||
if curl -fsSL "$watchexec_url" -o "$temp_dir/watchexec.tar.xz"; then
|
||||
# Verify checksum if available (SHA512 for watchexec)
|
||||
if [ -n "$expected_checksum" ]; then
|
||||
local actual_checksum
|
||||
actual_checksum=$(sha512sum "$temp_dir/watchexec.tar.xz" | awk '{print $1}')
|
||||
if [ "$actual_checksum" != "$expected_checksum" ]; then
|
||||
log_error "Checksum mismatch for watchexec"
|
||||
rm -rf "$temp_dir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
tar -xJf "$temp_dir/watchexec.tar.xz" -C "$temp_dir" --strip-components=1
|
||||
sudo mv "$temp_dir/watchexec" /usr/local/bin/watchexec
|
||||
sudo chmod +x /usr/local/bin/watchexec
|
||||
if command_exists cargo; then
|
||||
cargo install watchexec-cli
|
||||
else
|
||||
log_error "Failed to download watchexec"
|
||||
# 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
|
||||
rm -rf "$temp_dir"
|
||||
fi
|
||||
|
||||
# direnv hook
|
||||
|
|
|
|||
Loading…
Reference in a new issue