From e764a26daeaeb2d47d51948659deaa82f9b0b835 Mon Sep 17 00:00:00 2001 From: Octavian Sima Date: Tue, 31 Mar 2026 16:48:01 -0700 Subject: [PATCH 1/6] feat(sandbox): add Factory Droid sandbox image Signed-off-by: Octavian Sima --- sandboxes/droid/Dockerfile | 28 ++++++++ sandboxes/droid/README.md | 28 ++++++++ sandboxes/droid/policy.yaml | 132 ++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 sandboxes/droid/Dockerfile create mode 100644 sandboxes/droid/README.md create mode 100644 sandboxes/droid/policy.yaml diff --git a/sandboxes/droid/Dockerfile b/sandboxes/droid/Dockerfile new file mode 100644 index 00000000..91ecd1ef --- /dev/null +++ b/sandboxes/droid/Dockerfile @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1.4 + +# SPDX-License-Identifier: Apache-2.0 + +# Factory Droid sandbox image for OpenShell +# +# Builds on the community base sandbox and adds Factory's Droid CLI. +# Build: docker build -t openshell-droid --build-arg BASE_IMAGE=openshell-base . +# Run: openshell sandbox create --from droid + +ARG BASE_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest +FROM ${BASE_IMAGE} + +USER root + +# Install Droid CLI (pinned for reproducibility) +RUN npm install -g droid@0.90.0 + +# Copy sandbox policy +COPY policy.yaml /etc/openshell/policy.yaml + +# Create Droid config directory +RUN mkdir -p /sandbox/.factory && \ + chown sandbox:sandbox /sandbox/.factory + +USER sandbox + +ENTRYPOINT ["/bin/bash"] diff --git a/sandboxes/droid/README.md b/sandboxes/droid/README.md new file mode 100644 index 00000000..7f92d681 --- /dev/null +++ b/sandboxes/droid/README.md @@ -0,0 +1,28 @@ +# Factory Droid Sandbox + +OpenShell sandbox image pre-configured with [Factory Droid CLI](https://docs.factory.ai/) for AI-powered software engineering. + +## What's Included + +- **Droid CLI** (`droid@0.90.0`) — Factory's AI coding agent +- Everything from the [base sandbox](../base/README.md) + +## Build + +```bash +docker build -t openshell-droid . +``` + +To build against a specific base image: + +```bash +docker build -t openshell-droid --build-arg BASE_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest . +``` + +## Usage + +### Create a sandbox + +```bash +openshell sandbox create --from droid +``` diff --git a/sandboxes/droid/policy.yaml b/sandboxes/droid/policy.yaml new file mode 100644 index 00000000..243eb766 --- /dev/null +++ b/sandboxes/droid/policy.yaml @@ -0,0 +1,132 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +version: 1 + +# --- Sandbox setup configuration (queried once at startup) --- + +filesystem_policy: + include_workdir: true + read_only: + - /usr + - /lib + - /proc + - /dev/urandom + - /app + - /etc + - /var/log + read_write: + - /sandbox + - /tmp + - /dev/null + +landlock: + compatibility: best_effort + +process: + run_as_user: sandbox + run_as_group: sandbox + +# --- Network policies (queried per-CONNECT request) --- +# +# Each named policy maps a set of allowed (binary, endpoint) pairs. +# Binary identity is resolved via /proc/net/tcp inode lookup + /proc/{pid}/exe. +# Ancestors (/proc/{pid}/status PPid walk) and cmdline paths are also matched. +# SHA256 integrity is enforced in Rust via trust-on-first-use, not here. + +network_policies: + + # --- Factory Droid core infrastructure --- + droid: + name: droid + endpoints: + # Main API / LLM proxy + - { host: api.factory.ai, port: 443 } + # Auth, OAuth callbacks, CLI updates + - { host: app.factory.ai, port: 443 } + # Droid Computers relay (HTTPS + WSS) + - { host: relay.factory.ai, port: 443 } + # Error tracking + - { host: "*.ingest.us.sentry.io", port: 443 } + - { host: "*.ingest.sentry.io", port: 443 } + binaries: + - { path: "/usr/lib/node_modules/droid/**" } + - { path: /usr/local/bin/droid } + - { path: /usr/bin/node } + + # --- BYOK: direct model provider access --- + droid_byok: + name: droid-byok + endpoints: + - { host: api.anthropic.com, port: 443 } + - { host: api.openai.com, port: 443 } + - { host: generativelanguage.googleapis.com, port: 443 } + - { host: "*.googleapis.com", port: 443 } + - { host: api.groq.com, port: 443 } + - { host: api.fireworks.ai, port: 443 } + - { host: api.deepinfra.com, port: 443 } + - { host: openrouter.ai, port: 443 } + - { host: api-inference.huggingface.co, port: 443 } + binaries: + - { path: "/usr/lib/node_modules/droid/**" } + - { path: /usr/local/bin/droid } + - { path: /usr/bin/node } + + # --- GitHub (full read + write access) --- + github: + name: github + endpoints: + - host: github.com + port: 443 + protocol: rest + tls: terminate + enforcement: enforce + rules: + - allow: + method: GET + path: "/**/info/refs*" + - allow: + method: POST + path: "/**/git-upload-pack" + - allow: + method: POST + path: "/**/git-receive-pack" + - host: api.github.com + port: 443 + protocol: rest + tls: terminate + enforcement: enforce + binaries: + - { path: /usr/bin/git } + - { path: /usr/bin/gh } + - { path: "/usr/lib/node_modules/droid/**" } + - { path: /usr/bin/node } + + # --- npm registry (MCP server installs) --- + npm: + name: npm + endpoints: + - { host: registry.npmjs.org, port: 443 } + binaries: + - { path: /usr/bin/node } + - { path: /usr/local/bin/npm } + + # --- PyPI (Python package installs) --- + pypi: + name: pypi + endpoints: + - { host: pypi.org, port: 443 } + - { host: files.pythonhosted.org, port: 443 } + - { host: github.com, port: 443 } + - { host: objects.githubusercontent.com, port: 443 } + - { host: api.github.com, port: 443 } + - { host: downloads.python.org, port: 443 } + binaries: + - { path: /sandbox/.venv/bin/python } + - { path: /sandbox/.venv/bin/python3 } + - { path: /sandbox/.venv/bin/pip } + - { path: /app/.venv/bin/python } + - { path: /app/.venv/bin/python3 } + - { path: /app/.venv/bin/pip } + - { path: /usr/local/bin/uv } + - { path: "/sandbox/.uv/python/**" } From b4e0241b42805f7cd6dbb480f609ce5e13756a5d Mon Sep 17 00:00:00 2001 From: Octavian Sima Date: Wed, 1 Apr 2026 10:18:25 -0700 Subject: [PATCH 2/6] add read-only network policy --- sandboxes/droid/policy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/sandboxes/droid/policy.yaml b/sandboxes/droid/policy.yaml index 243eb766..052f2937 100644 --- a/sandboxes/droid/policy.yaml +++ b/sandboxes/droid/policy.yaml @@ -96,6 +96,7 @@ network_policies: protocol: rest tls: terminate enforcement: enforce + access: read-only binaries: - { path: /usr/bin/git } - { path: /usr/bin/gh } From e6da62283c73d5bb9b81ab11aa99207728dcf594 Mon Sep 17 00:00:00 2001 From: Octavian Sima Date: Wed, 1 Apr 2026 10:37:25 -0700 Subject: [PATCH 3/6] more network policy allows --- sandboxes/droid/policy.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sandboxes/droid/policy.yaml b/sandboxes/droid/policy.yaml index 052f2937..5be55114 100644 --- a/sandboxes/droid/policy.yaml +++ b/sandboxes/droid/policy.yaml @@ -44,8 +44,16 @@ network_policies: - { host: api.factory.ai, port: 443 } # Auth, OAuth callbacks, CLI updates - { host: app.factory.ai, port: 443 } + # WorkOS authentication (device code / OAuth login flow) + - { host: api.workos.com, port: 443 } # Droid Computers relay (HTTPS + WSS) - { host: relay.factory.ai, port: 443 } + # CLI auto-update CDN + - { host: downloads.factory.ai, port: 443 } + # Documentation / changelog + - { host: docs.factory.ai, port: 443 } + # Customer OTEL telemetry + - { host: "*.run.app", port: 443 } # Error tracking - { host: "*.ingest.us.sentry.io", port: 443 } - { host: "*.ingest.sentry.io", port: 443 } From 9dd9878d227763406a89ed1743ee200f3fac580d Mon Sep 17 00:00:00 2001 From: Octavian Sima Date: Fri, 3 Apr 2026 10:14:08 -0700 Subject: [PATCH 4/6] fix --- sandboxes/droid/Dockerfile | 1 + sandboxes/droid/README.md | 66 +++++++++++++++++++++++++++++++++++-- sandboxes/droid/policy.yaml | 4 +-- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/sandboxes/droid/Dockerfile b/sandboxes/droid/Dockerfile index 91ecd1ef..b40e16e5 100644 --- a/sandboxes/droid/Dockerfile +++ b/sandboxes/droid/Dockerfile @@ -1,5 +1,6 @@ # syntax=docker/dockerfile:1.4 +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # Factory Droid sandbox image for OpenShell diff --git a/sandboxes/droid/README.md b/sandboxes/droid/README.md index 7f92d681..c594e333 100644 --- a/sandboxes/droid/README.md +++ b/sandboxes/droid/README.md @@ -21,8 +21,70 @@ docker build -t openshell-droid --build-arg BASE_IMAGE=ghcr.io/nvidia/openshell- ## Usage -### Create a sandbox +### 1. Set up inference routing + +Direct access to external inference endpoints is blocked inside OpenShell sandboxes (SSRF protection). Use OpenShell's `inference.local` routing instead: + +```bash +# Create an NVIDIA provider with your API key +openshell provider create \ + --name nvidia \ + --type nvidia \ + --credential "NVIDIA_API_KEY=nvapi-YOUR_KEY_HERE" + +# Configure inference routing +openshell inference set \ + --provider nvidia \ + --model "nvidia/nemotron-3-super-120b-a12b" \ + --no-verify +``` + +### 2. Create the sandbox + +The `--provider` flag is **required** so that `inference.local` is available inside the sandbox: + +```bash +openshell sandbox create --from droid --provider nvidia +``` + +### 3. Configure Droid inside the sandbox + +```bash +mkdir -p /sandbox/.factory +cat > /sandbox/.factory/settings.json << 'EOF' +{ + "cloudSessionSync": false, + "includeCoAuthoredByDroid": false, + "enableDroidShield": false, + "commandDenylist": [], + "modelPolicy": { "allowCustomModels": true, "allowAllFactoryModels": false }, + "customModels": [{ + "id": "custom:nvidia/nemotron-3-super-120b-a12b", + "model": "nvidia/nemotron-3-super-120b-a12b", + "baseUrl": "https://inference.local/v1", + "apiKey": "EMPTY", + "displayName": "Nemotron Super 120B", + "maxContextLimit": 131072, + "enableThinking": true, + "maxOutputTokens": 16384, + "noImageSupport": true, + "provider": "generic-chat-completion-api" + }], + "sessionDefaultSettings": { + "model": "custom:nvidia/nemotron-3-super-120b-a12b", + "autonomyMode": "auto-low" + } +} +EOF +``` + +Key details: +- `baseUrl` must be `https://inference.local/v1` (not the external NVIDIA endpoint) +- `apiKey` can be `EMPTY` because the OpenShell privacy router injects credentials from the provider config + +### 4. Run Droid ```bash -openshell sandbox create --from droid +export FACTORY_API_KEY=EMPTY +droid exec --skip-permissions-unsafe --model "custom:nvidia/nemotron-3-super-120b-a12b" "echo hello" ``` diff --git a/sandboxes/droid/policy.yaml b/sandboxes/droid/policy.yaml index 5be55114..7b48020c 100644 --- a/sandboxes/droid/policy.yaml +++ b/sandboxes/droid/policy.yaml @@ -59,7 +59,7 @@ network_policies: - { host: "*.ingest.sentry.io", port: 443 } binaries: - { path: "/usr/lib/node_modules/droid/**" } - - { path: /usr/local/bin/droid } + - { path: /usr/bin/droid } - { path: /usr/bin/node } # --- BYOK: direct model provider access --- @@ -77,7 +77,7 @@ network_policies: - { host: api-inference.huggingface.co, port: 443 } binaries: - { path: "/usr/lib/node_modules/droid/**" } - - { path: /usr/local/bin/droid } + - { path: /usr/bin/droid } - { path: /usr/bin/node } # --- GitHub (full read + write access) --- From 7e856d40d42fdf0b026190eff95d42616059e8b7 Mon Sep 17 00:00:00 2001 From: Octavian Sima Date: Fri, 3 Apr 2026 10:20:22 -0700 Subject: [PATCH 5/6] more comment --- sandboxes/droid/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sandboxes/droid/README.md b/sandboxes/droid/README.md index c594e333..31df9f13 100644 --- a/sandboxes/droid/README.md +++ b/sandboxes/droid/README.md @@ -23,7 +23,7 @@ docker build -t openshell-droid --build-arg BASE_IMAGE=ghcr.io/nvidia/openshell- ### 1. Set up inference routing -Direct access to external inference endpoints is blocked inside OpenShell sandboxes (SSRF protection). Use OpenShell's `inference.local` routing instead: +Direct access to NVIDIA inference endpoints (`inference-api.nvidia.com`, `integrate.api.nvidia.com`) is blocked inside OpenShell sandboxes (SSRF protection). Use OpenShell's `inference.local` routing instead: ```bash # Create an NVIDIA provider with your API key @@ -85,6 +85,6 @@ Key details: ### 4. Run Droid ```bash -export FACTORY_API_KEY=EMPTY +export FACTORY_API_KEY=fk-YOUR_KEY_HERE droid exec --skip-permissions-unsafe --model "custom:nvidia/nemotron-3-super-120b-a12b" "echo hello" ``` From 6bfcce2e2ad157de45704655a9bc6eb7df125a0c Mon Sep 17 00:00:00 2001 From: Octavian Sima Date: Fri, 3 Apr 2026 12:18:24 -0700 Subject: [PATCH 6/6] more general readme --- sandboxes/droid/README.md | 66 ++------------------------------------- 1 file changed, 2 insertions(+), 64 deletions(-) diff --git a/sandboxes/droid/README.md b/sandboxes/droid/README.md index 31df9f13..7f92d681 100644 --- a/sandboxes/droid/README.md +++ b/sandboxes/droid/README.md @@ -21,70 +21,8 @@ docker build -t openshell-droid --build-arg BASE_IMAGE=ghcr.io/nvidia/openshell- ## Usage -### 1. Set up inference routing - -Direct access to NVIDIA inference endpoints (`inference-api.nvidia.com`, `integrate.api.nvidia.com`) is blocked inside OpenShell sandboxes (SSRF protection). Use OpenShell's `inference.local` routing instead: - -```bash -# Create an NVIDIA provider with your API key -openshell provider create \ - --name nvidia \ - --type nvidia \ - --credential "NVIDIA_API_KEY=nvapi-YOUR_KEY_HERE" - -# Configure inference routing -openshell inference set \ - --provider nvidia \ - --model "nvidia/nemotron-3-super-120b-a12b" \ - --no-verify -``` - -### 2. Create the sandbox - -The `--provider` flag is **required** so that `inference.local` is available inside the sandbox: - -```bash -openshell sandbox create --from droid --provider nvidia -``` - -### 3. Configure Droid inside the sandbox - -```bash -mkdir -p /sandbox/.factory -cat > /sandbox/.factory/settings.json << 'EOF' -{ - "cloudSessionSync": false, - "includeCoAuthoredByDroid": false, - "enableDroidShield": false, - "commandDenylist": [], - "modelPolicy": { "allowCustomModels": true, "allowAllFactoryModels": false }, - "customModels": [{ - "id": "custom:nvidia/nemotron-3-super-120b-a12b", - "model": "nvidia/nemotron-3-super-120b-a12b", - "baseUrl": "https://inference.local/v1", - "apiKey": "EMPTY", - "displayName": "Nemotron Super 120B", - "maxContextLimit": 131072, - "enableThinking": true, - "maxOutputTokens": 16384, - "noImageSupport": true, - "provider": "generic-chat-completion-api" - }], - "sessionDefaultSettings": { - "model": "custom:nvidia/nemotron-3-super-120b-a12b", - "autonomyMode": "auto-low" - } -} -EOF -``` - -Key details: -- `baseUrl` must be `https://inference.local/v1` (not the external NVIDIA endpoint) -- `apiKey` can be `EMPTY` because the OpenShell privacy router injects credentials from the provider config - -### 4. Run Droid +### Create a sandbox ```bash -export FACTORY_API_KEY=fk-YOUR_KEY_HERE -droid exec --skip-permissions-unsafe --model "custom:nvidia/nemotron-3-super-120b-a12b" "echo hello" +openshell sandbox create --from droid ```