Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ Setup, configuration, limits, and troubleshooting live in the
| Command | What |
| ------------------------------------------------ | ----------------------------------------------------------------------- |
| `/createos-sandbox:offload <dir> <cmd>` | one-shot: stage → run → pull artifacts → destroy |
| `/createos-sandbox:exec <file> [args]` | run one untrusted/ad-hoc source file in a throwaway box |
| `/createos-sandbox:fanout <dir> <cmd1> [cmd2 …]` | run each command in its own throwaway box, in parallel |
| `/createos-sandbox:shell` | instant throwaway interactive Linux (destroyed on exit) |
| `/createos-sandbox:up` · `run` · `sync` · `down` | reusable per-repo box + file sync for live dev loops |
Expand Down
13 changes: 12 additions & 1 deletion packages/claude-code-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The plugin is a **thin Claude-facing surface** over the `createos` CLI. It ships

| Piece | Path | Role |
| ------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Slash commands** | `commands/*.md` | 20 commands (`offload`, `fanout`, `shell`, …), each a thin wrapper that calls `scripts/cos` |
| **Slash commands** | `commands/*.md` | 22 commands (`offload`, `exec`, `fanout`, `shell`, …), each a thin wrapper that calls `scripts/cos` |
| **Skill** | `skills/using-createos-sandbox/SKILL.md` + `references/` | teaches Claude _when_ to reach for the sandbox on its own, with depth loaded on demand |
| **Hooks** | `hooks/hooks.json` + `scripts/` | `SessionStart` publishes the driver's absolute path; `PreToolUse(Bash)` nudges on heavy build/test commands |
| **Driver** | `scripts/cos` | the actual logic — staging, egress, keepalive, sync, networking, lifecycle, state |
Expand Down Expand Up @@ -125,6 +125,7 @@ claude --plugin-dir /path/to/createos-plugin/packages/claude-code-plugin
| Command | Summary |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| [`offload`](#offload--one-shot) `[flags] <dir> <cmd>` | one-shot: stage → run (keepalive) → pull → destroy |
| [`exec`](#exec--remote-code-execution) `[-l lang] [-i stdin] [-t secs] [-N] <file\|-> [args]` | run one source file (untrusted/ad-hoc) in a throwaway box |
| [`fanout`](#fanout--parallel-boxes) `[-j N] [flags] <dir> <cmd1> [cmd2] …` | run each command in its own throwaway box, in parallel |
| [`agent`](#coding-agents) `[flags] <agent> <dir> <prompt>` | run claude/codex/opencode/pi/cursor on your code in a box |
| [`shell`](#shell--throwaway-linux) `[-s] [-r] [-e\|-p\|-E]` | instant throwaway interactive Linux (destroyed on exit) |
Expand Down Expand Up @@ -176,6 +177,16 @@ The core command. Stages a directory into a fresh box, runs a command, optionall
/createos-sandbox:offload -p python-uv -o dist . "uv sync --frozen && uv run python -m build"
```

### Exec — remote code execution

Run untrusted code or any ad-hoc script — anything you would rather not run locally — as one source file in a throwaway box. No directory to stage.

```
/createos-sandbox:exec [-l lang] [-i stdin-file] [-t secs] [-N] [-p preset] [-e dom] <file> [args...]
```

Languages `py js mjs cjs ts go sh rb c cpp rs`, picked from the extension or `-l`. `-i` feeds stdin, `-t` caps wall-clock time (default 120 s, exit 124), `-N` denies all egress (default is unrestricted, like `offload`). stdout/stderr and the exit code are the program's own.

**Keepalive:** long or quiet compiles no longer die to exec-stream idle resets — the command runs detached with a heartbeat and re-attaches if the stream drops, so the build (and its cache) survives.

### Fanout — parallel boxes
Expand Down
11 changes: 11 additions & 0 deletions packages/claude-code-plugin/commands/exec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
description: Remote code execution — run untrusted code or any ad-hoc script/snippet (py, js, mjs, cjs, ts, go, sh, rb, c, cpp, rs) in a throwaway CreateOS box instead of on this machine. Timeout, optional stdin, real exit code, auto-destroys.
argument-hint: "[-l lang] [-i stdin-file] [-t secs] [-N] [-p preset] [-e dom] <file> [args...]"
allowed-tools: Bash
---

Run one source file in a disposable CreateOS Sandbox. Flags precede `<file>`; everything after `<file>` goes to the program untouched. Language comes from the extension (`-l` overrides). Egress is unrestricted by default; `-p`/`-e` allow just those hosts, `-N` blocks outbound connections. `-i` feeds a local file to stdin; `-t` is a wall-clock limit (default 120 s, exit 124 when hit).

!`if test -n "$ARGUMENTS"; then "${CLAUDE_PLUGIN_ROOT}/scripts/cos" exec $ARGUMENTS; else "${CLAUDE_PLUGIN_ROOT}/scripts/cos" exec; fi`

Report the program's stdout, stderr and the `cos: exit=N time=Ns` line above. Exit 124 means the timeout killed it.
83 changes: 83 additions & 0 deletions packages/claude-code-plugin/scripts/cos
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,87 @@ example:
EOF
}

# ─────────────────────────────────────────────────── run one untrusted source file
# Egress stays open by default (snippets often call APIs). -N denies all: any rule flips
# CreateOS to deny-by-default and an IP rule is enforced at once (hostname rules are not),
# so one unroutable TEST-NET-1 address allows nothing.
DENY_ALL_EGRESS=192.0.2.1/32
cmd_exec(){ # no _norm: it would rewrite the program's own --flags after <file>; getopts stops at <file>
local shape=s-1vcpu-1gb rootfs=devbox:1 lang="" stdin="" to=120 deny=0
COS_EGRESS=(); local -a _d; local OPTIND=1 o doms d
while getopts "s:r:l:i:t:e:p:v:Nh" o; do case $o in
s) shape=$OPTARG;; r) rootfs=$OPTARG;; l) lang=$OPTARG;; i) stdin=$OPTARG;; t) to=$OPTARG;;
v) add_env "$OPTARG";;
e) COS_EGRESS+=(--egress "$OPTARG");;
p) doms=$(egress_preset "$OPTARG") || die "unknown egress preset '$OPTARG' (have: $EGRESS_PRESETS)"
read -ra _d <<<"$doms"; for d in "${_d[@]}"; do COS_EGRESS+=(--egress "$d"); done;;
N) deny=1;;
h) exec_usage; exit 0;;
*) exec_usage >&2; exit 2;; esac; done
shift $((OPTIND-1))
if [ $# -lt 1 ]; then exec_usage; exit 0; fi
local file=$1; shift
numeric "$to" || die "-t wants whole seconds, got '$to'"
[ -z "$stdin" ] || [ -f "$stdin" ] || die "no such stdin file: $stdin"
local src=$file
if [ "$file" = - ]; then src=$(mktemp); cat >"$src"; else [ -f "$file" ] || die "no such file: $file"; fi
[ -n "$lang" ] || lang=${file##*.}
[ "$lang" != - ] && [ "$lang" != "$file" ] || die "cannot tell the language — pass -l py|js|mjs|cjs|ts|go|sh|rb|c|cpp|rs"

local main run
case "$lang" in
py|python) main=main.py; run="python3 main.py";;
js|node) main=main.js; run="node main.js";;
mjs) main=main.mjs; run="node main.mjs";;
cjs) main=main.cjs; run="node main.cjs";;
ts|typescript) main=main.ts; run="bun main.ts";;
go) main=main.go; run="go run main.go";;
sh|bash) main=main.sh; run="bash main.sh";;
rb|ruby) main=main.rb; run="ruby main.rb";;
c) main=main.c; run="gcc -O2 -o main main.c && ./main";;
cpp|cc|cxx) main=main.cpp; run="g++ -O2 -o main main.cpp && ./main";;
rs|rust) main=main.rs; run="rustc -O -o main main.rs && ./main";;
*) die "unsupported language '$lang' — py|js|mjs|cjs|ts|go|sh|rb|c|cpp|rs (anything else: cos offload)";;
esac
local a; for a in "$@"; do run="$run $(printf '%q' "$a")"; done

[ "$deny" = 1 ] && { COS_EGRESS=(--egress "$DENY_ALL_EGRESS"); echo "cos: egress DENIED (-N)" >&2; }

OFFLOAD_ID=$(create_box "cos-x-$$-${RANDOM}" "$shape" "$rootfs")
KEEP=0; trap on_offload_exit EXIT
wait_running "$OFFLOAD_ID" 30 || die "box $OFFLOAD_ID not running after 30s"
"$CLI" sandbox push "$OFFLOAD_ID" "$src" "/work/$main" >/dev/null 2>&1 || die "push failed"
[ "$file" = - ] && rm -f "$src"
local in=/dev/null
if [ -n "$stdin" ]; then
"$CLI" sandbox push "$OFFLOAD_ID" "$stdin" /work/.stdin >/dev/null 2>&1 || die "stdin push failed"; in=.stdin
fi

# ponytail: buffered exec, fine for snippets; a job that outlives the stream belongs in offload (keepalive)
local t0 rc=0; t0=$(date +%s)
"$CLI" sandbox exec "$OFFLOAD_ID" -- bash -lc "cd /work && timeout -k 5 $to bash -c $(printf '%q' "$run") <$in" || rc=$?
echo "cos: exit=$rc time=$(( $(date +%s) - t0 ))s$([ "$rc" = 124 ] && echo " (killed: -t ${to}s timeout)")" >&2
return "$rc"
}
exec_usage(){ cat <<'EOF'
cos exec — run one source file (untrusted/ad-hoc code) in a throwaway box (auto-destroyed).
cos exec [flags] <file|-> [args...] ('-' reads the code from stdin; needs -l)
flags:
-l lang py | js | mjs | cjs | ts | go | sh | rb | c | cpp | rs (default: from the file extension)
-i file feed this local file to the program's stdin
-t secs wall-clock limit, default 120 (exit 124 on timeout)
-s shape -r rootfs -v KEY[=VAL]
-N deny ALL egress (box reaches nothing) — for code you suspect of exfiltration
-e <domain> / -p <preset> allow just these hosts (default: unrestricted egress)
stdout/stderr pass through; exit code is the program's; 'cos: exit=N time=Ns' goes to stderr.
example:
cos exec -i input.txt solution.py
cos exec -l py - <<'PY'
print(sum(range(10)))
PY
EOF
}

# ───────────────────────────────────────────────────────────── reusable project box
cmd_up(){
_norm "$@"; set -- ${NORMA[@]+"${NORMA[@]}"}
Expand Down Expand Up @@ -1339,6 +1420,7 @@ main_usage(){ cat <<'EOF'
cos — CreateOS sandbox as remote compute. (run `cos install` to put `cos` on PATH)
cos auth check sign-in (CREATEOS_API_KEY, or `createos login` in a real terminal)
cos offload [flags] <dir> <cmd> one-shot: stage→run(keepalive)→pull→destroy (cos offload -h for flags)
cos exec [flags] <file|-> [args] run one untrusted source file (py|js|ts|go|sh|rb|c|cpp|rs); -N denies egress (cos exec -h)
cos agent [flags] <agent> <dir> <prompt> run claude|codex|opencode|pi|cursor on your code in a box
against OpenRouter / any OpenAI- or Anthropic-compatible provider (cos agent -h)
cos fanout [-j N][flags] <dir> <cmd>... run each <cmd> in its own throwaway box, in parallel (cos fanout -h)
Expand Down Expand Up @@ -1377,6 +1459,7 @@ case "$sub" in
install) cmd_install "$@";;
auth) cmd_auth "$@";;
offload) cmd_offload "$@";;
exec) cmd_exec "$@";;
agent) cmd_agent "$@";;
fanout) cmd_fanout "$@";;
shell) cmd_shell "$@";;
Expand Down
22 changes: 20 additions & 2 deletions packages/claude-code-plugin/skills/using-createos-sandbox/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: using-createos-sandbox
description: Use when you need to run code OFF the user's machine — heavy/long builds or test suites, untrusted or unknown code, a parallel test/config matrix across many boxes, an instant clean Linux to try a tool, a live dev-server/watcher you edit against, reaching a box-side service from localhost (port tunnel) or sharing it on the public web (HTTPS preview URL), a multi-machine cluster on one private network, a WireGuard VPN into that network, mounting an S3 bucket of data, handing a coding task to another agent (Claude Code, Codex, OpenCode, Pi, Cursor) running on OpenRouter or any OpenAI-/Anthropic-compatible provider, or work that needs a real screen — a graphical Linux desktop with a browser that you drive by screenshot/click/type and the user can watch over noVNC. Offloads to ephemeral CreateOS Sandboxes via the `cos` helper (stage → exec → pull → auto-destroy), plus fanout, a scratch shell, and an opt-in reusable box with sync, tunnel, expose, desktop/computer-use, cluster, disk, vpn, pause/resume, custom images, and snapshot/fork. Also use to answer any question about CreateOS Sandbox itself — its REST API, SDKs (TypeScript, Go, Python, Rust, C#, Java), CLI commands, limits, lifecycle, egress, networks, disks, templates, webhooks, or integrations — by fetching the relevant live docs page listed in references/docs.md.
description: Use when you need to run code OFF the user's machine — ALWAYS for untrusted or unknown code, and for any ad-hoc script or snippet you would otherwise run locally (remote code execution: `cos exec <file>`), heavy/long builds or test suites, a parallel test/config matrix across many boxes, an instant clean Linux to try a tool, a live dev-server/watcher you edit against, reaching a box-side service from localhost (port tunnel) or sharing it on the public web (HTTPS preview URL), a multi-machine cluster on one private network, a WireGuard VPN into that network, mounting an S3 bucket of data, handing a coding task to another agent (Claude Code, Codex, OpenCode, Pi, Cursor) running on OpenRouter or any OpenAI-/Anthropic-compatible provider, or work that needs a real screen — a graphical Linux desktop with a browser that you drive by screenshot/click/type and the user can watch over noVNC. Offloads to ephemeral CreateOS Sandboxes via the `cos` helper (stage → exec → pull → auto-destroy), plus fanout, a scratch shell, and an opt-in reusable box with sync, tunnel, expose, desktop/computer-use, cluster, disk, vpn, pause/resume, custom images, and snapshot/fork. Also use to answer any question about CreateOS Sandbox itself — its REST API, SDKs (TypeScript, Go, Python, Rust, C#, Java), CLI commands, limits, lifecycle, egress, networks, disks, templates, webhooks, or integrations — by fetching the relevant live docs page listed in references/docs.md.
---

# Using CreateOS Sandbox as remote compute
Expand Down Expand Up @@ -38,7 +38,8 @@ Every `cos` command except `install` and `auth` runs this check first, so an una

| Situation | Why offload |
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| **Untrusted / unknown code** — a snippet, a fresh npm/pip package, scraped code, a PoC exploit | Isolation. The blast radius is one disposable box, not the laptop. |
| **Untrusted / unknown code** — a snippet, a fresh npm/pip package, scraped code, a PoC exploit | Isolation. The blast radius is one disposable box, not the laptop. One file → `exec`. |
| **Any ad-hoc script** — a one-off Python/JS/shell/Go snippet to compute, parse, probe or try something | Keep the laptop clean; `exec` runs it remotely and returns stdout, stderr and the exit code. |
| **Heavy build or test suite** — big `make`, full test run, compile, benchmark | Keeps the laptop free; runs on a box sized for it. |
| **Parallel/matrix work** — same job across N configs, test shards, batch | `fanout` — each command in its own throwaway box, concurrently, results collected. |
| **Quick scratch Linux** — try a CLI/tool/snippet on a clean box | `shell` — instant keyless box, destroyed on exit (interactive; the user runs it). |
Expand All @@ -60,6 +61,7 @@ Do NOT offload trivial commands, anything needing the user's local secrets/SSH/c
Almost every task is one of two shapes, and picking the wrong one wastes a lot of motion:

- **"Run this and tell me the result"** — a test suite, a build, a script, anything with an end. → **`cos offload <dir> <cmd>`.** One command. It creates the box, ships the directory, runs, and destroys the box. Nothing to clean up.
- **"Run this one piece of code"** — a snippet you wrote or were handed, untrusted code, a solution to test against an input. → **`cos exec <file>`** (or `cos exec -l py -` with the code on stdin). No directory to stage.
- **"Keep a box around while I work"** — a dev server you'll hit repeatedly, a watcher reacting to edits, a session spanning many commands. → **`cos up`**, then `run`/`sync`, then `pause` or `down`.

If you find yourself doing any of the following, you have picked the wrong shape and should stop and use `offload` instead:
Expand Down Expand Up @@ -97,6 +99,22 @@ Long, quiet builds survive a dropped connection: the command runs detached with

For the full flag table, the egress presets, the enforcement caveats, fanout, and the OOM/disk/bandwidth traps on heavy builds → **`references/offload-and-egress.md`**.

### Exec — remote code execution for one file

Code you would rather not run on the user's machine — untrusted, generated, or just not yours to run locally — goes to `exec`: it writes the file into a fresh box, runs it with the right toolchain, and destroys the box.

```bash
cos exec -i input.txt solution.py arg1 # stdin from a file, args after the file
cos exec -t 10 -N suspect.js # 10 s limit, no network at all
cos exec -l go - <<'GO'
package main
import "fmt"
func main() { fmt.Println("hi") }
GO
```

Languages: `py js mjs cjs ts go sh rb c cpp rs` (from the extension, or `-l`). Everything after `<file>` reaches the program unchanged. stdout/stderr pass through, the exit code is the program's, exit 124 means the `-t` limit (default 120 s) killed it, and `cos: exit=N time=Ns` goes to stderr. Egress is unrestricted by default like `offload`; `-N` denies all of it (enforced by an IP rule, so it applies immediately). More than one file, or dependencies to install → `offload`.

### Fanout — same input, many boxes, in parallel

```bash
Expand Down
Loading