Security and process isolation
Orvanta provides multiple layers of process isolation to protect your infrastructure from potentially malicious or buggy code execution. This page covers the isolation mechanisms available and how to configure them.
Overview
Section titled “Overview”Orvanta workers execute user-provided code in various languages. To protect the worker process and the underlying infrastructure, Orvanta implements multiple isolation strategies:
- PID Namespace Isolation — Process memory and environment variable protection (disabled by default, requires configuration)
- NSJAIL Sandboxing — Filesystem, network, and resource isolation (optional)
- Agent Workers — Workers without direct database access, communicating via the API
- Worker Groups — Logical separation of workers that can be used to run workers on separate clusters with different network/resource access
Why isolation matters
Section titled “Why isolation matters”Without proper isolation, user scripts could:
- Access parent worker process memory to extract credentials and secrets
- Read environment variables from parent processes
- Interfere with other jobs running on the same worker
- Access files outside their job directory
- Make unrestricted network connections
- Consume unlimited system resources
PID namespace isolation
Section titled “PID namespace isolation”What is PID namespace isolation?
Section titled “What is PID namespace isolation?”PID (Process ID) namespace isolation creates a separate process namespace for each job using Linux’s unshare command. This prevents jobs from:
- Seeing parent worker processes in
psoutput - Reading parent process memory via
/proc/$pid/mem - Accessing parent environment variables via
/proc/$pid/environ - Accessing parent file descriptors via
/proc/$pid/fd
Why PID isolation matters
Section titled “Why PID isolation matters”With PID isolation, the job runs in its own namespace and cannot see the parent worker process, preventing access to the worker’s memory and environment variables.
Enabling PID namespace isolation
Section titled “Enabling PID namespace isolation”To enable PID namespace isolation, you need to:
- Set the environment variable on your worker:
ENABLE_UNSHARE_PID=true- Enable privileged mode in
docker-compose.yml(required for the default--mount-procflag):
orvanta_worker: privileged: trueImportant: PID isolation is disabled by default in the main docker-compose.yml (both settings are commented out). You must manually uncomment these settings to enable it.
Step-by-step enablement
Section titled “Step-by-step enablement”In your docker-compose.yml, find the worker service and uncomment these lines:
orvanta_worker: image: ${WM_IMAGE} # ... other settings ... # Uncomment these two lines: # privileged: true # <- Uncomment this environment: - DATABASE_URL=${DATABASE_URL} - MODE=worker - WORKER_GROUP=default # - ENABLE_UNSHARE_PID=true # <- Uncomment thisAfter uncommenting:
orvanta_worker: image: ${WM_IMAGE} privileged: true # Now enabled environment: - DATABASE_URL=${DATABASE_URL} - MODE=worker - WORKER_GROUP=default - ENABLE_UNSHARE_PID=true # Now enabledRequirements
Section titled “Requirements”- Linux only — Not supported on Windows or macOS
- util-linux package — Provides the
unsharecommand (usually pre-installed) - Privileged mode — Required in Docker for the default
--mount-procflag - User namespaces — Must be enabled in kernel (check with
sysctl kernel.unprivileged_userns_cloneon Debian/Ubuntu)
Configuration
Section titled “Configuration”Default isolation flags
Section titled “Default isolation flags”By default, PID isolation uses these flags:
UNSHARE_ISOLATION_FLAGS="--user --map-root-user --pid --fork --mount-proc"Important: While --user --map-root-user enables unprivileged user namespaces, the --mount-proc flag requires privileged: true in Docker. This is necessary to provide an isolated /proc filesystem.
What each flag does:
--user --map-root-user— Creates user namespace and maps current user to root inside it--pid --fork— Creates isolated PID namespace (both flags required together)--mount-proc— Mounts isolated/procfilesystem (requires privileged mode in Docker)
Note: The --fork flag is required when using --pid. Custom configurations must include --fork for PID namespace isolation to work correctly.
Custom flags
Section titled “Custom flags”You can customize the isolation flags:
# More restrictive: add network isolation (still requires privileged mode)UNSHARE_ISOLATION_FLAGS="--user --map-root-user --pid --fork --mount-proc --net"
# Truly unprivileged: without --mount-proc (no privileged mode needed)# Note: Less isolated /proc - jobs can still see host processes in /procUNSHARE_ISOLATION_FLAGS="--user --map-root-user --pid --fork"
# Alternative: Skip user namespace (requires privileged mode)UNSHARE_ISOLATION_FLAGS="--pid --fork --mount-proc"Recommendation: Use the default flags with privileged: true for best security. Only use truly unprivileged mode if you cannot enable privileged mode and understand the security tradeoffs.
Tini for signal handling
Section titled “Tini for signal handling”When PID namespace isolation is enabled and tini is available, Orvanta uses tini as PID 1 inside the namespace. Tini properly handles signal forwarding, which ensures:
- OOM-killed processes return exit code 137 (128 + SIGKILL) instead of ambiguous errors
- Zombie processes are properly reaped
- Signals are correctly forwarded to child processes
Tini is included in the official Orvanta Docker images. If tini is not available, Orvanta falls back to running without it (with a warning about potentially incorrect OOM exit codes).
You can customize the tini path:
UNSHARE_TINI_PATH=/custom/path/to/tiniFailure behavior
Section titled “Failure behavior”If ENABLE_UNSHARE_PID=true but unshare is unavailable or fails, the worker will panic at startup with a detailed error message:
ENABLE_UNSHARE_PID is set but unshare test failed.Error: unshare: Operation not permittedFlags: --user --map-root-user --pid --fork --mount-procSolutions:• Check if user namespaces are enabled: 'sysctl kernel.unprivileged_userns_clone'• For Docker: Requires 'privileged: true' in docker-compose for --mount-proc flag• For Kubernetes/Helm: Requires 'privileged: true' in securityContext for --mount-proc flag• Try different flags via UNSHARE_ISOLATION_FLAGS env var (remove --mount-proc for unprivileged)• Alternative: Use NSJAIL instead• Disable: Set ENABLE_UNSHARE_PID=falseNote: This fail-fast behavior only occurs when ENABLE_UNSHARE_PID=true. If unset or false (the default), the worker starts normally without isolation.
Platform support
Section titled “Platform support”| Platform | Supported | Notes |
|---|---|---|
| Linux (docker-compose) | Yes | Requires manual configuration (disabled by default) |
| Linux (bare metal) | Yes | Requires util-linux package |
| Docker Desktop (Linux) | Yes | Works in Linux containers with privileged mode |
| Docker Desktop (Windows) | No | Use agent workers without direct DB access |
| Docker Desktop (macOS) | No | Set ENABLE_UNSHARE_PID=false for macOS workers |
| Kubernetes / Helm | Yes | Requires privileged: true in securityContext |
Agent workers
Section titled “Agent workers”Another approach to isolation is using agent workers. Agent workers:
- Do not have direct database access
- Communicate with the Orvanta server only via the API
- Cannot access database credentials or other workers’ data
- Provide network-level isolation from sensitive infrastructure
This approach is particularly useful when:
- You want to run workers in untrusted environments
- You need workers in different network zones without database access
- You want an additional layer of security beyond process isolation
Agent workers can be combined with PID namespace isolation or NSJAIL for defense-in-depth.
NSJAIL sandboxing
Section titled “NSJAIL sandboxing”What is NSJAIL?
Section titled “What is NSJAIL?”NSJAIL is a process isolation tool from Google that provides:
- Filesystem isolation — Jobs can only access their job directory and explicitly mounted paths
- Network restrictions — Optional network isolation
- Resource limits — CPU, memory, and process limits
- User namespace — Jobs run as unprivileged users even when worker runs as root
When is NSJAIL used?
Section titled “When is NSJAIL used?”NSJAIL is disabled by default. All Orvanta images include the nsjail binary, so no special image is required.
Enabling NSJAIL
Section titled “Enabling NSJAIL”To enable NSJAIL sandboxing, set the environment variable:
DISABLE_NSJAIL=falseWhen to enable NSJAIL:
- You need filesystem isolation beyond PID namespaces
- You want to restrict network access from jobs
- You need resource limits per job (CPU, memory)
- You’re running untrusted code and need defense-in-depth
NSJAIL configuration
Section titled “NSJAIL configuration”NSJAIL behavior is controlled by configuration files for each language. You can view the default configurations in the backend/orvanta-worker/nsjail directory of the Orvanta source (Python, Deno, Bash, and all other language configs).
These configurations control resource limits, mount points, network isolation, and other security settings. The configs are embedded into the Orvanta binary at compile time.
Without NSJAIL (default):
- Jobs have full filesystem access under the worker’s user permissions
- Jobs can read any files the worker can read
- You can still enable PID namespace isolation separately for process/memory protection
Running nsjail without privileged: true
Section titled “Running nsjail without privileged: true”By default, nsjail requires privileged: true because it creates user namespaces, which need unmasked /proc access that only privileged containers provide. However, you can run nsjail without privileged: true by disabling user namespaces.
Required environment variables:
DISABLE_NSJAIL=false— enable nsjailDISABLE_NUSER=true— disable user namespace creation
Required capabilities (minimal set):
securityContext: allowPrivilegeEscalation: true capabilities: drop: [ALL] add: - SYS_ADMIN # nsjail: PID/mount namespaces and /proc mount - SYS_RESOURCE # worker: oom_score_adj to protect worker from OOM killer - SETPCAP # nsjail: prctl(PR_SET_SECUREBITS) for capability dropping seccompProfile: type: Unconfined appArmorProfile: type: UnconfinedUsing capabilities: add: [ALL] also works if your security policy allows it.
Security tradeoff:
The only isolation you lose is UID remapping — with privileged: true, nsjail maps root outside to UID 1000 inside the jail, whereas with DISABLE_NUSER=true the jailed process runs as root (UID 0). However, this is a better tradeoff overall: privileged: true removes the container boundary entirely (full host access, all devices, unmasked /proc, no seccomp, no apparmor), so a jail escape gives full host access. With DISABLE_NUSER=true and limited capabilities, a jail escape only reaches the container, which remains sandboxed. You’re trading an inner defense layer (UID remapping) for a much stronger outer one (container boundary).
Isolation comparison
Section titled “Isolation comparison”| Feature | NSJAIL | PID Namespace | None |
|---|---|---|---|
| Filesystem isolation | Full | No | No |
| Network isolation | Optional | No | No |
| Memory protection | Yes | Yes | No |
| Process visibility | Hidden | Hidden | Visible |
| Environment protection | Yes | Yes | No |
| Resource limits | Yes | No | No |
Isolation hierarchy
Section titled “Isolation hierarchy”When multiple isolation methods are available, Orvanta uses them in this priority order:
- NSJAIL (if nsjail binary available and
DISABLE_NSJAIL=false) — Provides comprehensive isolation - PID Namespace (if
ENABLE_UNSHARE_PID=trueand unshare available) — Provides process isolation - None (if neither enabled or available) — No isolation
Important: NSJAIL and PID namespace isolation are not used simultaneously. If NSJAIL is available and enabled, it takes precedence and PID namespace isolation is not used.
Force sandboxing
Section titled “Force sandboxing”Instance-level setting (job_isolation)
Section titled “Instance-level setting (job_isolation)”The job_isolation instance setting can be set to nsjail_sandboxing to enable NSJAIL sandboxing for all jobs across the instance, regardless of the per-worker DISABLE_NSJAIL environment variable. Sandboxing is enabled when either job_isolation is set to nsjail_sandboxing or DISABLE_NSJAIL=false — neither overrides the other.
Configure it from Instance settings or via the Infrastructure as code YAML configuration.
When job_isolation is set to nsjail_sandboxing but the NSJAIL binary is not available on a worker, all jobs on that worker will fail until NSJAIL is installed or the setting is changed. NSJAIL availability is always probed at worker startup regardless of DISABLE_NSJAIL.
All language executors (Python, TypeScript/Bun, Deno, Go, Bash, Rust, C#, Java, Ansible, PHP, Ruby) respect this setting through a centralized is_sandboxing_enabled() check.
Per-script sandbox annotation
Section titled “Per-script sandbox annotation”You can enable nsjail sandboxing on a per-script basis using the sandbox annotation. This works for Python, TypeScript (Bun and Deno), and Bash scripts. When present, the script runs inside an nsjail sandbox even if sandboxing is not enabled globally.
The script will fail with a clear error if nsjail is not available on the worker.
#sandboxecho "This script runs inside an NSJAIL sandbox"# sandboxdef main(): return "sandboxed Python"// sandboxexport async function main() { return "sandboxed TypeScript";}The annotation is logged as “sandbox mode (nsjail)” in job execution logs.
The sandbox annotation can be combined with volume annotations to run scripts with persistent file storage inside a sandbox.
Recommended configurations
Section titled “Recommended configurations”PID namespace isolation (recommended for production)
Section titled “PID namespace isolation (recommended for production)”To enable PID namespace isolation, add to your docker-compose.yml:
orvanta_worker: privileged: true environment: - ENABLE_UNSHARE_PID=trueWhy enable this: Protects worker credentials from being accessed by jobs through process memory and environment variables. This is a critical security feature for production deployments.
NSJAIL sandboxing (maximum security)
Section titled “NSJAIL sandboxing (maximum security)”environment: - DISABLE_NSJAIL=falseProvides comprehensive isolation including filesystem, network, and resource limits.
Security best practices
Section titled “Security best practices”- Enable isolation before production — Isolation is disabled by default. You should manually enable either NSJAIL or PID isolation before deploying to production
- Use PID isolation at minimum — At a minimum, enable PID namespace isolation with
ENABLE_UNSHARE_PID=trueandprivileged: true - Consider NSJAIL for untrusted code — Use NSJAIL if you run code from untrusted sources or need filesystem isolation
- Consider agent workers — For sensitive environments, use agent workers that don’t have direct database access and communicate only via the API
- Test your isolation — Verify isolation is working by checking worker logs for isolation status messages
- Use worker groups — Separate untrusted workloads onto dedicated workers with stricter isolation, or run workers on separate clusters with different network/resource access
- Keep systems updated — Ensure kernel and util-linux are up to date
- Review user permissions — Limit who can create scripts in your Orvanta instance using roles and permissions
Troubleshooting
Section titled “Troubleshooting”Worker fails to start with “unshare: Operation not permitted”
Section titled “Worker fails to start with “unshare: Operation not permitted””Cause: User namespaces are disabled in the kernel.
Solution:
# Check if user namespaces are enabledsysctl kernel.unprivileged_userns_clone
# Enable if disabled (requires root)sysctl -w kernel.unprivileged_userns_clone=1
# Make permanent (add to /etc/sysctl.conf)echo "kernel.unprivileged_userns_clone=1" >> /etc/sysctl.confDocker container: “unshare: unshare failed: Operation not permitted”
Section titled “Docker container: “unshare: unshare failed: Operation not permitted””Cause: The --mount-proc flag requires privileged mode in Docker.
Solution: Enable privileged mode in docker-compose.yml:
orvanta_worker: privileged: true environment: - ENABLE_UNSHARE_PID=trueAlternative: If you cannot use privileged mode, remove --mount-proc from the flags (less secure):
orvanta_worker: environment: - ENABLE_UNSHARE_PID=true - UNSHARE_ISOLATION_FLAGS=--user --map-root-user --pid --forkNote: Without --mount-proc, jobs may still be able to see host processes in /proc.
Kubernetes/Helm: unshare fails
Section titled “Kubernetes/Helm: unshare fails”Cause: The --mount-proc flag requires privileged mode.
Solution: Enable privileged mode in the pod spec:
securityContext: privileged: trueAnd set the environment variable:
env: - name: ENABLE_UNSHARE_PID value: 'true'For Helm deployments, set the appropriate values to enable privileged mode and the environment variable.
Windows workers fail to start
Section titled “Windows workers fail to start”Cause: PID isolation is Linux-only.
Solution: Set ENABLE_UNSHARE_PID=false for Windows workers.
Jobs fail with “Cannot allocate memory”
Section titled “Jobs fail with “Cannot allocate memory””Cause: Insufficient resources for isolation overhead.
Solution:
- Increase worker memory limits
- Reduce number of concurrent jobs
- Use
DISABLE_NSJAIL=trueand rely on PID isolation only
AWS EKS with Bottlerocket AMI
Section titled “AWS EKS with Bottlerocket AMI”Cause: Bottlerocket AMI sets user.max_user_namespaces=0 by default, which prevents user namespace creation required for PID isolation and NSJAIL.
Solutions:
-
Switch to a different AMI (recommended): Use Amazon Linux 2023 or Amazon Linux 2 instead of Bottlerocket for your EKS node groups.
-
Disable PID isolation: Set
disableUnsharePid: truein Helm values (global or per-worker-group). Note: This reduces security isolation. -
Configure Bottlerocket kernel parameters: Use a custom launch template with user data to increase
max_user_namespaces:
[settings.kernel.sysctl]"user.max_user_namespaces" = "65536"See the AWS EKS launch template documentation for details.
Related documentation
Section titled “Related documentation”- Worker Groups — Logical worker separation
- Agent Workers — Workers without direct database access
- Dedicated Workers — Workers assigned to specific scripts
- Self-Hosting — Deployment configuration
- Environment Variables — All worker configuration options
- Docker — Container-based execution
- Instance Settings — Instance-level configuration
- Roles and Permissions — Access control
- Audit Logs — Activity tracking and compliance
- Workspace Secret Encryption — Secret encryption at rest