@idlercloud/dsh-git-bash 0.2.0 → 0.3.0

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.en.md CHANGED
@@ -36,9 +36,11 @@ In addition, the existing pwsh tool is still kept, because cases such as the reg
36
36
 
37
37
  Currently verified only with DSH `0.2.2-rc.1`. Both the compatibility declaration and DSH peer dependencies are restricted to that version. Revalidate the plugin before upgrading DSH.
38
38
 
39
- **It does not go through a file sandbox, so it runs only under full access.**
39
+ **It does not go through a file sandbox, so it runs only under `danger-full-access`.**
40
40
 
41
- Git Bash is an MSYS2 process. Under the Windows restricted token it needs fork, pipes and a private temp directory, and behaves unreliably, so this plugin has no usable sandbox backend. The plugin checks the file policy before invoking the tool and refuses non-full-access calls outright.
41
+ Git Bash is an MSYS2 process. Under the Windows restricted token it needs fork, pipes and a private temp directory, and behaves unreliably, so this plugin has no usable sandbox backend. The plugin checks the file policy of each call before spawning and refuses anything other than `danger-full-access`.
42
+
43
+ In a narrower session, the model can retry the same command with `sandbox_permissions: "danger-full-access"` and a `justification`; the host asks the user for approval, and the grant applies to that one call only. When the host composes no approval service, that path is unavailable, so use `pwsh` or switch the session policy.
42
44
 
43
45
  ## Non-goals
44
46
 
@@ -56,7 +58,7 @@ Git Bash is an MSYS2 process. Under the Windows restricted token it needs fork,
56
58
  - Git for Windows installed (provides `bash.exe`).
57
59
  - DSH `0.2.2-rc.1`.
58
60
 
59
- Desktop is a special case: normally install through the built-in plugin manager by entering the package name `@idlercloud/dsh-git-bash`. To install a specific version, enter `@idlercloud/dsh-git-bash@0.2.0`.
61
+ Desktop is a special case: normally install through the built-in plugin manager by entering the package name `@idlercloud/dsh-git-bash`. To install a specific version, enter `@idlercloud/dsh-git-bash@0.3.0`.
60
62
 
61
63
  Otherwise, install into the target profile:
62
64
 
@@ -121,7 +123,7 @@ npm test
121
123
  npm run test:git-bash
122
124
  ```
123
125
 
124
- `npm test` builds and runs unit tests plus a real tool-service integration test. The integration test asserts permission enforcement through the real `ToolsService` and execution pipeline, without Git Bash or a running DSH instance. It explicitly skips non-Windows platforms because the plugin only registers its guard on Windows.
126
+ `npm test` builds and runs unit tests plus a real tool-service integration test. The integration test asserts permission enforcement and the one-time escalation flow through the real `ToolsService` and execution pipeline, without Git Bash or a running DSH instance. It explicitly skips non-Windows platforms because the check runs only on Windows.
125
127
 
126
128
  `npm run test:git-bash` separately builds and runs `test/harness.mjs`, requiring Windows and an available Git Bash installation. It executes commands through real cordis and dsh-bash-local and asserts the results, returning a nonzero exit code on failure. It is excluded from the default test suite and does not require starting DSH.
127
129
 
package/README.md CHANGED
@@ -36,9 +36,11 @@
36
36
 
37
37
  目前仅在 DSH `0.2.2-rc.1` 上验证,兼容性声明与 DSH peer 依赖均限定为该版本。升级 DSH 前需要重新验证本插件。
38
38
 
39
- **不经过文件沙箱,因此只在完全权限下运行**。
39
+ **不经过文件沙箱,只在 `danger-full-access` 下运行。**
40
40
 
41
- Git Bash 是 MSYS2 进程,在 Windows 受限令牌下需要 fork、管道与私有临时目录,行为不可靠,所以本插件没有可用的沙箱后端。插件会在调用工具前检查文件策略,非完全权限会直接拒绝调用。
41
+ Git Bash 是 MSYS2 进程,在 Windows 受限令牌下需要 fork、管道与私有临时目录,行为不可靠,所以本插件没有可用的沙箱后端。插件在 spawn 之前检查这次调用的文件策略,不是 `danger-full-access` 就拒绝。
42
+
43
+ 受限会话里,模型可以带 `sandbox_permissions: "danger-full-access"` 和 `justification` 重试同一条命令,由宿主弹出审批;批准只对这一次调用生效,会话策略不变。宿主没有装配审批服务时,这条路径不可用,只能切换会话策略或改用 `pwsh`。
42
44
 
43
45
  ## 非目标
44
46
 
@@ -56,7 +58,7 @@ Git Bash 是 MSYS2 进程,在 Windows 受限令牌下需要 fork、管道与
56
58
  - 已安装 Git for Windows(含 `bash.exe`)。
57
59
  - DSH `0.2.2-rc.1`。
58
60
 
59
- 桌面端是特殊的,一般通过内置的插件管理器安装,输入包名 `@idlercloud/dsh-git-bash` 即可安装,如需安装特定版本,可以输入 `@idlercloud/dsh-git-bash@0.2.0`。
61
+ 桌面端是特殊的,一般通过内置的插件管理器安装,输入包名 `@idlercloud/dsh-git-bash` 即可安装,如需安装特定版本,可以输入 `@idlercloud/dsh-git-bash@0.3.0`。
60
62
 
61
63
  其他情况下,把插件安装到目标 profile:
62
64
 
@@ -121,7 +123,7 @@ npm test
121
123
  npm run test:git-bash
122
124
  ```
123
125
 
124
- `npm test` 会先构建,再运行单元测试和真实工具服务集成测试。集成测试通过真实 `ToolsService` 与工具执行流水线断言权限拦截,无需安装 Git Bash 或启动 DSH;由于插件只在 Windows 注册守卫,该用例在非 Windows 平台明确跳过。
126
+ `npm test` 会先构建,再运行单元测试和真实工具服务集成测试。集成测试通过真实 `ToolsService` 与工具执行流水线断言权限拦截与单次提权流程,无需安装 Git Bash 或启动 DSH;由于插件只在 Windows 执行该检查,该用例在非 Windows 平台明确跳过。
125
127
 
126
128
  `npm run test:git-bash` 单独构建并运行 `test/harness.mjs`,需要 Windows 和可用的 Git Bash。它使用真实 cordis 与 dsh-bash-local 执行命令并断言检查结果,失败时返回非零退出码,不属于默认测试集,也不需要启动 DSH。
127
129
 
package/lib/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * 本插件只做三件事:
8
8
  * - 解析 Git for Windows 里 bash.exe 的绝对路径并调用
9
9
  * - 给模型补一段系统提示词,说明这个是 MSYS 的 bash;
10
- * - 在会话文件策略不是完全权限时,于 spawn 之前拒绝 `bash` 调用。
10
+ * - 文件策略不是完全权限时,于 spawn 之前拒绝 `bash` 调用;受限会话可申请单次提权。
11
11
  *
12
12
  * @module dsh-git-bash
13
13
  */
@@ -29,7 +29,7 @@ export type PluginConfig = BashConfig & {
29
29
  bashPath?: string;
30
30
  };
31
31
  /**
32
- * 挂载执行器与权限闸门。
32
+ * 挂载执行器与系统提示词。
33
33
  *
34
34
  * @param ctx - 挂载点 context(位于 isolate 隔离作用域内)。
35
35
  * @param config - 组合条目配置,可含 bashPath / cwd / timeoutMs 等。
package/lib/index.js CHANGED
@@ -7,19 +7,19 @@
7
7
  * 本插件只做三件事:
8
8
  * - 解析 Git for Windows 里 bash.exe 的绝对路径并调用
9
9
  * - 给模型补一段系统提示词,说明这个是 MSYS 的 bash;
10
- * - 在会话文件策略不是完全权限时,于 spawn 之前拒绝 `bash` 调用。
10
+ * - 文件策略不是完全权限时,于 spawn 之前拒绝 `bash` 调用;受限会话可申请单次提权。
11
11
  *
12
12
  * @module dsh-git-bash
13
13
  */
14
14
  import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local';
15
15
  import z from '@deepseek-ai/schemastery';
16
- import { bashDenialReason, FULL_ACCESS_MODE, installBashGuard } from './guard.js';
16
+ import { bashDenialReason, FULL_ACCESS_MODE } from './policy.js';
17
17
  import { installGitBashPrompt } from './prompt.js';
18
18
  import { BashResolver } from './resolve.js';
19
19
  import { normalizeWorkdir } from './workdir.js';
20
20
  /** 加载器读取的插件元数据。 */
21
21
  export const name = '@idlercloud/dsh-git-bash';
22
- export const inject = ['subprocess', 'tools'];
22
+ export const inject = ['subprocess'];
23
23
  export const Config = z.object({
24
24
  ...LocalBashExecutor.Config.dict,
25
25
  bashPath: z.string().description('Git Bash 路径,可使用绝对路径、相对路径或 PATH 中的可执行文件名。'),
@@ -38,7 +38,11 @@ class GitBashExecutor extends LocalBashExecutor {
38
38
  return FULL_ACCESS_MODE;
39
39
  }
40
40
  assertPolicy(spec) {
41
- const reason = bashDenialReason({ mode: spec.sandboxPolicy?.mode });
41
+ // 工具会把审批通过的调用级 policy 写进 spec,所以这里看到的是升级后的模式。
42
+ const reason = bashDenialReason({
43
+ mode: spec.sandboxPolicy?.mode,
44
+ escalationAvailable: this.ctx.get('approval') !== undefined,
45
+ });
42
46
  if (reason) {
43
47
  const error = new Error(reason);
44
48
  error.code = 'GIT_BASH_POLICY_DENIED';
@@ -87,7 +91,7 @@ class GitBashExecutor extends LocalBashExecutor {
87
91
  }
88
92
  }
89
93
  /**
90
- * 挂载执行器与权限闸门。
94
+ * 挂载执行器与系统提示词。
91
95
  *
92
96
  * @param ctx - 挂载点 context(位于 isolate 隔离作用域内)。
93
97
  * @param config - 组合条目配置,可含 bashPath / cwd / timeoutMs 等。
@@ -98,8 +102,7 @@ export function apply(ctx, config) {
98
102
  return;
99
103
  const resolver = new BashResolver({ bashPath: config.bashPath });
100
104
  installGitBashPrompt(ctx);
101
- installBashGuard(ctx);
102
- ctx.logger.info('dsh-git-bash: `bash` is denied before spawn unless the session file policy is `%s`.', FULL_ACCESS_MODE);
105
+ ctx.logger.info('dsh-git-bash: `bash` requires `%s`; a narrower file policy can request per-call approval.', FULL_ACCESS_MODE);
103
106
  // 构造即注册:构造函数把执行器挂到隔离作用域的 ctx.shell 上,实例由 context 持有。
104
107
  new GitBashExecutor(ctx, config, resolver);
105
108
  }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * dsh-git-bash — 文件策略判定。
3
+ *
4
+ * 本插件的 `bash` 工具运行在 Git Bash(MSYS2)上,没有可用的沙箱后端:MSYS2 进程在
5
+ * Windows 受限令牌下需要 fork、创建管道、写入私有临时目录,无法可靠运行。因此执行器
6
+ * 只接受 `danger-full-access` 的调用。
7
+ *
8
+ * 受限会话里的调用在 spawn 之前拒绝。宿主装配了审批服务时,错误文本提示模型带
9
+ * `sandbox_permissions: "danger-full-access"` 和 `justification` 申请这一次调用的
10
+ * 提权;审批通过后工具把调用级 policy 换成 `danger-full-access`,执行器的检查随之放行。
11
+ *
12
+ * @module dsh-git-bash/policy
13
+ */
14
+ import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
15
+ /** 本执行器唯一接受的模式。 */
16
+ export declare const FULL_ACCESS_MODE: SandboxMode;
17
+ /** 收集到的调用事实。 */
18
+ export interface BashAccessFacts {
19
+ mode?: SandboxMode;
20
+ hasPolicy?: boolean;
21
+ error?: unknown;
22
+ /** 宿主是否装配了审批服务。 */
23
+ escalationAvailable?: boolean;
24
+ }
25
+ /**
26
+ * 纯函数,不做 IO。只有精确的 `danger-full-access` 放行;有异常、缺服务、
27
+ * 模式未知都返回拒绝文案。
28
+ *
29
+ * @param facts - 调用事实。
30
+ * @param facts.mode - 解析出来的沙箱模式,未解析时省略。
31
+ * @param facts.hasPolicy - 宿主是否提供 sandboxPolicy 服务,默认 true。
32
+ * @param facts.error - 解析策略时的异常,没有时省略。
33
+ * @param facts.escalationAvailable - 宿主是否提供审批服务,默认 false。
34
+ * @returns 拒绝原因;undefined 表示放行。
35
+ */
36
+ export declare function bashDenialReason({ mode, hasPolicy, error, escalationAvailable, }?: BashAccessFacts): string | undefined;
package/lib/policy.js ADDED
@@ -0,0 +1,45 @@
1
+ /** 本执行器唯一接受的模式。 */
2
+ export const FULL_ACCESS_MODE = 'danger-full-access';
3
+ /** 拒绝文案的固定前缀:说明 Git Bash 与文件沙箱的关系。 */
4
+ const WHY = '`bash` runs in Git for Windows Git Bash, which cannot enforce the DSH file sandbox, ' +
5
+ 'so it runs only under `danger-full-access`. ';
6
+ /** 有审批服务时给出的单次提权路径。 */
7
+ const ESCALATION = 'Retry the same command with `sandbox_permissions: "danger-full-access"` and a `justification` ' +
8
+ 'to request one-time approval for this call. ';
9
+ /** 其余情况给出的退路。 */
10
+ const NEXT = 'Use the `pwsh` tool for commands this policy governs, or ask the user to switch the session access mode.';
11
+ /** 把任意抛出物转成一段可读的原因。 */
12
+ function describeError(error) {
13
+ return error instanceof Error ? error.message : String(error);
14
+ }
15
+ /**
16
+ * 把具体原因接在 {@link WHY} 之后,拼成一条拒绝文案。
17
+ * @param detail - 本次拒绝的具体原因。
18
+ * @param escalation - 是否附上单次提权路径。
19
+ */
20
+ function denyReason(detail, escalation = false) {
21
+ return WHY + detail + (escalation ? ESCALATION : '') + NEXT;
22
+ }
23
+ /**
24
+ * 纯函数,不做 IO。只有精确的 `danger-full-access` 放行;有异常、缺服务、
25
+ * 模式未知都返回拒绝文案。
26
+ *
27
+ * @param facts - 调用事实。
28
+ * @param facts.mode - 解析出来的沙箱模式,未解析时省略。
29
+ * @param facts.hasPolicy - 宿主是否提供 sandboxPolicy 服务,默认 true。
30
+ * @param facts.error - 解析策略时的异常,没有时省略。
31
+ * @param facts.escalationAvailable - 宿主是否提供审批服务,默认 false。
32
+ * @returns 拒绝原因;undefined 表示放行。
33
+ */
34
+ export function bashDenialReason({ mode, hasPolicy = true, error, escalationAvailable = false, } = {}) {
35
+ if (error !== undefined) {
36
+ return denyReason(`Resolving the file policy failed (${describeError(error)}). `);
37
+ }
38
+ if (hasPolicy === false) {
39
+ return denyReason('This host exposes no sandbox policy service, so the file policy cannot be verified. ');
40
+ }
41
+ if (mode === FULL_ACCESS_MODE)
42
+ return undefined;
43
+ const actual = mode === undefined ? 'unknown' : `\`${mode}\``;
44
+ return denyReason(`The file policy is ${actual}. `, escalationAvailable && mode !== undefined);
45
+ }
package/lib/prompt.js CHANGED
@@ -12,7 +12,8 @@ export const GIT_BASH_PROMPT = 'The `bash` tool runs commands in Git for Windows
12
12
  '`workdir` accepts Windows paths (`C:/...`) and MSYS drive paths (`/c/...`, normalized to `C:/...`); ' +
13
13
  'drive-less MSYS paths such as `/tmp` are rejected. `workdir` is not shell-expanded, so `~` and `$VAR` ' +
14
14
  'are taken literally. ' +
15
- 'This tool does not support a file sandbox, so it is available only under `danger-full-access`. ' +
15
+ 'This tool has no file sandbox: under a narrower policy, widen this call directly to ' +
16
+ '`danger-full-access`; `workspace-write` does not make Git Bash runnable. ' +
16
17
  'Prefer `bash` for command-line work. Use `pwsh` only when the task is Windows-native, such as cmdlets, ' +
17
18
  'the registry, services, and WMI, or when `bash` is unavailable.';
18
19
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idlercloud/dsh-git-bash",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Git for Windows bash tool for DSH on Windows; fork of Fishquito7/dsh-gitbash.",
5
5
  "author": "idlercloud",
6
6
  "type": "module",
package/lib/guard.d.ts DELETED
@@ -1,47 +0,0 @@
1
- /**
2
- * dsh-git-bash — 非完全权限拒绝。
3
- *
4
- * 本插件的 `bash` 工具运行在 Git Bash(MSYS2)上。MSYS2 进程在 Windows 受限令牌下
5
- * 需要 fork、创建管道、写入私有临时目录,无法可靠运行,因此没有可用的沙箱后端。
6
- * 工具只在文件策略解析为 `danger-full-access` 时执行:有会话时读该会话的策略,
7
- * 无会话时读实例默认模式;`read-only`、`workspace-write`、策略缺失或解析失败时,
8
- * 调用在 spawn 之前返回拒绝。
9
- *
10
- * 拒绝点用 `ctx.tools.guard()`:guard 在每个 `tools/pre-execute` 监听器之后、
11
- * 工具体执行之前求值,只能拒绝、不能放行。把检查放在 pre-execute 监听器里会被
12
- * 其他策略或审批插件的 allow 覆盖,guard 不会。
13
- *
14
- * 插件入口只在 win32 调用本函数。
15
- *
16
- * @module dsh-git-bash/guard
17
- */
18
- import type { Context } from '@deepseek-ai/cordis';
19
- import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
20
- export declare const BASH_TOOL_NAME = "bash";
21
- export declare const FULL_ACCESS_MODE: SandboxMode;
22
- /** 收集到的调用事实。 */
23
- export interface BashAccessFacts {
24
- mode?: SandboxMode;
25
- hasPolicy?: boolean;
26
- error?: unknown;
27
- }
28
- /**
29
- * 纯函数,不做 IO。只有精确的 `danger-full-access` 放行;有异常、缺服务、
30
- * 模式未知都返回拒绝文案。
31
- *
32
- * @param facts - 调用事实。
33
- * @param facts.mode - 解析出来的沙箱模式,未解析时省略。
34
- * @param facts.hasPolicy - 宿主是否提供 sandboxPolicy 服务,默认 true。
35
- * @param facts.error - 解析策略时的异常,没有时省略。
36
- * @returns 拒绝原因;undefined 表示放行。
37
- */
38
- export declare function bashDenialReason({ mode, hasPolicy, error }?: BashAccessFacts): string | undefined;
39
- /**
40
- * 注册工具层守卫。
41
- *
42
- * 只处理名为 `bash` 的调用,其余调用直接放行,不读取策略。
43
- *
44
- * @param ctx - 插件 context,需已注入 tools 服务。
45
- * @returns 撤销器。
46
- */
47
- export declare function installBashGuard(ctx: Context): () => void;
package/lib/guard.js DELETED
@@ -1,100 +0,0 @@
1
- import { scopeOf } from '@deepseek-ai/dsh-scope';
2
- export const BASH_TOOL_NAME = 'bash';
3
- export const FULL_ACCESS_MODE = 'danger-full-access';
4
- /** 拒绝文案的前后缀:原因之后给两条出路 */
5
- const WHY = '`bash` runs in Git for Windows Git Bash, which cannot enforce the DSH file sandbox, ' +
6
- 'so it runs only while the file policy is `danger-full-access`. ';
7
- const NEXT = ' Use the `pwsh` tool for commands this policy governs, or ask the user to switch the session access mode.';
8
- /** 把任意抛出物转成一段可读的原因。 */
9
- function describeError(error) {
10
- return error instanceof Error ? error.message : String(error);
11
- }
12
- /**
13
- * 把原因接在 {@link WHY} 之后,拼成一条拒绝文案。
14
- * @param detail - 本次拒绝的具体原因。
15
- */
16
- function denyReason(detail) {
17
- return WHY + detail + NEXT;
18
- }
19
- /**
20
- * 纯函数,不做 IO。只有精确的 `danger-full-access` 放行;有异常、缺服务、
21
- * 模式未知都返回拒绝文案。
22
- *
23
- * @param facts - 调用事实。
24
- * @param facts.mode - 解析出来的沙箱模式,未解析时省略。
25
- * @param facts.hasPolicy - 宿主是否提供 sandboxPolicy 服务,默认 true。
26
- * @param facts.error - 解析策略时的异常,没有时省略。
27
- * @returns 拒绝原因;undefined 表示放行。
28
- */
29
- export function bashDenialReason({ mode, hasPolicy = true, error } = {}) {
30
- if (error !== undefined) {
31
- return denyReason(`Resolving the file policy failed (${describeError(error)}).`);
32
- }
33
- if (hasPolicy === false) {
34
- return denyReason('This host exposes no sandbox policy service, so the file policy cannot be verified.');
35
- }
36
- if (mode !== FULL_ACCESS_MODE) {
37
- const actual = mode === undefined ? 'an unknown mode' : `\`${mode}\``;
38
- return denyReason(`The file policy is ${actual}.`);
39
- }
40
- return undefined;
41
- }
42
- /**
43
- * 收集一次调用的事实:sandboxPolicy 服务、解析出的模式。
44
- *
45
- * 所有异常都转成 facts,不向工具层抛出:抛出的异常会成为普通工具错误,模型无法
46
- * 区分这是权限拒绝。
47
- *
48
- * @param ctx - 插件 context。
49
- * @param agent - 发起调用的 agent。
50
- * @returns `{ hasPolicy, mode?, error? }`。
51
- */
52
- function collectFacts(ctx, agent) {
53
- const service = ctx.get('sandboxPolicy');
54
- if (service === undefined)
55
- return { hasPolicy: false };
56
- try {
57
- const { mode } = service.resolve({ session: agent?.session });
58
- return { hasPolicy: true, mode };
59
- }
60
- catch (error) {
61
- return { hasPolicy: true, error };
62
- }
63
- }
64
- /**
65
- * 这次 `bash` 调用是否属于本插件注册的工具。
66
- *
67
- * 工具层允许 agent 作用域的同名定义遮蔽全局工具。其他插件可能注册自己的 `bash`,
68
- * 策略与本插件不同;只按名字拦截会拒绝这些工具。因此比较调用方本步能执行的定义与
69
- * 插件注册作用域中的定义:两者都存在且是同一个对象时才由本插件裁决。
70
- *
71
- * @param ctx - 插件 context。
72
- * @param exec - 待执行的工具调用。
73
- * @returns 该调用是否由本插件裁决。
74
- */
75
- function ownsBashCall(ctx, exec) {
76
- const visible = ctx.tools.executable(BASH_TOOL_NAME, exec.agent, exec.toolSet);
77
- const registered = ctx.tools.get(BASH_TOOL_NAME, scopeOf(ctx));
78
- return visible !== undefined && visible === registered;
79
- }
80
- /**
81
- * 注册工具层守卫。
82
- *
83
- * 只处理名为 `bash` 的调用,其余调用直接放行,不读取策略。
84
- *
85
- * @param ctx - 插件 context,需已注入 tools 服务。
86
- * @returns 撤销器。
87
- */
88
- export function installBashGuard(ctx) {
89
- return ctx.tools.guard((exec) => {
90
- if (exec.name !== BASH_TOOL_NAME)
91
- return undefined;
92
- if (!ownsBashCall(ctx, exec))
93
- return undefined;
94
- const reason = bashDenialReason(collectFacts(ctx, exec.agent));
95
- if (reason === undefined)
96
- return undefined;
97
- ctx.logger.info('dsh-git-bash: denied `bash` before spawn — %s', reason);
98
- return reason;
99
- });
100
- }