Status: v1.0.0 baseline draft (active, not a release-ready claim) Last updated: 2026-05-16
This document is an operator-focused security guide for Ruff's host-effect runtime APIs.
Ruff is not a sandbox. Running Ruff code is equivalent to running local code with the current process privileges unless you explicitly apply capability restrictions and external isolation controls.
Release readiness remains tracked in ROADMAP.md.
Provide deterministic runtime controls and failure contracts for host-effect APIs so operators can run trusted automation safely and run untrusted scripts with explicit least-privilege policy.
- Runtime capability gating for host-effect APIs (
--untrusted,--allow-*). - Deterministic runtime-denied/misuse errors.
- Built-in guardrails for selected high-risk surfaces (filesystem/network/process/archive/static serving).
- Full process/container sandboxing.
- Kernel-level isolation.
- Guaranteed data exfiltration prevention without host firewall/policy controls.
- Multi-tenant isolation in one Ruff process.
ruff run and ruff test-run default to trusted mode when no capability flags are provided.
Trusted mode means all host-effect capabilities are enabled and scripts run with ambient user permissions.
Use --untrusted to switch to deny-by-default mode.
In --untrusted mode, host-effect calls fail unless explicitly re-enabled via --allow-* flags.
--allow-*flags imply restricted baseline with only requested capabilities enabled.--allow-allforce-enables all capabilities and should be treated as trusted mode.
| Flag | Capability | Typical APIs Unlocked | Primary Risk |
|---|---|---|---|
--allow-fs-read |
Filesystem read | read_file, read_lines, read_binary_file, metadata/path reads |
Data disclosure |
--allow-fs-write |
Filesystem write | write_file, append_file, write_binary_file, mkdir/write helpers |
Data tampering |
--allow-fs-delete |
Filesystem delete | delete_file, delete-adjacent flows |
Data loss |
--allow-process-exec |
Direct process execution | spawn_process, pipe_commands |
Arbitrary command execution |
--allow-shell-exec |
Shell-string execution | execute, execute_status |
Shell injection/command abuse |
--allow-env-read |
Environment read | env, env_list, related env readers |
Secret leakage |
--allow-env-write |
Environment write | env_set and env mutation |
Process/session tampering |
--allow-net-client |
Outbound network | http_get/post/request, TCP/UDP client operations |
Data exfiltration/SSRF-style pivots |
--allow-net-server |
Listener/network server | http_server.listen, server-side sockets |
Local service exposure |
--allow-net |
Net client + server | Union of network-client/network-server surfaces | Combined network risk |
--allow-database |
Database access | db_connect, query/transaction helpers |
Unauthorized data access |
--allow-clock |
Clock/time | now, timestamp helpers |
Timing side-channel support |
--allow-random |
Randomness | random, random helpers |
Nondeterministic workflows |
--allow-all |
All capabilities | All host-effect APIs | Full ambient-host risk |
Per-function capability metadata is maintained in docs/STANDARD_LIBRARY.md and contract-tested in tests/stdlib_reference_contract.rs.
Relevant APIs: execute, execute_status, spawn_process, pipe_commands.
Policy boundaries:
spawn_processandpipe_commandsrequire--allow-process-exec.executeandexecute_statusrequire--allow-shell-exec.
Operational guidance:
- Prefer argv-array APIs (
spawn_process,pipe_commands) over shell strings. - Never pass untrusted input directly into shell command strings.
- Keep
inherit_envdisabled unless explicitly required. - Use
timeout_ms,max_output_bytes, and env allow/deny controls for bounded execution.
Relevant APIs: HTTP/TCP/UDP helpers.
Policy boundaries:
- Outbound operations require
--allow-net-client. - Listener/server operations require
--allow-net-server. --allow-netenables both.
Built-in guardrails:
- TCP connect timeout:
10000 ms - TCP/UDP read-write timeout:
30000 ms - HTTP client timeout:
30000 ms - Max network response/receive body:
8 MiB - HTTP native URL validation:
- only
httpandhttpsschemes are accepted. - malformed URLs and missing hosts fail early with deterministic diagnostics before request execution.
- only
- Outbound destination policy mode (env-controlled):
RUFF_NET_DESTINATION_POLICY=allow_all(default): preserves backward-compatible permissive destination behavior.RUFF_NET_DESTINATION_POLICY=deny_private: blocks outbound HTTP/TCP/UDP client destinations that resolve to loopback/private/link-local/multicast/unspecified IP ranges.RUFF_ALLOW_PRIVATE_NETWORK_DESTINATIONS=1: explicit trusted-local override when strict policy mode is enabled.- Invalid policy values fail deterministically with:
Invalid RUFF_NET_DESTINATION_POLICY. - Strict-mode blocked destinations fail deterministically with:
blocked by outbound destination policy.
Operational guidance:
- Restrict egress and ingress at OS/network policy layers.
- Bind server listeners to explicit interfaces and non-privileged ports.
- Treat large unvalidated payloads as hostile by default.
html_response(...) is a raw response-construction helper. It does not sanitize or escape attacker-controlled content.
Threat model boundary:
- If untrusted input is interpolated into HTML without escaping, downstream browsers can execute injected markup/script.
- Ruff runtime/server controls do not rewrite response bodies for XSS safety.
Safer usage patterns:
- Prefer JSON responses (
http_response) for untrusted data APIs. - Keep user-controlled data in text nodes only, and escape at least
&,<,>,", and'before interpolation. - For rich HTML pages, render from trusted templates and pass pre-sanitized content only.
Example defensive escaping helper (Ruff script-level):
func escape_html(input) {
let out := replace(input, "&", "&")
out := replace(out, "<", "<")
out := replace(out, ">", ">")
out := replace(out, "\"", """)
out := replace(out, "'", "'")
return out
}
Relevant APIs: read/write/delete/path/directory/archive helpers.
Policy boundaries:
- Filesystem read paths require
--allow-fs-read. - Filesystem write paths require
--allow-fs-write. - Filesystem delete paths require
--allow-fs-delete.
Built-in file IO guardrails:
- Whole-file read operations capped at
8 MiB. - Write payload operations capped at
8 MiB. write_fileandwrite_binary_filerequire explicitoverwrite=truefor replacement.delete_filerejects directory paths.
unzip hardening:
- Rejects absolute paths,
..traversal, drive-prefixed names, null-byte names, and symlink entries. - Fails extraction on first unsafe entry.
- Enforces extraction limits: 1024 entries, 16 MiB per entry, 64 MiB total uncompressed bytes.
Operational guidance:
- Treat archive extraction as a high-risk write surface.
- Constrain writable roots for Ruff processes.
- Avoid running untrusted archive workflows in privileged directories.
Relevant APIs: connection/query/pool/transaction helpers.
Policy boundaries:
- Database access requires
--allow-database.
Operational guidance:
- Use least-privileged DB accounts.
- Restrict DB network reachability to required endpoints.
- Parameterize query data wherever possible.
Relevant APIs: hash/password/AES/RSA helpers.
Policy boundaries:
- Crypto helpers are not capability-gated separately today; they execute within runtime trust context.
Operational guidance:
- Keep keys and secrets out of source files.
- Use external secret management and key rotation workflows.
- Treat crypto API errors as hard failures.
ruff serve is intended for local static preview/testing and should not be treated as a hardened internet-facing platform.
Key defaults and controls:
- Root-bound canonical path checks and traversal rejection.
- Single-pass percent-decoding with malformed encoding/null-byte rejection.
- Hidden/private path blocking (
.env,.git, backup/swap-style names). - Deterministic request limits (line/header/body sizes, header count, max connections).
- Safe MIME fallback (
application/octet-stream) for unknown/extensionless paths. - Baseline response headers (
X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer). - Hardened-mode headers with
--hardened.
Operator guidance:
- Use a reverse proxy, TLS termination, and network ACLs for shared environments.
- Treat
ruff serveas preview infrastructure, not as a full production edge server.
These examples intentionally use VM-default ruff run paths. Use --interpreter only for targeted compatibility/debug isolation when diagnosing a known runtime-path divergence.
ruff run --untrusted --allow-fs-read ./script.ruffruff run --untrusted --allow-net-client ./fetch.ruffruff run --allow-all ./untrusted.ruffruff run --untrusted --allow-shell-exec ./script_that_builds_shell_strings.ruffFor high-risk or shared environments, apply host/container controls in addition to Ruff runtime capability policy:
- Run Ruff in containers or VM sandboxes with least privileges.
- Use read-only filesystems where possible; mount narrow writable directories.
- Drop Linux capabilities and apply seccomp/AppArmor/SELinux profiles.
- Apply strict outbound and inbound firewall rules.
- Use dedicated low-privilege service accounts.
- Isolate secrets from environment variables when possible.
Ruff's JIT/VM internals still require unsafe for FFI pointer boundaries and generated function-pointer execution. For v1 hardening work, the policy is:
- concentrate function-pointer
unsafeinvocation behind shared wrapper helpers - document pointer lifetime and ownership invariants at wrapper boundaries
- avoid ad hoc
unsafecallsites insrc/vm.rsexecution paths - require targeted regression tests whenever
unsafeboundaries are moved
Current hardening status:
- VM JIT function-pointer invocation is centralized through
src/jit.rswrappers:invoke_compiled_fn(...)invoke_compiled_fn_with_arg(...)
- VM callsites no longer invoke compiled JIT pointers through scattered inline
unsafeblocks.
Security boundary changes must include test updates and document updates in this file.
Primary regression suites:
cargo test --test native_api_security_boundaries
cargo test --test runtime_security
cargo test --test serve_command_integrationThese suites cover capability denial/allow behavior, archive and path safety boundaries, request-boundary handling, and deterministic failure contracts.
Cross-platform module-escape coverage strategy:
- Unix builds run a real symlink-escape integration regression in
tests/runtime_security.rs(runtime_security_rejects_module_symlink_escape). - Non-Unix environments use deterministic module-name traversal hardening coverage via
runtime_security_module_loader_rejects_parent_traversal_import_name_cross_platform(integration) plus module-loader unit contracts that reject unsafe import names before filesystem resolution. - This split avoids flaky Windows symlink privilege assumptions while preserving deterministic escape-boundary coverage for release gates.