Context: Real code fix regardless of environment — highest blast radius in the review.
Problem: Free-play student submissions in C#/Python/JS run unsandboxed (in-process Roslyn Assembly.Load + Invoke; bare interpreter subprocesses) inside a container that holds RabbitMQ/Mongo/Redis/Gitea credentials. Guardrails are substring blocklists, bypassable via reflection, string concatenation, or __import__. A runaway loop cannot be cancelled (the invoke ignores the token) and unbounded output OOMs the worker. (C/C++ ARE correctly sandboxed via wasmtime — the model exists, just not applied to C#/Python/JS.)
Fix: Execute untrusted code in a real sandbox — locked-down container / gVisor / Firecracker with no secrets, no network, seccomp, memory/CPU cgroups, non-root — plus a hard wall-clock kill and output-size caps. Keep blocklists only as defense-in-depth.
Acceptance criteria:
No untrusted code runs in-process or with network/secret access
Wall-clock timeout hard-kills execution; output is bounded
Reflection / __import__ bypass no longer yields host or secret access
Effort: L
**Context:** Real code fix regardless of environment — highest blast radius in the review.
**Problem:** Free-play student submissions in C#/Python/JS run unsandboxed (in-process Roslyn `Assembly.Load` + `Invoke`; bare interpreter subprocesses) inside a container that holds RabbitMQ/Mongo/Redis/Gitea credentials. Guardrails are substring blocklists, bypassable via reflection, string concatenation, or `__import__`. A runaway loop cannot be cancelled (the invoke ignores the token) and unbounded output OOMs the worker. (C/C++ ARE correctly sandboxed via wasmtime — the model exists, just not applied to C#/Python/JS.)
**Evidence:**
- `SpikerSoft.Business/Domain/CodeExecution/Execution/RoslynCodeExecutor.cs:235-259` (`Assembly.Load` + `entry.Invoke`)
- `PythonLessonExecutor.cs:101-165` (raw CPython subprocess)
- Blocklists: `CodeExecutionWorkerService.cs:812-814`, `:652-686`
**Fix:** Execute untrusted code in a real sandbox — locked-down container / gVisor / Firecracker with no secrets, no network, seccomp, memory/CPU cgroups, non-root — plus a hard wall-clock kill and output-size caps. Keep blocklists only as defense-in-depth.
**Acceptance criteria:**
- No untrusted code runs in-process or with network/secret access
- Wall-clock timeout hard-kills execution; output is bounded
- Reflection / `__import__` bypass no longer yields host or secret access
**Effort:** L
spikerj
added the agentic label 2026-07-05 20:24:35 +00:00
Current-state audit (2026-07-17) — partial mitigation landed since filing; core gap stands
Re-verified the per-language safety boundary against master. Confirms this ticket's analysis and records what has changed since 2026-07-05:
Partial mitigation now in place (post-dates this ticket): the coderunner stack scoped its OpenBao role to secret/services/codeexecution only (#546 / epic #543). Per spikersoft-coderunner/docker-stack.yml, a sandbox escape reading /run/secrets now exposes only this worker's own git password — not the broad bao_services_* vault. That partially satisfies the "no secret access" acceptance criterion. The container also runs non-root (Dockerfile: USER $APP_UID).
Still open (the core ask is unchanged):
The container remains attached to the mongo, redis, and rabbitmq overlay networks and holds its own runtime connection strings in-process, so an escape can still reach those data stores over the network even without vault access.
Python/JS/C# safety boundary is still a substring blocklist only (PythonForbiddenFragments = __import__, eval(, os.system, ... — evaded by getattr(__builtins__,'ev'+'al'), ().__class__.__bases__[0].__subclasses__(), exec(bytes.fromhex(...))). Python runs as a raw python3 "<script>" subprocess (no -I, no restricted env).
Confirmed already-solid (no action needed) — the model to copy:
C / C++: compiled to wasm32-wasi, run via wasmtime run --dir={workDir}::/ main.wasm — per-run temp dir as the only mount, no network, stdio only, wall-clock timeout with Kill(entireProcessTree:true). (ClangLessonExecutor.cs)
SQL: in-process DuckDB, ephemeral in-memory, no fs/network.
No shell-injection surface anywhere: all subprocess spawns use UseShellExecute=false + ArgumentList (argv), not shell strings.
So the remaining work is squarely: bring C#/Python/JS up to the C/C++ wasmtime (or gVisor/Firecracker) isolation model + network egress removal from the untrusted-exec container. Effort L as noted.
### Current-state audit (2026-07-17) — partial mitigation landed since filing; core gap stands
Re-verified the per-language safety boundary against `master`. Confirms this ticket's analysis and records what has changed since 2026-07-05:
**Partial mitigation now in place (post-dates this ticket):** the coderunner stack scoped its OpenBao role to `secret/services/codeexecution` only (#546 / epic #543). Per `spikersoft-coderunner/docker-stack.yml`, a sandbox escape reading `/run/secrets` now exposes only this worker's own git password — **not** the broad `bao_services_*` vault. That partially satisfies the "no secret access" acceptance criterion. The container also runs non-root (`Dockerfile`: `USER $APP_UID`).
**Still open (the core ask is unchanged):**
- The container remains attached to the `mongo`, `redis`, and `rabbitmq` overlay networks and holds its own runtime connection strings in-process, so an escape can still reach those data stores over the network even without vault access.
- Python/JS/C# safety boundary is still a substring blocklist only (`PythonForbiddenFragments` = `__import__`, `eval(`, `os.system`, ... — evaded by `getattr(__builtins__,'ev'+'al')`, `().__class__.__bases__[0].__subclasses__()`, `exec(bytes.fromhex(...))`). Python runs as a raw `python3 "<script>"` subprocess (no `-I`, no restricted env).
**Confirmed already-solid (no action needed) — the model to copy:**
- **C / C++**: compiled to `wasm32-wasi`, run via `wasmtime run --dir={workDir}::/ main.wasm` — per-run temp dir as the only mount, no network, stdio only, wall-clock timeout with `Kill(entireProcessTree:true)`. (`ClangLessonExecutor.cs`)
- **SQL**: in-process DuckDB, ephemeral in-memory, no fs/network.
- No shell-injection surface anywhere: all subprocess spawns use `UseShellExecute=false` + `ArgumentList` (argv), not shell strings.
So the remaining work is squarely: bring **C#/Python/JS** up to the C/C++ wasmtime (or gVisor/Firecracker) isolation model + network egress removal from the untrusted-exec container. Effort **L** as noted.
Board-sweep status (2026-07-22): core sandbox NOT built (07-17 audit stands); only blast-radius mitigation landed (#546 scoped codeexec AppRole). REMAINING: the actual wasmtime-style sandbox for C#/Python/JS.
Board-sweep status (2026-07-22): core sandbox NOT built (07-17 audit stands); only blast-radius mitigation landed (#546 scoped codeexec AppRole). REMAINING: the actual wasmtime-style sandbox for C#/Python/JS.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context: Real code fix regardless of environment — highest blast radius in the review.
Problem: Free-play student submissions in C#/Python/JS run unsandboxed (in-process Roslyn
Assembly.Load+Invoke; bare interpreter subprocesses) inside a container that holds RabbitMQ/Mongo/Redis/Gitea credentials. Guardrails are substring blocklists, bypassable via reflection, string concatenation, or__import__. A runaway loop cannot be cancelled (the invoke ignores the token) and unbounded output OOMs the worker. (C/C++ ARE correctly sandboxed via wasmtime — the model exists, just not applied to C#/Python/JS.)Evidence:
SpikerSoft.Business/Domain/CodeExecution/Execution/RoslynCodeExecutor.cs:235-259(Assembly.Load+entry.Invoke)PythonLessonExecutor.cs:101-165(raw CPython subprocess)CodeExecutionWorkerService.cs:812-814,:652-686Fix: Execute untrusted code in a real sandbox — locked-down container / gVisor / Firecracker with no secrets, no network, seccomp, memory/CPU cgroups, non-root — plus a hard wall-clock kill and output-size caps. Keep blocklists only as defense-in-depth.
Acceptance criteria:
__import__bypass no longer yields host or secret accessEffort: L
Current-state audit (2026-07-17) — partial mitigation landed since filing; core gap stands
Re-verified the per-language safety boundary against
master. Confirms this ticket's analysis and records what has changed since 2026-07-05:Partial mitigation now in place (post-dates this ticket): the coderunner stack scoped its OpenBao role to
secret/services/codeexecutiononly (#546 / epic #543). Perspikersoft-coderunner/docker-stack.yml, a sandbox escape reading/run/secretsnow exposes only this worker's own git password — not the broadbao_services_*vault. That partially satisfies the "no secret access" acceptance criterion. The container also runs non-root (Dockerfile:USER $APP_UID).Still open (the core ask is unchanged):
mongo,redis, andrabbitmqoverlay networks and holds its own runtime connection strings in-process, so an escape can still reach those data stores over the network even without vault access.PythonForbiddenFragments=__import__,eval(,os.system, ... — evaded bygetattr(__builtins__,'ev'+'al'),().__class__.__bases__[0].__subclasses__(),exec(bytes.fromhex(...))). Python runs as a rawpython3 "<script>"subprocess (no-I, no restricted env).Confirmed already-solid (no action needed) — the model to copy:
wasm32-wasi, run viawasmtime run --dir={workDir}::/ main.wasm— per-run temp dir as the only mount, no network, stdio only, wall-clock timeout withKill(entireProcessTree:true). (ClangLessonExecutor.cs)UseShellExecute=false+ArgumentList(argv), not shell strings.So the remaining work is squarely: bring C#/Python/JS up to the C/C++ wasmtime (or gVisor/Firecracker) isolation model + network egress removal from the untrusted-exec container. Effort L as noted.
Board-sweep status (2026-07-22): core sandbox NOT built (07-17 audit stands); only blast-radius mitigation landed (#546 scoped codeexec AppRole). REMAINING: the actual wasmtime-style sandbox for C#/Python/JS.