cage command - github.com/RutgerLubbers/cage - Go Packages

cage

command module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

Cage

A cross-platform security sandbox CLI tool that restricts file system access for untrusted commands.

Note: This is a fork of Warashi/cage with additional features including deny rule carve-outs, improved symlink handling, and enhanced built-in presets.

Overview

Cage provides a unified way to run potentially untrusted commands or scripts with file system restrictions across Linux and macOS. It's designed for scenarios where you need to:

  • Run AI coding assistants (Claude, Aider, Cursor) with filesystem protection
  • Limit file system modifications during development
  • Protect secrets (SSH keys, AWS credentials) from untrusted code
  • Process sensitive data with controlled output locations

Features

  • Write-only restriction (default): Commands can read any file but cannot write unless explicitly allowed
  • Strict mode: Restrict read access too—only allow explicit paths
  • Secrets protection: Built-in presets to block access to SSH keys, cloud credentials, shell history
  • Cross-platform: Works on Linux (kernel 5.13+) and macOS
  • Flexible permissions: Grant write access via --allow, read access via --allow-read (strict mode)
  • Deny rules: Block specific paths with --deny (with optional except carve-outs for read-only access)
  • Deny carve-outs: Exclude specific subdirectories from deny rules with except
  • Preset system: Built-in and custom presets with inheritance
  • Auto-presets: Automatically apply presets based on command name
  • Transparent execution: Uses syscall.Exec to replace the process, preventing sandbox bypass

Installation

Homebrew (macOS/Linux)
brew install RutgerLubbers/tap/cage
Go Install
go install github.com/RutgerLubbers/cage@latest
From Source
git clone https://github.com/RutgerLubbers/cage
cd cage
go build

Usage

Basic Syntax
cage [flags] <command> [args...]
Flags
Write Access
  • --allow <path>: Grant write access to a specific path (can be used multiple times)
  • --allow-keychain: Allow write access to the macOS keychain (macOS only)
  • --allow-git: Allow access to git common directory. Only needed for git worktrees — in a worktree, .git is a file pointing to the main repo's git data. This flag finds and allows that shared directory. For regular repos, --allow . already includes .git.
  • --allow-all: Disable all restrictions (useful for debugging)
Strict Mode & Read Access
  • --strict: Enable strict mode (don't allow / read access by default)
  • --allow-read <path>: Grant read access to specific paths (only meaningful with --strict)
Deny Rules
  • --deny <path>: Deny both read and write access (read deny only effective on macOS); use except in config for carve-outs
Presets
  • --preset <name>: Use a predefined preset configuration (can be used multiple times)
  • --no-defaults: Skip default presets defined in config
  • --list-presets: List available presets
  • --show-preset <name>: Show the contents of a preset
  • -o <format>: Output format for --show-preset: text (default) or yaml
  • --config <path>: Path to custom configuration file
Utility
  • --dry-run: Show the generated sandbox profile without executing
  • --version: Print version information
Examples
Run a script with temporary directory access
cage -allow /tmp python analyze.py input.txt
# Note: On macOS, /tmp is a symlink to /private/tmp
# You may need to use: cage -allow /private/tmp python analyze.py input.txt
Build a project with restricted output directories
cage -allow ./build -allow ./dist -- npm run build
Analyze untrusted scripts safely
cage python suspicious_script.py
Process data with controlled output
cage -allow ./output -- ./process_data.sh /sensitive/data
Allow keychain access (macOS)
cage -allow-keychain -- security add-generic-password -s "MyService" -a "username" -w
Debug mode (no restrictions)
cage -allow-all -- make install
Enable git operations in worktrees
# --allow-git is needed for git WORKTREES (not regular repos)
# In a worktree, .git is a file pointing to the main repo's .git directory
# --allow-git finds and allows access to that common git directory

# For regular repos, --allow . already includes .git - no need for --allow-git
cage --allow . -- git commit -m "Update files"

# For worktrees, --allow-git is required to access the shared git data
cage --allow-git --allow . -- git commit -m "Update files"
Using presets
# Use built-in presets
cage --preset builtin:npm -- npm install
cage --preset builtin:cargo -- cargo build

# Combine built-in presets for security
cage --preset builtin:strict-base --preset builtin:secure-home --allow . -- ./script.sh

# Use custom preset from config file
cage --preset my-custom-preset -- ./script.sh

# List all available presets (built-in and custom)
cage --list-presets

# View preset contents
cage --show-preset builtin:secure-home
cage --show-preset builtin:strict-base -o yaml

# Auto-presets in action (when configured)
cage claude help  # Automatically applies claude-code preset
cage npm install  # Automatically applies npm preset
Strict mode for secrets protection
# Strict mode restricts read access to explicit paths only
cage --strict --allow-read /usr --allow-read /etc --allow . -- make

# Use built-in secure preset (strict mode + secure home)
cage --preset builtin:secure -- npm install
Deny rules (macOS full support, Linux write-only)
# Deny access to home with carve-outs
cage --preset builtin:secure-home --allow . -- python script.py

# Use built-in secure-home preset
cage --preset builtin:secure-home --allow . -- ./untrusted-script.sh
Configuration File

Cage supports YAML configuration files to define presets. The configuration file is searched in the following order:

  1. Path specified with --config flag
  2. $XDG_CONFIG_HOME/cage/presets.yaml
  3. $HOME/.config/cage/presets.yaml
  4. $HOME/.config/cage/presets.yml
Built-in Presets

Cage ships with these built-in presets (use with --preset builtin:NAME):

Preset Description
builtin:secure Recommended. Strict mode + system reads + $HOME denied with read-only carve-outs + CWD write + all dev tools
builtin:strict-base Minimal system read access with strict mode enabled (no home access)
builtin:secure-home Denies all of $HOME, then carves out read-only exceptions for safe directories
builtin:npm Node.js paths (~/.npm, ~/.bun, node_modules) - additive, use with --allow .
builtin:cargo Rust paths (~/.cargo, ~/.rustup, target) - additive, use with --allow .
builtin:java Java/JVM paths (~/.m2, ~/.gradle, target, build) - additive, use with --allow .
builtin:go Go paths (~/go, ~/.cache/go-build) - additive, use with --allow .

Example configuration file:

# Default presets applied to ALL commands
defaults:
  presets:
    - "builtin:secure"

presets:
  # Extend secure preset with keychain access for AI tools
  ai-coder:
    extends:
      - "builtin:secure"
    allow:
      - path: "/tmp"
        eval-symlinks: true  # Resolves /tmp -> /private/tmp on macOS
      - "$HOME/.config/claude"
    allow-keychain: true
    allow-git: true
  
  # Simple preset
  npm:
    allow:
      - "."
      - "$HOME/.npm"
      - "$HOME/.cache/npm"
  
  # Preset that skips defaults
  unrestricted:
    skip-defaults: true
    allow:
      - "."

auto-presets:
  # Exact command match
  - command: claude
    presets:
      - ai-coder
  
  # Regex pattern match
  - command-pattern: ^(npm|npx|yarn|pnpm)$
    presets:
      - npm

Presets support the following options:

  • extends: List of presets to inherit from (including builtin:* presets)
  • skip-defaults: Skip default presets when this preset is used (boolean)
  • strict: Enable strict mode (don't allow / read by default)
  • allow: List of paths to grant write access
  • read: List of read-only paths (only used when strict: true)
  • deny: List of paths to deny read+write (read deny only effective on macOS)
    • Supports except field for carve-outs that restore read-only access
  • allow-git: Enable access to git common directory (boolean)
  • allow-keychain: Enable macOS keychain access (boolean)

The allow field in presets supports both simple string paths and objects with an eval-symlinks option. When eval-symlinks is set to true, the symlink will be resolved to its target path before granting access.

presets:
  symlink-aware:
    allow:
      # Simple string (default: eval-symlinks = false)
      - "./direct-path"
      
      # Object with eval-symlinks option
      - path: "/tmp"
        eval-symlinks: true  # Resolves symlink to actual path
      
      # Mixed usage
      - "$HOME/.cache"
      - path: "$HOME/.local/share"
        eval-symlinks: true

This is particularly useful when:

  • Working with tools that create symlinks to actual data directories
  • macOS's /tmp is a symlink to /private/tmp
  • Using symlinked configuration directories
  • Working with package managers that use symlinks

Example use case for macOS:

presets:
  macos-tmp:
    allow:
      - path: "/tmp"
        eval-symlinks: true  # Automatically resolves to /private/tmp
Deny Rules with Carve-outs (Exceptions)

Deny rules support an except field that allows you to carve out specific subdirectories from a broader deny rule. Important: The except carve-outs restore read-only access, not write access. Use explicit allow paths to grant write access.

presets:
  protect-with-exceptions:
    deny:
      # Deny $HOME but allow read-only access to safe directories
      - path: "$HOME"
        except:
          - "$HOME/Documents"      # Read-only access
          - "$HOME/Downloads"      # Read-only access
          - "$HOME/.gitconfig"     # Read-only access
    allow:
      - "."                        # Write access to CWD
      - "$HOME/.claude"            # Write access to Claude config

How carve-outs work:

  • On macOS: The deny rule is emitted first, then allow rules for each exception. Since SBPL evaluates rules in order (last match wins), the exceptions override the deny with read-only access.
  • On Linux: Carve-outs work for write denies only. Exception paths are tracked and excluded from the deny set.

The simplified model follows this philosophy:

  1. Deny broadly: Block entire directories (like $HOME)
  2. Carve out reads: Use except to restore read-only access to safe paths
  3. Explicitly allow writes: Use allow for paths that need write access
Auto-Presets

Cage can automatically apply presets based on the command being executed. This feature helps reduce typing and ensures consistent permissions for common tools.

Example auto-presets configuration:

presets:
  claude-code:
    allow:
      - "$HOME/.config/claude"
      - "$HOME/tmp"
      - "/tmp"
    allow-keychain: true
    
  npm:
    allow:
      - "."
      - "$HOME/.npm"
      
auto-presets:
  # Exact command match
  - command: claude
    presets:
      - claude-code
      - tmp
      
  # Regex pattern match
  - command-pattern: ^(npm|npx|yarn)$
    presets:
      - npm
      
  # Multiple presets can be applied
  - command: git
    presets:
      - git-enabled
      - tmp

Auto-preset rules support:

  • command: Exact command name match (basename of the command)
  • command-pattern: Regular expression pattern to match command names
  • presets: List of preset names to apply

Note: Auto-presets are merged with explicit --preset flags. Command-line presets are processed first, maintaining their priority over auto-presets.

Platform Implementation

Linux
  • Uses Landlock LSM via go-landlock
  • Requires kernel 5.13 or later
  • Allowlist-only: Cannot deny subpaths under allowed parents
  • No glob patterns: Paths must be literal
  • Read denies only warn—use strict mode for read protection
  • Restrictions inherit to all child processes (kernel-enforced)
macOS
  • Uses sandbox-exec with custom sandbox profiles
  • Full allowlist AND denylist support
  • Supports glob patterns via regex
  • All deny rules fully enforced
  • Restrictions inherit to all child processes (kernel-enforced)
Other Platforms
  • Returns an error indicating sandboxing is not implemented

Security Policy

Cage enforces the following security policy:

Operation Default With --strict With --allow
File Read ✅ Allowed ❌ Denied (need --allow-read) ✅ Allowed
File Write ❌ Denied ❌ Denied ✅ Allowed for path
File Execute ✅ Allowed ✅ Allowed (if readable) ✅ Allowed
Network Access ✅ Allowed ✅ Allowed ✅ Allowed
Process Creation ✅ Allowed ✅ Allowed ✅ Allowed
Linux Limitation: Protecting Secrets

On Linux, deny rules for reads cannot be enforced due to Landlock's allowlist-only model. The only way to protect secrets on Linux is to use strict mode:

presets:
  linux-secure:
    strict: true          # Don't allow / read
    read:                 # Explicitly list what CAN be read
      - "/usr"
      - "/lib"
      - "/etc"
      - "$HOME/Documents"
      # .ssh, .aws NOT listed = NOT readable
    allow:
      - "."

Environment Variables

IN_CAGE

When a command is executed inside cage, the IN_CAGE environment variable is automatically set to 1. This allows programs to detect if they are running within the cage sandbox.

# Check if running inside cage from a shell script
if [ "$IN_CAGE" = "1" ]; then
    echo "Running inside cage sandbox"
    # Adjust behavior accordingly
fi

This can be useful for:

  • Adjusting application behavior when running in a restricted environment
  • Providing warnings about limited functionality
  • Debugging sandbox-related issues
  • Conditional logging or telemetry
Homebrew Integration

When using Homebrew and cage together on macOS, you may encounter an issue with the standard Homebrew configuration. The typical Homebrew setup includes the following line in .zprofile:

eval "$(/opt/homebrew/bin/brew shellenv)"

However, when cage executes commands (such as when used with Claude Code), this configuration can cause the following error:

/opt/homebrew/Library/Homebrew/cmd/shellenv.sh: line 18: /bin/ps: Operation not permitted

This occurs because the shellenv script attempts to execute /bin/ps, which is blocked by the sandbox restrictions. To resolve this issue, modify your .zprofile to conditionally evaluate shellenv only when not running inside cage:

if [[ -z $IN_CAGE ]]; then
  eval "$(/opt/homebrew/bin/brew shellenv)"
fi

This configuration:

  • Allows normal Homebrew functionality when using your shell directly
  • Prevents the error when commands are executed within cage
  • Works because cage inherits the necessary environment variables from the parent shell, making the shellenv evaluation unnecessary

Development

Building
go build
Testing

Run the comprehensive end-to-end test suite:

./test_e2e.sh
Nix Development Environment

The project includes Nix flakes for reproducible development:

nix develop

Use Cases

1. Development Workflows

Restrict build outputs to specific directories:

cage -allow ./build -allow ./node_modules -- npm install
cage -allow ./dist -- npm run build
2. Security Testing

Safely analyze potentially malicious scripts:

cage python malware_sample.py
cage -allow /tmp/analysis -- ./suspicious_binary
3. Data Processing

Process sensitive data with controlled output locations:

cage -allow ./reports -- python generate_report.py /confidential/data.csv
4. LLM Code Agents

The recommended way to run AI coding assistants:

# In ~/.config/cage/presets.yaml
presets:
  ai-coder:
    extends:
      - "builtin:strict-base"
      - "builtin:secure-home"
    allow:
      - "."
      - path: "/tmp"
        eval-symlinks: true
    allow-keychain: true
    allow-git: true

auto-presets:
  - command-pattern: ^(claude|aider|cursor|opencode|windsurf)$
    presets:
      - ai-coder

Then simply run:

cage claude --dangerously-skip-permissions
cage aider

Or with shell aliases in ~/.bashrc or ~/.zshrc:

alias claude='cage claude'
alias aider='cage aider'

Limitations

  • Sandboxing is only implemented for Linux and macOS
  • Linux requires kernel 5.13 or later for Landlock support
  • Linux: Cannot deny read access under allowed parents (use strict mode instead)
  • Linux: Glob patterns not supported (enumerate paths explicitly)
  • Network and process execution are not restricted

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL