DeepSHPlugins
dsh-code-runtime-container 项目预览
GITHUB / COMMUNITY PROJECT

dsh-code-runtime-container

安全与运维TypeScriptMIT

`ctx.codeRuntime` seam 的容器隔离后端:每个 Code Mode 程序跑在全新容器里,无网络、根文件系统只读、丢弃全部 capability,内存/CPU/进程数上限由内核强制。

0 Starstancheng33 更新时间 2026-08-16
访问 GitHub 仓库
dsh-code-runtime-container 项目预览

项目介绍

中文

`ctx.codeRuntime` seam 的容器隔离后端:每个 Code Mode 程序跑在全新容器里,无网络、根文件系统只读、丢弃全部 capability,内存/CPU/进程数上限由内核强制。

English

Container-isolated backend for the `ctx.codeRuntime` seam: each Code Mode program runs in a fresh container with no network, a read-only rootfs, dropped capabilities, and kernel-enforced memory, CPU and pid ceilings.

项目信息与收录信号

快照 2026-09-11
作者 / 组织
tancheng33
仓库
tancheng33/dsh-code-runtime-container
GitHub Stars
0
Forks
0
开发语言
TypeScript
许可证
MIT
最近推送
2026-08-16
Manifest
数据中检测到声明

公开仓库不自动等于可商用;许可证与当前兼容性以原项目文档为准。Manifest 和数据评分不构成安装安全认证。

数据评分

36/ 100
维护活跃
82
文档与实用信息
85
生态关注度
0
安装便捷信号
75
项目声明信号
100

README 含完整安装与使用说明。

评分来自已有数据快照的五维模型,仅作浏览参考,不是实际使用评测或安全检测结论。

安装与使用

所有项目资料与许可均以对应 GitHub 仓库为准。

dsh plugin --profile web add github:tancheng33/dsh-code-runtime-container

复制命令不会自动执行。请先阅读仓库文档,确认环境要求与许可证。

项目截图

仓库 README

GitHub 原文

已保存的仓库文档快照;文档抓取时间未记录,可能与上方目录介绍处于不同版本。安装前请核对当前仓库说明。

dsh-code-runtime-container

npm license

English | 中文

A container-isolated backend for the DeepSeek Harness code-execution seam (ctx.codeRuntime). Code Mode programs run in a fresh container with no network, a read-only root filesystem, every capability dropped, and kernel-enforced memory, CPU, and pid ceilings.

Why

The seam declares three well-known isolation substrates and ships one. From @deepseek-ai/dsh-code-runtime's own README:

Only the worker-thread backend ships — 'process'/'container' are declared well-known isolation values with no implementation; a hard security boundary awaits a container backend.

And the shipped backend is equally explicit about its posture:

Containment, not a security boundary: trust posture is bash-equivalent by design.

That is a reasonable default — Code Mode programs are model-written code, and so is everything bash runs. But it means a run_code program executes inside the agent's own process, with the agent's network access, the agent's filesystem, and whatever the agent's environment holds. This backend is for deployments where that is not acceptable.

worker-thread (shipped)this backend
Substratea Worker in the agent processa fresh container per run
Networkfull agent network access--network=none
Filesystemthe whole host, as the agent userread-only rootfs, no host mount
Privilegesthe agent's--cap-drop=ALL, no-new-privileges, nobody
Runaway CPUmeasured busy-time budgetkernel CPU quota + wall clock
Runaway memoryV8 heap capcgroup memory limit → OOM kill
Fork bomb—--pids-limit
Cold start~milliseconds~200 ms

Install

dsh plugin --profile <name> add dsh-code-runtime-container

Requires a container engine on the host and the image available locally:

docker pull node:22-alpine

The bundle patch disables the worker-thread code-runtime row as it inserts this one: ctx.codeRuntime accepts exactly one implementation per context.

No image build and no bind mount are needed — the in-container runner is passed to node -e on the command line, so the stock upstream image works as-is. podman and nerdctl work through dockerPath.

Configuration

Every default is the restrictive one; a backend whose point is isolation must not be safe only after configuration.

KeyDefaultMeaning
dockerPathdockerContainer CLI. Anything argv-compatible with docker run.
imagenode:22-alpineNeeds a node on PATH and nothing else.
networknoneDocker network mode. The main reason to use this backend.
memory512mMemory ceiling; an overrun is an OOM kill reported as worker-exit.
cpus1Kernel-enforced CPU quota.
pidsLimit128Process ceiling: a fork bomb hits it, not the host.
user65534:65534nobody:nogroup. Empty uses the image default.
readOnlyRootfstrueRead-only root, with a tmpfs at /tmp.
tmpfsMb64Size of that tmpfs.
workspacePath''Absolute host path to mount at /workspace. Empty mounts nothing.
workspaceReadOnlytrueMount the workspace read-only.
extraArgs[]Extra docker run flags. An escape hatch that can weaken every default above.
maxWallMs120000Wall-clock ceiling for one run, including container start.
maxOutputBytes4194304Combined cap on logs plus completion value.

Programs that must read project files need a mount:

- id: code-runtime-container
  config:
    workspacePath: /Users/me/projects/app
    workspaceReadOnly: true
    # …restate the rest; a patch replaces a row's whole config

Threat model

The seam is explicit that isolation is "a label for deployments and diagnostics, not a security claim". So here is the actual claim, stated narrowly.

What a program cannot do, verified by tests against a real container:

  • Reach the network (fetch fails; --network=none).
  • Write to the root filesystem (EROFS).
  • See any host path — /workspace does not exist unless you mount it.
  • Run as root (uid is 65534).
  • Observe or affect another run: each run is a fresh container, and a global set in one run is gone in the next.
  • Outlive its budget: a while(true){} is killed, and the container is killed by name — not merely the docker run client.
  • Forge a result. Every control frame carries a per-run nonce delivered over stdin and held only in the runner's module scope — never on a global, never in argv or env, both of which the program can read. A program writing {"t":"done","value":"FORGED"} to stdout has that line accounted as output, not control.
  • Corrupt the transport by patching intrinsics: JSON.stringify and process.stdout.write are captured before the program runs.

What this does NOT protect against:

  • A kernel or container-runtime escape. This is a container, not a VM. A local privilege-escalation bug in the kernel, the runtime, or the engine defeats it. If your threat model includes that, use a VM-backed engine (dockerPath pointing at a Kata/Firecracker-compatible CLI) or an isolated host.
  • Whatever you mount. A read-write workspacePath is a real write path into your project. That is the point of the option, and it is the one setting that meaningfully widens the boundary.
  • Whatever extraArgs opens. It is passed to docker run verbatim; --network=host there undoes the headline property.
  • The bindings themselves. A program can call every host function the consumer exposed, as many times as its budget allows. Isolating the *program* does not narrow the *tools* — that is the tool-registry's gate, not this seam's. See dsh-egress-guard for the tool-call side.
  • Docker socket access from the agent. This plugin runs a docker client as the agent user. A user who can reach the Docker socket can generally reach root on the host; that is a property of your Docker setup, not of this plugin.

Semantics

The seam contract is honored as written:

  • Errors are result fields, not rejections. Every program outcome — exception, non-erasable TypeScript, timeout, abort, OOM, lossy completion, output overflow — resolves as { logs, error: { kind, message } }. run() rejects only for contract misuse: a disposed runtime, or a binding namespace that violates the portable-identifier rules.
  • Portable identifiers are enforced from the seam's own exported sets (PORTABLE_RESERVED_WORDS, RESERVED_BINDING_GLOBALS, RESERVED_ERROR_MEMBERS, DUNDER_MEMBER), not restated here — so lambda is refused on this TypeScript backend exactly as it would be on a Python one, and a widening of the union reaches this backend with a dependency bump.
  • Binding members are own properties of a null-prototype object, so a function named __proto__ or constructor is an ordinary member.
  • A declared errorClass is materialized in the program, so e instanceof ToolCallError works and the failed member name lands on the declared property.
  • Top-level await and return work. The program is wrapped, stripped, and sliced back out by byte offset (mode: 'strip' preserves positions), so a bare return is not a syntax error.
  • Erasable syntax only, matching the shipped backend: enum and namespaces are a program failure, not a silent transform.
  • Dispose to quiescence. Teardown marks the runtime unusable, kills every in-flight container, and awaits each exit.

How this differs from the worker backend

  • computeMs has no equivalent. The worker backend meters measured event-loop busy time to stop a hot loop hiding behind a pending dispatch. Across a container boundary that measurement is not available, so CPU is bounded by the kernel (--cpus) and elapsed time by maxWallMs. This is a genuine behavioral difference: a program that sleeps for a long time consumes wall budget here, where the worker backend would only charge it busy time.
  • Cold start is ~200 ms per run, against roughly a millisecond for a worker. Set against an LLM round trip this is small, but it is not free.
  • isolation reports 'container', which the seam treats as informational.

Tests

63 tests, 26 of which run against a real container engine — including every isolation claim above, both budget kinds, cancellation, substrate death, and the two hostile-program cases (a forged protocol frame and a replaced JSON.stringify).

npm test                                  # unit tests only

docker pull node:22-alpine
DSH_CONTAINER_TEST=1 npm test             # + live container suite

Limitations

  • TypeScript only. language is 'typescript'. The seam declares 'python' as the other well-known value and dsh-tools already ships a Python SDK renderer, but no Python backend exists yet — including this one.
  • One container per run, no pooling. That is what makes cross-run state unrepresentable; it is also where the ~200 ms goes.
  • No streaming logs. The seam's run() is one-shot: logs arrive on the resolved result. A killed program still shows what it printed before it died.
  • Image pulls are not managed. A missing image surfaces as a worker-exit failure naming the engine's output; pull it ahead of time.
  • No multiplexed stdio. The protocol shares the container's stdout with program output, disambiguated by the nonce. Program bytes are never lost — they become logs — but a program emitting gigabytes of output hits maxOutputBytes rather than a stream backpressure path.

License

MIT

使用前核对

安装步骤、兼容版本与授权范围以原项目当前文档为准。Stars 和评分是快照中的导航信号。

安装与排错 · 项目问题反馈

同类项目