mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Add Python SandboxExec example.
PiperOrigin-RevId: 967520968
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user