@lixiangzhong/dsh-shell-secrets
v0.1.1
Published
DSH bundle: re-inject KEY/PASSWORD/SECRET/TOKEN variables from the launching environment into dsh bash subprocesses under their original names
Maintainers
Readme
@lixiangzhong/dsh-shell-secrets
一个 dsh(DeepSeek Harness)插件:把启动 dsh 时环境里
名字匹配 KEY / PASSWORD / SECRET / TOKEN 的变量,以原始变量名注入 bash 工具的子进程。
export GITHUB_TOKEN=ghp_xxx PGPASSWORD=secret
dsh web之后模型通过 bash 工具执行命令时,gh、psql、aws、curl 这类自己读环境变量的 CLI 无需改写:
gh auth status # 直接读 $GITHUB_TOKEN
psql -h db -U app # 直接读 $PGPASSWORD⚠️ 安全须知
这个插件有意绕过 dsh 的凭证隔离机制。启用之后,匹配到的变量会进入每一个 bash 子进程 —— 包括
npm install的 postinstall 脚本,以及模型运行的任意第三方二进制。只要模型执行
env,这些值就会出现在工具结果里,也就是会话日志与模型上下文里。插件无法对 输出做脱敏,也无法撤回已经写进日志的值。建议:
- 只在受信的 workspace 和会话里使用;
- 验证时只列变量名,不要打印值:
env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|_KEY$';- 一旦有值进了日志又不想留,直接轮换该密钥。
为什么需要它
dsh 在启动子进程前会做一次固定的凭证清理:名字匹配 /KEY|PASSWORD|SECRET|TOKEN/i 的变量、
以及所有 DSH_*,都不会传给 bash 子进程。因此即使用户在自己的 shell 里 export GITHUB_TOKEN=...,
模型执行的命令里也读不到它。
dsh 官方提供的插件接缝 ctx.shellEnv 只能注册 DSH_* 前缀的变量,命令里得写成
$DSH_GITHUB_TOKEN,对 gh、psql 这类直接读 GITHUB_TOKEN / PGPASSWORD 的 CLI 没用。
本插件改为接管 shell 执行器,把同一批变量以原始名字放回子进程环境 —— 走的仍是 dsh
文档化的落点(ShellExecSpec.env,语义是"在凭证清理之后合并")。
安装
环境要求:已安装 dsh CLI(本插件在 dsh 进程内运行,Node 版本要求 ≥ 22);用 dsh plugin
管理插件时需要 pnpm(dsh 自身用 pnpm 管理 profile 依赖)。
从 npm 安装(推荐)
dsh plugin --profile web add @lixiangzhong/dsh-shell-secretsdsh plugin 会把包装进 ~/.dsh/profiles/<profile>/node_modules,并把包名追加到
dsh.profile.bundles 层(因为包里声明了 dsh.bundle.patch),不需要手改任何 YAML。
--profile 按需替换(web / tui / headless …)。装好后重启 dsh 才会生效。
从源码或离线 tarball 安装
# 目录形式
dsh plugin --profile web add file:/path/to/dsh-shell-secrets
# tarball 形式(npm pack 产物)
dsh plugin --profile web add file:./lixiangzhong-dsh-shell-secrets-0.1.0.tgz本地安装必须使用 file:(会落成真实目录),不要用 link::软链会让 Node 按真实路径去
源码目录解析 dsh 自身的包,从而加载到两份 harness 模块实例,服务注册会出问题。
升级与卸载
dsh plugin --profile web update @lixiangzhong/dsh-shell-secrets # 升级到最新版
dsh plugin --profile web add @lixiangzhong/[email protected] # 或指定版本
dsh plugin --profile web remove @lixiangzhong/dsh-shell-secrets # 卸载(依赖 + bundle 层一起移除)升级/卸载后同样需要重启 dsh。
生效时机
| 改动 | 生效方式 |
| --- | --- |
| 启动环境里的密钥变量 | 重启 dsh |
| DSH_SHELL_SECRETS_* 配置 | 重启 dsh |
| 插件版本(安装 / 升级 / 卸载) | 重启 dsh |
变量与配置都来自 dsh 启动时的那个环境,进程启动后不再变化。
配置
全部通过环境变量(在启动 dsh 之前设置):
| 变量 | 默认 | 说明 |
| --- | --- | --- |
| DSH_SHELL_SECRETS_PATTERN | KEY\|PASSWORD\|SECRET\|TOKEN(忽略大小写) | 自定义匹配正则。默认值与 dsh 自身的凭证清理规则一致,保证"被清掉的正好被补回来" |
| DSH_SHELL_SECRETS_INCLUDE | 空 | 逗号分隔的 glob(* / ?),优先于 PATTERN,例如 *_TOKEN,AWS_* |
| DSH_SHELL_SECRETS_EXCLUDE | 空 | 逗号分隔的 glob 排除项,例如 MONKEY,KEYBOARD_*(修掉子串误伤) |
| DSH_SHELL_SECRETS_MAX_BYTES | 8192 | 单个值的长度上限,超长跳过 |
| DSH_SHELL_SECRETS_DISABLE | 空 | 设为任意非空值即停用注入(不必卸载插件) |
规则细节:
DSH_*永不注入:保持 dsh "外部传入的DSH_*会被丢弃" 的既有约定,避免伪造框架自身的事实。- 默认规则是子串匹配,因此
MONKEY、KEYBOARD_LAYOUT这类名字也会命中 —— 用EXCLUDE排除即可。 - 空值、纯空白、超长值会被跳过;含换行的值仍会注入(保真优先),但会在装载日志里提示。
- 配置非法(正则写错、上限非正整数)会让插件装载失败并报错:宁可启动时报错,也不要静默不注入。
- 装载日志只打印变量名与原因,绝不打印值。
快速验证
# 1) 在启动 dsh 的 shell 里设置一个测试变量,然后启动 dsh
export MY_TEST_PASSWORD=probe-ok
dsh web# 2) 在 dsh 的 bash 工具里执行
echo "${MY_TEST_PASSWORD-unset}" # → probe-ok(原始名字可用)
# 只列名字,不要打印值
env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|SECRET$|_KEY$' | sort
env | cut -d= -f1 | grep '^DSH_' # 应只有 DSH_HOME / DSH_SHELL / DSH_SESSION_ID / DSH_WEB_URL已知限制
- 只处理 bash 执行器。使用持久 PTY shell 的场景(
minimalagent preset、sdk-minimalprofile) 走的是另一套terminals服务,不在覆盖范围内。 - Windows 的 pwsh 未覆盖(机制上可同构补充,尚未实现)。
- 只从启动环境读取,不读文件;dsh 启动之后才 export 的变量不会生效。
- 变量集合在插件装载时确定一次,运行期不再刷新。
兼容性
本插件通过覆写 shell 执行器实现,因此绑定了 dsh 的内部接口:
@deepseek-ai/dsh-bash-sandbox导出的SandboxBashExecutor,及其resolve/run/start方法;ShellExecSpec.env的合并语义(在凭证清理之后合并);ctx.shell服务名与bash-sandbox配置行。
为了尽早暴露不兼容,插件装载时会做一次自检,并在 dsh 日志里以 error 级别报告(不阻断启动,
以免误报导致 dsh 起不来):
- 基类是否仍提供
resolve/run/start; - 插件解析到的 harness 包与 dsh 自身使用的是否是同一份(避免 profile 里装出第二份 harness 包)。
已测试环境
| dsh | Node | 平台 | 结果 | | --- | --- | --- | --- | | 0.1.5-rc.1 | 26.x | macOS | 前台与后台任务路径均注入成功,单元测试 13/13 |
其它版本/平台(尤其 Windows)尚未验证,欢迎反馈。dsh 升级后建议确认一次:
echo "${GITHUB_TOKEN-unset}" # 或任一敏感变量
env | cut -d= -f1 | grep -E '_TOKEN$|PASSWORD$|SECRET$|_KEY$' # 只列名字常见问题
装了但命令里读不到变量
- 变量是在启动 dsh 的那个 shell 里 export 的吗?(
.zshrc里 export 也可以,但要重启 dsh) - 装/改配置之后重启 dsh 了吗?
- 用
env | cut -d= -f1 | grep '^DSH_'之外的方式确认名字是否在注入清单里;装载日志会打印可注入密钥变量 N 个: ...,若为 0 并伴随警告,说明启动环境里没有匹配项或PATTERN写错了。
dsh plugin add 报 ERR_PNPM_UNEXPECTED_STORE
profile 的 node_modules 与当前 pnpm 的大版本不一致(store 版本不同)。两种处理:使用与
profile 一致的 pnpm 版本重建一次(cd ~/.dsh/profiles/<profile> && pnpm install),或者统一
本机的 pnpm 版本。
dsh plugin add 报 404 / 找不到包
使用了企业或国内镜像,而镜像尚未同步新包。显式指定官方 registry:
dsh plugin --profile web add @lixiangzhong/dsh-shell-secrets --registry=https://registry.npmjs.org误把值打印出来了
无法撤回,请轮换该密钥;后续验证只列名字。
工作原理(简述)
- 插件在装载时读一次启动环境,按配置挑出需要注入的变量(纯函数逻辑,见
lib/secrets.js); - 用
SecretsBashExecutor接管 dsh 的 shell 执行器(覆写run与start两个方法); - 每次执行前把这些变量并入
ShellExecSpec.env—— 该字段由 dsh 在凭证清理之后合并, 因此原始变量名能进入子进程环境,同时不会覆盖 dsh 自己注入的DSH_*(那些走另一个字段)。
插件只做"把已有变量放回去",不读文件、不访问网络、不做持久化。
开发
node --test # 单元测试(纯逻辑,零依赖)
npm pack --dry-run # 查看发布产物本地联调:把仓库目录以 file: 方式装进 profile(见上文),改完代码后重新
dsh plugin remove && add 并重启 dsh。仓库内的 deploy.mjs 提供了不依赖 pnpm 的
备用部署方式(拷贝 + 手写挂载行),适用于无法使用 pnpm 的环境。
维护者笔记(本机环境、踩过的坑、发布流程)见 NOTES.md。
