@@ -142,6 +142,61 @@ biome check --write # Lint & format JavaScript files (installed via mise)
142142When editing ` Cargo.toml ` or other TOML files, run ` taplo fmt ` to format them
143143according to the project's ` taplo.toml ` configuration.
144144
145+ ## Sandbox Architecture
146+
147+ The gateway runs user commands inside isolated containers (Docker or Apple
148+ Container). Key files:
149+
150+ - ` crates/tools/src/sandbox.rs ` — ` Sandbox ` trait, ` DockerSandbox ` ,
151+ ` AppleContainerSandbox ` , ` SandboxRouter ` , image build/list/clean helpers
152+ - ` crates/tools/src/exec.rs ` — ` ExecTool ` that routes commands through the
153+ sandbox
154+ - ` crates/cli/src/sandbox_commands.rs ` — ` moltis sandbox ` CLI subcommands
155+ - ` crates/config/src/schema.rs ` — ` SandboxConfig ` with default packages list
156+
157+ ### Pre-built images
158+
159+ Both backends support ` build_image ` : generate a Dockerfile with ` FROM <base> `
160+ + ` RUN apt-get install ... ` , then run ` docker build ` / ` container build ` .
161+ The image tag is a deterministic hash of the base image + sorted package
162+ list (` sandbox_image_tag ` ). The gateway pre-builds at startup; if the image
163+ already exists it's a no-op.
164+
165+ ### Config-driven packages
166+
167+ Default packages are defined in ` default_sandbox_packages() ` in ` schema.rs ` .
168+ On first run (no config file), a ` moltis.toml ` is written with all defaults
169+ including the full packages list. Users edit that file to add/remove packages
170+ and restart — the image tag changes automatically, triggering a rebuild.
171+
172+ ### Shared helpers
173+
174+ ` sandbox_image_tag ` , ` sandbox_image_exists ` , ` list_sandbox_images ` ,
175+ ` remove_sandbox_image ` , ` clean_sandbox_images ` are module-level public
176+ functions in ` sandbox.rs ` , parameterised by CLI binary name. The
177+ ` SandboxConfig::from(&config_schema::SandboxConfig) ` impl converts the
178+ config-crate types to tools-crate types — use it instead of manual
179+ field-by-field conversion.
180+
181+ ## Security
182+
183+ ### WebSocket Origin validation (CSWSH protection)
184+
185+ The WebSocket upgrade handler in ` server.rs ` validates the ` Origin ` header.
186+ Cross-origin requests are rejected with 403. Loopback variants (` localhost ` ,
187+ ` 127.0.0.1 ` , ` ::1 ` ) are treated as equivalent. Non-browser clients (no
188+ Origin header) are allowed through.
189+
190+ This prevents the attack class from GHSA-g8p2 -7wf7-98mq where a malicious
191+ webpage could connect to the local gateway WebSocket from the victim's
192+ browser.
193+
194+ ### SSRF protection
195+
196+ ` web_fetch.rs ` resolves DNS and checks the resulting IP against blocked
197+ ranges (loopback, private, link-local, CGNAT) before making HTTP requests.
198+ Any changes to web_fetch must preserve this check.
199+
145200## CLI Auth Commands
146201
147202The ` auth ` subcommand (` crates/cli/src/auth_commands.rs ` ) provides:
@@ -150,6 +205,15 @@ The `auth` subcommand (`crates/cli/src/auth_commands.rs`) provides:
150205- ` moltis auth reset-identity ` — clear identity and user profile (triggers
151206 onboarding on next load)
152207
208+ ## CLI Sandbox Commands
209+
210+ The ` sandbox ` subcommand (` crates/cli/src/sandbox_commands.rs ` ) provides:
211+
212+ - ` moltis sandbox list ` — list pre-built ` moltis-sandbox:* ` images
213+ - ` moltis sandbox build ` — build image from config (base + packages)
214+ - ` moltis sandbox remove <tag> ` — remove a specific image
215+ - ` moltis sandbox clean ` — remove all sandbox images
216+
153217## Sensitive Data Handling
154218
155219Never use plain ` String ` for passwords, API keys, tokens, or any secret
@@ -185,7 +249,6 @@ Rules:
185249- ** RwLock guards** : when a ` RwLock<Option<Secret<String>>> ` read guard is
186250 followed by a write in the same function, scope the read guard in a block
187251 ` { let guard = lock.read().await; ... } ` to avoid deadlocks.
188-
189252## Provider Implementation Guidelines
190253
191254### Async all the way down
0 commit comments