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:
guessthepw 2026-01-24 23:16:34 -05:00
parent 20fa7fa3c5
commit 9ee89df424
3 changed files with 62 additions and 86 deletions

View file

@ -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

View file

@ -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

View file

@ -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