@deepseek-ai/dsh-sandbox 0.1.5-rc.1 → 0.1.6-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +3 -3
- package/README.zh.md +8 -8
- package/lib/index.js +78 -2
- package/lib/types/diagnostics.d.ts +42 -0
- package/lib/types/index.d.ts +3 -1
- package/package.json +6 -6
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/sandbox/sandbox/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 88e873428115eb7a3880253b668a4de5b42d6e6d
|
|
6
|
+
README.zh.md: b0144d12395cf01667ef7c2045a1d5f722936a4d
|
package/README.md
CHANGED
|
@@ -81,9 +81,9 @@ This section explains the design decisions behind the contract and points at the
|
|
|
81
81
|
|
|
82
82
|
### Design philosophy
|
|
83
83
|
|
|
84
|
-
- **
|
|
84
|
+
- **One execution world.** The filesystem, subprocess and sandbox providers operate on the same filesystem and kernel. Remote compositions replace all three providers; confinement resolves asynchronously in that world.
|
|
85
85
|
- **Policy rides the call.** `SandboxPolicy` is carried per call, never fixed on the provider: two consumers may confine under different policies at the same instant, and an escalated retry is a new call with a wider policy. Defaulting and resolution are explicit consumer steps.
|
|
86
|
-
- **Fail closed.** `confine()`
|
|
86
|
+
- **Fail closed.** `confine()` resolves to enforcing argv or rejects with `SandboxUnavailableError`; silent unconfined passthrough is forbidden, and functional probes arbitrate multi-runner chains.
|
|
87
87
|
- **One vocabulary for denial and escalation.** The marker and hint texts and the strictly-wider ladder live here so the bash and fs families cannot drift apart.
|
|
88
88
|
|
|
89
89
|
### Source map
|
|
@@ -141,7 +141,7 @@ Conditional error text is visible for that call and retained in history until co
|
|
|
141
141
|
|
|
142
142
|
#### KV Cache effect
|
|
143
143
|
|
|
144
|
-
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV
|
|
144
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV Cache entries.
|
|
145
145
|
|
|
146
146
|
### Escalation request and outcome
|
|
147
147
|
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "
|
|
2
|
+
description: "面向用户与维护者的进程沙箱服务约定,用于组合、使用或扩展与宿主共享文件系统和内核的子进程限制机制。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
使用 `dsh-sandbox`,可以让子进程及其派生的所有进程在逐调用文件访问策略下运行。命令可以禁止写入(`read-only`)、只写入工作区(`workspace-write`),或不受限制地运行(`danger-full-access`)。无法强制执行所请求的模式时,调用以 `SANDBOX_UNAVAILABLE`
|
|
12
|
+
使用 `dsh-sandbox`,可以让子进程及其派生的所有进程在逐调用文件访问策略下运行。命令可以禁止写入(`read-only`)、只写入工作区(`workspace-write`),或不受限制地运行(`danger-full-access`)。无法强制执行所请求的模式时,调用以 `SANDBOX_UNAVAILABLE` 失败,绝不会不受限制地运行。调用被拒绝后,模型可以请求一个严格更宽的模式,交由人类批准一次。这种限制只适用于与宿主共享内核和文件系统的进程;需要隔离整个环境时,请使用容器、microVM 或远程执行器。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -29,11 +29,11 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 何时选择
|
|
31
31
|
|
|
32
|
-
当组合需要在宿主机上隔离子进程时选择本包:本地后端与受限执行器都实现这个约定,因此在 `ctx.sandbox` 后挂载 `sandbox-local`、在 `ctx.shell`
|
|
32
|
+
当组合需要在宿主机上隔离子进程时选择本包:本地后端与受限执行器都实现这个约定,因此在 `ctx.sandbox` 后挂载 `sandbox-local`、在 `ctx.shell` 后挂载受限执行器,就能使每次 bash 或 pwsh 调用默认在限制下运行。当进程必须在隔离环境中运行时请另选方案——容器、microVM 或远程执行器会替换整个 `ctx.shell`/`ctx.fs` 能力,而不是在这里添加后端。
|
|
33
33
|
|
|
34
34
|
### 隔离命令
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
挂载服务、后端与受限执行器;随附的组合由[基础组合包(base bundle)](../../bundle/base/cordis.patch.yml)定义。
|
|
37
37
|
|
|
38
38
|
```yaml
|
|
39
39
|
- id: sandbox
|
|
@@ -81,9 +81,9 @@ kind: "package-reference"
|
|
|
81
81
|
|
|
82
82
|
### 设计理念
|
|
83
83
|
|
|
84
|
-
-
|
|
84
|
+
- **同一执行环境。** 文件系统、子进程与沙箱提供方使用同一文件系统和内核。远端组合替换这三个提供方,并在该环境中异步解析限制。
|
|
85
85
|
- **策略随调用传递。** `SandboxPolicy` 逐调用携带,绝不在提供方上固定:两个消费方可以同时按不同策略隔离,获批的升权重试只是用更宽策略发起的新调用。默认与解析是消费方显式步骤。
|
|
86
|
-
- **故障关闭。** `confine()`
|
|
86
|
+
- **故障关闭。** `confine()` 异步返回用于强制执行限制的 argv,或以 `SandboxUnavailableError` 拒绝;绝不允许静默无限制放行,功能探测用于仲裁多 runner 链。
|
|
87
87
|
- **统一的拒绝与升权词汇。** 标记与提示文本以及严格更宽阶梯都放在这里,使 bash 与 fs 家族不会漂移。
|
|
88
88
|
|
|
89
89
|
### 源码地图
|
|
@@ -93,7 +93,7 @@ kind: "package-reference"
|
|
|
93
93
|
| [`src/index.ts`](src/index.ts) | 插件入口:`SandboxProvider` 服务、模式/强制执行/策略类型、故障关闭错误 |
|
|
94
94
|
| [`src/escalation.ts`](src/escalation.ts) | 升权词汇:更宽模式阶梯、参数校验、拒绝与提示标记、审批编排 |
|
|
95
95
|
| [`src/roots.ts`](src/roots.ts) | 可写根目录推导,Seatbelt profile 与进程内 fs 栅栏共享 |
|
|
96
|
-
| — |
|
|
96
|
+
| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 |
|
|
97
97
|
|
|
98
98
|
### 升权编排
|
|
99
99
|
|
|
@@ -165,7 +165,7 @@ sandbox mode "<mode>" is requested but no sandbox backend is usable on this host
|
|
|
165
165
|
这些限制说明该 seam 何时不合适,或何时需要特别运维。它们是当前包约束,不是通用沙箱对比或任务积压。
|
|
166
166
|
|
|
167
167
|
- **文件操作是完整的策略词汇**——该 seam 不表达网络、进程、系统调用、设备或凭据限制。
|
|
168
|
-
-
|
|
168
|
+
- **只支持与宿主共享文件系统和内核的限制**——容器、microVM 与远程执行需要替换能力实现,而不是在此添加提供方。
|
|
169
169
|
- **拒绝报告是一种 stderr 方言**——该 seam 返回后端签名,而非类型化运行时拒绝通道,需要分类的消费方必须从子进程输出推断。
|
|
170
170
|
- **Runner 诊断使用带内通道**——退出状态与 stderr 证据无法证明匹配行由哪个进程写入,因此故意模仿 runner 的受限子进程可能造成错误的可用性或诊断归因;这无法绕过隔离,带外 runner 状态通道暂缓实现。
|
|
171
171
|
- **每个上下文只有一个提供方**——同时组合不同沙箱机制需要提供方级阶梯或独立 Cordis 上下文;调用方逐调用选择策略,而非后端标识。
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import { HarnessError } from "@deepseek-ai/dsh-llm";
|
|
3
3
|
import { assertNever } from "@deepseek-ai/dsh-util-values";
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
4
|
+
import { accessSync, constants, realpathSync, statSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
//#region lib/types/escalation.js
|
|
7
7
|
/**
|
|
@@ -161,6 +161,82 @@ function writableRoots(policy) {
|
|
|
161
161
|
].map(canonicalPath))];
|
|
162
162
|
}
|
|
163
163
|
//#endregion
|
|
164
|
+
//#region lib/types/diagnostics.js
|
|
165
|
+
/** Classify observed sandbox runner failures and file-effect denials. */
|
|
166
|
+
/** Node-local spawn codes proven to identify executable resolution or permission failure. */
|
|
167
|
+
const EXECUTABLE_SPAWN_CODES = new Set(["EACCES", "ENOENT"]);
|
|
168
|
+
/** Whether the caller-owned spawn cwd can be entered. */
|
|
169
|
+
function isUsableWorkdir(path) {
|
|
170
|
+
try {
|
|
171
|
+
if (!statSync(path).isDirectory()) return false;
|
|
172
|
+
accessSync(path, constants.X_OK);
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Attribute only Node ENOENT/EACCES failures whose error path equals argv[0]
|
|
180
|
+
* after independently ruling out the caller-owned cwd. A supplied error path
|
|
181
|
+
* must exactly identify the runner; without one, the syscall must. With a
|
|
182
|
+
* usable cwd, these codes describe resolution or execute permission for that
|
|
183
|
+
* argv[0] or its shebang interpreter.
|
|
184
|
+
* The workdir is checked at classification time, not atomically with spawn;
|
|
185
|
+
* concurrent path replacement may change attribution but cannot permit an
|
|
186
|
+
* unconfined execution.
|
|
187
|
+
* @param error - the original spawn rejection.
|
|
188
|
+
* @param runnerProgram - provider argv[0], the executable that establishes confinement.
|
|
189
|
+
* @param workdir - the caller-owned spawn cwd, checked independently for usability.
|
|
190
|
+
* @returns whether the rejection has executable-specific runner evidence.
|
|
191
|
+
*/
|
|
192
|
+
function isRunnerSpawnFailure(error, runnerProgram, workdir) {
|
|
193
|
+
if (runnerProgram === void 0 || !isUsableWorkdir(workdir)) return false;
|
|
194
|
+
if (typeof error !== "object" || error === null) return false;
|
|
195
|
+
const { code, path, syscall } = error;
|
|
196
|
+
if (typeof code !== "string" || !EXECUTABLE_SPAWN_CODES.has(code)) return false;
|
|
197
|
+
if (typeof syscall !== "string") return false;
|
|
198
|
+
const exactSyscall = `spawn ${runnerProgram}`;
|
|
199
|
+
if (path === void 0) return syscall === exactSyscall;
|
|
200
|
+
if (typeof path !== "string" || path.length === 0 || path !== runnerProgram) return false;
|
|
201
|
+
return syscall === "spawn" || syscall === exactSyscall;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Classify one settled process against the selected backend's structured
|
|
205
|
+
* runner-failure rules. Each rule requires a nonzero exit, its optional
|
|
206
|
+
* exit-code gate, and a fatal signature on one stderr line after exact
|
|
207
|
+
* informational lines are excluded.
|
|
208
|
+
* @param exitCode - process exit code; null means signal termination.
|
|
209
|
+
* @param stderr - collected stderr text, left unchanged.
|
|
210
|
+
* @param rules - structured runner-failure rules from the active wrap.
|
|
211
|
+
* @returns the first matching fatal line, or undefined when evidence is insufficient.
|
|
212
|
+
*/
|
|
213
|
+
function classifyRunnerFailure(exitCode, stderr, rules) {
|
|
214
|
+
if (exitCode === null || exitCode === 0) return void 0;
|
|
215
|
+
const lines = stderr.split(/\r?\n/);
|
|
216
|
+
for (const rule of rules) {
|
|
217
|
+
if (rule.allowedExitCodes !== void 0 && !rule.allowedExitCodes.includes(exitCode)) continue;
|
|
218
|
+
const informationalLines = new Set((rule.informationalLines ?? []).map((line) => line.toLowerCase()));
|
|
219
|
+
const fatalSignatures = rule.fatalSignatures.filter((signature) => signature.trim().length > 0).map((signature) => signature.toLowerCase());
|
|
220
|
+
for (const line of lines) {
|
|
221
|
+
const lowered = line.toLowerCase();
|
|
222
|
+
if (informationalLines.has(lowered)) continue;
|
|
223
|
+
if (fatalSignatures.some((signature) => lowered.includes(signature))) return { detail: line };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Match a non-zero exit against case-insensitive stderr signatures.
|
|
229
|
+
* @param exitCode - process exit code; null means signal termination.
|
|
230
|
+
* @param stderr - collected stderr text.
|
|
231
|
+
* @param signatures - substrings identifying the selected backend's dialect.
|
|
232
|
+
* @returns whether this is a non-zero exit whose stderr matches a signature.
|
|
233
|
+
*/
|
|
234
|
+
function matchesSignature(exitCode, stderr, signatures) {
|
|
235
|
+
if (exitCode === null || exitCode === 0) return false;
|
|
236
|
+
const lowered = stderr.toLowerCase();
|
|
237
|
+
return signatures.some((signature) => lowered.includes(signature.toLowerCase()));
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
164
240
|
//#region lib/types/index.js
|
|
165
241
|
/**
|
|
166
242
|
* Service Definition for the same-world process-confinement capability seam: wrap exact subprocess argv under a
|
|
@@ -199,4 +275,4 @@ var SandboxProvider = class extends Service {
|
|
|
199
275
|
}
|
|
200
276
|
};
|
|
201
277
|
//#endregion
|
|
202
|
-
export { ESCALATION_TARGETS, SANDBOX_UNAVAILABLE, SandboxProvider, SandboxProvider as default, SandboxUnavailableError, WIDER_MODES, approveEscalation, canonicalPath, escalationHintMarker, sandboxDenialMarker, validateEscalationArgs, writableRoots };
|
|
278
|
+
export { ESCALATION_TARGETS, SANDBOX_UNAVAILABLE, SandboxProvider, SandboxProvider as default, SandboxUnavailableError, WIDER_MODES, approveEscalation, canonicalPath, classifyRunnerFailure, escalationHintMarker, isRunnerSpawnFailure, matchesSignature, sandboxDenialMarker, validateEscalationArgs, writableRoots };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { RunnerFailureRule } from './index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Attribute only Node ENOENT/EACCES failures whose error path equals argv[0]
|
|
4
|
+
* after independently ruling out the caller-owned cwd. A supplied error path
|
|
5
|
+
* must exactly identify the runner; without one, the syscall must. With a
|
|
6
|
+
* usable cwd, these codes describe resolution or execute permission for that
|
|
7
|
+
* argv[0] or its shebang interpreter.
|
|
8
|
+
* The workdir is checked at classification time, not atomically with spawn;
|
|
9
|
+
* concurrent path replacement may change attribution but cannot permit an
|
|
10
|
+
* unconfined execution.
|
|
11
|
+
* @param error - the original spawn rejection.
|
|
12
|
+
* @param runnerProgram - provider argv[0], the executable that establishes confinement.
|
|
13
|
+
* @param workdir - the caller-owned spawn cwd, checked independently for usability.
|
|
14
|
+
* @returns whether the rejection has executable-specific runner evidence.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isRunnerSpawnFailure(error: unknown, runnerProgram: string | undefined, workdir: string): boolean;
|
|
17
|
+
/** Fatal runner evidence retained for infrastructure-error detail. */
|
|
18
|
+
interface RunnerFailureMatch {
|
|
19
|
+
/** The original stderr line that matched a fatal signature. */
|
|
20
|
+
detail: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Classify one settled process against the selected backend's structured
|
|
24
|
+
* runner-failure rules. Each rule requires a nonzero exit, its optional
|
|
25
|
+
* exit-code gate, and a fatal signature on one stderr line after exact
|
|
26
|
+
* informational lines are excluded.
|
|
27
|
+
* @param exitCode - process exit code; null means signal termination.
|
|
28
|
+
* @param stderr - collected stderr text, left unchanged.
|
|
29
|
+
* @param rules - structured runner-failure rules from the active wrap.
|
|
30
|
+
* @returns the first matching fatal line, or undefined when evidence is insufficient.
|
|
31
|
+
*/
|
|
32
|
+
export declare function classifyRunnerFailure(exitCode: number | null, stderr: string, rules: readonly RunnerFailureRule[]): RunnerFailureMatch | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Match a non-zero exit against case-insensitive stderr signatures.
|
|
35
|
+
* @param exitCode - process exit code; null means signal termination.
|
|
36
|
+
* @param stderr - collected stderr text.
|
|
37
|
+
* @param signatures - substrings identifying the selected backend's dialect.
|
|
38
|
+
* @returns whether this is a non-zero exit whose stderr matches a signature.
|
|
39
|
+
*/
|
|
40
|
+
export declare function matchesSignature(exitCode: number | null, stderr: string, signatures: readonly string[]): boolean;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -134,10 +134,12 @@ export declare abstract class SandboxProvider extends Service {
|
|
|
134
134
|
* `['bash', '-c', command]`.
|
|
135
135
|
* @param policy - the file-effect policy this execution runs under,
|
|
136
136
|
* carried per call (see {@link SandboxPolicy}).
|
|
137
|
+
* @param signal - cancellation while the provider resolves the policy and runner.
|
|
137
138
|
* @returns the argv to spawn instead, plus the enforcement completeness
|
|
138
139
|
* the selected backend achieves for it.
|
|
139
140
|
*/
|
|
140
|
-
abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv
|
|
141
|
+
abstract confine(argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): Promise<ConfinedArgv>;
|
|
141
142
|
}
|
|
142
143
|
export default SandboxProvider;
|
|
144
|
+
export { classifyRunnerFailure, isRunnerSpawnFailure, matchesSignature } from './diagnostics.ts';
|
|
143
145
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-sandbox",
|
|
3
3
|
"description": "Abstract process-sandbox seam (ctx.sandbox) for the DeepSeek Harness: same-world confinement vocabulary and the SandboxProvider contract",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
30
|
+
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.1",
|
|
31
31
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
32
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
32
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
36
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
37
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
36
|
+
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.1",
|
|
37
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@deepseek-ai/dsh-util-values": "^0.1.
|
|
40
|
+
"@deepseek-ai/dsh-util-values": "^0.1.6-alpha.1"
|
|
41
41
|
}
|
|
42
42
|
}
|