From 6d9c2a612e2a0251bd44920ba1f117b60b48b82a Mon Sep 17 00:00:00 2001 From: Pierre Tomasina Date: Mon, 2 Feb 2026 18:21:21 +0800 Subject: [PATCH 1/2] docs: rewrite documentation for clarity and supply chain focus - Emphasize supply chain attack prevention as primary threat - Add bun profile throughout (completions, aliases, examples) - Remove unsupported allow_domains/deny_domains from config - Update deny_read list to match base.toml - Fix shell integration setup instructions - Add sxb alias for bun workflow - Apply humanizer rules: remove AI patterns, add direct voice - Consolidate redundant sections in README - Ensure consistency across README and all docs/ --- README.md | 364 ++++++++++++++++++-------------------- docs/CONFIGURATION.md | 116 ++++++------ docs/PROFILES.md | 105 ++++++----- docs/SECURITY.md | 120 ++++++------- docs/SHELL_INTEGRATION.md | 144 ++++----------- shell/sx.bash | 15 +- shell/sx.fish | 23 +-- shell/sx.zsh | 2 + 8 files changed, 394 insertions(+), 495 deletions(-) diff --git a/README.md b/README.md index 63864d7..7d18a96 100644 --- a/README.md +++ b/README.md @@ -4,299 +4,281 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![macOS](https://img.shields.io/badge/platform-macOS-lightgrey.svg)](https://developer.apple.com/documentation/security/app_sandbox) -> **TL;DR:** Run untrusted code without exposing your SSH keys, AWS credentials, or personal files. Uses macOS Seatbelt (`sandbox-exec`) with deny-by-default permissions. +> **TL;DR:** Run untrusted code without risking supply chain attacks. That sketchy npm package can't steal your SSH keys or AWS credentials. Uses macOS Seatbelt (`sandbox-exec`) with deny-by-default permissions. -A lightweight Rust CLI that wraps shell commands and terminals in macOS Seatbelt sandboxes. Protect your system from malicious npm packages, supply chain attacks, compromised dependencies, and untrusted build scripts. +A lightweight Rust CLI that wraps shell commands in macOS Seatbelt sandboxes. That npm package you just installed? It can't read your `~/.ssh` keys or `~/.aws` credentials. Can't steal what you can't see. -**Key features:** -- **Credential protection** - Blocks access to `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.config/gh` -- **Network isolation** - Offline by default, localhost-only, or full access -- **Filesystem isolation** - Deny-by-default reads, scoped writes -- **Claude Code integration** - Safe agentic loops with `--dangerously-skip-permissions` -- **Zero overhead** - Native macOS sandbox, no containers or VMs +Supply chain attacks are everywhere. A single compromised dependency tries to exfiltrate your secrets? It can't—filesystem is deny-by-default. Your credentials aren't readable, even with network enabled. No containers, no VMs, just native macOS sandboxing. ## Quick Start ```bash -# Install via Homebrew brew tap agentic-dev3o/sx brew install sx -# Run commands in an isolated sandbox (network blocked, credentials protected) -sx -- bun lint +# That's it. Now run untrusted code: +sx -- npm run build sx -- cargo test sx -- ./build.sh -# Start an interactive sandboxed shell +# Or start an interactive sandboxed shell sx ``` -That's it. Your credentials (`~/.ssh`, `~/.aws`, `~/.gnupg`) and personal files are protected from malicious scripts. +Your secrets stay secret. Malicious postinstall scripts get nothing. -## Installation - -### Homebrew (Recommended) +## Profiles -```bash -brew tap agentic-dev3o/sx -brew install sx -``` +Profiles stack. Combine them: `sx online rust -- cargo build` -### From Source +| Profile | What it does | +|---------|--------------| +| `base` | Minimal sandbox (always included) | +| `online` | Full network access | +| `localhost` | 127.0.0.1 only | +| `rust` | Cargo/rustup paths | +| `bun` | `~/.bun` + parent directory listing for module resolution | +| `claude` | Claude Code paths (includes `online`) | +| `gpg` | GPG signing | -For development or if you want full control over the build: +### Examples ```bash -git clone https://github.com/agentic-dev3o/sandbox-shell.git -cd sandbox-shell -cargo install --path . -``` - -Or build manually: +# Bun +sx bun -- bun install # Offline, from cache +sx bun online -- bun install # Download deps -```bash -cargo build --release -sudo cp target/release/sx /usr/local/bin/ -``` +# Rust +sx rust -- cargo test # Offline tests +sx rust online -- cargo build # Download crates -### Verify +# Claude Code - the whole point +sx claude -- claude --dangerously-skip-permissions --continue -```bash -sx --version -sx --help +# Interactive shell with network +sx online ``` -**Requirements:** macOS (uses Apple's Seatbelt sandbox). Building from source requires Rust 1.70+. - -## Use Cases - -- **npm/bun/yarn projects** - Protect against malicious postinstall scripts -- **Cloning untrusted repos** - Safely explore code without risk -- **Running build scripts** - Isolate `make`, `./configure`, custom scripts -- **CI/CD local testing** - Reproduce pipeline isolation locally -- **Claude Code / AI agents** - Safe agentic loops without exposing credentials -- **Security research** - Analyze suspicious code safely - -## Why Use sx? - -When you run `npm install` or clone an untrusted repo, malicious code can: -- Steal your SSH keys and AWS credentials -- Access your personal documents -- Exfiltrate data over the network - -`sx` prevents this by running commands in a sandbox that blocks access to sensitive paths and network by default. - -### Why sx Instead of Claude's Native Sandbox? +## Claude Code Integration -Claude Code offers a built-in sandbox mode, but it allows **read-only access to your entire filesystem** by default. This means a compromised npm package or malicious build script can still read your `~/.ssh` keys, `~/.aws` credentials, and other secrets. +Claude Code has a built-in sandbox mode. Sounds great, except it allows **read-only access to your entire filesystem**. A compromised dependency can still read your `~/.ssh` keys, `~/.aws` credentials, and exfiltrate them. -`sx` takes a **deny-by-default** approach: sensitive paths are blocked from reading, not just writing. - -This makes `sx` ideal for **agentic loops** where you want Claude to run autonomously: +`sx` is deny-by-default. Sensitive paths are blocked from *reading*, not just writing. Malicious code can't steal what it can't see. ```bash -# Run Claude Code with dangerous permissions inside sx sandbox -sx online claude -- claude --dangerously-skip-permissions - -# Or start a sandboxed shell for an agentic session -sx online claude +sx claude -- claude --dangerously-skip-permissions --continue ``` -With this setup: -- Claude can execute commands without permission prompts (agentic loop) -- Malicious code cannot read your SSH keys, AWS credentials, or personal files -- Network access is controlled (offline by default, or scoped with `online`/`localhost`) +Claude runs agentic, no permission prompts. Supply chain attacks in dependencies? They get sandboxed too. That's the setup I use. -This is the best of both worlds: full automation for trusted AI workflows, with protection against supply chain attacks in dependencies. +## Installation -## Usage +### Homebrew -``` -sx [OPTIONS] [PROFILES]... [-- ...] +```bash +brew tap agentic-dev3o/sx +brew install sx ``` -### Common Patterns +### From Source ```bash -# Offline sandbox (default) - run untrusted code safely -sx -- npm run build # Build with cached deps -sx -- ./scripts/setup.sh # Run untrusted scripts -sx rust -- cargo test # Run tests isolated - -# Localhost only - for dev servers -sx localhost -- npm start # Allows 127.0.0.1 only - -# Online - when network is required -sx online rust -- cargo build # Download crates - -# Debug what's being blocked -sx --trace -- cargo build # Real-time violation log -sx --explain rust # Show what would be allowed -sx --dry-run rust # Preview sandbox profile +git clone https://github.com/agentic-dev3o/sandbox-shell.git +cd sandbox-shell +cargo install --path . ``` -### Options - -| Option | Description | -|--------|-------------| -| `-v, --verbose` | Show sandbox configuration | -| `-d, --debug` | Enable debug mode (log all denials) | -| `-t, --trace` | Trace sandbox violations in real-time (see note below) | -| `--trace-file ` | Write trace output to file instead of stderr | -| `-n, --dry-run` | Print sandbox profile without executing | -| `-c, --config ` | Use specific config file | -| `--no-config` | Ignore all config files | -| `--explain` | Show what would be allowed/denied | -| `--init` | Create `.sandbox.toml` in current directory | -| `--offline` | Block all network (default) | -| `--online` | Allow all network | -| `--localhost` | Allow localhost only | -| `--allow-read ` | Allow read access to path | -| `--allow-write ` | Allow write access to path | -| `--deny-read ` | Deny read access to path (override allows) | - -> **Note on `--trace`:** The trace output shows sandbox violations from **all sandboxed processes** on the system, not just the current session. This is a limitation of macOS sandbox logging, which doesn't include session identifiers in denial logs. If you're running multiple `sx` sessions simultaneously, violations from all sessions will appear in each trace output. +Requires macOS and Rust 1.70+. -## Profiles +## Configuration -Profiles are composable configurations that stack together: +### Global Config (`~/.config/sx/config.toml`) -| Profile | Description | -|---------|-------------| -| `base` | Minimal sandbox (always included) | -| `online` | Full network access | -| `localhost` | Localhost-only network | -| `rust` | Rust/Cargo toolchain | -| `claude` | Claude Code support | -| `gpg` | GPG signing support | +Your personal paths go here. Terminal, shell prompt, directory jumper… -Combine profiles: `sx online rust -- cargo build` +```toml +[filesystem] +allow_read = [ + # Shell prompt + "~/.config/starship.toml", + "~/.cache/starship/", + + # zoxide + "~/.local/share/zoxide/", + + # Ghostty users - you need this or terminal breaks in sandbox + "/Applications/Ghostty.app/Contents/Resources/terminfo", + + # Claude Code plugins + # "~/projects/my-plugins/", +] + +allow_write = [ + "~/.local/share/zoxide/", + "~/Library/Application Support/zoxide/", + "~/.cache/", +] +``` -## Configuration +**Ghostty users:** add that terminfo path or you'll get display issues. Ask me how I know. ### Project Config (`.sandbox.toml`) -Create a config in your project root: +Per-project overrides: ```bash sx --init ``` -Example: - ```toml [sandbox] profiles = ["rust"] -# network = "localhost" [filesystem] allow_write = ["/tmp/build"] -[network] -allow_domains = ["api.example.com"] - [shell] pass_env = ["NODE_ENV", "DEBUG"] ``` -### Global Config (`~/.config/sx/config.toml`) +Custom profiles go in `~/.config/sx/profiles/name.toml`. -System-wide defaults and custom profiles. +## Usage + +``` +sx [OPTIONS] [PROFILES]... [-- ...] +``` + +```bash +# Offline (default) +sx -- npm run build +sx -- ./scripts/setup.sh + +# Localhost only - for dev servers +sx localhost -- npm start + +# Online +sx online rust -- cargo audit +sx bun online -- bun install + +# Debug what's blocked +sx --trace -- cargo build # Real-time violation log +sx --explain rust # Show allowed/denied +sx --dry-run rust # Preview seatbelt profile +``` + +### Options + +| Option | Description | +|--------|-------------| +| `-v, --verbose` | Show sandbox configuration | +| `-d, --debug` | Log all denials | +| `-t, --trace` | Real-time violation stream | +| `--trace-file ` | Write trace to file | +| `-n, --dry-run` | Print profile, don't execute | +| `-c, --config ` | Use specific config | +| `--no-config` | Ignore all configs | +| `--explain` | Show what's allowed/denied | +| `--init` | Create `.sandbox.toml` | +| `--offline` | Block network (default) | +| `--online` | Allow network | +| `--localhost` | 127.0.0.1 only | +| `--allow-read ` | Allow read | +| `--allow-write ` | Allow write | +| `--deny-read ` | Deny read (overrides allows) | + +| `--trace` shows violations from *all* sandboxed processes on the system, not just yours. macOS limitation. ## Security Model -### What's Protected by Default +### Always Denied (even if you allow `~`) -| Path | Description | -|------|-------------| +These paths are explicitly blocked. Even if your config allows the home directory, these stay protected: + +| Path | What | +|------|------| | `~/.ssh` | SSH keys | | `~/.aws` | AWS credentials | -| `~/.gnupg` | GPG keys | -| `~/.config/gh` | GitHub CLI tokens | -| `~/.netrc` | Network credentials | | `~/.docker/config.json` | Docker credentials | | `~/Documents`, `~/Desktop`, `~/Downloads` | Personal files | +Everything else (`~/.config/gh`, `~/.netrc`, `~/.gnupg`…) is blocked by deny-by-default. Use profiles like `gpg` to allow specific paths when needed. + ### Network Modes -| Mode | Flag | Description | -|------|------|-------------| -| Offline | (default) | All network blocked | -| Localhost | `localhost` | Only 127.0.0.1 allowed | -| Online | `online` | Full network access | +| Mode | Flag | Effect | +|------|------|--------| +| Offline | (default) | All blocked | +| Localhost | `localhost` | 127.0.0.1 only | +| Online | `online` | Full access | ### How It Works -1. **Reads:** Denied by default. Only system paths (`/usr`, `/bin`, `/Library`, `/System`) allowed. -2. **Writes:** Denied by default. Only working directory and `/tmp` allowed. -3. **Network:** Blocked by default. Use `online` or `localhost` profiles. +**Reads:** denied by default. Only `/usr`, `/bin`, `/Library`, `/System`. -## Shell Integration +**Writes:** denied by default. Only working directory and `/tmp`. -Optional shell integration provides prompt indicators, tab completion, and aliases. +**Network:** blocked by default. + +## Use Cases + +Supply chain attacks are the main threat. That one compromised package in your dependency tree running a postinstall script, exfiltrating `~/.aws` to some random server. Or worse, dropping malware. + +`sx` makes npm/bun/yarn safe. Also: untrusted repos, random build scripts, CI/CD isolation locally, Claude Code agentic loops, security research… + +## Shell Integration -### Installation +Prompt indicators, tab completion, aliases. -**Zsh** - Add to `~/.zshrc`: +**Zsh** (`~/.zshrc`): ```bash -# If installed via Homebrew source $(brew --prefix)/share/sx/sx.zsh - -# Or from source -source /path/to/sandbox-shell/shell/sx.zsh ``` -**Bash** - Add to `~/.bashrc`: +**Bash** (`~/.bashrc`): ```bash source $(brew --prefix)/share/sx/sx.bash -# Or: source /path/to/sandbox-shell/shell/sx.bash ``` -**Fish** - Copy to config: +**Fish**: ```fish cp $(brew --prefix)/share/sx/sx.fish ~/.config/fish/conf.d/ ``` -### Features - -**Prompt indicator** - Shows sandbox mode with color coding: -- `[sx:offline]` (red) - Network blocked -- `[sx:localhost]` (yellow) - Localhost only -- `[sx:online]` (green) - Full network access - -**Aliases:** -| Alias | Command | Description | -|-------|---------|-------------| -| `sxo` | `sx online` | Full network access | -| `sxl` | `sx localhost` | Localhost only | -| `sxr` | `sx online rust` | Rust with network | -| `sxc` | `sx online gpg claude` | Claude Code with GPG | +### Prompt Colors -**Tab completion** - Complete profiles, options, and commands. +- `[sx:offline]` red - network blocked +- `[sx:localhost]` yellow - localhost only +- `[sx:online]` green - full network -## Development +### Aliases -```bash -cargo test # Run tests -cargo build # Debug build -cargo run -- --help # Run from source -``` +| Alias | Command | +|-------|---------| +| `sxo` | `sx online` | +| `sxl` | `sx localhost` | +| `sxb` | `sx bun online` | +| `sxr` | `sx online rust` | +| `sxc` | `sx online gpg claude` | -## Comparison with Alternatives +## Comparison | Tool | Platform | Overhead | Credential Protection | Network Control | |------|----------|----------|----------------------|-----------------| -| **sx** | macOS | None (native) | ✅ Deny-by-default | ✅ Offline/localhost/online | -| Docker | Cross-platform | Container runtime | ⚠️ Manual config | ⚠️ Manual config | +| **sx** | macOS | None | ✅ Deny-by-default | ✅ Offline/localhost/online | +| Docker | Cross-platform | Container runtime | ⚠️ Manual | ⚠️ Manual | | Firejail | Linux | Minimal | ✅ Profiles | ✅ Profiles | -| Claude sandbox | macOS | None | ❌ Read-only everywhere | ❌ No control | -| VM (Parallels, etc.) | Cross-platform | Heavy | ✅ Full isolation | ✅ Full isolation | +| Claude sandbox | macOS | None | ❌ Read-only everywhere | ❌ None | +| VM | Cross-platform | Heavy | ✅ Full | ✅ Full | -**When to use sx:** -- You're on macOS and want native performance -- You need to protect credentials from untrusted code -- You want fine-grained network control (offline by default) -- You're running Claude Code in agentic mode +## Development + +```bash +cargo fmt +cargo test +cargo build +cargo run -- --help +``` ## License @@ -304,8 +286,4 @@ MIT ## Contributing -Contributions welcome! Please read the security model before submitting PRs that modify sandbox behavior. - ---- - -**Keywords:** macOS sandbox, seatbelt, sandbox-exec, secure shell, isolated terminal, credential protection, supply chain security, npm security, Claude Code sandbox, agentic AI security, developer security tools +PRs welcome. Read the security model before touching sandbox behavior. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 057a708..4b9fa00 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1,94 +1,68 @@ # Configuration -`sx` uses a layered configuration system with global, project, and CLI options. +`sx` uses a layered configuration system: global config, project config, CLI flags. -## Configuration Files +## Global Config (`~/.config/sx/config.toml`) -### Global Configuration - -Location: `~/.config/sx/config.toml` +Your personal paths. Terminal, shell prompt, directory jumper… ```toml [sandbox] default_network = "offline" # offline | online | localhost -default_profiles = ["base"] # profiles to always include -shell = "/bin/zsh" # shell to use inside sandbox -prompt_indicator = true # show sandbox indicator in prompt -log_file = "~/.sx/violations.log" +default_profiles = ["base"] # always include these +shell = "/bin/zsh" # shell inside sandbox +prompt_indicator = true # show [sx:mode] in prompt +inherit_base = true # include base profile [filesystem] -allow_read = ["/usr", "/bin"] # paths to allow reading -deny_read = ["~/.ssh", "~/.aws"] # paths to deny reading -allow_write = ["/tmp"] # paths to allow writing - -[network] -allow_domains = [] # domains to allow when online -deny_domains = [] # domains to block even when online +allow_read = [ + # Shell prompt + "~/.config/starship.toml", + "~/.cache/starship/", + + # zoxide + "~/.local/share/zoxide/", + + # Ghostty users - required or terminal breaks + "/Applications/Ghostty.app/Contents/Resources/terminfo", +] +allow_write = [ + "~/.local/share/zoxide/", + "~/Library/Application Support/zoxide/", + "~/.cache/", +] +deny_read = [] # additional paths to block [shell] -pass_env = ["TERM", "PATH"] # env vars to pass through -deny_env = ["AWS_*", "*_SECRET*"] # env vars to block +pass_env = ["CUSTOM_VAR"] # env vars to pass through +deny_env = ["*_SECRET*"] # env vars to block (wildcards) +set_env = { CI = "true" } # env vars to set inside sandbox ``` -### Project Configuration +## Project Config (`.sandbox.toml`) -Location: `.sandbox.toml` in project root +Per-project overrides. Create with `sx --init`. ```toml [sandbox] -inherit_global = true # inherit from global config -profiles = ["rust", "online"] # additional profiles for this project +profiles = ["rust"] # profiles for this project network = "localhost" # override network mode +inherit_global = true # inherit from global config +inherit_base = true # include base profile (false for full custom) [filesystem] -allow_read = ["./target"] +allow_read = ["./vendor"] +allow_write = ["./target", "/tmp/build"] deny_read = ["./secrets"] -allow_write = ["./target", "./build"] [shell] -pass_env = ["RUST_LOG"] -set_env = { CI = "true" } +pass_env = ["RUST_LOG", "NODE_ENV"] +set_env = { DEBUG = "1" } ``` -## Configuration Precedence - -1. CLI flags (highest priority) -2. Project config (`.sx.toml`) -3. Global config (`~/.config/sx/config.toml`) -4. Built-in defaults (lowest priority) - -## Network Modes - -| Mode | Description | -|------|-------------| -| `offline` | Block all network access (default) | -| `online` | Allow all network access | -| `localhost` | Allow only localhost (127.0.0.1) connections | - -## Filesystem Rules - -- **allow_read**: Paths the sandbox can read from -- **deny_read**: Paths explicitly denied (overrides allows) -- **allow_write**: Paths the sandbox can write to (besides working directory) - -The working directory always has full read/write access. - -## Environment Variables - -- **pass_env**: Environment variables passed into the sandbox -- **deny_env**: Environment variables blocked (supports wildcards) -- **set_env**: Environment variables to set inside the sandbox - -### Wildcard Patterns - -Environment variable patterns support wildcards: -- `AWS_*` - matches any variable starting with `AWS_` -- `*_SECRET*` - matches variables containing `_SECRET` -- `*_KEY` - matches variables ending with `_KEY` - ## Custom Profiles -Create custom profiles in `~/.config/sx/profiles/`: +Create in `~/.config/sx/profiles/`: ```toml # ~/.config/sx/profiles/myproject.toml @@ -101,3 +75,19 @@ allow_write = ["~/.myproject/cache"] [shell] pass_env = ["MYPROJECT_TOKEN"] ``` + +Use with `sx myproject -- command`. + +## Precedence + +1. CLI flags (highest) +2. Project config (`.sandbox.toml`) +3. Global config (`~/.config/sx/config.toml`) +4. Built-in defaults (lowest) + +## Environment Wildcards + +`deny_env` supports wildcards: +- `AWS_*` - matches `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`… +- `*_SECRET*` - matches `DATABASE_SECRET`, `MY_SECRET_KEY`… +- `*_KEY` - matches `API_KEY`, `SSH_KEY`… diff --git a/docs/PROFILES.md b/docs/PROFILES.md index ab0b496..5115923 100644 --- a/docs/PROFILES.md +++ b/docs/PROFILES.md @@ -1,82 +1,101 @@ # Profiles -Profiles are composable sandbox configurations that can be combined to create the right security posture for your project. +Profiles are composable sandbox configs. Stack them: `sx online rust -- cargo build` ## Built-in Profiles ### base -The foundational profile included by default. Provides: -- Read access to system directories (`/usr`, `/bin`, `/sbin`, `/opt`) -- Read access to temp directories (`/tmp`, `/var/folders`) -- Denies access to sensitive directories (`~/.ssh`, `~/.aws`, `~/.gnupg`) -- Basic environment variables (`TERM`, `PATH`, `HOME`, `USER`) +Always included (unless `inherit_base = false`). Provides: +- Read access to system directories (`/usr`, `/bin`, `/sbin`, `/Library`, `/System`) +- Read access to shell configs (`~/.zshrc`, `~/.bashrc`…) +- Write access to `/tmp` and session temp dir +- Basic env vars (`TERM`, `PATH`, `HOME`, `USER`, `SHELL`) + +**Always denied** (even if you allow `~`): +- `~/.ssh` +- `~/.aws` +- `~/.docker/config.json` +- `~/Documents`, `~/Desktop`, `~/Downloads` ### online -Enables full network access. +Full network access. ```bash -sx --profile online +sx online -- curl https://example.com ``` ### localhost -Allows network connections only to localhost (127.0.0.1). +127.0.0.1 only. For dev servers. ```bash -sx --profile localhost +sx localhost -- npm start ``` ### rust -For Rust projects: -- Read access: `~/.cargo`, `~/.rustup` -- Write access: `~/.cargo/registry` -- Network domains: `crates.io`, `static.crates.io` +Rust/Cargo toolchain: +- Read/write: `~/.cargo`, `~/.rustup` +- Env: `CARGO_HOME`, `RUSTUP_HOME` + +```bash +sx rust online -- cargo build +``` + +### bun + +Bun runtime: +- Read/write: `~/.bun` +- Parent directory listing for module resolution (`/Users`, `~`) +- Env: `BUN_INSTALL`, `NODE_ENV` ```bash -sx --profile rust +sx bun online -- bun install ``` ### claude -For Claude Code projects: -- Read/Write access: `~/.claude` -- Network domains: `api.anthropic.com` -- Passes: `ANTHROPIC_API_KEY` +Claude Code: +- Read/write: `~/.claude`, `~/.claude.json` +- Includes `online` network +- Env: `ANTHROPIC_API_KEY` ```bash -sx --profile claude +sx claude -- claude --dangerously-skip-permissions --continue ``` ### gpg -For GPG signing: -- Read/Write access: `~/.gnupg` +GPG signing: +- Read/write: `~/.gnupg` ```bash -sx --profile gpg +sx gpg -- git commit -S -m "signed" ``` -## Profile Composition +## Combining Profiles -Profiles can be combined. The order matters for network mode (last one wins): +Order matters for network mode (last wins). Filesystem paths merge. ```bash -# Rust project with full network access +# Rust with network sx rust online -- cargo build -# Rust project with localhost only -sx rust localhost -- cargo test +# Rust offline (tests with cached deps) +sx rust -- cargo test -# GPG signing with network access -sx gpg online -- git commit -S -m "signed commit" +# Claude with GPG signing +sx claude gpg -- claude --dangerously-skip-permissions + +# Bun with network +sx bun online -- bun install ``` ## Custom Profiles -Create custom profiles in `~/.config/sx/profiles/` or `./profiles/`: +Create in `~/.config/sx/profiles/`: ```toml # ~/.config/sx/profiles/mycompany.toml @@ -86,9 +105,6 @@ network_mode = "online" allow_read = ["/opt/mycompany"] allow_write = ["~/.mycompany/cache"] -[network] -allow_domains = ["api.mycompany.com", "*.internal.mycompany.com"] - [shell] pass_env = ["MYCOMPANY_TOKEN"] ``` @@ -96,23 +112,20 @@ pass_env = ["MYCOMPANY_TOKEN"] Use it: ```bash -sx --profile mycompany +sx mycompany -- ./run.sh ``` -## Profile Merging Rules - -When multiple profiles are composed: - -1. **Network mode**: Last profile with a network mode wins -2. **Filesystem paths**: Union of all paths (no duplicates) -3. **Network domains**: Union of all domains -4. **Environment variables**: Union of all pass/deny lists +## Project Profiles -## Project-Specific Profiles - -Define profiles in `.sandbox.toml`: +In `.sandbox.toml`: ```toml [sandbox] profiles = ["rust", "localhost"] ``` + +## Merging Rules + +1. **Network mode:** last profile with a mode wins +2. **Filesystem paths:** union (no duplicates) +3. **Env vars:** union of pass/deny lists diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 4d534cf..a29fd0c 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -1,66 +1,66 @@ # Security Model -`sx` uses macOS Seatbelt (sandbox-exec) to create a secure execution environment that restricts filesystem and network access. +`sx` uses macOS Seatbelt (`sandbox-exec`) to isolate processes. Deny-by-default. ## Threat Model -`sx` protects against: +Supply chain attacks. That one compromised npm package in your dependency tree running a postinstall script, trying to exfiltrate `~/.aws` or plant malware. -1. **Malicious npm packages** - Prevents untrusted code from accessing sensitive files -2. **Supply chain attacks** - Limits damage from compromised dependencies -3. **Data exfiltration** - Network restrictions prevent unauthorized data transmission -4. **Credential theft** - Denies access to SSH keys, cloud credentials, and secrets +`sx` protects against: +- **Credential theft** - Can't read `~/.ssh`, `~/.aws`, `~/.docker/config.json` +- **Data exfiltration** - Filesystem is deny-by-default, network is offline by default +- **Malware drops** - Write access limited to working directory and `/tmp` ## Security Layers ### Deny by Default -All access is denied by default. Only explicitly allowed operations succeed: +Everything blocked unless explicitly allowed: -``` +```scheme (version 1) (deny default) ``` ### Filesystem Isolation -| Category | Default | -|----------|---------| -| Working directory | Full access | -| System binaries | Read-only | -| Temp directories | Read/Write | -| Sensitive directories | Denied | - -**Denied by default:** -- `~/.ssh` - SSH keys -- `~/.aws` - AWS credentials -- `~/.gnupg` - GPG keys -- `~/.config/gh` - GitHub CLI tokens -- `~/.netrc` - Network credentials -- `~/.docker/config.json` - Docker credentials -- `~/Documents`, `~/Desktop`, `~/Downloads` - Personal files +| Category | Access | +|----------|--------| +| Working directory | Read/write | +| System binaries (`/usr`, `/bin`) | Read-only | +| Temp (`/tmp`) | Read/write | +| Everything else | Denied | -### Network Isolation +**Always denied** (even if you allow `~`): -Three modes control network access: +| Path | What | +|------|------| +| `~/.ssh` | SSH keys | +| `~/.aws` | AWS credentials | +| `~/.docker/config.json` | Docker credentials | +| `~/Documents`, `~/Desktop`, `~/Downloads` | Personal files | -| Mode | Allowed | -|------|---------| -| `offline` | No network (default) | +Everything else (`~/.config/gh`, `~/.netrc`, `~/.gnupg`…) is blocked by deny-by-default. Use profiles like `gpg` to allow specific paths. + +### Network Isolation + +| Mode | Effect | +|------|--------| +| `offline` (default) | All blocked | | `localhost` | 127.0.0.1 only | -| `online` | All network | +| `online` | Full access | -### Environment Sanitization +Even with `online`, your credentials can't be read. Can't exfiltrate what you can't see. -Sensitive environment variables are blocked by default: -- `AWS_*` - AWS credentials -- `*_SECRET*` - Secrets -- `*_PASSWORD*` - Passwords -- `*_KEY` - API keys +### Environment Sanitization -## Seatbelt Profile +Blocked by default: +- `AWS_*` +- `*_SECRET*` +- `*_PASSWORD*` +- `*_KEY` -The generated Seatbelt profile includes: +## Generated Seatbelt Profile ```scheme (version 1) @@ -71,11 +71,11 @@ The generated Seatbelt profile includes: (allow process-exec) (allow signal (target self)) -; System read access -(allow sysctl-read) -(allow file-read-metadata) +; Required for path resolution +(allow file-read* (literal "/")) +(allow file-read-metadata) ; Required for DNS resolution -; Working directory (full access) +; Working directory (allow file* (subpath "/path/to/project")) ; Denied paths (override allows) @@ -87,41 +87,21 @@ The generated Seatbelt profile includes: (allow file-read* (subpath "/bin")) ; Network (based on mode) -; offline: (nothing) +; offline: nothing ; localhost: (allow network-outbound (to ip "localhost:*")) ; online: (allow network*) ``` -## Security Verification - -Run the security test suite: - -```bash -./scripts/test-security.sh -``` - -Tests verify: -1. Sensitive directories are denied -2. Default network is offline -3. Deny rules take precedence -4. Profile composition is secure -5. Environment sanitization works - ## Limitations -1. **Root bypass** - Sandbox can be bypassed with root privileges -2. **Kernel vulnerabilities** - Sandbox depends on kernel security -3. **Covert channels** - Side-channel attacks are not prevented -4. **Existing processes** - Only affects new processes, not existing ones +1. **Root bypass** - Root can escape any sandbox +2. **Kernel bugs** - Sandbox depends on kernel security +3. **Side channels** - Timing attacks not prevented +4. **Existing processes** - Only affects new processes ## Best Practices -1. **Always use offline mode** unless network is required -2. **Use localhost mode** for local development servers -3. **Audit custom profiles** before use -4. **Review violations** in log file -5. **Keep sx updated** for security fixes - -## Reporting Vulnerabilities - -Report security issues privately. Do not open public issues for vulnerabilities. +1. Default to `offline` unless network required +2. Use `localhost` for dev servers +3. Review custom profiles before trusting them +4. Use `--trace` to debug denials diff --git a/docs/SHELL_INTEGRATION.md b/docs/SHELL_INTEGRATION.md index 8a81912..e0b0e25 100644 --- a/docs/SHELL_INTEGRATION.md +++ b/docs/SHELL_INTEGRATION.md @@ -1,15 +1,17 @@ # Shell Integration -`sx` provides shell integration for a seamless development experience with prompt indicators and convenient aliases. +Prompt indicators, tab completion, aliases. -## Quick Setup +## Setup ### Zsh Add to `~/.zshrc`: ```bash -eval "$(sx shell zsh)" +source $(brew --prefix)/share/sx/sx.zsh +# Or from source: +# source /path/to/sandbox-shell/shell/sx.zsh ``` ### Bash @@ -17,146 +19,78 @@ eval "$(sx shell zsh)" Add to `~/.bashrc`: ```bash -eval "$(sx shell bash)" +source $(brew --prefix)/share/sx/sx.bash ``` ### Fish -Add to `~/.config/fish/config.fish`: - ```fish -sx shell fish | source +cp $(brew --prefix)/share/sx/sx.fish ~/.config/fish/conf.d/ ``` ## Features ### Prompt Indicator -When inside a sandbox, your prompt shows an indicator: +Inside a sandbox, prompt shows the mode: ``` -[sx] ~/projects/myapp $ +[sx:offline] ~/projects/myapp $ +[sx:localhost] ~/projects/myapp $ +[sx:online] ~/projects/myapp $ ``` -With colored output (default): -- 🔒 Yellow indicator for visual feedback -- Clear distinction between sandboxed and normal shells +Color-coded: +- `[sx:offline]` red - network blocked +- `[sx:localhost]` yellow - localhost only +- `[sx:online]` green - full network ### Aliases -The shell integration provides convenient aliases: - -| Alias | Command | Description | -|-------|---------|-------------| -| `sxs` | `sx` | Start sandbox shell | -| `sxe` | `sx --explain` | Explain what sandbox would do | -| `sxd` | `sx --dry-run` | Show generated profile | -| `sxo` | `sx --profile online` | Start with network access | -| `sxl` | `sx --profile localhost` | Start with localhost access | +| Alias | Command | Use case | +|-------|---------|----------| +| `sxo` | `sx online` | Full network | +| `sxl` | `sx localhost` | Dev servers | +| `sxb` | `sx bun online` | Bun with network | +| `sxr` | `sx online rust` | Rust with network | +| `sxc` | `sx online gpg claude` | Claude Code with GPG | ### Tab Completion -Completions for all `sx` commands and options: +Profiles, options, commands: ```bash sx -- ---config --dry-run --explain --help ---network --profile --shell --version -``` - -## Manual Integration +--config --dry-run --explain --help +--offline --online --localhost --trace -If you prefer manual setup: - -### Prompt Function - -```bash -# Check if inside sandbox -sx_prompt() { - if [ -n "$SX_SANDBOX" ]; then - echo "[sx] " - fi -} - -# Add to prompt -PS1='$(sx_prompt)'"$PS1" +sx +base online localhost rust +bun claude gpg ``` -### Environment Detection - -Inside a sandbox, these environment variables are set: +## Environment Variables -| Variable | Value | Description | -|----------|-------|-------------| -| `SX_SANDBOX` | `1` | Indicates sandbox is active | -| `SX_PROFILE` | Profile name | Current profile(s) | -| `SX_NETWORK` | Mode | Network mode | - -### Example Usage +Inside a sandbox, `SANDBOX_MODE` is set: ```bash -# Check if in sandbox -if [ "$SX_SANDBOX" = "1" ]; then - echo "Running in sandbox with profile: $SX_PROFILE" -fi - -# Conditional behavior -if [ "$SX_NETWORK" = "offline" ]; then - echo "Network is disabled" +if [[ -n "$SANDBOX_MODE" ]]; then + echo "Running in sandbox: $SANDBOX_MODE" fi ``` -## Customization - -### Disable Prompt Indicator - -In config: - -```toml -[sandbox] -prompt_indicator = false -``` - -Or via CLI: - -```bash -sx --no-prompt-indicator -``` - -### Custom Prompt Style - -```bash -# Plain text (no colors) -export SX_PROMPT_STYLE=plain - -# Colored (default) -export SX_PROMPT_STYLE=colored -``` - -### Custom Indicator - -Override the default indicator: - -```bash -export SX_PROMPT_INDICATOR="🔒 " -``` +Values: `offline`, `localhost`, `online` ## Troubleshooting ### Prompt not showing -1. Ensure shell integration is sourced -2. Check `$SX_SANDBOX` is set inside sandbox -3. Verify prompt modification order +1. Check shell integration is sourced +2. Verify `$SANDBOX_MODE` is set inside sandbox +3. Check prompt order (sx prepends to existing `$PROMPT`) ### Completions not working -1. Ensure completion system is initialized -2. Check shell integration is loaded -3. Restart shell after changes - -### Slow startup - -Shell integration is minimal. If startup is slow: -1. Check for network calls in shell config -2. Profile with `time sx shell zsh` +1. Completions need `compdef` (zsh) or `complete` (bash) +2. Restart shell after adding integration +3. Check shell integration loaded: `type _sx` diff --git a/shell/sx.bash b/shell/sx.bash index 0023d69..902449b 100644 --- a/shell/sx.bash +++ b/shell/sx.bash @@ -26,8 +26,8 @@ fi # Completions _sx_completions() { local cur="${COMP_WORDS[COMP_CWORD]}" - local profiles="base online localhost node python rust go claude gpg git" - local options="--help --version --verbose --debug --dry-run --explain --init --offline --online --localhost" + local profiles="base online localhost rust bun claude gpg" + local options="--help --version --verbose --debug --trace --trace-file --dry-run --config --no-config --explain --init --offline --online --localhost --allow-read --allow-write --deny-read" if [[ "$cur" == -* ]]; then COMPREPLY=($(compgen -W "$options" -- "$cur")) @@ -39,9 +39,8 @@ _sx_completions() { complete -F _sx_completions sx # Aliases -alias sxo='sx online' -alias sxl='sx localhost' -alias sxn='sx online node' -alias sxp='sx online python' -alias sxr='sx online rust' -alias sxc='sx online claude' +alias sxo='sx online' # Online network access +alias sxl='sx localhost' # Localhost only +alias sxb='sx bun online' # Bun with network +alias sxr='sx online rust' # Rust with network +alias sxc='sx online gpg claude' # Claude Code with GPG signing diff --git a/shell/sx.fish b/shell/sx.fish index 26b6fc9..9ca1ad9 100644 --- a/shell/sx.fish +++ b/shell/sx.fish @@ -33,29 +33,32 @@ complete -c sx -s h -l help -d 'Show help' complete -c sx -s V -l version -d 'Show version' complete -c sx -s v -l verbose -d 'Verbose output' complete -c sx -s d -l debug -d 'Debug mode' +complete -c sx -s t -l trace -d 'Trace violations' +complete -c sx -l trace-file -d 'Write trace to file' complete -c sx -s n -l dry-run -d 'Show profile without executing' +complete -c sx -s c -l config -d 'Use config file' +complete -c sx -l no-config -d 'Ignore all config files' complete -c sx -l explain -d 'Show what would be allowed/denied' complete -c sx -l init -d 'Initialize .sandbox.toml' complete -c sx -l offline -d 'Block all network' complete -c sx -l online -d 'Allow all network' complete -c sx -l localhost -d 'Allow localhost only' +complete -c sx -l allow-read -d 'Allow read access to path' +complete -c sx -l allow-write -d 'Allow write access to path' +complete -c sx -l deny-read -d 'Deny read access to path' # Profile completions complete -c sx -a 'base' -d 'Minimal sandbox' complete -c sx -a 'online' -d 'Full network access' complete -c sx -a 'localhost' -d 'Localhost network only' -complete -c sx -a 'node' -d 'Node.js/npm toolchain' -complete -c sx -a 'python' -d 'Python toolchain' complete -c sx -a 'rust' -d 'Rust/Cargo toolchain' -complete -c sx -a 'go' -d 'Go toolchain' +complete -c sx -a 'bun' -d 'Bun runtime' complete -c sx -a 'claude' -d 'Claude Code support' complete -c sx -a 'gpg' -d 'GPG signing support' -complete -c sx -a 'git' -d 'Git with signing' # Aliases -alias sxo 'sx online' -alias sxl 'sx localhost' -alias sxn 'sx online node' -alias sxp 'sx online python' -alias sxr 'sx online rust' -alias sxc 'sx online claude' +alias sxo 'sx online' # Online network access +alias sxl 'sx localhost' # Localhost only +alias sxb 'sx bun online' # Bun with network +alias sxr 'sx online rust' # Rust with network +alias sxc 'sx online gpg claude' # Claude Code with GPG signing diff --git a/shell/sx.zsh b/shell/sx.zsh index f4b0b58..82ac94e 100644 --- a/shell/sx.zsh +++ b/shell/sx.zsh @@ -29,6 +29,7 @@ _sx() { 'online:Full network access' 'localhost:Localhost network only' 'rust:Rust/Cargo toolchain' + 'bun:Bun runtime' 'claude:Claude Code support' 'gpg:GPG signing support' ) @@ -76,5 +77,6 @@ compdef _sx sx # Aliases for common patterns alias sxo='sx online' # Online network access alias sxl='sx localhost' # Localhost only +alias sxb='sx bun online' # Bun with network alias sxr='sx online rust' # Rust with network alias sxc='sx online gpg claude' # Claude Code with GPG signing From f33e47cd544803f5befae6551da50709f57c96d5 Mon Sep 17 00:00:00 2001 From: Pierre Tomasina Date: Mon, 2 Feb 2026 18:36:20 +0800 Subject: [PATCH 2/2] docs: remove redundant TL;DR and unnecessary personal comment --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 7d18a96..fcc27f4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![macOS](https://img.shields.io/badge/platform-macOS-lightgrey.svg)](https://developer.apple.com/documentation/security/app_sandbox) -> **TL;DR:** Run untrusted code without risking supply chain attacks. That sketchy npm package can't steal your SSH keys or AWS credentials. Uses macOS Seatbelt (`sandbox-exec`) with deny-by-default permissions. - A lightweight Rust CLI that wraps shell commands in macOS Seatbelt sandboxes. That npm package you just installed? It can't read your `~/.ssh` keys or `~/.aws` credentials. Can't steal what you can't see. Supply chain attacks are everywhere. A single compromised dependency tries to exfiltrate your secrets? It can't—filesystem is deny-by-default. Your credentials aren't readable, even with network enabled. No containers, no VMs, just native macOS sandboxing. @@ -120,7 +118,7 @@ allow_write = [ ] ``` -**Ghostty users:** add that terminfo path or you'll get display issues. Ask me how I know. +**Ghostty users:** add that terminfo path or you'll get display issues. ### Project Config (`.sandbox.toml`)