Add Python SandboxExec example.

PiperOrigin-RevId: 967520968
This commit is contained in:
Caroline Zhu
2026-08-19 18:52:41 -07:00
committed by gVisor bot
parent f5be6a6d38
commit 99ceedfd7f
3 changed files with 387 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
# gVisor SandboxExec Examples
This directory provides clear, end-to-end examples demonstrating how to use
**gVisor SandboxExec** language bindings to build secure, programmable
sandboxes.
--------------------------------------------------------------------------------
## What is gVisor?
[gVisor](https://gvisor.dev) is an application kernel that provides strong
workload isolation by intercepting application system calls in user space. Its
`runsc` runtime provides a secure sandbox boundary between untrusted code and
the host Linux kernel.
--------------------------------------------------------------------------------
## What is SandboxExec?
**SandboxExec** language bindings (available in Python, Go, and more) give
developers a direct, programmatic API to spawn and manage lightweight gVisor
sandboxes on demand. With SandboxExec, you can:
1. Create and tear down sandboxes programmatically using language-native
constructs (e.g., Python `with` statements).
2. Configure fine-grained filesystem isolation (read-only input mounts,
writable output directories, and memory-backed scratch spaces).
3. Enforce strict network isolation (`none`, `host`, or `sandbox`) using the
`NetworkMode` enum.
4. Execute commands with custom working directories, environment variables, and
per-command execution timeouts.
--------------------------------------------------------------------------------
## Examples Catalog
Each subfolder contains a standalone, well-documented example for a specific
language binding:
| Language | Directory | Description |
| :----------------- | :------------------- | :------------------------------ |
| **Python** | [`python/`](python/) | Safe code runner CLI |
: : : demonstrating mounts, timeouts, :
: : : and network isolation. :
| **Go** *(Planned)* | `go/` | Go bindings reference |
: : : implementation. :
--------------------------------------------------------------------------------
## Getting Started
Check out the language-specific folder (e.g., [`python/`](python/)) for detailed
installation instructions, prerequisites, and runnable examples.
+169
View File
@@ -0,0 +1,169 @@
# gVisor SandboxExec - Python Example: Safe Code Runner
This directory contains a complete, runnable command-line example showing how to
use the **gVisor Python SandboxExec bindings** (`gvisor`) to safely run
untrusted scripts and code snippets in an isolated sandbox.
--------------------------------------------------------------------------------
## 1. Overview
[gVisor](https://gvisor.dev) is an application kernel and OCI runtime (`runsc`)
that provides strong workload isolation by intercepting system calls in user
space.
--------------------------------------------------------------------------------
## 2. How the Python Binding Works Under the Hood
The `gvisor` Python package creates a standard Open Container Initiative (OCI)
bundle (`config.json` and `rootfs/`) and invokes `runsc` which uses the OCI
bundle to execute commands inside the sandbox. We'll walk through how to install
`runsc` in the Prerequisites section.
--------------------------------------------------------------------------------
## 3. gVisor Features Demonstrated in this Example
The `safe_runner.py` CLI script showcases the primary capabilities of the Python
SandboxExec API:
1. **Context-Managed Lifecycle (`Sandbox`)**: Automatic sandbox creation and
guaranteed cleanup of container processes and runtime directories via
Python's `with` statement.
2. **Filesystem Isolation (`Mount`)**:
* `Mount.bind(..., readonly=True)`: Mounts host scripts or dependencies as
read-only so the sandbox cannot modify them.
* `Mount.bind(..., readonly=False)`: Mounts a designated host directory to
safely collect generated outputs.
* `Mount.tmpfs("/tmp")`: Allocates an isolated, ephemeral in-memory
scratch space.
* `Mount.proc("/proc")`: Provides an isolated process filesystem view.
3. **Network Isolation (`NetworkMode`)**:
* `NetworkMode.NONE` (`"none"`, default): Disables network access
completely to prevent untrusted code from exfiltrating data or reaching
network services. Compatible with rootless/non-root execution.
* `NetworkMode.HOST` (`"host"`): Shares the host network namespace,
allowing outbound and inbound connectivity in rootless environments.
* `NetworkMode.SANDBOX` (`"sandbox"`): Runs a dedicated gVisor netstack in
an isolated network namespace (requires host root UID 0).
4. **Execution Timeouts (`timeout=...`)**: Prevents infinite loops or
denial-of-service by enforcing strict per-command execution time limits.
5. **Environment & Working Directory Control**: Allows configuring default or
per-command working directories and environment variables.
--------------------------------------------------------------------------------
## 4. Prerequisites
To run sandboxes, install the `runsc` binary and point the Python bindings to
it.
### Installing `runsc`
1. **Install `runsc`**: Follow the
[gVisor installation guide](https://gvisor.dev/docs/user_guide/install/).
2. **Set `RUNSC_PATH`**: Point the `RUNSC_PATH` environment variable to your
`runsc` executable:
```bash
export RUNSC_PATH=/path/to/runsc
```
The `gvisor` package requires `RUNSC_PATH` to find and run `runsc`.
--------------------------------------------------------------------------------
## 5. Installing the Python Package
### Option A: From PyPI
```bash
pip install gvisor
```
### Option B: Building from Source (Local Wheel)
```bash
# from sandboxexec/sandbox/python
python3 -m build
pip install dist/gvisor-*.whl
```
--------------------------------------------------------------------------------
## 6. CLI Usage & Recipes
`safe_runner.py` provides a simple command-line interface for executing code
safely.
### CLI Flags
| Flag | Description | Default |
| :-------------------- | :---------------------------------------- | :------ |
| `-c`, `--code` | Inline Python or shell code to execute | None |
| `-s`, `--script` | Path to a host script file to execute | None |
| `-o`, `--output-dir` | Host directory to mount read-write for | None |
: : output files (`/output`) : :
| `-t`, `--timeout` | Maximum execution time in seconds | `5.0` |
| `-n`, `--network` | Sandbox network mode (`none`, `host`, | `none` |
: : `sandbox`) : :
| `-w`, `--working-dir` | Initial container working directory | `/` |
| `-e`, `--env` | Environment variables in `KEY=VAL` format | None |
--------------------------------------------------------------------------------
### Example Recipes
#### 1. Run an Inline Code Snippet
```bash
python3 safe_runner.py --code "print('Hello from inside gVisor!')"
```
#### 2. Run a Script File Safely
```bash
# Create a sample script
echo "import os; print('Running in PID namespace, PID:', os.getpid())" > sample.py
# Execute in sandbox
python3 safe_runner.py --script sample.py
```
#### 3. Capture Output Artifacts
```bash
mkdir -p ./results
# Run a script that writes to /output
python3 safe_runner.py \
--code "with open('/output/result.txt', 'w') as f: f.write('Processed successfully\n')" \
--output-dir ./results
cat ./results/result.txt
```
#### 4. Guard Against Runaway Code with Timeouts
```bash
# Terminated automatically after 2 seconds
python3 safe_runner.py --code "import time; time.sleep(10)" --timeout 2
```
#### 5. Verify Default Network Isolation
```bash
# Fails because networking is disabled by default (NetworkMode.NONE)
python3 safe_runner.py --code "import urllib.request; urllib.request.urlopen('https://google.com')"
```
#### 6. Enable Outbound Network Access in Rootless Environments
```bash
# Outbound connectivity works via host network sharing (NetworkMode.HOST)
python3 safe_runner.py \
--code "import urllib.request; print(urllib.request.urlopen('https://google.com').status)" \
--network host
```
+165
View File
@@ -0,0 +1,165 @@
# Copyright 2026 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Example CLI demonstrating safe script execution using gVisor SandboxExec."""
import argparse
import os
import sys
from typing import List, Optional, Tuple, Union
from gvisor import sandbox
def run_in_sandbox(
code: Optional[str] = None,
script_path: Optional[str] = None,
output_dir: Optional[str] = None,
timeout: float = 5.0,
network: Union[sandbox.NetworkMode, str] = sandbox.NetworkMode.NONE,
working_dir: str = "/",
env: Optional[List[str]] = None,
interpreter: str = "python3",
) -> Tuple[int, str, str]:
"""Executes code or a script file inside an isolated gVisor sandbox.
Args:
code: Inline code string to execute.
script_path: Path to a host script file to execute.
output_dir: Host directory mounted read-write to /output inside sandbox.
timeout: Maximum execution timeout in seconds.
network: Network mode ('none', 'sandbox', 'host', or NetworkMode enum).
working_dir: Initial working directory inside container.
env: List of environment variables in KEY=VALUE format.
interpreter: Command/interpreter to run code or script (default: 'python3').
Returns:
A tuple of (exit_code, stdout, stderr).
"""
mounts = [
sandbox.Mount.tmpfs("/tmp"),
sandbox.Mount.proc("/proc"),
]
if output_dir:
os.makedirs(output_dir, exist_ok=True)
mounts.append(
sandbox.Mount.bind(
source=os.path.abspath(output_dir),
destination="/output",
readonly=False,
)
)
if script_path:
abs_script = os.path.abspath(script_path)
script_dir, script_name = os.path.split(abs_script)
mounts.append(
sandbox.Mount.bind(
source=script_dir,
destination="/scripts",
readonly=True,
)
)
cmd = [interpreter, f"/scripts/{script_name}"]
elif code:
cmd = [interpreter, "-c", code]
else:
raise ValueError("Either code or script_path must be provided.")
try:
with sandbox.Sandbox(
network=network,
working_dir=working_dir,
mounts=mounts,
env=env,
) as sb:
stdout, stderr = sb.exec(cmd[0], *cmd[1:], timeout=timeout)
return 0, stdout, stderr
except sandbox.Error as e:
return 1, "", str(e)
def main():
parser = argparse.ArgumentParser(
description="Run untrusted code in an isolated gVisor sandbox."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-c", "--code", help="Inline code snippet to execute.")
group.add_argument(
"-s", "--script", help="Path to host script file to execute."
)
parser.add_argument(
"-i",
"--interpreter",
default="python3",
help="Interpreter binary to run with (default: python3).",
)
parser.add_argument(
"-o", "--output-dir", help="Host directory mounted read-write to /output."
)
parser.add_argument(
"-t",
"--timeout",
type=float,
default=5.0,
help="Timeout in seconds (default: 5.0).",
)
parser.add_argument(
"-n",
"--network",
default="none",
choices=["none", "sandbox", "host"],
help=(
"Network mode: 'none' (default, isolated), 'sandbox' (isolated"
" netstack, requires root), 'host' (shares host network)."
),
)
parser.add_argument(
"-w",
"--working-dir",
default="/",
help="Working directory in container (default: /).",
)
parser.add_argument(
"-e",
"--env",
action="append",
help="Environment variable (KEY=VALUE). Can be repeated.",
)
args = parser.parse_args()
code, stdout, stderr = run_in_sandbox(
code=args.code,
script_path=args.script,
output_dir=args.output_dir,
timeout=args.timeout,
network=args.network,
working_dir=args.working_dir,
env=args.env,
interpreter=args.interpreter,
)
if stdout:
print(stdout, end="" if stdout.endswith("\n") else "\n")
if stderr:
print(f"Error: {stderr}", file=sys.stderr)
sys.exit(code)
if __name__ == "__main__":
main()