dsh-perm-gate 0.2.1-beta.3 → 0.2.1-beta.4

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.zh.md CHANGED
@@ -19,17 +19,21 @@
19
19
 
20
20
  > **▼ DSH 版本适配**
21
21
  >
22
- > | DSH 版本 | 加载 | 设置注册 | 宿主门 | 客户端半 |
23
- > | --- | --- | --- | --- | --- |
24
- > | 0.1.0-rc.7 ~ 0.1.1-rc.x | ✅ | `ctx.settings.register(ns, schema, { base })` | ✅ `tools/pre-execute` 一致 | ✅ 仅类型导入 |
25
- > | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ✅ | `register` 仍保留(另加 `installSection`) | ✅ `tools/pre-execute` 一致 | ✅ 仅类型导入 |
22
+ > 两个 DSH 版本线从两个长期分支分别维护,各有一套版本号系列、`engines.dsh`
23
+ > 和 npm 分发标签([发布布局](./RELEASING.md)):
26
24
  >
27
- > 一份产物同时支持两版本。两处版本敏感点都用**能力探测**而非版本号判断:
28
- > ① 设置注册走 `register`,它在所有目标版本都存在(`installSection` 是 0.1.2 的
29
- > **新增**而非替代);② `effectivePolicy` 在**两版本**中都是 user-approval 服务的
30
- > **私有**方法,故只在 `typeof` 探测后调用,缺失或抛错时降级为「策略未知」。
31
- > 客户端 bundle 对 `@deepseek-ai/*` 无任何值导入,因此 0.1.2 的
32
- > `dsh-client-runtime` → `dsh-client-store` 改名不会影响它。
25
+ > | DSH 版本 | 分支 | 版本号 | npm 标签 |
26
+ > | --- | --- | --- | --- |
27
+ > | 0.1.0-rc.7 ~ 0.1.1-rc.x | `legacy` | `1.x` | `@legacy` |
28
+ > | 0.1.2-alpha.1+(含 0.1.5-rc.2) | `main` | `0.2.x` | `@latest` |
29
+ >
30
+ > `@deepseek-ai/dsh-client-runtime` 在 `0.1.2-alpha.1` 中已被**移除**——不仅仅是更名。
31
+ > `legacy` 线仍通过它访问 `ctx.slots`;`main` 从
32
+ > `@deepseek-ai/dsh-client-ui-renderer/client` 获得相同的声明。两处版本敏感
33
+ > 接缝通过**能力探测**处理,而非版本号检查:(1)设置注册使用 `register`,两线
34
+ > 都存在(`installSection` 是新增项,不是替代);(2)`effectivePolicy` 在两
35
+ > 线上都是 user-approval 服务的**私有**方法,因此通过 `typeof` 探测读取,缺失
36
+ > 或抛错时降级为「策略未知」。
33
37
 
34
38
  版本 **0.2.1-beta.3** —— 变更见 [Changelog](./CHANGELOG.md)。
35
39
 
@@ -54,7 +58,8 @@
54
58
  - **会话放行** — 精确的 `(工具, 规范化 fingerprint)` grant,带 `TTL` + `maxUses`;换目标绝不复用。子代理继承但不可自授。
55
59
  - **纯函数规则引擎** — glob/regex 编译 + ReDoS 上限、坏规则 loud fail、按源内容哈希缓存。
56
60
  - **审计** — 每次决策写为 `{ignorable:true}` 事件并带 `callId`;模型可见理由与记录一致。
57
- - **Permissive 档位** — 一个**独立审批模式**(区别于只读、完全权限与白名单档),既不是"自动审批",也不授予泛化权限。前端只暴露**一个开关**(`permissive`),后台三个审批策略**可组合**、由插件设置决定——仍对 P0 保持 fail-closed。
61
+ - **Permissive 档位** — 一个**独立审批模式**(区别于只读、完全权限与白名单档),既不是"自动审批",也不授予泛化权限。前端只暴露**一个开关**(`permissive`),后台四个审批策略**可组合**、由插件设置决定——仍对 P0 保持 fail-closed。
62
+ - **沙箱提权自动答复**(`trustEscalation`)— 沙箱提权是从 shell / pwsh / edit 工具**体内部**(`tools/pre-execute` 之后)发出的,所以门禁从未见过它,一个它自动放行的调用仍会弹出确认。开启后,门禁以 `callId` 精确匹配已放行调用并直接答复。
58
63
 
59
64
  ## 安装
60
65
 
@@ -101,6 +106,7 @@ P0 硬拒绝始终单调且不可协商。
101
106
  permissiveStrategies: # 后台策略,可组合
102
107
  trustAutoAllow: true # 作用域内安全操作自动放行;危险/未知转 ask
103
108
  alwaysConfirm: false # 一律逐次 ask;允许控件附带重复允许/迁白名单按钮
109
+ trustEscalation: true # 门禁已放行的调用,其自身的沙箱提权免确认
104
110
  llmAssist: false # 先由 LLM 分类裁决;ask/无分类器时回退到人工
105
111
  ```
106
112
  (llmAssist 的真实接收 LLM 在设置页填 `classifierEndpoint` / `classifierModel`,OpenAI 兼容的自定义 API
@@ -110,7 +116,27 @@ P0 硬拒绝始终单调且不可协商。
110
116
  「允许控件」含两个扩展按钮:**本会话重复允许该类**(会话限次 grant,`approveRepeat`)与
111
117
  **允许所有类型**(把命令词持久写进 `permissions.yaml` 的 allow 白名单,`approveAllowEverywhere`)。
112
118
  `llmAssist` 调用配置的真实 LLM(任意 OpenAI 兼容 API)自动裁决 `ask`,结果不确定/出错时回退人工
113
- 接缝——始终 fail-closed。`permissive` 关闭时,门禁行为与之前完全一致。
119
+ 接缝——始终 fail-closed。`trustEscalation`(档位开启时默认开)答复门禁已放行的调用在其工具体内
120
+ 提出的 `sandbox_permissions` 提权;见下文。`permissive` 关闭时,门禁行为与之前完全一致。
121
+
122
+ ### 沙箱提权:为何 `safe` 裁决仍会弹窗
123
+
124
+ 一个工具调用可能触发**两个独立的审批**。门禁负责第一个——它的 `ask`,在 `tools/pre-execute`
125
+ 瀑布上。第二个来自工具体内部的 `approveEscalation`,在 `tools/execute` 时刻,只要模型传了
126
+ `sandbox_permissions` + `justification`;此时 `tools/pre-execute` 已结算,门禁的放行从未到达它。
127
+ LLM 评定为 `safe` 且门禁自动放行的调用因此仍会弹出确认。
128
+
129
+ `trustEscalation` 填补这个缺口。门禁记住每个它正面向上放行的调用(以宿主 `callId` 为键,提权
130
+ 请求会重复该值),并在本处自行答复 `allowed-once`。它仅在**全部满足**时适用:
131
+
132
+ - Permissive 档位开启且 `trustEscalation` 开启;
133
+ - 请求携带门禁放行的 `callId`,且工具名匹配;
134
+ - 原因为已知的提权,指明 `workspace-write` 或 `danger-full-access`。
135
+
136
+ 其余所有情况——未知原因、不同的调用、门禁要求或拒绝的调用、`approval: never` 透传——都保持交给人
137
+ 工,因此未来 DSH 措辞变更时 fail-closed。自动答复记录在事件流中
138
+ (`verdict: "escalation-auto"`,`mode: <目标模式>`)。关闭开关可使沙箱放宽保持人工审批,其余
139
+ 自动放行不变。
114
140
 
115
141
  ### 权限下拉里可选档位
116
142
 
@@ -132,7 +158,7 @@ Full access 之间。DSH 的 bundle patch 对这个 map 是**整表替换**而
132
158
  ### 在 UI 里可配置
133
159
 
134
160
  该档位也可在运行时从 **设置 → 插件 → Permissive 审批档** 调整(插件浏览器端渲染的
135
- `settings.plugins.tab` 页面):一个开关切换 `permissive`,三个开关编辑后台
161
+ `settings.plugins.tab` 页面):一个开关切换 `permissive`,四个开关编辑后台
136
162
  `permissiveStrategies`。host 端 live 读取该命名空间,改动对下一条工具调用即时生效,无需重启。
137
163
  这是一个独立审批类,**不是** DSH 的"auto-approval"档。
138
164
 
package/lib/client.js CHANGED
@@ -20,6 +20,8 @@ window.__ModuleLoader__.load({
20
20
  "card.strategy.trustAutoAllow": "trustAutoAllow — 作用域内安全操作自动放行,危险/未知转 ask(中间档基线)",
21
21
  "card.strategy.alwaysConfirm": "alwaysConfirm — 一律逐次 ask;审批面板「允许控件」含两个扩展按钮:「本会话重复允许该类」(会话语限次 grant)与「允许所有类型」(命令词持久迁入 rulesFile 白名单)",
22
22
  "card.strategy.llmAssist": "llmAssist — 自定义 LLM 按风险分级裁决:safe 自动放行;deletion/credential/remote/system/bulk 硬风险自动拒绝;neutral 进入确认学习;失败/超时回退人工",
23
+ "card.strategy.trustEscalation": "trustEscalation — 沙箱升级免确认:本门禁已放行的调用,其工具内部发起的沙箱提权(shell / pwsh / edit 的 sandbox_permissions)由门禁直接批准,不再弹窗",
24
+ "card.strategy.trustEscalationHint": "沙箱提权审批发生在 tools/pre-execute 之后、工具体内部,门禁原本看不到它,因此「LLM 判安全已自动放行」的调用仍会弹窗要求你批准提权。开启后,仅对门禁确实放行过的那一次调用(按 callId 精确匹配)免确认,其余一律照旧走人工;目标模式非 workspace-write / danger-full-access 或理由无法识别时同样回退人工。关闭后提权保持人工把关。",
23
25
  "card.readonly": "只读",
24
26
  "card.unavailable": "设置命名空间不可用:请确认 dsh-perm-gate 已装配进 profile。",
25
27
  "card.llmReceiver": "llmAssist 接收 LLM(OpenAI 兼容,可用自定义 API)",
@@ -127,6 +129,8 @@ window.__ModuleLoader__.load({
127
129
  "card.strategy.trustAutoAllow": "trustAutoAllow — safe in-scope ops auto-allow; dangerous/unknown ask (baseline middle tier)",
128
130
  "card.strategy.alwaysConfirm": "alwaysConfirm — every crossing asks; the approval \"allow controls\" get two extended buttons: \"repeat-allow this type this session\" (bounded session grant) and \"allow every occurrence\" (persist the command word into the rulesFile whitelist)",
129
131
  "card.strategy.llmAssist": "llmAssist — a custom LLM grades risk: safe auto-allows; deletion/credential/remote/system/bulk hard risks auto-deny; neutral enters confirm-learning; failure/timeout falls back to the human",
132
+ "card.strategy.trustEscalation": "trustEscalation — sandbox escalation without a prompt: for a call this gate already allowed, the sandbox widening a shell / pwsh / edit tool raises from inside itself is approved here instead of asking you",
133
+ "card.strategy.trustEscalationHint": "A sandbox escalation is asked from inside the tool body, after tools/pre-execute, so the gate never saw it — a call the LLM graded safe and the gate auto-allowed still prompted you to approve the widening. With this on, only the exact call the gate allowed (matched by callId) skips the prompt; everything else goes to the human as before, as does any unrecognized reason or a target other than workspace-write / danger-full-access. Turn it off to keep widening human-gated.",
130
134
  "card.readonly": "Read-only",
131
135
  "card.unavailable": "Settings namespace unavailable: make sure dsh-perm-gate is assembled into this profile.",
132
136
  "card.llmReceiver": "llmAssist receiving LLM (OpenAI-compatible; any custom API)",
@@ -742,6 +746,29 @@ window.__ModuleLoader__.load({
742
746
  }
743
747
  })]
744
748
  }),
749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
750
+ style: rowStyle,
751
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
752
+ htmlFor: "plugin-config-perm-gate-escalation",
753
+ style: { fontSize: "12px" },
754
+ children: t("card.strategy.trustEscalation")
755
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
756
+ id: "plugin-config-perm-gate-escalation",
757
+ type: "checkbox",
758
+ checked: strategies.trustEscalation ?? true,
759
+ disabled: readonly || !effective,
760
+ onChange: (event) => {
761
+ scope.set("permissiveStrategies", {
762
+ ...strategies,
763
+ trustEscalation: event.currentTarget.checked
764
+ });
765
+ }
766
+ })]
767
+ }),
768
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
769
+ style: hintStyle,
770
+ children: t("card.strategy.trustEscalationHint")
771
+ }),
745
772
  strategies.llmAssist === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
746
773
  style: sectionStyle,
747
774
  children: [