Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docker-compose-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,19 @@ services:
volumes:
- ./policy-service/tls:/usr/local/app/tls:ro
- ./policy-service/configs:/usr/local/app/configs:ro
# Uncomment for PYTHON_SANDBOX_MODE=docker:
# - /var/run/docker.sock:/var/run/docker.sock:ro
<<: *service-template
expose:
- '5006'

# Uncomment for PYTHON_SANDBOX_MODE=docker:
# python-sandbox:
# build:
# context: ./policy-service/docker/python-sandbox
# dockerfile: Dockerfile
# image: guardian/python-sandbox:latest

prometheus:
image: prom/prometheus:v2.44.0
volumes:
Expand Down
9 changes: 9 additions & 0 deletions docker-compose-production-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,19 @@ services:
volumes:
- ./policy-service/tls:/usr/local/app/tls:ro
- ./policy-service/configs:/usr/local/app/configs:ro
# Uncomment for PYTHON_SANDBOX_MODE=docker:
# - /var/run/docker.sock:/var/run/docker.sock:ro
<<: *service-template
expose:
- '5006'

# Uncomment for PYTHON_SANDBOX_MODE=docker:
# python-sandbox:
# build:
# context: ./policy-service/docker/python-sandbox
# dockerfile: Dockerfile
# image: guardian/python-sandbox:latest

prometheus:
image: prom/prometheus:v2.44.0
volumes:
Expand Down
9 changes: 9 additions & 0 deletions docker-compose-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,19 @@ services:
volumes:
- ./policy-service/tls:/usr/local/app/tls:ro
- ./policy-service/configs:/usr/local/app/configs:ro
# Uncomment for PYTHON_SANDBOX_MODE=docker:
# - /var/run/docker.sock:/var/run/docker.sock:ro
<<: *service-template
expose:
- '5006'

# Uncomment for PYTHON_SANDBOX_MODE=docker:
# python-sandbox:
# build:
# context: ./policy-service/docker/python-sandbox
# dockerfile: Dockerfile
# image: guardian/python-sandbox:latest

prometheus:
image: prom/prometheus:v2.44.0
volumes:
Expand Down
9 changes: 9 additions & 0 deletions docker-compose-quickstart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,19 @@ services:
volumes:
- ./policy-service/tls:/usr/local/app/tls:ro
- ./policy-service/configs:/usr/local/app/configs:ro
# Uncomment for PYTHON_SANDBOX_MODE=docker:
# - /var/run/docker.sock:/var/run/docker.sock:ro
<<: *service-template
expose:
- '5006'

# Uncomment for PYTHON_SANDBOX_MODE=docker:
# python-sandbox:
# build:
# context: ./policy-service/docker/python-sandbox
# dockerfile: Dockerfile
# image: guardian/python-sandbox:latest

queue-service:
image: gcr.io/hedera-registry/queue-service:${GUARDIAN_VERSION:-latest}
depends_on:
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,19 @@ services:
volumes:
- ./policy-service/tls:/usr/local/app/tls:ro
- ./policy-service/configs:/usr/local/app/configs:ro
# Uncomment for PYTHON_SANDBOX_MODE=docker:
# - /var/run/docker.sock:/var/run/docker.sock:ro
<<: *service-template
expose:
- '5006'

# Uncomment for PYTHON_SANDBOX_MODE=docker:
# python-sandbox:
# build:
# context: ./policy-service/docker/python-sandbox
# dockerfile: Dockerfile
# image: guardian/python-sandbox:latest

prometheus:
image: prom/prometheus:v2.44.0
volumes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ A new dropdown setting has been added to the Custom Logic block in the Policy Ed

#### Use Case

Choose "Python" when you want to leverage Pythons expressive syntax and advanced computation libraries for policy logic.
Choose "Python" when you want to leverage Python's expressive syntax and advanced computation libraries for policy logic.

### 2. Python Scripting Support

Expand Down Expand Up @@ -69,23 +69,172 @@ This field helps track the Guardian system version that was used to generate or
* Python execution is subject to the limitations and security constraints defined in Guardian's runtime.
{% endhint %}

### 4. Supported Python Libraries and its Versions

| Library Name | Version |
| :----------: | :-----: |
| numpy | 1.26.4 |
| scipy | 1.12.0 |
| sympy | 1.12 |
| pandas | 2.2.0 |
| pint | 0.25.1 |
| duckdb | 1.0.0 |
| sqlalchemy | 2.0.29 |
| cftime | 1.6.3 |
| matplotlib | 3.5.2 |
| seaborn | 0.13.2 |
| bokeh | 3.4.1 |
| altair | 5.3.0 |
| cartopy | 0.23.0 |
| astropy | 6.0.1 |
| statsmodels | 0.14.2 |
| networkx | 3.3 |
### 4. Supported Python Libraries

#### Installed Libraries

| Library Name | Import Name | Version |
| :----------: | :---------: | :-----: |
| numpy | `numpy` | 1.26.4 |
| scipy | `scipy` | 1.12.0 |
| sympy | `sympy` | 1.12 |
| pandas | `pandas` | 2.2.0 |
| pint | `pint` | 0.25.3 |
| cftime | `cftime` | 1.6.3 |
| astropy | `astropy` | 6.0.1 |
| statsmodels | `statsmodels` | 0.14.2 |
| networkx | `networkx` | 3.3 |
| scikit-learn | `sklearn` | 1.4.2 |
| xarray | `xarray` | 2024.3.0 |
| geopandas | `geopandas` | 0.14.3 |

{% hint style="info" %}
Library versions listed are for the default Pyodide mode. Docker mode may have newer versions as it uses native CPython with pip.
{% endhint %}

#### Docker-Only Libraries

These libraries require native C/C++ dependencies (GDAL) and are only available in Docker mode:

| Library Name | Import Name | Purpose |
| :----------: | :---------: | :------ |
| rasterio | `rasterio` | Read/write raster geospatial data (GeoTIFF, satellite imagery) |
| rioxarray | `rioxarray` | Bridge between xarray and rasterio — CRS management, reprojection |

#### Python Built-in Modules (always available)

| Module | Purpose |
| :----: | :------ |
| `calendar` | Calendar rendering, weekday calculations |
| `datetime` | Date/time types and arithmetic |
| `collections` | OrderedDict, Counter, defaultdict, namedtuple |
| `math` | Basic math functions (sin, log, sqrt, pi) |
| `copy` | Deep/shallow copy of objects |

#### Available as Transitive Dependencies (no explicit install needed)

| Library | Import Name | Purpose | Installed via |
| :-----: | :---------: | :------ | :------------ |
| python-dateutil | `dateutil` | Smart date parsing, relative deltas | pandas |
| six | `six` | Python 2/3 compatibility | pandas → python-dateutil |
| matplotlib | `matplotlib` | Data visualization | networkx (transitive) |

#### Removed Libraries (Issue #5505)

The following libraries were removed as part of sandbox hardening. They are unnecessary for computation — their data processing features are covered by pandas, and they were designed to work with external resources (databases, networks, web servers) that are not available in the sandbox.

| Library | Reason for Removal |
| :-----: | :----------------- |
| duckdb | SQL database engine; covered by pandas |
| sqlalchemy | SQL toolkit/ORM; covered by pandas |
| bokeh | Visualization; unnecessary for computation |
| altair | Visualization; unnecessary for computation |
| cartopy | Map visualization; unnecessary for computation |
| seaborn | Visualization; unnecessary for computation |

### 5. Execution Modes

Guardian supports two execution modes for Python custom logic blocks, controlled by the `PYTHON_SANDBOX_MODE` environment variable.

#### Pyodide Mode (default)

The default mode runs Python code using Pyodide (CPython compiled to WebAssembly) inside a Node.js Worker Thread.

* **No additional infrastructure required** — works out of the box
* **Startup:** packages are pre-cached at policy-service startup for faster execution
* **Limitation:** some C-extension packages (rasterio, rioxarray) are unavailable in WASM

**Configuration:** No env var needed (default), or explicitly set `PYTHON_SANDBOX_MODE=pyodide`

#### Docker Mode (experimental)

Runs Python code in an ephemeral Docker container using native CPython 3.12. Provides OS-level isolation.

**Container security flags:**

| Flag | Purpose |
| :--- | :------ |
| `--network=none` | All network access blocked |
| `--cap-drop=ALL` | No Linux capabilities |
| `--security-opt=no-new-privileges` | Prevent privilege escalation |
| `--read-only` | Read-only root filesystem |
| `--user=1001:1001` | Non-root execution |
| `--log-driver=none` | No container log storage |
| `--pull=never` | Never pull untrusted images |
| `--tmpfs /tmp` | Writable scratch space (noexec, destroyed on exit) |

**Setup:**

1. Build the sandbox image:
```bash
docker buildx build -t guardian/python-sandbox:latest policy-service/docker/python-sandbox
```
Or via docker-compose:
```bash
docker compose -f docker-compose-build.yml build python-sandbox
```

2. Set the environment variable in policy-service configuration:
```
PYTHON_SANDBOX_MODE=docker
```

3. Ensure the policy-service container has Docker socket access. For docker-compose deployments, uncomment the Docker socket volume mount and the `python-sandbox` image build definition in the relevant compose file:
- `docker-compose-build.yml`, `docker-compose.yml`, `docker-compose-production.yml`, `docker-compose-production-build.yml`, `docker-compose-quickstart.yml` — uncomment the Docker socket volume and `python-sandbox` image build

{% hint style="warning" %}
Docker mode requires the Docker daemon to be available. The policy-service needs access to the Docker socket to spawn sandbox containers. For production deployments, consider using a Docker API proxy to restrict operations to sandbox container management only.
{% endhint %}

### 6. Sandbox Security

Python code in custom logic blocks runs in a sandboxed environment. The following restrictions are enforced:

#### Pyodide Mode Restrictions

| Restriction | Details |
| :---------- | :----- |
| JavaScript bridge (`from js import ...`) | Blocked via module stub + import hook |
| `pyodide.http` network access | Blocked via module stub + import hook |
| `os.system`, `os.popen`, `os.exec*`, `os.spawn*` | All replaced with blocked function |
| `subprocess.run`, `subprocess.Popen` | All execution functions replaced |
| `socket.socket`, `socket.connect` | All networking functions replaced |
| `os.environ` (secrets) | Cleared on startup (only HOME/PATH kept) |
| `importlib.reload` | Blocked to prevent undoing patches |
| `builtins.__import__` | Guarded via closure to prevent bypass |
| Execution timeout | Configurable via `PYTHON_SANDBOX_TIMEOUT_MS` (default 120s) |

#### Docker Mode Restrictions

All restrictions above are provided by Docker container isolation:

* **Network:** `--network=none` blocks all connections (verified: HTTP requests fail)
* **File system:** `--read-only` + no host mounts — container sees only its own minimal filesystem
* **Processes:** commands run inside isolated container only, destroyed after execution
* **Environment:** `os.environ` cleared before user code runs
* **Resources:** container destroyed with `--rm` after each execution

#### Vulnerability Comparison

| Attack Vector | Pyodide Mode | Docker Mode |
| :------------ | :----------- | :---------- |
| Network requests | Blocked (Python-level) | Blocked (OS-level `--network=none`) |
| Host filesystem access | Blocked (WASM virtual FS) | Blocked (`--read-only`, no mounts) |
| Process execution | Blocked (functions replaced) | Runs inside isolated container |
| `os.environ` secrets | Cleared | Cleared + container has own env |
| `ctypes` C function calls | Not blocked (needed by pandas, harmless in WASM) | Runs inside isolated container |
| Python introspection bypass | Possible (known limitation) | Irrelevant — container is isolated |
| Memory/CPU exhaustion | Timeout only | Timeout + container destroyed |

{% hint style="info" %}
* **Pyodide mode** is suitable when users are trusted or semi-trusted. It blocks common attack vectors but is vulnerable to sophisticated Python introspection attacks.
* **Docker mode** is suitable for untrusted code. OS-level isolation makes Python-level bypasses irrelevant — the container has no network, no host access, and is destroyed after execution.
{% endhint %}

### 7. Configuration Reference

| Environment Variable | Default | Description |
| :------------------- | :------ | :---------- |
| `PYTHON_SANDBOX_MODE` | `pyodide` | Execution mode: `pyodide` (default) or `docker` |
| `PYTHON_SANDBOX_TIMEOUT_MS` | `120000` | Execution timeout in milliseconds (both modes) |
| `PYTHON_SANDBOX_IMAGE` | `guardian/python-sandbox:latest` | Docker sandbox image name (Docker mode only) |
3 changes: 3 additions & 0 deletions policy-service/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ COPY --link --from=deps /usr/local/app/node_modules node_modules/
COPY --link --from=deps /usr/local/app/package.json ./
COPY --link --from=build /usr/local/app/dist dist/

# Allow node user to write Pyodide package cache (warmup downloads wheels at startup)
RUN chown -R node:node node_modules/pyodide/ 2>/dev/null || true

# Change the user to node
USER node

Expand Down
6 changes: 6 additions & 0 deletions policy-service/docker/python-sandbox/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
npm-debug.log
.git
.gitignore
README.md
*.md
21 changes: 21 additions & 0 deletions policy-service/docker/python-sandbox/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM python:3.12.10-slim

WORKDIR /sandbox

# Install system dependencies for geospatial libraries
RUN apt-get update && apt-get install -y --no-install-recommends \
gdal-bin libgdal-dev \
&& rm -rf /var/lib/apt/lists/*

# Install Python packages (pinned to major.minor for reproducibility)
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Copy entrypoint
COPY entrypoint.py ./

# Create non-root user and fix permissions
RUN adduser --disabled-password --uid 1001 sandbox && chown -R sandbox:sandbox /sandbox
USER sandbox

ENTRYPOINT ["python3", "entrypoint.py"]
Loading
Loading