@lixiangzhong/dsh-shell-secrets 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +153 -237
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,307 +1,223 @@
1
1
  # @lixiangzhong/dsh-shell-secrets
2
2
 
3
- 把**启动 dsh 的那个环境里**名字匹配 `KEY|PASSWORD|SECRET|TOKEN`(大小写不敏感)的变量,
4
- 以**原始变量名**重新注入 dsh 的 bash 子进程。
3
+ [![npm version](https://img.shields.io/npm/v/@lixiangzhong/dsh-shell-secrets.svg)](https://www.npmjs.com/package/@lixiangzhong/dsh-shell-secrets)
4
+ [![license](https://img.shields.io/npm/l/@lixiangzhong/dsh-shell-secrets.svg)](./LICENSE)
5
5
 
6
- > ## ⚠️ 先读这一段
7
- >
8
- > 这个插件**有意绕过** harness 的凭证 scrub。启用之后,匹配到的变量会进入**每一个**
9
- > bash 子进程 —— 包括 `npm install` 的 postinstall、以及模型运行的任意第三方二进制。
10
- > 只要模型执行 `env`,这些值就会出现在 tool result(也就是会话日志和模型上下文)里。
11
- >
12
- > 请在**受信 workspace + 受信会话**里使用;不要把值直接 `echo`/`env` 到会被记录的输出里
13
- > (验证时用 `env | cut -d= -f1 | grep ...` 只列名字)。插件本身无法对输出脱敏。
6
+ 一个 [dsh](https://www.npmjs.com/package/@deepseek-ai/dsh)(DeepSeek Harness)插件:把**启动 dsh 时环境里**
7
+ 名字匹配 `KEY` / `PASSWORD` / `SECRET` / `TOKEN` 的变量,以**原始变量名**注入 bash 工具的子进程。
14
8
 
15
9
  ```bash
16
10
  export GITHUB_TOKEN=ghp_xxx PGPASSWORD=secret
17
11
  dsh web
18
- # 之后模型通过 bash 工具执行:
19
- # gh auth status # 直接读 $GITHUB_TOKEN,无需改写
20
- # psql -h db -U app # 直接读 $PGPASSWORD
21
12
  ```
22
13
 
23
- ## 它解决的问题
14
+ 之后模型通过 bash 工具执行命令时,`gh`、`psql`、`aws`、`curl` 这类自己读环境变量的 CLI **无需改写**:
24
15
 
25
- harness 的 subprocess 层有一条固定的凭证 scrub:
26
-
27
- ```
28
- scrubbedParentEnv() // 剔除 /KEY|PASSWORD|SECRET|TOKEN/i 以及所有 DSH_*
29
- childEnv(extra) = { ...scrubbedParentEnv(), ...extra } // extra 不再过滤
16
+ ```bash
17
+ gh auth status # 直接读 $GITHUB_TOKEN
18
+ psql -h db -U app # 直接读 $PGPASSWORD
30
19
  ```
31
20
 
32
- 所以 `~/.dsh/.env`、启动环境里的密钥都进不了 bash 子进程。官方的插件接缝 `ctx.shellEnv`
33
- 只能注册 `DSH_*` 键(命令里得写 `$DSH_GITHUB_TOKEN`),`gh`/`mysql`/`aws` 这类自己读
34
- 环境变量的 CLI 就用不上。本插件改为接管 `ctx.shell`,往文档化的
35
- `ShellExecSpec.env`("merged after the credential scrub")里注入原始变量名。
21
+ ---
36
22
 
37
- ## 安装 / 卸载
23
+ ## ⚠️ 安全须知
38
24
 
39
- 两条路径,**二选一**,不要同时用(`deploy.mjs` 检测到 bundle 安装时会提示)。
25
+ > 这个插件**有意绕过** dsh 的凭证隔离机制。启用之后,匹配到的变量会进入**每一个** bash 子进程 ——
26
+ > 包括 `npm install` 的 postinstall 脚本,以及模型运行的任意第三方二进制。
27
+ >
28
+ > 只要模型执行 `env`,这些值就会出现在工具结果里,也就是**会话日志与模型上下文**里。插件无法对
29
+ > 输出做脱敏,也无法撤回已经写进日志的值。
30
+ >
31
+ > 建议:
32
+ >
33
+ > - 只在**受信的 workspace 和会话**里使用;
34
+ > - 验证时只列变量名,不要打印值:`env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|_KEY$'`;
35
+ > - 一旦有值进了日志又不想留,**直接轮换该密钥**。
40
36
 
41
- ### 方式 A:作为 dsh plugin(bundle 形式,推荐)
37
+ ---
42
38
 
43
- 本包在 `package.json` 里声明了 `dsh.bundle.patch`,所以 `dsh plugin` 会把它自动加进
44
- `dsh.profile.bundles` 层,不需要手改 profile 的任何 YAML:
39
+ ## 为什么需要它
45
40
 
46
- ```bash
47
- # npm 安装(发布后)
48
- dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets
49
- dsh plugin --profile web update @lixiangzhong/dsh-shell-secrets # 升级
41
+ dsh 在启动子进程前会做一次固定的凭证清理:名字匹配 `/KEY|PASSWORD|SECRET|TOKEN/i` 的变量、
42
+ 以及所有 `DSH_*`,都不会传给 bash 子进程。因此即使用户在自己的 shell 里 `export GITHUB_TOKEN=...`,
43
+ 模型执行的命令里也读不到它。
50
44
 
51
- # 从本地源码 / tarball 安装(开发或内部分发)
52
- dsh plugin --profile web add file:/Users/lxz/gopath/src/github.com/lixiangzhong/dsh-shell-secrets
53
- dsh plugin --profile web add file:./dsh-shell-secrets-0.1.0.tgz
45
+ dsh 官方提供的插件接缝 `ctx.shellEnv` 只能注册 `DSH_*` 前缀的变量,命令里得写成
46
+ `$DSH_GITHUB_TOKEN`,对 `gh`、`psql` 这类直接读 `GITHUB_TOKEN` / `PGPASSWORD` 的 CLI 没用。
54
47
 
55
- # 卸载(同时移除依赖、bundle 层、node_modules)
56
- dsh plugin --profile web remove @lixiangzhong/dsh-shell-secrets
48
+ 本插件改为接管 shell 执行器,把同一批变量以**原始名字**放回子进程环境 —— 走的仍是 dsh
49
+ 文档化的落点(`ShellExecSpec.env`,语义是"在凭证清理之后合并")。
57
50
 
58
- # 改完本地源码后刷新已安装拷贝
59
- dsh plugin --profile web remove @lixiangzhong/dsh-shell-secrets && \
60
- dsh plugin --profile web add file:/Users/lxz/gopath/src/github.com/lixiangzhong/dsh-shell-secrets
51
+ ## 安装
61
52
 
62
- node deploy.mjs --check # 两种安装方式都适用(只看合成后的行)
63
- ```
53
+ **环境要求**:已安装 `dsh` CLI(本插件在 dsh 进程内运行,Node 版本要求 ≥ 22);用 `dsh plugin`
54
+ 管理插件时需要 `pnpm`(dsh 自身用 pnpm 管理 profile 依赖)。
64
55
 
65
- 要点:
66
-
67
- - 从 npm 装的是**版本化的 registry 依赖**,`dsh plugin … update` 就能升级;本地 `file:` 目录
68
- 依赖没有版本概念,升级=remove + add(见下一条)。
69
- - **本地安装必须用 `file:`,不能用 `link:`**。`link:` 会把包软链到源码目录,Node 按 realpath
70
- 解析 `@deepseek-ai/dsh-bash-sandbox` 时会从源码目录向上找而失败(实测 `MODULE_NOT_FOUND`);
71
- `file:` 是真实目录(pnpm 硬链接),realpath 落在 `~/.dsh/profiles` 内,能解析到 harness
72
- 自己那份模块(实测 hoisted 与 `.pnpm` 两种布局都解析到 npx 安装目录)。
73
- - 目录依赖是**硬链接**:原地修改源码会立刻反映到已安装副本;但编辑器"写临时文件再 rename"
74
- 的保存方式会换 inode 断链,所以**刷新请用上面的 remove + add**(实测 `pnpm update` 与
75
- `pnpm install --force` 对目录依赖都会报 "Already up to date" 而不重新拷贝)。
76
- - `bundle` 列表在 **dsh 启动时**读取,装/卸之后要重启 dsh 才生效。
77
- - **pnpm 版本/store 必须与 profile 现有的 `node_modules` 匹配**。`dsh plugin` 只是转发给
78
- PATH 上的 `pnpm`;如果 profile 的 `node_modules` 是用另一个大版本的 pnpm 装的,会直接失败:
79
-
80
- ```
81
- ERR_PNPM_UNEXPECTED_STORE Unexpected store location
82
- The dependencies at "~/.dsh/profiles/web/node_modules" are currently linked from the store at
83
- "~/.dsh/Library/pnpm/store/v11"; pnpm now wants to use the store at ".../store/v10"
84
- ```
85
-
86
- 两条出路:把匹配的 pnpm 放到 PATH 前面(本机 store v11 ↔ `~/Library/pnpm/bin/pnpm` 11.8.0):
87
-
88
- ```bash
89
- PATH="$HOME/Library/pnpm/bin:$PATH" dsh plugin --profile web add file:<此目录>
90
- ```
91
-
92
- 或者在 profile 目录里用当前的 pnpm 重建一次 `node_modules`(`cd ~/.dsh/profiles/web && pnpm install`)。
93
- - 从 npm 装时,**pnpm 也要指对 registry**:本机全局 registry 是腾讯镜像(`npm_config_registry`
94
- 环境变量,优先级高于 `~/.npmrc`),刚发布的包在镜像上通常还没同步,会 `404`。给这条命令显式指定:
95
-
96
- ```bash
97
- PATH="$HOME/Library/pnpm/bin:$PATH" dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets \
98
- --registry=https://registry.npmjs.org
99
- ```
100
-
101
- (`dsh plugin` 会把参数原样转发给 pnpm,所以 pnpm 自己的 flag 也能直接写。)
102
- - 包里的 `peerDependencies` 把 harness 包声明为**可选** peer:只为表达耦合关系,配合 profile
103
- 自带的 `autoInstallPeers: false`,pnpm 不会偷偷装第二份 harness 包(否则会出现两个 cordis /
104
- 两个 `Service` 实例,服务注册行为会错乱)。
105
- - 装好后 `dsh.profile.bundles` 会变成 `['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app',
106
- 'dsh-shell-secrets']`,profile 的 `cordis.patch.yml` 应保持干净的 `[]`。
107
-
108
- ### 方式 B:deploy.mjs(拷贝 + 手工挂载,不依赖 pnpm)
109
-
110
- 适合不想让 pnpm 参与、或要装进多个 profile 的场景:
56
+ ### 从 npm 安装(推荐)
111
57
 
112
58
  ```bash
113
- node deploy.mjs # 拷贝到 ~/.dsh/profiles/web/plugins/dsh-shell-secrets + 写入挂载行
114
- node deploy.mjs --check # dsh --profile web --dump-config 校验挂载结果
115
- node deploy.mjs --remove # 回滚:移除挂载行 + 删除插件目录
116
- PROFILE=tui node deploy.mjs # 换 profile(默认 web)
59
+ dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets
117
60
  ```
118
61
 
119
- 它做两件事:
120
-
121
- 1. **拷贝**(不是软链)插件到 `~/.dsh/profiles/<profile>/plugins/dsh-shell-secrets/`。
122
- 必须是拷贝:`@deepseek-ai/dsh-bash-sandbox` 要从 `~/.dsh/profiles/node_modules`(指向
123
- npx 安装目录的软链)解析到 harness **同一份模块实例**,软链会让 Node 按 realpath 从
124
- 工作区向上找包而失败,两个模块实例混用会出问题。
125
- 2. 在 `~/.dsh/profiles/<profile>/cordis.patch.yml` 的 `# >>> dsh-shell-secrets` /
126
- `# <<< dsh-shell-secrets` 标记段内写入:
127
-
128
- ```yaml
129
- - id: bash-sandbox
130
- disabled: true # 不关掉官方行就会有两个 shell 服务,cordis 会大声报重复服务
131
- - insert:
132
- - id: shell-secrets
133
- name: ./plugins/dsh-shell-secrets/lib/index.js
134
- config:
135
- timeoutMs: 60000 # 与官方 bash-sandbox 行同值
136
- disabled: !!js process.platform === 'win32'
137
- ```
62
+ `dsh plugin` 会把包装进 `~/.dsh/profiles/<profile>/node_modules`,并把包名追加到
63
+ `dsh.profile.bundles` 层(因为包里声明了 `dsh.bundle.patch`),**不需要手改任何 YAML**。
138
64
 
139
- ### 方式 A 用的 bundle patch
65
+ `--profile` 按需替换(`web` / `tui` / `headless` …)。装好后**重启 dsh** 才会生效。
140
66
 
141
- `cordis.patch.yml`(包内,`dsh.bundle.patch` 指向它)内容与上面等价,只是 `name` 相对包自身:
67
+ ### 从源码或离线 tarball 安装
142
68
 
143
- ```yaml
144
- - id: bash-sandbox
145
- disabled: true
146
- - insert:
147
- - id: shell-secrets
148
- name: ./lib/index.js # 锚定成 <包目录>/lib/index.js
149
- config:
150
- timeoutMs: 60000
151
- disabled: !!js process.platform === 'win32'
69
+ ```bash
70
+ # 目录形式
71
+ dsh plugin --profile web add file:/path/to/dsh-shell-secrets
72
+
73
+ # tarball 形式(npm pack 产物)
74
+ dsh plugin --profile web add file:./lixiangzhong-dsh-shell-secrets-0.1.0.tgz
152
75
  ```
153
76
 
154
- ## 配置(全部来自启动环境)
77
+ 本地安装必须使用 `file:`(会落成真实目录),**不要用 `link:`**:软链会让 Node 按真实路径去
78
+ 源码目录解析 dsh 自身的包,从而加载到**两份** harness 模块实例,服务注册会出问题。
155
79
 
156
- | 变量 | 默认 | 说明 |
157
- | --- | --- | --- |
158
- | `DSH_SHELL_SECRETS_PATTERN` | `KEY\|PASSWORD\|SECRET\|TOKEN`(i) | 自定义匹配正则(与 scrub 同语义是刻意的) |
159
- | `DSH_SHELL_SECRETS_INCLUDE` | 空 | 逗号分隔 glob(`*`/`?`),**优先于** pattern,如 `*_TOKEN,AWS_*` |
160
- | `DSH_SHELL_SECRETS_EXCLUDE` | 空 | 逗号分隔 glob 排除项,如 `MONKEY,KEYBOARD_*`(修子串误伤) |
161
- | `DSH_SHELL_SECRETS_MAX_BYTES` | `8192` | 单值上限,超长跳过 |
162
- | `DSH_SHELL_SECRETS_DISABLE` | 空 | 非空即停用注入(不卸载插件) |
80
+ ### 升级与卸载
163
81
 
164
- 规则细节:
165
-
166
- - **`DSH_*` 永不注入**:维持 harness "ambient `DSH_*` 被丢弃" 的不变量,避免伪造 harness 事实。
167
- - 空值/纯空白、超长值跳过;含换行的值仍注入(保真优先)但会在装载日志里提醒。
168
- - 非法正则/非法上限会让**装配失败并抛错**:宁可 dsh 启动失败,也不要静默不注入。
169
- - 未命中的变量不会进日志(否则整个环境变量表都会变成噪音)。
82
+ ```bash
83
+ dsh plugin --profile web update @lixiangzhong/dsh-shell-secrets # 升级到最新版
84
+ dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets@0.1.0 # 或指定版本
85
+ dsh plugin --profile web remove @lixiangzhong/dsh-shell-secrets # 卸载(依赖 + bundle 层一起移除)
86
+ ```
170
87
 
171
- 日志只打印**名字与原因,绝不打印值**。
88
+ 升级/卸载后同样**需要重启 dsh**。
172
89
 
173
- ## 生效时机
90
+ ### 生效时机
174
91
 
175
- | 改了什么 | 怎么生效 |
92
+ | 改动 | 生效方式 |
176
93
  | --- | --- |
177
94
  | 启动环境里的密钥变量 | 重启 dsh |
178
95
  | `DSH_SHELL_SECRETS_*` 配置 | 重启 dsh |
179
- | `cordis.patch.yml` | web profile `patchReload: live`,保存即热生效 |
180
- | 插件 JS 代码 | 必须重启 dsh(ESM 模块缓存) |
96
+ | 插件版本(安装 / 升级 / 卸载) | 重启 dsh |
97
+
98
+ 变量与配置都来自 **dsh 启动时的那个环境**,进程启动后不再变化。
99
+
100
+ ## 配置
101
+
102
+ 全部通过环境变量(在**启动 dsh 之前**设置):
103
+
104
+ | 变量 | 默认 | 说明 |
105
+ | --- | --- | --- |
106
+ | `DSH_SHELL_SECRETS_PATTERN` | `KEY\|PASSWORD\|SECRET\|TOKEN`(忽略大小写) | 自定义匹配正则。默认值与 dsh 自身的凭证清理规则一致,保证"被清掉的正好被补回来" |
107
+ | `DSH_SHELL_SECRETS_INCLUDE` | 空 | 逗号分隔的 glob(`*` / `?`),**优先于** `PATTERN`,例如 `*_TOKEN,AWS_*` |
108
+ | `DSH_SHELL_SECRETS_EXCLUDE` | 空 | 逗号分隔的 glob 排除项,例如 `MONKEY,KEYBOARD_*`(修掉子串误伤) |
109
+ | `DSH_SHELL_SECRETS_MAX_BYTES` | `8192` | 单个值的长度上限,超长跳过 |
110
+ | `DSH_SHELL_SECRETS_DISABLE` | 空 | 设为任意非空值即停用注入(不必卸载插件) |
111
+
112
+ 规则细节:
181
113
 
182
- 代码改动后记得 `node deploy.mjs` 重新拷贝。
114
+ - **`DSH_*` 永不注入**:保持 dsh "外部传入的 `DSH_*` 会被丢弃" 的既有约定,避免伪造框架自身的事实。
115
+ - 默认规则是**子串匹配**,因此 `MONKEY`、`KEYBOARD_LAYOUT` 这类名字也会命中 —— 用 `EXCLUDE` 排除即可。
116
+ - 空值、纯空白、超长值会被跳过;含换行的值仍会注入(保真优先),但会在装载日志里提示。
117
+ - 配置非法(正则写错、上限非正整数)会让**插件装载失败并报错**:宁可启动时报错,也不要静默不注入。
118
+ - 装载日志只打印**变量名与原因,绝不打印值**。
183
119
 
184
- ## 验证
120
+ ## 快速验证
185
121
 
186
122
  ```bash
187
- # 启动 dsh 的 shell
188
- export MY_TEST_PASSWORD=probe-ok MY_TEST_TOKEN=probe-token
123
+ # 1) 在启动 dsh 的 shell 里设置一个测试变量,然后启动 dsh
124
+ export MY_TEST_PASSWORD=probe-ok
189
125
  dsh web
190
126
  ```
191
127
 
192
- 之后在 bash 工具里:
193
-
194
128
  ```bash
195
- echo "${MY_TEST_PASSWORD-unset}" # probe-ok(原始名字可用)
196
- # 只列名字,别 grep 值:tool result 会把它写进会话日志与模型上下文!
129
+ # 2) 在 dsh 的 bash 工具里执行
130
+ echo "${MY_TEST_PASSWORD-unset}" # probe-ok(原始名字可用)
131
+
132
+ # 只列名字,不要打印值
197
133
  env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|SECRET$|_KEY$' | sort
198
- env | cut -d= -f1 | grep '^DSH_' # 只应有 DSH_HOME/DSH_SHELL/DSH_SESSION_ID/DSH_WEB_URL
134
+ env | cut -d= -f1 | grep '^DSH_' # 应只有 DSH_HOME / DSH_SHELL / DSH_SESSION_ID / DSH_WEB_URL
199
135
  ```
200
136
 
201
- 单元测试(纯逻辑,零依赖):
137
+ ## 已知限制
202
138
 
203
- ```bash
204
- node --test
205
- ```
139
+ - **只处理 bash 执行器**。使用持久 PTY shell 的场景(`minimal` agent preset、`sdk-minimal` profile)
140
+ 走的是另一套 `terminals` 服务,不在覆盖范围内。
141
+ - **Windows 的 pwsh 未覆盖**(机制上可同构补充,尚未实现)。
142
+ - **只从启动环境读取**,不读文件;dsh 启动之后才 export 的变量不会生效。
143
+ - 变量集合在插件装载时确定一次,运行期不再刷新。
206
144
 
207
- ## 实现注意事项(踩过的坑)
145
+ ## 兼容性
208
146
 
209
- - **执行器类里不能用 `#private` 字段/私有方法。** cordis 的 `getTraceable()` 会把服务包成
210
- Proxy,方法调用时 `this` 被替换成 shadow 代理,私有字段的 brand 校验会直接失败:
211
- `Error: Receiver must be an instance of class SecretsBashExecutor`,且每个命令都失败。
212
- 官方服务一律用普通字段/方法,照做即可(本插件用 `this.secrets` + `mergeSecrets()`)。
213
- - **挂载会在运行中的 dsh 上热生效**(web profile 是 `patchReload: live`)。如果新代码有致命
214
- 缺陷,这个会话的 bash 会立刻不可用;实测**把标记段删掉不足以卸载**已挂载的执行器
215
- (服务行的卸载不会自动回滚),此时只能重启 dsh 恢复。所以:改执行器代码后建议先重启再挂载,
216
- 或者接受一次"重启即恢复"的窗口。
217
- - 插件 JS 改动受 ESM 模块缓存影响,**同一个进程内换文件内容不会重新导入**(同 URL 命中缓存)。
147
+ 本插件通过覆写 shell 执行器实现,因此绑定了 dsh 的内部接口:
218
148
 
219
- ## 兼容性与已测版本
149
+ - `@deepseek-ai/dsh-bash-sandbox` 导出的 `SandboxBashExecutor`,及其 `resolve` / `run` / `start` 方法;
150
+ - `ShellExecSpec.env` 的合并语义(在凭证清理之后合并);
151
+ - `ctx.shell` 服务名与 `bash-sandbox` 配置行。
220
152
 
221
- 本插件绑的是 harness 内部接口,所以发布版带一个**装载期自检**(`compatibilityIssues()`):
153
+ 为了尽早暴露不兼容,插件装载时会做一次自检,并在 dsh 日志里以 `error` 级别报告(**不阻断启动**,
154
+ 以免误报导致 dsh 起不来):
222
155
 
223
- | 检查项 | 失败时 |
224
- |---|---|
225
- | `SandboxBashExecutor` 是否仍是类,且带 `resolve/run/start` | `logger.error` 报警(不阻断启动) |
226
- | 本插件解析到的 `@deepseek-ai/dsh-bash-sandbox` 与 harness 自己用的是否**同一份**(防止 profile 装出第二份 harness 包 → 两个 cordis 实例) | `logger.error` 报警(不阻断启动) |
156
+ - 基类是否仍提供 `resolve` / `run` / `start`;
157
+ - 插件解析到的 harness 包与 dsh 自身使用的是否是**同一份**(避免 profile 里装出第二份 harness 包)。
227
158
 
228
- 自检**刻意不抛异常**:宁可 dsh 正常启动 + 日志醒目报错,也不要因为误报把用户的 dsh 弄成起不来。
229
- 真正的致命问题(导出的不是类)会在 import 期直接炸。
230
-
231
- 已实测环境:
159
+ **已测试环境**
232
160
 
233
161
  | dsh | Node | 平台 | 结果 |
234
- |---|---|---|---|
235
- | 0.1.5-rc.1(npx 安装) | v26.7.0 | macOS (darwin) | 前台 + 后台路径均注入成功;`deploy.mjs --check` 通过;单测 13/13 |
236
-
237
- 未覆盖:`minimal` 预设 / `sdk-minimal` profile 的持久 PTY shell;Windows pwsh。
162
+ | --- | --- | --- | --- |
163
+ | 0.1.5-rc.1 | 26.x | macOS | 前台与后台任务路径均注入成功,单元测试 13/13 |
238
164
 
239
- dsh 升级后请重跑:
165
+ 其它版本/平台(尤其 Windows)尚未验证,欢迎反馈。dsh 升级后建议确认一次:
240
166
 
241
167
  ```bash
242
- node --test && node deploy.mjs --check
243
- env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|SECRET$|_KEY$' # 只列名字确认注入仍在
168
+ echo "${GITHUB_TOKEN-unset}" # 或任一敏感变量
169
+ env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|SECRET$|_KEY$' # 只列名字
244
170
  ```
245
171
 
246
- ## 发布到 npm
172
+ ## 常见问题
173
+
174
+ **装了但命令里读不到变量**
175
+
176
+ 1. 变量是在**启动 dsh 的那个 shell** 里 export 的吗?(`.zshrc` 里 export 也可以,但要重启 dsh)
177
+ 2. 装/改配置之后**重启 dsh** 了吗?
178
+ 3. 用 `env | cut -d= -f1 | grep '^DSH_'` 之外的方式确认名字是否在注入清单里;装载日志会打印
179
+ `可注入密钥变量 N 个: ...`,若为 0 并伴随警告,说明启动环境里没有匹配项或 `PATTERN` 写错了。
180
+
181
+ **`dsh plugin add` 报 `ERR_PNPM_UNEXPECTED_STORE`**
182
+
183
+ profile 的 `node_modules` 与当前 `pnpm` 的大版本不一致(store 版本不同)。两种处理:使用与
184
+ profile 一致的 pnpm 版本重建一次(`cd ~/.dsh/profiles/<profile> && pnpm install`),或者统一
185
+ 本机的 pnpm 版本。
186
+
187
+ **`dsh plugin add` 报 404 / 找不到包**
188
+
189
+ 使用了企业或国内镜像,而镜像尚未同步新包。显式指定官方 registry:
247
190
 
248
191
  ```bash
249
- npm version patch # minor/major
250
- npm publish # publishConfig 已固定 registry=npmjs、access=public
192
+ dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets --registry=https://registry.npmjs.org
251
193
  ```
252
194
 
253
- - **登录也要指 registry**。若生效 registry 是只读镜像(本机 `npm_config_registry` 指向腾讯镜像),
254
- `npm login` 会打到镜像上并报 `E409 ... user registration disabled`。二选一:
255
-
256
- ```bash
257
- # a) 用 npmjs 的 Access Token(推荐,尤其是账号开了 2FA 时)
258
- # 在 https://www.npmjs.com/settings/<用户名>/tokens 生成 Granular Access Token(Read and write),
259
- # 然后写进 ~/.npmrc(token 按 host 存,不受 registry 配置影响):
260
- # //registry.npmjs.org/:_authToken=npm_xxxxxxxx
261
- npm whoami --registry=https://registry.npmjs.org # 应输出你的用户名
262
-
263
- # b) 或者直接登录 npmjs(npm 11 默认 web 登录;也可加 --auth-type=legacy 走用户名/密码/OTP)
264
- npm login --registry=https://registry.npmjs.org
265
- ```
266
-
267
- 注意:环境变量 `npm_config_registry` 优先级高于 project/user `.npmrc`,所以"在项目里放
268
- `.npmrc` 改 registry"在这里**无效**,只能用 CLI flag 或按 host 存的 token。
269
- - `publishConfig.registry` 显式指向 `https://registry.npmjs.org`:本机 `npm_config_registry`
270
- 是只读镜像时,不加这个会发布失败。
271
- - 发布前自查:`npm pack --dry-run`(应只含 `lib/`、`cordis.patch.yml`、`README.md`、`LICENSE`、
272
- `package.json`)、`node --test`。
273
- - 发布后把安装方式切到 registry 版本(pnpm 也要指 registry,见上文;镜像对新包有同步延迟):
274
-
275
- ```bash
276
- PATH="$HOME/Library/pnpm/bin:$PATH" dsh plugin --profile web remove @lixiangzhong/dsh-shell-secrets
277
- PATH="$HOME/Library/pnpm/bin:$PATH" dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets \
278
- --registry=https://registry.npmjs.org
279
- npm view @lixiangzhong/dsh-shell-secrets version --registry=https://registry.npmjs.org
280
- ```
281
-
282
- ## 风险与边界
283
-
284
- 1. **这是有意绕过凭证 scrub**:匹配到的变量会进入**每一个** bash 子进程,包括
285
- `npm install` 的 postinstall 与任意第三方二进制。只在受信 workspace 使用。
286
- 2. 值会随命令输出进入模型可见的 tool result(例如模型跑 `env`)。插件无法对输出脱敏。
287
- 3. 绑定了 harness 内部结构:`SandboxBashExecutor` 默认导出、`run`/`start` 语义、
288
- `ShellExecSpec.env` 的"scrub 之后再合并"约定、`bash-sandbox` 行的存在,
289
- 以及 `ctx.shell` 服务名。dsh 升级后请重新跑一遍:
290
- ```bash
291
- node --test && node deploy.mjs --check
292
- ```
293
- 并确认 `env` 里密钥仍在。
294
- 4. 若将来 harness 给 `ShellExecSpec` 增加了插件级 env 贡献接缝,应迁移过去并删掉
295
- 执行器替换(少一层内部耦合)。
296
- 5. **不覆盖**:`minimal` 预设 / `sdk-minimal` profile 的持久 PTY shell(走 `terminals`
297
- 服务,不经 `ctx.shell`);Windows 的 pwsh(`SandboxPwshExecutor` 同样已导出,可同构补充)。
298
-
299
- ## 目录
195
+ **误把值打印出来了**
300
196
 
197
+ 无法撤回,请轮换该密钥;后续验证只列名字。
198
+
199
+ ## 工作原理(简述)
200
+
201
+ 1. 插件在装载时读一次启动环境,按配置挑出需要注入的变量(纯函数逻辑,见 `lib/secrets.js`);
202
+ 2. 用 `SecretsBashExecutor` 接管 dsh 的 shell 执行器(覆写 `run` 与 `start` 两个方法);
203
+ 3. 每次执行前把这些变量并入 `ShellExecSpec.env` —— 该字段由 dsh 在凭证清理**之后**合并,
204
+ 因此原始变量名能进入子进程环境,同时不会覆盖 dsh 自己注入的 `DSH_*`(那些走另一个字段)。
205
+
206
+ 插件只做"把已有变量放回去",不读文件、不访问网络、不做持久化。
207
+
208
+ ## 开发
209
+
210
+ ```bash
211
+ node --test # 单元测试(纯逻辑,零依赖)
212
+ npm pack --dry-run # 查看发布产物
301
213
  ```
302
- lib/secrets.js 密钥选择纯逻辑(可单测)
303
- lib/index.js SecretsBashExecutor extends SandboxBashExecutor
304
- test/secrets.test.js node --test
305
- deploy.mjs 部署/回滚/校验
306
- cordis.patch.yml 作为独立 bundle 安装(dsh plugin add file:<dir>)时用的行
307
- ```
214
+
215
+ 本地联调:把仓库目录以 `file:` 方式装进 profile(见上文),改完代码后重新
216
+ `dsh plugin remove && add` 并重启 dsh。仓库内的 `deploy.mjs` 提供了不依赖 pnpm 的
217
+ 备用部署方式(拷贝 + 手写挂载行),适用于无法使用 pnpm 的环境。
218
+
219
+ 维护者笔记(本机环境、踩过的坑、发布流程)见 [`NOTES.md`](./NOTES.md)。
220
+
221
+ ## 许可证
222
+
223
+ [MIT](./LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lixiangzhong/dsh-shell-secrets",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DSH bundle: re-inject KEY/PASSWORD/SECRET/TOKEN variables from the launching environment into dsh bash subprocesses under their original names",
5
5
  "license": "MIT",
6
6
  "author": "lixiangzhong",