@morlay/dsh-sandbox-local 0.0.3-alpha.6 → 0.0.3-alpha.7

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.md CHANGED
@@ -93,20 +93,35 @@
93
93
  把本包作为**独立 bundle** 采用的部署直接列进 `dsh.profile.bundles` 即可;行不带 config
94
94
  (schema 默认是空规则),patch 内容见该文件。
95
95
 
96
- **本部署(`@morlay/dsh-preset`)不走这条路径**:装配(禁用官方两行 + 插入
97
- `sandbox-local` 行)与 `access` 规则一起维护在 preset 的 bundle patch 里,因此示例 app 的
98
- `dsh.profile.bundles` 不需要列出本包,只需要 profile 的依赖树能解析模块名
99
- (`@morlay/dsh-preset` 已在 `dependencies` 声明本包)。patch 层级的合并顺序与放置理由见
100
- [设计 host 层部署配置](../../preset/dsh-preset/.agents/designs/20260917-host层部署配置.md)。
96
+ **本部署走的就是这条路径**:示例 app 的 `dsh.profile.bundles` 列出了本包(排在
97
+ [`@morlay/dsh-profile`](../../profile/dsh-profile/README.md) 之前),因此"禁用官方两行 + 插入本行"由这份
98
+ patch 负责;`access` 规则的值由 `dsh-profile` 按 id 做 config 覆盖——装配与配置各归一处
99
+ (`@morlay/dsh-profile` 已在 `dependencies` 声明本包)。patch 层级的合并顺序与放置理由见
100
+ [设计 host 层部署配置](../../profile/dsh-profile/.agents/designs/20260917-host层部署配置.md)。
101
101
  两种采用方式互斥:同时上线会重复插入同一行。
102
102
 
103
+ ## 接管 `sandbox:policy`
104
+
105
+ 换了 `ctx.sandbox` / `ctx.fs`,模型看到的那段运行时策略文本也得跟着换:上游
106
+ `@deepseek-ai/dsh-sandbox-policy` 在**全局层**注册 `sandbox:policy`,文本只描述官方策略(只读 /
107
+ workspace-write / 全权 + workspace root),不知道本部署追加的 `rw` / `r-` / `--` 规则。
108
+
109
+ 全局层同名注册会抛错(`NamedEntries.insert`),上游给的官方路径是**按 agent 作用域覆盖**:本包在
110
+ `system-prompt/assemble` 时为每个 agent 在它的 `ctx` 上注册同名 context,装配时近的作用域遮蔽全局那条
111
+ ([`src/policy.ts`](./src/policy.ts))。文本保留官方三种 mode 的语义(重写中文),并在规则非空时追加
112
+ 本部署的额外可写根 / 只读项 / 拒绝项;没有 agent 的装配(agentless 调用)保持官方那条。
113
+
114
+ **升级注意**:这段文本是照上游 `renderPolicyContext` 的语义重写的——上游改了模式集合或措辞时测试不会自动
115
+ 发现,同步上游时要读一眼
116
+ `vendor/deepseek-harness/packages/sandbox/sandbox-policy/src/index.ts`。
117
+
103
118
  ## 前提
104
119
 
105
120
  - 官方 `sandbox` 与 `fs-sandbox` 行必须禁用:同一 scope 内重复注册同名服务会 fail loud
106
121
  (`service "sandbox" has been registered at …`),而不是覆盖。
107
122
  - 启用规则的层必须同时做三件事——禁用官方两行、插入本包行、写规则:只做后两件时官方
108
123
  实现仍在提供 `ctx.sandbox` / `ctx.fs`,规则没有生效点,沙箱静默退回「只有工作区 +
109
- `/tmp` 可写」(命令照常跑,没有报错)。装配守卫见 `@morlay/dsh-preset` 的
124
+ `/tmp` 可写」(命令照常跑,没有报错)。装配守卫见 `@morlay/dsh-profile` 的
110
125
  `patch.spec.ts`。
111
126
  - 规则与 `runnerCommand` 互斥:自定义 runner 的 argv 方言无法识别,此时配了规则会在
112
127
  `confine` 抛错(宁可失败也不让规则静默失效)。
package/cordis.patch.yml CHANGED
@@ -8,8 +8,10 @@
8
8
  # 拦住读与写、`r-` / `rw` 条目参与写判定)。
9
9
  #
10
10
  # 这组 patch 供把本包作为独立 bundle 采用的部署使用:行不带 config,schema 默认是空规则。
11
- # 本部署(`@morlay/dsh-preset`)不走这条路径——它的 patch 自己禁用官方两行、插入本行
12
- # 并写上规则,因此不会加载本 bundle;两种采用方式同时上线会重复插入同一行。
11
+ # 行不带 config(schema 默认是空规则):规则的**值**由配置层给——本部署由 `@morlay/dsh-profile`
12
+ # 按 id 做 config 覆盖。两层都住 app 的 `dsh.profile.bundles`,本包排在前面(配置层要能找到这一行)。
13
+ #
14
+ # 若别处再自行"禁官方两行 + 插入本行",就会与本 bundle 重复插入同一行——采用方式二选一。
13
15
 
14
16
  - id: sandbox
15
17
  disabled: true
package/dist/index.mjs CHANGED
@@ -242,6 +242,76 @@ var ConfigurableFileSystem = class extends LocalFileSystem {
242
242
  }
243
243
  };
244
244
  //#endregion
245
+ //#region src/policy.ts
246
+ /**
247
+ * 接管 `sandbox:policy` 那条运行时上下文。
248
+ *
249
+ * 上游 [`@deepseek-ai/dsh-sandbox-policy`] 在**全局层**注册它,文本只描述官方策略(只读 /
250
+ * workspace-write / 全权 + workspace root)——它不知道本部署追加的 `rw` / `r-` / `--` 规则,
251
+ * 模型因此拿不到"哪些额外路径可写、哪些被拒"。本包替换了 `ctx.sandbox` / `ctx.fs`,这条文本也得跟着换。
252
+ *
253
+ * 全局层同名注册会抛错(`NamedEntries.insert`),上游给的官方路径是**按 agent 作用域覆盖**:
254
+ * `systemPrompt.context()` 在 scope 上注册同名项,装配时近的作用域遮蔽全局那条
255
+ * (`ScopedLayers.merge`)。所以这里在每个 agent 的 `ctx` 上注册一次。
256
+ */
257
+ /** 上游注册的运行时上下文名:同名才叫接管。 */
258
+ const SANDBOX_POLICY_CONTEXT = "sandbox:policy";
259
+ /**
260
+ * 策略文本:官方三种 mode 的语义(重写中文,与其它注入文案一致)+ 本部署追加的规则。
261
+ * @param policy - 该会话解析出来的策略(模式与 workspace root)。
262
+ * @param rules - 本部署解析后的访问规则(已展开环境变量模板)。
263
+ * @returns 给模型看的一段文本。
264
+ */
265
+ function renderPolicyContext(policy, rules) {
266
+ const base = (() => {
267
+ switch (policy.mode) {
268
+ case "read-only": return "当前 DSH 文件策略:只读。任何可用的操作都不能修改 standing 模式下的文件。别仅凭这条策略就拒绝必需的修改:照常尝试可用工具,并遵循它给出的拒绝与升级指引。";
269
+ case "workspace-write": return `当前 DSH 文件策略:workspace-write(工作区可写)。任何可用的操作都可修改会话工作区 ${JSON.stringify(policy.workspaceRoot)} 下的文件;部分平台临时目录同样可写。`;
270
+ case "danger-full-access": return "当前 DSH 文件策略:danger-full-access(全权)。DSH 文件沙箱不再限制可用操作对文件的修改。";
271
+ }
272
+ })();
273
+ const extras = [];
274
+ if (rules.allowWrite.length > 0) extras.push(`本部署额外授权的可写根:${rules.allowWrite.join("、")}。`);
275
+ if (rules.readOnly.length > 0) extras.push(`只读(一律不可写):${rules.readOnly.join("、")}。`);
276
+ if (rules.deny.length > 0) extras.push(`拒绝访问(读与写都拒):${rules.deny.join("、")}。`);
277
+ return extras.length === 0 ? base : `${base} ${extras.join("")}`;
278
+ }
279
+ /**
280
+ * 在给定作用域注册同名策略文本(agent 的 `ctx`,或 preset 子树)。
281
+ * @param scope - 拥有这次注册的 ctx;它的 scope 因此遮蔽全局那条。
282
+ * @param rules - 本部署的访问规则。
283
+ * @param resolve - 按会话解析策略(生产传 `ctx.sandboxPolicy.resolve`)。
284
+ */
285
+ function registerPolicyContext(scope, rules, resolve) {
286
+ scope.inject(["systemPrompt"], (scoped) => {
287
+ scoped.systemPrompt.context({
288
+ name: SANDBOX_POLICY_CONTEXT,
289
+ order: scoped.systemPrompt.getContextOrder("SANDBOX_POLICY"),
290
+ text: (context) => {
291
+ const session = context.agent?.session;
292
+ return session === void 0 ? "" : renderPolicyContext(resolve(session), rules);
293
+ }
294
+ });
295
+ });
296
+ }
297
+ /**
298
+ * 每个 agent 注册一次(装配期做,与 scope 出口同一时机与理由:创建期服务可用性还在变)。
299
+ * @param ctx - 本插件的 ctx(host 平面)。
300
+ * @param rules - 本部署的访问规则。
301
+ * @param resolve - 按会话解析策略。
302
+ */
303
+ function installPolicyContext(ctx, rules, resolve) {
304
+ const registered = /* @__PURE__ */ new WeakSet();
305
+ ctx.on("system-prompt/assemble", (_assembly, context, next) => {
306
+ const agent = context.agent;
307
+ if (agent !== void 0 && !registered.has(agent)) {
308
+ registered.add(agent);
309
+ registerPolicyContext(agent.ctx, rules, resolve);
310
+ }
311
+ return next();
312
+ });
313
+ }
314
+ //#endregion
245
315
  //#region src/dialects.ts
246
316
  const SEPARATOR = "--";
247
317
  function separatorIndex(argv) {
@@ -353,8 +423,7 @@ const Config = z.object({
353
423
  //#region src/index.ts
354
424
  const name = "sandbox-local";
355
425
  const inject = ["sandboxPolicy"];
356
- function warnAboutDegradedRules(ctx, config) {
357
- const rules = ruleSourceOf(config, process.env);
426
+ function warnAboutDegradedRules(ctx, rules) {
358
427
  const grants = rules.allowWrite.length > 0;
359
428
  const readOnly = rules.readOnly.length > 0;
360
429
  const denials = rules.deny.length > 0;
@@ -364,9 +433,11 @@ function warnAboutDegradedRules(ctx, config) {
364
433
  if (grants && process.platform === "win32") ctx.logger.warn("sandbox-local: \"rw\" entries cannot be granted to confined subprocesses on win32; ctx.fs covers the extra roots, the Windows ACL runner does not");
365
434
  }
366
435
  function apply(ctx, config) {
367
- warnAboutDegradedRules(ctx, config);
436
+ const rules = ruleSourceOf(config, process.env);
437
+ warnAboutDegradedRules(ctx, rules);
368
438
  new ConfigurableSandboxProvider(ctx, config);
369
439
  new ConfigurableFileSystem(ctx, config);
440
+ installPolicyContext(ctx, rules, (session) => ctx.sandboxPolicy.resolve({ session }));
370
441
  }
371
442
  //#endregion
372
443
  export { Config, apply, inject, name };
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Configurable Sandbox",
4
+ "description": "Replaces the shipped process sandbox and filesystem fence with implementations that add extra writable roots and access denials."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "可配置沙箱",
4
+ "description": "替换官方的进程沙箱与文件系统围栏:在官方语义之上追加额外可写根与拒绝项。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/dsh-sandbox-local",
3
- "version": "0.0.3-alpha.6",
3
+ "version": "0.0.3-alpha.7",
4
4
  "description": "Configurable sandbox bundle: replaces the shipped process-sandbox provider and filesystem fence with implementations that add extra writable roots and access denials on top of the upstream semantics.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -15,6 +15,7 @@
15
15
  "url": "https://github.com/morlay/dsh-plugin.git"
16
16
  },
17
17
  "files": [
18
+ "locale/*.json",
18
19
  "dist",
19
20
  "src",
20
21
  "cordis.patch.yml",
@@ -24,26 +25,34 @@
24
25
  "exports": {
25
26
  ".": "./dist/index.mjs",
26
27
  "./package.json": "./package.json",
27
- "./cordis.patch.yml": "./cordis.patch.yml"
28
+ "./cordis.patch.yml": "./cordis.patch.yml",
29
+ "./locale/*.json": "./locale/*.json"
28
30
  },
29
31
  "devDependencies": {
30
- "@deepseek-ai/cordis": "4.0.3",
31
- "@deepseek-ai/dsh-fs": "0.1.7-alpha.1",
32
- "@deepseek-ai/dsh-fs-local": "0.1.7-alpha.1",
33
- "@deepseek-ai/dsh-fs-sandbox": "0.1.7-alpha.1",
34
- "@deepseek-ai/dsh-sandbox": "0.1.7-alpha.1",
35
- "@deepseek-ai/dsh-sandbox-local": "0.1.7-alpha.1",
36
- "@deepseek-ai/dsh-sandbox-policy": "0.1.7-alpha.1"
32
+ "@deepseek-ai/cordis": "4.0.4",
33
+ "@deepseek-ai/dsh-agent": "0.1.7-alpha.2",
34
+ "@deepseek-ai/dsh-fs": "0.1.7-alpha.2",
35
+ "@deepseek-ai/dsh-fs-local": "0.1.7-alpha.2",
36
+ "@deepseek-ai/dsh-fs-sandbox": "0.1.7-alpha.2",
37
+ "@deepseek-ai/dsh-sandbox": "0.1.7-alpha.2",
38
+ "@deepseek-ai/dsh-sandbox-local": "0.1.7-alpha.2",
39
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.7-alpha.2",
40
+ "@deepseek-ai/dsh-scope": "0.1.7-alpha.2",
41
+ "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
42
+ "@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.2"
37
43
  },
38
44
  "peerDependencies": {
39
- "@deepseek-ai/cordis": "4.0.3",
40
- "@deepseek-ai/dsh-fs": "0.1.7-alpha.1",
41
- "@deepseek-ai/dsh-fs-local": "0.1.7-alpha.1",
42
- "@deepseek-ai/dsh-fs-sandbox": "0.1.7-alpha.1",
43
- "@deepseek-ai/dsh-sandbox": "0.1.7-alpha.1",
44
- "@deepseek-ai/dsh-sandbox-local": "0.1.7-alpha.1",
45
- "@deepseek-ai/dsh-sandbox-policy": "0.1.7-alpha.1",
46
- "@deepseek-ai/schemastery": "3.18.3"
45
+ "@deepseek-ai/cordis": "4.0.4",
46
+ "@deepseek-ai/dsh-agent": "0.1.7-alpha.2",
47
+ "@deepseek-ai/dsh-fs": "0.1.7-alpha.2",
48
+ "@deepseek-ai/dsh-fs-local": "0.1.7-alpha.2",
49
+ "@deepseek-ai/dsh-fs-sandbox": "0.1.7-alpha.2",
50
+ "@deepseek-ai/dsh-sandbox": "0.1.7-alpha.2",
51
+ "@deepseek-ai/dsh-sandbox-local": "0.1.7-alpha.2",
52
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.7-alpha.2",
53
+ "@deepseek-ai/dsh-session": "0.1.7-alpha.2",
54
+ "@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.2",
55
+ "@deepseek-ai/schemastery": "3.18.4"
47
56
  },
48
57
  "dsh": {
49
58
  "bundle": {
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { Context } from "@deepseek-ai/cordis";
2
2
  import type { Config } from "./config.ts";
3
3
  import { ConfigurableFileSystem } from "./fs.ts";
4
- import { ruleSourceOf } from "./rules.ts";
4
+ import { installPolicyContext } from "./policy.ts";
5
+ import { ruleSourceOf, type RuleSource } from "./rules.ts";
5
6
  import { ConfigurableSandboxProvider } from "./sandbox.ts";
6
7
 
7
8
  export { Config } from "./config.ts";
@@ -10,8 +11,7 @@ export const name = "sandbox-local";
10
11
 
11
12
  export const inject = ["sandboxPolicy"];
12
13
 
13
- function warnAboutDegradedRules(ctx: Context, config: Config): void {
14
- const rules = ruleSourceOf(config, process.env);
14
+ function warnAboutDegradedRules(ctx: Context, rules: RuleSource): void {
15
15
  const grants = rules.allowWrite.length > 0;
16
16
  const readOnly = rules.readOnly.length > 0;
17
17
  const denials = rules.deny.length > 0;
@@ -34,7 +34,10 @@ function warnAboutDegradedRules(ctx: Context, config: Config): void {
34
34
  }
35
35
 
36
36
  export function apply(ctx: Context, config: Config): void {
37
- warnAboutDegradedRules(ctx, config);
37
+ const rules = ruleSourceOf(config, process.env);
38
+ warnAboutDegradedRules(ctx, rules);
38
39
  new ConfigurableSandboxProvider(ctx, config);
39
40
  new ConfigurableFileSystem(ctx, config);
41
+ // 替换了 ctx.sandbox / ctx.fs,`sandbox:policy` 那段运行时文本也要跟着换(见 policy.ts)。
42
+ installPolicyContext(ctx, rules, (session) => ctx.sandboxPolicy.resolve({ session }));
40
43
  }
package/src/policy.ts ADDED
@@ -0,0 +1,98 @@
1
+ import type { Agent } from "@deepseek-ai/dsh-agent";
2
+ import type { Context } from "@deepseek-ai/cordis";
3
+ import type { SandboxExecutionPolicy } from "@deepseek-ai/dsh-sandbox";
4
+ import type { Session } from "@deepseek-ai/dsh-session";
5
+ import type {} from "@deepseek-ai/dsh-system-prompt";
6
+ import type { RuleSource } from "./rules.ts";
7
+
8
+ /**
9
+ * 接管 `sandbox:policy` 那条运行时上下文。
10
+ *
11
+ * 上游 [`@deepseek-ai/dsh-sandbox-policy`] 在**全局层**注册它,文本只描述官方策略(只读 /
12
+ * workspace-write / 全权 + workspace root)——它不知道本部署追加的 `rw` / `r-` / `--` 规则,
13
+ * 模型因此拿不到"哪些额外路径可写、哪些被拒"。本包替换了 `ctx.sandbox` / `ctx.fs`,这条文本也得跟着换。
14
+ *
15
+ * 全局层同名注册会抛错(`NamedEntries.insert`),上游给的官方路径是**按 agent 作用域覆盖**:
16
+ * `systemPrompt.context()` 在 scope 上注册同名项,装配时近的作用域遮蔽全局那条
17
+ * (`ScopedLayers.merge`)。所以这里在每个 agent 的 `ctx` 上注册一次。
18
+ */
19
+
20
+ /** 上游注册的运行时上下文名:同名才叫接管。 */
21
+ export const SANDBOX_POLICY_CONTEXT = "sandbox:policy";
22
+
23
+ /**
24
+ * 策略文本:官方三种 mode 的语义(重写中文,与其它注入文案一致)+ 本部署追加的规则。
25
+ * @param policy - 该会话解析出来的策略(模式与 workspace root)。
26
+ * @param rules - 本部署解析后的访问规则(已展开环境变量模板)。
27
+ * @returns 给模型看的一段文本。
28
+ */
29
+ export function renderPolicyContext(policy: SandboxExecutionPolicy, rules: RuleSource): string {
30
+ const base = ((): string => {
31
+ switch (policy.mode) {
32
+ case "read-only":
33
+ return "当前 DSH 文件策略:只读。任何可用的操作都不能修改 standing 模式下的文件。别仅凭这条策略就拒绝必需的修改:照常尝试可用工具,并遵循它给出的拒绝与升级指引。";
34
+ case "workspace-write":
35
+ return `当前 DSH 文件策略:workspace-write(工作区可写)。任何可用的操作都可修改会话工作区 ${JSON.stringify(policy.workspaceRoot)} 下的文件;部分平台临时目录同样可写。`;
36
+ case "danger-full-access":
37
+ return "当前 DSH 文件策略:danger-full-access(全权)。DSH 文件沙箱不再限制可用操作对文件的修改。";
38
+ }
39
+ })();
40
+
41
+ const extras: string[] = [];
42
+ if (rules.allowWrite.length > 0) {
43
+ extras.push(`本部署额外授权的可写根:${rules.allowWrite.join("、")}。`);
44
+ }
45
+ if (rules.readOnly.length > 0) {
46
+ extras.push(`只读(一律不可写):${rules.readOnly.join("、")}。`);
47
+ }
48
+ if (rules.deny.length > 0) {
49
+ extras.push(`拒绝访问(读与写都拒):${rules.deny.join("、")}。`);
50
+ }
51
+ return extras.length === 0 ? base : `${base} ${extras.join("")}`;
52
+ }
53
+
54
+ /**
55
+ * 在给定作用域注册同名策略文本(agent 的 `ctx`,或 preset 子树)。
56
+ * @param scope - 拥有这次注册的 ctx;它的 scope 因此遮蔽全局那条。
57
+ * @param rules - 本部署的访问规则。
58
+ * @param resolve - 按会话解析策略(生产传 `ctx.sandboxPolicy.resolve`)。
59
+ */
60
+ export function registerPolicyContext(
61
+ scope: Context,
62
+ rules: RuleSource,
63
+ resolve: (session: Session) => SandboxExecutionPolicy,
64
+ ): void {
65
+ // 回调参数 ctx 才是解锁了 `systemPrompt` 的那个 ctx,注册也就落在它的 scope 上。
66
+ scope.inject(["systemPrompt"], (scoped) => {
67
+ scoped.systemPrompt.context({
68
+ name: SANDBOX_POLICY_CONTEXT,
69
+ order: scoped.systemPrompt.getContextOrder("SANDBOX_POLICY"),
70
+ text: (context) => {
71
+ const session = context.agent?.session;
72
+ return session === undefined ? "" : renderPolicyContext(resolve(session), rules);
73
+ },
74
+ });
75
+ });
76
+ }
77
+
78
+ /**
79
+ * 每个 agent 注册一次(装配期做,与 scope 出口同一时机与理由:创建期服务可用性还在变)。
80
+ * @param ctx - 本插件的 ctx(host 平面)。
81
+ * @param rules - 本部署的访问规则。
82
+ * @param resolve - 按会话解析策略。
83
+ */
84
+ export function installPolicyContext(
85
+ ctx: Context,
86
+ rules: RuleSource,
87
+ resolve: (session: Session) => SandboxExecutionPolicy,
88
+ ): void {
89
+ const registered = new WeakSet<Agent>();
90
+ ctx.on("system-prompt/assemble", (_assembly, context, next) => {
91
+ const agent = context.agent as Agent | undefined;
92
+ if (agent !== undefined && !registered.has(agent)) {
93
+ registered.add(agent);
94
+ registerPolicyContext(agent.ctx, rules, resolve);
95
+ }
96
+ return next();
97
+ });
98
+ }