GitHub - sandbox-utils/sandbox-venv: 🔒🐍🐧 Secure container sandbox Python venv wrapper (original) (raw)

Build status Language: shell / Bash Source lines of code Script size Issues Sponsors

Problem statement

Python virtual environments (packagevirtualenvor built-in modulevenv) isolate your project’s interpreter and dependencies, but they offerno security or execution sandboxing like a virtual machine or a Docker container would. Therefore, running virtualenv Python programs as-is (unsecured),any rogue dependency* 🎯 or hacked library code🏴‍☠️ (et cet. ⚠️) can wreak havoc, including access all your private parts ‼️—think current user's credentials and personal bits like:

✱ Installing something as seemingly harmless as the popular **package poetry**pulls in nearly a hundred dependencies or over 70 MBof Python sources! 😬

In someone else's words:

Using virtualenv is more secure?

No. Not in the slightest.

Solution

In order to execute installed Python programs in secure virtual environments, one is better advised to either look to OS VM primitives like those provided by Docker and containers, e.g.:

podman run -it -v .:/src python:3 bash # ...

The simpler alternative is automatic lightweight container wrapping withbubblewrap (ofFlatpak fame) using sandbox-venv script from this repo.

Installation

There are no dependencies other than a POSIX shell withits standard set of utilities and bubblewrap. The installation process, as well as the script runtime, should behave similarly on all relevant compute platforms, including GNU/Linux and evenWindos/WSL. 🤞

Install the few, unlikely to be missing dependencies, e.g.

sudo apt install coreutils binutils bubblewrap libseccomp2 python3

A working XDG Desktop Portal is recommended to xdg-open hyperlinks

sudo apt install xdg-dbus-proxy xdg-desktop-portal* # Note: only need one

Download the script and put it somewhere on PATH

curl -vL 'https://bit.ly/sandbox-venv' | sudo tee /usr/local/bin/sandbox-venv sudo chmod +x /usr/local/bin/sandbox-venv # Mark executable

sandbox-venv --help

Usage: sandbox-venv [VENV_DIR] [BWRAP_OPTS]

sandbox-venv path/to/my-project/.venv

Usage

Whenever you create a new virtual environment, simply invoke sandbox-venv on it afterwards, e.g.:

cd project python -m venv .venv # Create a new project virtualenv sandbox-venv .venv # Passing virtualenv dir is optional; defaults to ".venv"

From now on, directory .venv and everything under it (in particular, everything in the bin folder, e.g. .venv/bin/python, .venv/bin/pip etc.) sets up and transparently runs in a secure container sandbox.

Extra Bubblewrap arguments

Other than the optional virtualenv dir, all arguments initially passed tosandbox-venv are forwarded to bubblewrap. See bubblewrap --help orman 1 bwrap.

You can also pass additional bubblewrap arguments to individual process invocations via $BWRAP_ARGS environment variable. E.g.:

BWRAP_ARGS='--bind /lib /lib'
python -c 'import os; print(os.listdir("/lib"))'

Note, a .env fileat project root is sourced for the initial environment.

See more specific examples below.

Filesystem mounts

The directory that contains your venv dir, i.e. .venv/.. orthe project directory, is mounted with read-write permissions, while everything else (including project/.git) is mounted read-only. In addition:

To mount extra endpoints, use Bubblewrap switches --bind or --bind-ro. Anything else not explicitly mounted by an extra CLI switch is lost upon container termination.

Linux Seccomp

Linux kernel seccomp facility for restricting syscalls isautomatically enabled when the appropriate package is availableapt install libseccomp2 python3-seccomp (requires virtualenv with --system-site-packages) or pip install pyseccomp (also requires libseccomp2). The initializing module sitecustomize.py installs a filter that thereafter only allows syscalls listed in the environment variableSANDBOX_SECCOMP_ALLOW=(or, by default, some 200 syscalls that should cover all non-special cases). You can populate the variable at runtime with a custom, stricter syscalls list or set it to blank (i.e. export SANDBOX_SECCOMP_ALLOW=) to force-disable seccomp completely.

Runtime monitoring

If environment variable VERBOSE= is set to a non-empty value, the full bwrap command line is emitted to stderr before execution.

You can list bubblewraped processes using thecommand lsnsor the following shell function:

list_bwrap () { lsns -u -W | { IFS= read header; echo "$header"; grep bwrap; }; }

list_bwrap # Function call

You can run $venv/bin/shell to spawn interactive shell inside the sandbox.

Environment variables

Debugging

To see what's failing, run the sandbox with something like strace -f -e '%file,%process' ....

Examples

The examples here deal in environment variables, but once configurations stabilize, you should probably usesandbox-venv init params and/or .env files.

To install a heavy package that requires a compiler, it is often easiest to supply it with full /usr and /lib:

BWRAP_ARGS='--ro-bind /usr /usr --ro-bind /lib /lib' pip install ...

You may need to expose your complex IDE bin/lib dirs. For use with JetBrains IDEs such as PyCharm, you need to give the sandbox access to the IDE runtime dir. We show this done globally at initialization via default args rather than via $BWRAP_ARGS variable:

IDE_DIR="/home/my_username/Downloads/pycharm" # E.g. sandbox-venv .venv --ro-bind "$IDE_DIR" "$IDE_DIR"

To pass extra environment variables, other than those filtered by default, use bwrap --setenv, e.g.:

BWRAP_ARGS='--setenv OPENAI_API_KEY c4f3b4b3' my-ai-prog

or a .env (dotenv) file

To run the sandboxed process as superuser(while still retaining all the security functionality of the container sandbox), e.g. to open privileged ports, use args:

BWRAP_ARGS='--uid 0 --cap-add cap_net_bind_service' python -m http.server 80

To run GUI (X11) apps, some prior success was achieved using e.g.:

BWRAP_ARGS='--bind /tmp/.X11-unix/X0 /tmp/.X11-unix/X8 --setenv DISPLAY :8'
python -m tkinter

See more examples on the ArchWiki.

Security Model

Entrypoints in $venv/bin are wrapped with exec bwrapso that every invocation runs inside a fresh Bubblewrap container.

In addition, $venv/bin/pip wrapper re-wraps any newly installed executables in $venv/bin, ensuring they always use the wrapped $venv/bin/python.

Rather than giving the sandbox full filesystem access, minimal shared library (*.so) dependencies, as well as specific host binaries (e.g. /usr/bin/python3, /usr/bin/git, /bin/sh etc.), are collected and made available inside the container.Most paths are bind-mounted read-only, while the current working directoryis mounted with RW permissions (with the exclusion of directories .venv and .git, which are layered over RO so as to not allow the sandboxed executable to modify them directly).

BWRAP_ARGS= environment variable lets you expand or relax the sandbox at runtime.

Additional confinement with seccomp is available.

Paths inside the sandbox mirror the host paths, potentially exposing your username, directory layout etc. This was done for simplicity—pull requests to solve paths anonymization are greatly appreciated!

flowchart TB Attacker[Malicious package / rogue dependency] Attacker -->|filesystem access| TryFS Attacker -->|use Linux syscalls| TrySys Attacker -->|network bind| TryNet

subgraph Threats TryFS[try to read ~/.ssh, /etc, or other host secrets] TrySys[call a forbidden / privileged syscall] TryNet[bind to a privileged port] end

TryFS -->|failure| FS TrySys -->|blocked| Seccomp[seccomp sandbox] TryNet -->|optional| Caps

subgraph Mitigations FS[Mount policy: project dir is RW; everything else is RO or absent] Seccomp[seccomp syscall whitelist SANDBOX_SECCOMP_ALLOW=] Caps[capabilities / UID mapping controlled by BWRAP_ARGS=] end

Loading

Contributing

You see a mistake—you fix it. Thanks!

Alternatives

  1. A popular alternative are the aforementioned Docker/OCI containers and manual management of their runtime. This comes free when the worked on project itself deals inContinerfiles.
  2. sandbox-run, a similar bubblewrap-based shell script to sandbox arbitrary executables.
  3. On Linux, AppArmor, even withapparmor.dapplied, doesn't ship a generic python profile, so one would go through explicit aa-exec --profile my-custom-env, but writing custom AppArmor profiles is less common than simply using containers.
  4. Firejail. An indie C project with virtually no dependencies (whichRed HatIBM has a perfectly reasonable position on) that sets up its own sandbox. I guess it's a matter of trust. Similarly to AppArmor, requires writing a custom profile.
  5. A customseccomp initialization script, executed at interpreter startup usingPYTHONSTARTUP= sitecustomizestartup hook.
  6. On macOS, sandbox-execor Apple Containerization®.

In comparison to the above, sandbox-venv is like chroot on steroids. It uses the same isolation primitives that containers use (process sandbox via Linux namespaces, isolated filesystem view), but without all of the container runtime baggage ... YMMV.