Repository navigation
unshare
Unshare is an OS-sandbox provider that runs code in Linux namespaces with a user-mode network stack (slirp4netns) and iptables-based socket rules. It enables isolation of filesystem, network, and other resources without requiring root for the application. See the unshare(2) documentation for details.
The host daemon builds an UnshareSetupConfig (DNS, mounts, netfilter rules, named pipe path, ignore paths) and launches unshare with flags from the template plus unshare.* parameters. The command line is:
unshare <flags> -- python -m pysandboxes.remote.unshare_setup <config_path> -- python -m pysandboxes.remote.main_sandbox ...
The daemon owns the slirp4netns lifecycle: after the unshare process starts, a background thread on the host runs slirp4netns (see slirp4netns_common.py) using a PID file, signals readiness on a pipe read inside unshare_setup, then optional port forwarding via the slirp API. unshare_setup brings up the namespace (tap, routes), applies bind mounts and overlay for ignore rules, writes /etc/hosts entries for relevant OUT ALLOW hostnames, configures iptables from socket rules, and execs into main_sandbox with config delivered through a named FIFO (same mechanism as other subprocess daemons: DaemonParameters written by the host for the child to read). The host talks to the sandbox via SSE on http://localhost:PORT (forwarded through slirp). Root is not required on the host to configure firewall rules inside the namespace.
| Aspect | Detail |
|---|---|
| Isolation | Full namespace stack (user, mount, network, etc.) with user-mode networking (slirp4netns) and iptables. |
| No root | Unprivileged user namespaces; no root required for firewall rules inside the sandbox. |
| Containers | Works in Docker, Podman, and Kubernetes with appropriate capabilities (SYS_ADMIN, NET_ADMIN) and securityContext. |
| Alignment with other providers | Same API (SSE, call_in_sandbox) as bwrap/firejail; slirp watcher and host port forwards are shared with the bwrap “network filtering” path. |
| Aspect | Detail |
|---|---|
| Prerequisites | Requires kernel.unprivileged_userns_clone=1, slirp4netns, iptables; AppArmor may restrict unprivileged user namespaces. |
| Privileges in containers | Docker/Podman/Kubernetes need --privileged or added capabilities (SYS_ADMIN, NET_ADMIN) and often seccompProfile: Unconfined or equivalent. |
| Execution model | Code runs as local root inside the sandbox; some applications may behave differently. |
-
Host daemon: Builds
UnshareSetupConfig(DNS list—slirp’s resolver 10.0.2.3 is primary so resolution works through tap0; upstream host DNS may be merged when not in a simple container), hosts lines for OUT ALLOW hostnames, mounts (system paths, Python, file rules), netfilter from socket rules, ignore_paths for overlay, and the named pipe path for the sandbox runtime config. - Config hand-off: The JSON config is written to a path under the daemon’s temp directory (FIFO in normal mode, or a plain file when debug launch is enabled) so the child can read it even when the project tree is not visible after namespace setup (e.g. some Docker layouts).
-
Launch:
unshare [template flags + unshare.*] -- python -m pysandboxes.remote.unshare_setup <config> -- <main_sandbox argv>. Environment includesPYTHONPATHpointing at the project root so the child can importpysandboxesafter mount namespaces change, plusSLIRP_READY_FD/PID_FILEfor coordination with slirp4netns. -
slirp4netns (host): The daemon writes the unshare PID to a pidfile and starts the slirp watcher thread; slirp attaches to that PID’s network namespace, creates tap0, and signals readiness on the pipe.
unshare_setupblocks until readiness, then ensures the interface and default route (slirp uses 10.0.2.2 as gateway, guest address 10.0.2.100 in the shared helper constants). -
unshare_setup: Applies mounts (including chroot-style layout as configured), iptables, hosts, then the daemon writes
DaemonParametersto the FIFO formain_sandbox. -
Port forwarding: From socket rules, inbound ALLOW TCP/UDP ports (plus the SSE port) are registered with slirp’s
add_hostfwdAPI so host 127.0.0.1 reaches the guest on the same port (same mechanism as bwrap’s filtering mode). - main_sandbox: Loads the config from the pipe, applies Python guards, starts the SSE server and serves RPC.
flowchart LR
subgraph Host ["Host"]
A[python_sb / Daemon]
B[unshare]
S[slirp4netns thread]
end
subgraph Namespace ["Namespace (guest)"]
C[unshare_setup\nmounts, iptables, wait slirp]
D[main_sandbox]
E[SSE server]
end
A -->|"Launch unshare"| B
A -->|"PID + watcher"| S
S -->|"tap0 / readiness pipe"| C
B -->|"Child"| C
C -->|"Exec"| D
D --> E
A <-->|"SSE localhost + hostfwd"| E
style Host fill:#e8eef4,stroke:#2f2617
style Namespace fill:#ebe0d0,stroke:#2f2617
To use unshare, install slirp4netns and iptables, and ensure kernel.unprivileged_userns_clone is set to 1 (typically enabled by default).
# Verify permissions
sudo sysctl kernel.unprivileged_userns_clone # Must be 1
sudo sysctl kernel.apparmor_restrict_unprivileged_userns # Must be 0
# Configure permissions
sudo sysctl -w kernel.unprivileged_userns_clone=1
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
# Permanently configure permissions
echo -e "kernel.unprivileged_userns_clone = 1
kernel.apparmor_restrict_unprivileged_userns = 0" | sudo tee /etc/sysctl.d/99-userns.conf > /dev/null
sudo sysctl -p /etc/sysctl.d/99-userns.conf
# Check
unshare -U -r /bin/sh -c "whoami"
# Install slirp4netns and iptables
sudo apt install slirp4netns iptablesIf AppArmor restricts unprivileged user namespaces:
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0echo "kernel.apparmor_restrict_unprivileged_userns = 0" | sudo tee /etc/sysctl.d/60-apparmor-unprivileged.confWhen launched, code runs as local root inside the sandbox. This user has no elevated privileges on the host once directory mounts and iptables rules are active.
Note: Some applications may behave differently when running as the 'root' user.
Unshare is compatible with Docker, though it requires elevated privileges. The project uses one image per OS provider: the image for unshare is python-sb-unshare (tagged e.g. :latest or :3.12). Build it from the project root with:
make build-image-unshareThen run with the code mounted and the provider image:
docker \
run -it --rm \
--privileged \
--network bridge \
-v "$(pwd)":/app \
-w /app \
python-sb-unshare:latest \
sh -c 'pip install -e . && OS_SANDBOX=unshare python-sb -m tests.integration_tests.tst_usage'Unshare is compatible with Podman, though it requires elevated privileges. Use the same provider image as for Docker:
make build-image-unsharepodman \
run -it --rm \
--privileged \
--network bridge \
-v "$(pwd)":/app \
-w /app \
python-sb-unshare:latest \
sh -c 'pip install -e . && OS_SANDBOX=unshare python-sb -m tests.integration_tests.tst_usage'Use the provider image python-sb-unshare:latest (built with make build-image-unshare). For minikube, build the images on the host and load them into the cluster: make minikube-build-images (runs make build-images, then minikube image load for each image). For this provider only: make build-image-unshare then minikube image load python-sb-unshare:latest.
Example YAML configuration:
apiVersion: v1
kind: Pod
metadata:
name: pysandboxes-test
spec:
containers:
- name: pysandboxes-test
image: python-sb-unshare:latest
imagePullPolicy: Never # Image loaded into minikube (make minikube-build-images)
workingDir: /app
securityContext:
privileged: true # Or add SYS_ADMIN + NET_ADMIN + seccompProfile: Unconfined
capabilities:
add:
- SYS_ADMIN # Allows unshare (namespace creation)
- NET_ADMIN # Allows network configuration (ip link, etc.)
seccompProfile:
type: Unconfined # Disables the filter blocking unshare
command: [ "/bin/sh", "-c", "sleep infinity" ]
volumeMounts:
- name: code-local
mountPath: /app
- name: dev-net-tun
mountPath: /dev/net/tun # Required by slirp4netns
volumes:
- name: code-local
hostPath:
path: /mnt/pysandboxes
type: Directory
- name: dev-net-tun
hostPath:
path: /dev/net/tun
type: CharDeviceRun the tests from the host (pod stays alive; tests run via kubectl exec). Example with minikube:
minikube mount "$(pwd):/mnt/pysandboxes" &
make minikube-build-images
kubectl apply -f tests/containers_tests/kube-pysandboxes.yaml
# Then run the container test suite: make container-tests (or pytest tests/containers_tests/)
kubectl delete pod pysandboxes-testYou can add unshare-specific options in .py-sandboxes. Any line of the form unshare.<option>=<value> is appended to the unshare invocation after the template flags: non-empty values become --<option>=<value>, empty values become --<option>. See pysandboxes/templates/unshare.template and unshare_sse_daemon.py for the default flag set and how they combine with overrides.
- Make sure
kernel.unprivileged_userns_cloneis1and that AppArmor does not restrict unprivileged user namespaces before using this provider. - Install slirp4netns and iptables: socket rules are enforced by iptables inside the namespace, behind the user-mode network stack.
- In Docker, Podman or Kubernetes, grant
--privilegedor the SYS_ADMIN and NET_ADMIN capabilities, often withseccompProfile: Unconfined. - Note that code runs as local
rootinside the sandbox. Test applications that behave differently under that user.