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
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ cos offload -p python-uv -p rust-cargo -x target -o dist . 'uv sync --frozen &&
# trusted heavy build, explicitly unrestricted egress
cos offload -E -s s-2vcpu-2gb . 'cargo build --release'

# untrusted script, outbound locked to exactly what it needs
cos offload -e pypi.org -e files.pythonhosted.org ./suspect 'python3 main.py'
# untrusted script with outbound access denied by an enforced IP rule
cos offload -e 192.0.2.1/32 ./suspect 'python3 main.py'
```

Two things about this that are easy to get wrong:

- **Egress is unrestricted by default.** A fresh box can reach anything; `cos` prints a one-line notice. Restricting is opt-in with `-p <preset>` or `-e <domain>`. So "run this untrusted thing in a sandbox" is only half done until you pass one of those.
- **Egress is unrestricted by default.** A fresh box can reach anything; `cos` prints a one-line notice. Hostname rules and hostname presets are accepted but are not enforced by the control plane. Use enforced IP/CIDR rules when restricting outbound access; `cos exec -N` denies all egress. For a multi-file offload with no outbound access, use `-e 192.0.2.1/32`, the same deny-all rule. Do not describe hostname presets as an exfiltration barrier.
- **Uploads are one-way.** Box-side changes never touch the local tree unless you ask with `-o <path>`. `.git`, `node_modules`, `target`, `.venv` and friends are excluded from the upload by default — dependencies are meant to be built _inside_ the box.

Long, quiet builds survive a dropped connection: the command runs detached with a heartbeat watcher that re-attaches if the stream dies. The real exit code is preserved.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,24 @@ Dependencies are meant to be built _inside_ the box, not shipped into it — tha

## Egress: how the firewall actually behaves

**The default is unrestricted.** A fresh box can reach any host on the internet. `cos` prints a one-line UNRESTRICTED notice so this is never silent. Restricting is opt-in: `-p <preset>`, `-e <domain>`, or both.
**The default is unrestricted.** A fresh box can reach any host on the internet. `cos` prints a one-line UNRESTRICTED notice so this is never silent. Use IP/CIDR rules for enforced restrictions. Hostname presets are accepted configuration, not enforced restrictions.

The rule grammar is allow-list only — there is no deny token, so every destination a job needs must be enumerated. Rules can be `host`, `host:port`, `*.host`, `ip`, `ip:port`, `cidr`, `cidr:port`, or `*` (which means allow everything).

Enforcement is not uniform, and the difference matters when the threat model is exfiltration:

- **IP and CIDR rules take effect immediately** and cannot be bypassed from inside the box.
- **Domain rules take roughly 30 seconds to apply** after being set, and they are a strong control for HTTPS traffic but a weak one for cleartext HTTP.
- **Hostname rules are not enforced.** `host`, `host:port`, and `*.host` are accepted, stored, and returned by the API, but ignored during enforcement. Do not claim that a hostname allow-list restricts HTTPS or HTTP traffic.

So: a domain allow-list is the right tool for "this build should only reach pypi and crates.io." For an adversarial workload where blocking exfiltration is the actual goal, prefer IP/CIDR rules, and expect the 30-second window after any domain-rule change.
For an adversarial workload, use IP/CIDR rules or deny outbound access with `cos exec -N`. For a multi-file offload, `-e 192.0.2.1/32` applies the same deny-all IP rule. Do not resolve registry hostnames to IPs as a workaround: DNS rotates, and CDN addresses are shared. This matches the verified control-plane behavior recorded in the repository root `CLAUDE.md`; re-check the control plane before changing these guarantees.

DNS keeps resolving even for blocked destinations — a blocked connection fails as a connection error (for example `curl` exit 35), not as a name-resolution failure. Debugging a restricted build by checking whether DNS works will mislead you.

## Egress presets

These presets expand to hostname rules. They do not enforce outbound restrictions
on the current control plane. Do not use them as a security boundary.

| Preset | Opens |
| ------------ | ------------------------------------------------------------------------------------------------- |
| `python-uv` | `astral.sh`, `releases.astral.sh`, `pypi.org`, `files.pythonhosted.org` |
Expand Down
10 changes: 7 additions & 3 deletions packages/codex-plugin/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "createos-sandbox-codex",
"displayName": "CreateOS Sandbox",
"version": "0.2.0",
"version": "0.3.0",
"description": "Run ad-hoc, heavy, or untrusted code OFF your machine in disposable CreateOS Sandboxes via the `cos` driver — offload, fanout, scratch shell, reusable box with sync, tunnel, expose, clusters, S3 disks, VPN, pause/resume, custom images, and a graphical desktop you drive by screenshot/click/type.",
"author": {
"name": "NodeOps",
Expand All @@ -10,11 +10,15 @@
"homepage": "https://createos.sh",
"keywords": ["sandbox", "createos", "remote-exec", "isolation", "desktop", "computer-use"],
"skills": "./skills/",
"hooks": "./hooks/hooks.json",
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "CreateOS Sandbox",
"shortDescription": "Offload code to disposable remote sandboxes",
"shortDescription": "Run remote jobs, inspect logs, and retrieve artifacts",
"developerName": "NodeOps",
"category": "Developer Tools",
"websiteURL": "https://createos.sh"
"websiteURL": "https://createos.sh",
"capabilities": ["Read", "Write", "Interactive"],
"defaultPrompt": ["Show my CreateOS sandbox jobs", "Run this project's tests in a CreateOS sandbox"]
}
}
3 changes: 3 additions & 0 deletions packages/codex-plugin/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Generated dependency template literals retain meaningful source whitespace.
mcp/*.mjs whitespace=-blank-at-eol
mcp/job-panel.html whitespace=-blank-at-eol
3 changes: 3 additions & 0 deletions packages/codex-plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
!package-lock.json
!.mcp.json
10 changes: 10 additions & 0 deletions packages/codex-plugin/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"mcpServers": {
"createos-sandbox": {
"command": "bash",
"args": ["${PLUGIN_ROOT}/scripts/start-mcp.sh"],
"env_vars": ["PATH", "CREATEOS_API_KEY", "CREATEOS_WORKSPACE_ROOT", "CREATEOS_JOB_DATA"],
"tool_timeout_sec": 60
}
}
}
21 changes: 21 additions & 0 deletions packages/codex-plugin/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,24 @@ protocol live in the monorepo root [`CLAUDE.md`](../../CLAUDE.md), which is
loaded automatically from this directory.

<!-- MESH:END -->

## Local implementation

- Root `plugin.json` and `mcp.json` are the portable package. The compatibility
overlay and `.mcp.json` remain for older Codex hosts. Hook definitions live in
`hooks/hooks.json`; `manifest.json` is retained only for older installations.
- `src/` contains the local MCP job server, durable job worker, and MCP Apps
panel. `npm run check` type-checks, builds, and tests them. Ship the generated
`mcp/` files with the plugin; installed plugins need no `node_modules`.
- The worker imports `../shared/sandbox-engine.ts` at build time. Do not copy or
replace that engine. Each job runs in a dedicated tmux server, survives MCP
client disconnects, and retains state in private workspace-scoped storage.
- MCP job egress is denied by default. Explicit unrestricted mode is available;
hostname presets are not presented as enforced controls.
- Retrieve individual artifacts as bounded bytes, never by extracting remote
archives into the checkout. Keep the sandbox if a requested pull fails.
- Canonical `scripts/cos`, `scripts/offload-hint.sh`, and the
`using-createos-sandbox` skill still live in `claude-code-plugin`. Changes
there must pass `../../scripts/sync-shared.sh --check`.
- This package is a local stdio integration. Hosted OAuth and a registered
ChatGPT connection are separate work; do not add a guessed server URL or app ID.
Loading