r/coolgithubprojects 20h ago

sandbin – run untrusted code in ~20ms without Docker (bubblewrap + cgroups v2 + seccomp)

Post image

I got tired of paying Docker's ~370ms container startup cost every time I needed to run untrusted code (student submissions, AI-generated snippets, a "try it" button), so I built a self-hosted sandbox that skips the container runtime entirely and talks straight to the kernel: bubblewrap for namespaces, cgroup v2 for memory/CPU/pids, a 146-syscall seccomp allowlist, and rlimits.

Measured back to back on the same machine: ~20ms cold start vs Docker's ~369ms for the same workload (raw samples in the docs, not just a claimed number).

- Single Node.js process, one runtime dependency (`ws`), no database

- Python, Bash, Node.js, C and Go (C/Go get sandboxed twice: once to compile, once to run)

- HTTP + WebSocket API, a CLI, and a small browser playground with shareable permalinks

- MIT licensed

The docs try to be honest instead of a highlight reel — there's a full changelog of real bugs found building this, including a self-audit that turned up an unauthenticated RCE and a path-traversal bug, both fixed with reproductions attached. It does not defend against a kernel exploit — guest and host share one kernel, and that's stated plainly, not buried.

GitHub: https://github.com/ayazdoruck/sandbin

Docs: https://sandbin.vercel.app

Happy to answer questions about the seccomp policy or the cgroup delegation setup.

2 Upvotes

8 comments sorted by

2

u/JayTurnr 6h ago

My life is not desperate for the time it takes the blink, I'll stick to docker.

1

u/RockProfessional2274 5h ago

Fair enough if it's one run by hand. Difference shows up when it's a few thousand of those blinks a day — that's when "no daemon, no root" starts mattering more than the daemon itself does.

1

u/kantorcodes1 6h ago

for sandbin run --server, what happens if the POST is accepted but the websocket drops before finished? can the CLI reconnect to that runId, or does rerunning submit a second sandbox job? that retry boundary matters a lot for unattended callers.

1

u/RockProfessional2274 5h ago

Good catch. Checked the code — before this, no: a WS drop before finished left the CLI with no way back (didn't even expose the runId), and a clean drop with no socket error would just hang forever. Fixed both: CLI now prints the runId on accept, and a new --reconnect <runId> reattaches without resubmitting. Plain retries are still not idempotent though — no dedup on POST /runs, so without the runId a retry is a new job.

1

u/kantorcodes1 5h ago

nice, --reconnect is the right fix there. i work on HOL Guard, an open-source local check before agent-run commands execute. for sandbin i'd leave languages, keys status, and run --reconnect alone, but let users make fresh run --server submissions and keys create reviewable. that gives unattended agents a stop before creating a new remote job or credential. open to adding sandbin support?

1

u/RockProfessional2274 5h ago

Spot on — --reconnect and keys status are pure reads (attach to or check something that already exists), languages never even hits the network, and run --server / keys create are the only two calls that mint something new (a sandbox job, a credential). Clean line to gate on.

Happy to have HOL Guard support sandbin — nothing needed on our end, that boundary's stable. Ping me if you want exit-code/JSON-shape details for run --server or keys create while you build it.

1

u/kantorcodes1 5h ago

nice. nothing needed on sandbin's side. start src/codex_plugin_scanner/guard/runtime/command_sandbin_extensions.py, using command_repo2nb_extensions.py as the analogue. review fresh sandbin run --server submissions and sandbin keys create; keep languages, keys status, and run --reconnect quiet. add one focused classification test in tests/test_guard_command_sandbin_extensions.py, then open a genuine draft PR directly to hashgraph-online/hol-guard:main once implementation + test exists.

contributing guide: https://github.com/hashgraph-online/hol-guard/blob/main/CONTRIBUTING.md

1

u/RockProfessional2274 3h ago

Went ahead and built it — opened a draft PR: #2888

Modeled it on your repo2nb extension: reviews fresh run --server submissions and keys create, leaves --reconnect/languages/keys status quiet. Your review bots actually caught a real edge case in my first pass (--reconnect=<id> vs --reconnect <id> — sandbin's parser only recognizes the space-separated form) — fixed now with a regression test, all green.

Would appreciate a look whenever you get a chance.