FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feature - sandboxing mcp tooling via zypi, and/or docker, and/or bubblewrap · Issue #265 · cecli-dev/cecli · GitHub

Repository navigation

feature - sandboxing mcp tooling via zypi, and/or docker, and/or bubblewrap #265

Open

Description

I wanted to stop by to add that if we begin using the MCP tools enabled in this branch, we should really consider adding sandboxing of some kind, gemini cli has some limited form of this, including tool exclusions

so you can exclude ShellCommand(rm -rf) from running for example, and add a "sandbox": true, which then runs the tool within a docker container.

I've been experimenting a bit with this using firecracker VMs, with openapi spec mcp generations

Its hard to believe how fast the tools have come in the last year, well maybe not considering how incredible aider was just 9 months ago, seems antiquated already.

This issue I believe also brings up a good example using cgroups via bubblewrap.

All very serious concerns, considering agent systems can dangerously make mistakes, intended or not.

Activity

  1. allen-munsch commented on Dec 16, 2025

    Author

    Here's a vibe coded script of what is meant via bubblewrap:

    #!/usr/bin/env bash
    ###########################################
    # sandbox - Simple command sandboxing via bubblewrap
    ###########################################
    # Usage: sandbox [options] -- <command> [args...]
    #
    # Examples:
    #   sandbox --ro ~/projects -- grep -r "TODO" .
    #   sandbox --profile aider --rw ~/code -- aider
    ###########################################
    
    set -euo pipefail
    
    VERSION="4.0.0"
    
    # --- Configuration ---
    declare -a RO_MOUNTS=()
    declare -a RW_MOUNTS=()
    declare -a ENV_VARS=()
    declare -a ENV_PASSTHROUGH=()
    declare -a ALLOW_SECRETS=()
    
    ALLOW_NETWORK=false
    ALLOW_GPU=false
    SHARE_HOME=false
    WORKDIR=""
    VERBOSE=false
    DRY_RUN=false
    PROFILE=""
    TIME_LIMIT=""
    
    # --- Colors ---
    if [[ -t 2 ]]; then
        RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'
        BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
    else
        RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC=''
    fi
    
    # --- Forbidden paths ---
    FORBIDDEN_WRITE=("/" "/bin" "/boot" "/etc" "/lib" "/lib64" "/opt" "/root" "/sbin" "/sys" "/usr" "/var")
    
    # --- Logging ---
    log_info() { [[ "$VERBOSE" == "true" ]] && echo -e "${BLUE}[INFO]${NC} $*" >&2 || true; }
    log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
    die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
    
    # --- Profiles ---
    apply_profile() {
        case "$1" in
            minimal)
                ;;
            dev)
                ALLOW_NETWORK=true
                SHARE_HOME=true
                ENV_PASSTHROUGH+=(EDITOR VISUAL TERM)
                ;;
            network)
                ALLOW_NETWORK=true
                ;;
            aider|mcp)
                # For Aider CLI + MCP tools
                ALLOW_NETWORK=true
                SHARE_HOME=true  # With secrets blocked
                
                ENV_PASSTHROUGH+=(
                    # API keys passed via env (not from files)
                    ANTHROPIC_API_KEY OPENAI_API_KEY OPENROUTER_API_KEY
                    GOOGLE_API_KEY AZURE_OPENAI_API_KEY GEMINI_API_KEY
                    # Git identity
                    GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL 
                    GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
                    # Terminal
                    TERM COLORTERM CLICOLOR FORCE_COLOR
                    # Aider config
                    AIDER_MODEL AIDER_DARK_MODE AIDER_AUTO_COMMITS
                    AIDER_GIT_ROOT AIDER_VIM AIDER_VOICE_LANGUAGE
                )
                ;;
            untrusted)
                TIME_LIMIT="${TIME_LIMIT:-60}"
                ;;
            *)
                die "Unknown profile: $1 (available: minimal, dev, network, aider, mcp, untrusted)"
                ;;
        esac
        log_info "Applied profile: $1"
    }
    
    # --- Path validation ---
    validate_path() {
        local path="${1/#\~/$HOME}"
        local mode="${2:-ro}"
        local resolved
        resolved="$(realpath -m "$path" 2>/dev/null || echo "$path")"
        
        if [[ "$mode" == "rw" ]]; then
            for forbidden in "${FORBIDDEN_WRITE[@]}"; do
                [[ "$resolved" == "$forbidden" ]] && die "Write access to '$resolved' forbidden"
            done
        fi
        
        [[ ! -e "$path" ]] && log_warn "Path does not exist: $path"
        echo "$resolved"
    }
    
    # --- Help ---
    show_help() {
        cat << 'EOF'
    sandbox - Run commands in an isolated bubblewrap sandbox
    
    USAGE:
        sandbox [options] -- <command> [args...]
    
    PROFILES:
        minimal     No network, no extras (default)
        dev         Network + read-only home (secrets blocked)
        network     Network access only  
        aider/mcp   Aider + MCP tools (network, env-based API keys, secrets blocked)
        untrusted   Maximum isolation with 60s timeout
    
    MOUNT OPTIONS:
        --ro <path>          Mount path read-only
        --rw <path>          Mount path read-write  
        --share-home         Mount $HOME read-only (secrets auto-blocked)
        --workdir, -C <path> Set working directory
    
    PERMISSIONS:
        --profile, -p <name> Apply a profile
        --net                Allow network access
        --gpu                Allow GPU access (/dev/dri, nvidia)
    
    ENVIRONMENT:
        --env <VAR=val>      Set environment variable
        --env-pass <VAR>     Pass variable from host environment
    
    SECURITY:
        --allow-secret <dir> Unblock a secret dir (e.g. .gnupg for signing)
        --time <seconds>     Kill process after timeout
    
    OUTPUT:
        --dry-run            Print bwrap command without executing
        --verbose, -v        Show verbose output
        --help, -h           Show this help
    
    BLOCKED SECRETS (with --share-home or aider/mcp profile):
        .ssh  .gnupg  .aws  .azure  .gcloud  .kube  .docker  .helm
        .password-store  .config/gh  .config/hub  browser profiles
        .bash_history  .zsh_history  and other shell/tool histories
    
    EXAMPLES:
        # Aider with API key from environment
        export ANTHROPIC_API_KEY=sk-ant-...
        sandbox --profile aider --rw ~/myproject -- aider
    
        # MCP filesystem server (sandboxed)
        sandbox --profile mcp --rw ~/data -- npx @modelcontextprotocol/server-filesystem ~/data
    
        # Allow GPG for signed commits  
        sandbox --profile aider --allow-secret .gnupg --rw ~/project -- aider
    
        # Read-only code search
        sandbox --ro ~/code -- grep -r "TODO" .
    
        # Untrusted script with timeout
        sandbox --profile untrusted --ro ~/scripts -- ./suspicious.sh
    
        # Preview the bwrap command
        sandbox --profile aider --rw ~/proj --dry-run -- aider
    EOF
    }
    
    # --- Argument parsing ---
    parse_args() {
        while [[ $# -gt 0 ]]; do
            case "$1" in
                --profile|-p)   shift; PROFILE="$1"; shift ;;
                --ro)           shift; RO_MOUNTS+=("$(validate_path "$1" ro)"); shift ;;
                --rw)           shift; RW_MOUNTS+=("$(validate_path "$1" rw)"); shift ;;
                --net)          ALLOW_NETWORK=true; shift ;;
                --gpu)          ALLOW_GPU=true; shift ;;
                --share-home)   SHARE_HOME=true; shift ;;
                --env)          shift; ENV_VARS+=("$1"); shift ;;
                --env-pass)     shift; ENV_PASSTHROUGH+=("$1"); shift ;;
                --allow-secret) shift; ALLOW_SECRETS+=("$1"); shift ;;
                --workdir|-C)   shift; WORKDIR="$1"; shift ;;
                --time)         shift; TIME_LIMIT="$1"; shift ;;
                --dry-run)      DRY_RUN=true; shift ;;
                --verbose|-v)   VERBOSE=true; shift ;;
                --help|-h)      show_help; exit 0 ;;
                --)             shift; break ;;
                -*)             die "Unknown option: $1" ;;
                *)              break ;;
            esac
        done
        
        COMMAND=("$@")
        [[ ${#COMMAND[@]} -eq 0 ]] && die "No command specified"
    }
    
    # --- Build bwrap command ---
    build_bwrap_command() {
        local -a cmd=(bwrap)
        
        # Isolation
        cmd+=(--unshare-user --unshare-pid --unshare-uts --unshare-ipc --unshare-cgroup)
        [[ "$ALLOW_NETWORK" != "true" ]] && cmd+=(--unshare-net)
        
        # Security
        cmd+=(--cap-drop ALL --no-new-privs --new-session --die-with-parent)
        cmd+=(--hostname sandbox)
        
        # Proc and dev
        cmd+=(--proc /proc --dev /dev)
        for dev in /dev/null /dev/zero /dev/random /dev/urandom /dev/tty; do
            [[ -e "$dev" ]] && cmd+=(--dev-bind-try "$dev" "$dev")
        done
        
        # Base system (read-only)
        for dir in /usr /bin /sbin /lib /lib64 /lib32; do
            [[ -d "$dir" ]] && cmd+=(--ro-bind "$dir" "$dir")
        done
        
        # Essential /etc files
        for f in /etc/ld.so.cache /etc/ld.so.conf /etc/passwd /etc/group /etc/hosts \
                 /etc/resolv.conf /etc/localtime /etc/ssl /etc/ca-certificates \
                 /etc/terminfo /etc/alternatives; do
            [[ -e "$f" ]] && cmd+=(--ro-bind-try "$f" "$f")
        done
        
        # Ephemeral mounts
        cmd+=(--tmpfs /tmp --tmpfs /var/tmp --tmpfs /run)
        
        # === Home directory handling ===
        if [[ "$SHARE_HOME" == "true" ]]; then
            cmd+=(--ro-bind "$HOME" "$HOME")
            
            # Sensitive dirs to block (overlay with tmpfs)
            local -a secret_dirs=(
                # Keys and credentials
                .ssh .gnupg .pki
                # Cloud providers
                .aws .azure .gcloud .config/gcloud
                # Container/k8s
                .kube .docker .helm
                # Package manager tokens  
                .npmrc .yarnrc .pypirc .netrc
                .gem/credentials .cargo/credentials .cargo/credentials.toml
                .composer/auth.json
                # Password managers
                .password-store .local/share/keyrings
                .config/op .config/keybase
                # CLI tokens
                .config/gh .config/hub .config/netlify .config/heroku .config/doctl
                # Browsers (cookies, passwords)
                .mozilla .config/google-chrome .config/chromium
                .config/BraveSoftware .config/vivaldi
                # Generic secrets
                .secrets .credentials .private .env
                # History files (can leak secrets)
                .bash_history .zsh_history .python_history
                .psql_history .mysql_history .node_repl_history
            )
            
            local blocked=0
            for dir in "${secret_dirs[@]}"; do
                # Check if explicitly allowed
                local allowed=false
                for allow in "${ALLOW_SECRETS[@]}"; do
                    [[ "$dir" == "$allow" || "$dir" == "${allow#$HOME/}" ]] && allowed=true && break
                done
                
                if [[ "$allowed" == "true" ]]; then
                    log_info "Secret allowed: ~/$dir"
                elif [[ -e "$HOME/$dir" ]]; then
                    cmd+=(--tmpfs "$HOME/$dir")
                    ((blocked++)) || true
                fi
            done
            
            log_info "Home shared (ro), $blocked secret paths blocked"
        else
            cmd+=(--tmpfs "$HOME")
            cmd+=(--dir "$HOME/.cache" --dir "$HOME/.config" --dir "$HOME/.local/share")
            log_info "Home: ephemeral tmpfs"
        fi
        
        # User mounts
        for path in "${RO_MOUNTS[@]}"; do
            [[ -e "$path" ]] && cmd+=(--ro-bind "$path" "$path")
        done
        for path in "${RW_MOUNTS[@]}"; do
            [[ -e "$path" ]] && cmd+=(--bind "$path" "$path")
        done
        
        # GPU
        if [[ "$ALLOW_GPU" == "true" ]]; then
            [[ -d /dev/dri ]] && cmd+=(--dev-bind /dev/dri /dev/dri)
            for nv in /dev/nvidia*; do
                [[ -e "$nv" ]] && cmd+=(--dev-bind "$nv" "$nv")
            done
        fi
        
        # Environment
        cmd+=(
            --setenv HOME "$HOME"
            --setenv USER "${USER:-$(whoami)}"
            --setenv PATH "/usr/local/bin:/usr/bin:/bin"
            --setenv TERM "${TERM:-xterm-256color}"
            --setenv LANG "${LANG:-C.UTF-8}"
            --setenv TMPDIR "/tmp"
        )
        
        for var in "${ENV_PASSTHROUGH[@]}"; do
            [[ -n "${!var:-}" ]] && cmd+=(--setenv "$var" "${!var}")
        done
        for envvar in "${ENV_VARS[@]}"; do
            cmd+=(--setenv "${envvar%%=*}" "${envvar#*=}")
        done
        
        # Working directory
        local workdir="${WORKDIR:-${RW_MOUNTS[0]:-${RO_MOUNTS[0]:-/tmp}}}"
        cmd+=(--chdir "$workdir")
        
        cmd+=(-- "${COMMAND[@]}")
        printf '%q ' "${cmd[@]}"
    }
    
    # --- Main ---
    main() {
        command -v bwrap &>/dev/null || die "bubblewrap not found. Install: apt install bubblewrap"
        
        parse_args "$@"
        [[ -n "$PROFILE" ]] && apply_profile "$PROFILE"
        
        local bwrap_cmd
        bwrap_cmd="$(build_bwrap_command)"
        
        if [[ "$DRY_RUN" == "true" ]]; then
            echo -e "${CYAN}${BOLD}Dry-run:${NC}"
            echo "$bwrap_cmd"
            exit 0
        fi
        
        if [[ "$VERBOSE" == "true" ]]; then
            echo -e "${CYAN}━━━ Sandbox ━━━${NC}" >&2
            echo -e "  Command: ${COMMAND[*]}" >&2
            echo -e "  Profile: ${PROFILE:-none}" >&2
            echo -e "  Network: $ALLOW_NETWORK" >&2
            echo -e "  RW: ${RW_MOUNTS[*]:-none}" >&2
            echo -e "${CYAN}━━━━━━━━━━━━━━━${NC}" >&2
        fi
        
        if [[ -n "$TIME_LIMIT" ]] && command -v timeout &>/dev/null; then
            exec timeout --signal=KILL "$((TIME_LIMIT + 5))" timeout --signal=TERM "$TIME_LIMIT" bash -c "$bwrap_cmd"
        else
            exec bash -c "$bwrap_cmd"
        fi
    }
    
    main "$@"
    
  2. dwash96 commented on Dec 16, 2025

    Collaborator

    So there is already a docker container that provides some sandboxing, the documentation in the README being

    docker pull dustinwashington/aider-ce
    docker run \
      -it \
      --user $(id -u):$(id -g) \
      --volume $(pwd):/app dustinwashington/aider-ce \
      --volume $(pwd)/.aider.conf.yml:/.aider.conf.yml \
      --volume $(pwd)/.aider.env:/.aider/.env \
      --config /app/.aider.conf.yml

    For the bubblewrap script above is that a script that runs in the same directory or is it intended to be something like:
    ./bubblewrap.sh -- aider-ce -config {file path} [...other arfs]

  3. allen-munsch commented on Dec 16, 2025

    Author

    So there is already a docker container that provides some sandboxing, the documentation in the README being

    docker pull dustinwashington/aider-ce
    docker run
    -it
    --user $(id -u):$(id -g)
    --volume $(pwd):/app dustinwashington/aider-ce
    --volume $(pwd)/.aider.conf.yml:/.aider.conf.yml
    --volume $(pwd)/.aider.env:/.aider/.env
    --config /app/.aider.conf.yml

    Ah yep, Thanks!

    For the bubblewrap script above is that a script that runs in the same directory or is it intended to be something like: ./bubblewrap.sh -- aider-ce -config {file path} [...other arfs]

    Yes, its just an example of using bubblewrap bwrap its meant to place a process into a cgroup, so it isolates any particular process, its a bit more lightweight than docker, which under the hood used to, or still does use crun, runc, which is just a layer over cgroups

    although, container escapes are like a thing, that's why i've been looking at firecracker vms, docker is enough though for the various foot guns that get vibed up sometimes.

    I'm actually putting together a python module for this called bubbleproc

    Effectively it would do a lightweight cgroup bwrap by monkey patching subprocess by wrapping subprocess executions in bwrap

    Here's a sketch of what I mean by that:

    #!/usr/bin/env python3
    """
    Examples and tests for the bubbleproc module.
    
    Run with: python examples.py
    """
    
    from bubbleproc import Sandbox, run, check_output, patch_subprocess, create_aider_sandbox
    import subprocess
    import os
    import tempfile
    
    
    def example_basic():
        """Basic usage - run a simple command."""
        print("=== Basic Usage ===")
        
        # Simple command (no filesystem access needed)
        result = run("echo 'Hello from bubbleproc!'", capture_output=True)
        print(f"Output: {result.stdout.strip()}")
        print(f"Return code: {result.returncode}")
    
    
    def example_filesystem():
        """Filesystem access control."""
        print("\n=== Filesystem Access ===")
        
        # Create a temp directory to work with
        with tempfile.TemporaryDirectory() as tmpdir:
            # Write a test file
            test_file = os.path.join(tmpdir, "test.txt")
            with open(test_file, "w") as f:
                f.write("Hello World\n")
            
            # Read-only access
            result = run(f"cat {test_file}", ro=[tmpdir], capture_output=True)
            print(f"Read file: {result.stdout.strip()}")
            
            # Read-write access
            result = run(
                f"echo 'Modified' >> {test_file} && cat {test_file}",
                rw=[tmpdir],
                capture_output=True
            )
            print(f"After write: {result.stdout.strip()}")
            
            # Verify the write persisted
            with open(test_file) as f:
                print(f"Actual content: {f.read().strip()}")
    
    
    def example_secrets_blocked():
        """Demonstrate that secrets are blocked."""
        print("\n=== Secrets Blocked ===")
        
        home = os.environ.get("HOME", "/tmp")
        
        # Try to read .ssh (should be empty/blocked)
        result = run(
            f"ls -la {home}/.ssh 2>&1 || echo 'Access blocked'",
            share_home=True,
            capture_output=True
        )
        print(f"Trying to access ~/.ssh: {result.stdout.strip()}")
        
        # Try to read .aws (should be empty/blocked)
        result = run(
            f"ls -la {home}/.aws 2>&1 || echo 'Access blocked'",
            share_home=True,
            capture_output=True
        )
        print(f"Trying to access ~/.aws: {result.stdout.strip()}")
    
    
    def example_network():
        """Network access control."""
        print("\n=== Network Access ===")
        
        # Without network (should fail)
        result = run(
            "curl -s --connect-timeout 2 https://example.com 2>&1 || echo 'Network blocked'",
            capture_output=True
        )
        print(f"Without --network: {result.stdout.strip()[:50]}...")
        
        # With network (should work)
        result = run(
            "curl -s --connect-timeout 5 https://example.com | head -c 100",
            network=True,
            capture_output=True
        )
        print(f"With --network: {result.stdout.strip()[:50]}...")
    
    
    def example_sandbox_object():
        """Using the Sandbox class for reusable configuration."""
        print("\n=== Sandbox Object ===")
        
        with tempfile.TemporaryDirectory() as tmpdir:
            # Configure once, use multiple times
            sb = Sandbox(
                rw=[tmpdir],
                network=False,
                env={"MY_VAR": "hello"},
            )
            
            # Run multiple commands with same config
            sb.run(f"echo $MY_VAR > {tmpdir}/out.txt")
            result = sb.run(f"cat {tmpdir}/out.txt", capture_output=True)
            print(f"Environment variable passed: {result.stdout.strip()}")
            
            # Check output helper
            output = sb.check_output("echo 'check_output works'")
            print(f"check_output: {output.strip()}")
    
    
    def example_aider_sandbox():
        """Sandbox configured for Aider usage."""
        print("\n=== Aider Sandbox ===")
        
        with tempfile.TemporaryDirectory() as project_dir:
            # Create a mock project
            with open(os.path.join(project_dir, "main.py"), "w") as f:
                f.write("print('hello')\n")
            
            # Create sandbox for aider
            sb = create_aider_sandbox(project_dir, network=True)
            
            # Show what aider would see
            result = sb.run(f"ls -la {project_dir}", capture_output=True)
            print(f"Project files: {result.stdout.strip()}")
            
            # Verify API key passthrough works (if set)
            api_key = os.environ.get("ANTHROPIC_API_KEY", "not-set")
            result = sb.run(
                "echo ${ANTHROPIC_API_KEY:0:10}...",  # Just first 10 chars
                capture_output=True
            )
            print(f"API key passed: {result.stdout.strip()}")
    
    
    def example_monkey_patch():
        """Monkey-patching subprocess for transparent sandboxing."""
        print("\n=== Monkey Patch ===")
        
        with tempfile.TemporaryDirectory() as tmpdir:
            # Patch subprocess
            patch_subprocess(rw=[tmpdir], network=False, share_home=True)
            
            # Now regular subprocess.run with shell=True is sandboxed
            result = subprocess.run(
                f"echo 'sandboxed!' > {tmpdir}/test.txt && cat {tmpdir}/test.txt",
                shell=True,
                capture_output=True,
                text=True
            )
            print(f"Patched subprocess.run: {result.stdout.strip()}")
            
            # Non-shell commands still work normally
            result = subprocess.run(["echo", "not sandboxed"], capture_output=True, text=True)
            print(f"Non-shell command: {result.stdout.strip()}")
            
            # Clean up
            from bubbleproc import unpatch_subprocess
            unpatch_subprocess()
    
    
    def example_dangerous_commands():
        """Show that dangerous commands are contained."""
        print("\n=== Dangerous Commands Contained ===")
        
        with tempfile.TemporaryDirectory() as safe_dir:
            # Create a file in the safe directory
            safe_file = os.path.join(safe_dir, "important.txt")
            with open(safe_file, "w") as f:
                f.write("important data\n")
            
            # Try to delete everything (should fail outside rw paths)
            result = run(
                "rm -rf / 2>&1 || echo 'Deletion blocked'",
                rw=[safe_dir],
                capture_output=True
            )
            print(f"rm -rf /: {result.stdout.strip()[:60]}")
            
            # The safe file should still exist
            print(f"Safe file exists: {os.path.exists(safe_file)}")
            
            # Try to access /etc/passwd for writing (should be read-only)
            result = run(
                "echo 'hacked' >> /etc/passwd 2>&1 || echo 'Write blocked'",
                capture_output=True
            )
            print(f"Write to /etc/passwd: {result.stdout.strip()}")
    
    
    def main():
        """Run all examples."""
        print("Sandbox Module Examples")
        print("=" * 50)
        
        examples = [
            ("Basic", example_basic),
            ("Filesystem", example_filesystem),
            ("Secrets Blocked", example_secrets_blocked),
            ("Network", example_network),
            ("Sandbox Object", example_sandbox_object),
            ("Aider Sandbox", example_aider_sandbox),
            ("Monkey Patch", example_monkey_patch),
            ("Dangerous Commands", example_dangerous_commands),
        ]
        
        for name, func in examples:
            try:
                func()
            except Exception as e:
                print(f"\n=== {name} ===")
                print(f"Error: {e}")
        
        print("\n" + "=" * 50)
        print("All examples complete!")
    
    
    if __name__ == "__main__":
        main()
    
    
  4. allen-munsch commented on Dec 16, 2025

    Author

    alright i did it, here's the vibed up library: https://github.com/allen-munsch/bubbleproc

    it should be basically a drop in replacement for subprocess.* stuff

  5. dwash96 commented on Dec 16, 2025

    Collaborator

    Neat! So for this issue, are we good with docker, or did you want to try to integrate your library with the core so something like the patch_subprocess() method is available for configuration?

  6. allen-munsch commented on Dec 16, 2025

    Author

    Neat! So for this issue, are we good with docker, or did you want to try to integrate your library with the core so something like the patch_subprocess() method is available for configuration?

    I'll look into integrating the idea, i'll fork and feature branch if i come up with something feasible.

    I'm not sure how it will go.

    Might easier to just default to docker.

    But, I've been struggling with the various cli agent tools on this front.

    It's just docker containers everywhere.

    It'd be nice to know that configuring something like Aider would allow these tools to just work with some simple guardrails, without all of the container management with extra steps.

    So just for reference, these would be the places where subprocess would get patched:

    It should also be noted, I haven't come up with a great solution for cross compatible sandboxing as cgroups don't exist in the same fashion for macintosh, and windows.

  7. strawberrymelonpanda commented on Dec 26, 2025

    But, I've been struggling with the various cli agent tools on this front.
    It's just docker containers everywhere.

    It'd be nice to know that configuring something like Aider would allow these tools to just work with some simple guardrails, without all of the container management with extra steps.

    I absolutely feel this. I use Docker, I don't dislike Docker, but... it's just too heavy for what I want in this situation.

    and windows.

    As a Windows user, I use WSL2 exclusively for projects, so we're probably not out entirely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL