DSH Plugins Marketplace

DSH Plugins

Plugins

/

Development & Infrastructure

/

dsh-sandbox-tune

d

dsh-sandbox-tune

Manifest valid

Lets build commands run inside the DSH sandbox instead of asking for elevation on every invocation: redirects pure caches (GOCACHE, NPM_CONFIG_CACHE, BUN_INSTALL_CACHE_DIR, PIP_CACHE_DIR) to a temp ro

UI (client)hasBundlePatch

dsh-sandbox-tune

DeepSeek Harness(DSH)的沙箱调优插件 —— 让 go build / go test / npm install 这类开发命令在沙箱内正常跑通,不再每次都弹提权审批窗。

DSH 的 workspace-write 沙箱只允许写「工作区 + /tmp + 临时目录」, 而构建工具的缓存、CLI 的配置目录都在工作区外,于是每条命令都被内核拒绝, 模型只能申请 danger-full-access 提权 —— 这就是弹窗的来源。

本插件从三个层面解决:缓存重定向、可写目录授权、按规则自动批准升权。

版本要求

项要求
DSH0.2.1-rc.2 或更高(本插件在 0.2.1-rc.2 上开发与验证)
运行平台macOS / Linux / Windows(能力有差异,见平台支持)
依赖使用 DSH 自带的 dsh-bash-sandbox / dsh-sandbox-local / dsh-shell 等,无需额外安装

插件通过 dsh plugin add 安装进 profile,随 DSH 版本升级自动跟随 (依赖以 peerDependencies 声明,由 DSH 运行时解析)。


目录


问题:为什么会有弹窗

DSH 的文件沙箱(workspace-write)只允许写三个地方:

工作区目录  +  /tmp  +  用户临时目录($TMPDIR)

而构建工具默认把缓存写到工作区之外:

工具默认位置在沙箱内
Go 编译缓存 GOCACHE~/Library/Caches/go-build❌ 拒绝写
Go 模块缓存 GOMODCACHE~/gvm/.../pkg/mod读 ✅ / 写 ❌
npm NPM_CONFIG_CACHE~/.npm❌ 拒绝写
Cargo CARGO_HOME~/.cargo❌ 拒绝写

于是每条编译命令都被内核拒绝(macOS Seatbelt 报 Operation not permitted), 模型只能申请 danger-full-access 提权 —— 这就是弹窗的来源。

实测对比(同一沙箱内):

GOCACHE=~/Library/Caches/go-build  go build ./...   → exit 1(operation not permitted)
GOCACHE=$TMPDIR/dsh/go-build       go build ./...   → exit 0 ✅

功能总览

本插件针对四类弹窗,按占比排列(基于本机 517 次历史审批统计):

弹窗原因占比本插件的解法
Go 等构建缓存写不进去28%缓存重定向 / 原地授权
写工作区外的用户配置目录(~/.config 等)27%用户级目录预设
gh 读钥匙串19%❌ 不支持(非文件路径问题)
写 ~/.dsh 的 profile 配置11%部分(可用 sandboxRoots 授权)
其余(进程管理、删除清理等)15%❌ 不适合用白名单放宽

三个能力模块

模块作用作用域
缓存处理把纯缓存重定向,或授权真实缓存目录全局 + 会话
可写目录授权把用户级目录加入沙箱白名单全局 + 会话
命令授权按规则自动批准沙箱升权仅全局

两种工作模式

mode 决定缓存怎么处理。只在执行器(sandbox-tune-bash)上配置一处, provider 会自动跟随。

模式做法与终端共用缓存后端要求
redirect(默认)用环境变量把纯缓存指到 $TMPDIR/dsh❌ 两套独立任何后端
in-place路径不变,把真实缓存目录授权为可写✅ 完全共用Seatbelt / bwrap

为什么默认 redirect:它只依赖环境变量,任何平台都能用,不需要沙箱 支持追加白名单。

什么时候用 in-place:想与普通终端共用同一份缓存(避免冷启动、不重复占 磁盘)。代价是依赖沙箱后端能力 —— 不支持时会自动回退到 redirect。

一个必须区分的前提

缓存分两类,可重定向性完全不同:

类型例子丢了会怎样处理方式
纯缓存(cache)GOCACHE、NPM_CONFIG_CACHE、PIP_CACHE_DIR只是变慢可重定向
依赖仓库(store)GOMODCACHE、CARGO_HOME编译直接失败只可原地授权

把 GOMODCACHE 指到空目录,go build 会立刻报「找不到模块」,比弹窗更糟。 所以 redirect 模式只动 cache 类;store 类只在 in-place 下被授权。

安装

推荐用预构建的 tarball(免源码构建,装完即用):

dsh plugin --profile web add https://github.com/kennethfan/dsh-sandbox-tune/releases/latest/download/dsh-sandbox-tune.tgz

也可以从 git 安装(会走源码构建,首次装稍慢):

dsh plugin --profile web add github:kennethfan/dsh-sandbox-tune

装完重启 DSH,插件自带的 cordis.patch.yml 会自动禁用 base 的 bash-sandbox / pwsh-sandbox / sandbox 三行并替换成自己的实现 (同名服务不能重复注册,所以原行必须禁用)。

为什么推荐 tarball:dsh plugin add 对 npm / git 源会触发 allowBuilds 构建授权,tarball 是预构建的,跳过这一步。

从源码开发

开发或改代码时用 link: 指向本地目录,改完重启 DSH 即生效:

git clone https://github.com/kennethfan/dsh-sandbox-tune.git
dsh plugin --profile web add link:$PWD/dsh-sandbox-tune

全局配置

全局配置对所有会话生效。有两种改法:图形界面(推荐)或直接改 YAML。

两种方式都无需重启 DSH —— 插件自己监听 cordis.patch.yml,改动会自动 重读并生效(详见配置热更新)。

界面操作

入口:左侧栏底部「设置」→ 导航里的「沙箱调优」。

全局设置:执行器卡片

页面顶部有一个蓝色的「全局」徽标,明确说明本页修改的是全局配置:

本页修改的是全局配置,对所有会话生效。想只改当前会话,请用会话标题右侧的 「沙箱调优」按钮,或输入 /sandbox-tune。

卡片一:执行器(sandbox-tune-bash)

决定缓存写到哪里。

控件对应字段说明
启用enabled关闭后完全退化为原生执行器
模式moderedirect(重定向)/ in-place(原地授权)
缓存根目录cacheRoot仅 redirect 模式可用;留空 → $TMPDIR/dsh
启用的工具预设presets点选 go / npm / bun / pip / cargo

选 in-place 时,「缓存根目录」输入框会自动禁用并变灰 —— 因为该模式下路径 保持不变,不需要指定。

卡片二:命令授权

全局设置:命令授权规则

控件对应字段说明
模式autoApproveModeoff / exact / safe-args
规则列表autoApproveCommands逐条添加,每行右侧 × 可删除

规则用「输入框 + 添加按钮」维护,回车也能添加。已添加的规则显示为表格行, 超长会自动省略(悬停看全文)。模式为 off 时输入框禁用。

卡片三:沙箱 provider

全局设置:provider 卡片

把真实缓存目录和用户级目录追加进沙箱可写白名单。

控件对应字段说明
启用enabled关闭后完全不改沙箱边界
自动探测路径autoDetect跑 go env 等只读命令拿真实路径
探测哪些工具presets点选要探测的工具
用户级目录userDirs勾选后加入白名单,每项都标了收益与风险

保存:改动即时写入,无需点「保存」按钮。若想放弃改动,用页面右上角的 「打开配置文件」直接编辑 YAML,或重新加载页面。

界面里的控件状态会反映当前值。例如上面截图里 ~/.cache 已勾选、 ~/.config 未勾选 —— 这就是当前的授权状态。

直接改 YAML

配置写在 profile 的 cordis.patch.yml。下面是三个卡片的完整字段。

执行器(sandbox-tune-bash)

- id: sandbox-tune-bash
  name: 'dsh-sandbox-tune/executor'
  config:
    enabled: true
    mode: redirect            # redirect | in-place
    cacheRoot: ''             # 仅 redirect 用;空 → $TMPDIR/dsh
    presets: [go, npm, bun, pip]
    env: {}                   # 额外/覆盖环境变量
    autoApproveMode: off      # off | exact | safe-args
    autoApproveCommands: []
字段默认含义
enabledtrue总开关;false 时完全退化为原生执行器
moderedirect见两种工作模式
cacheRoot$TMPDIR/dsh仅 redirect 模式使用,支持 ~
presets['go']启用哪些工具的缓存预设
env{}追加或覆盖的环境变量,优先级最高
autoApproveModeoff命令授权模式,见命令授权规则
autoApproveCommands[]命令授权规则列表

cacheRoot 放哪? 默认在 $TMPDIR 下,好处是无需额外授权就能写; 代价是 macOS 会周期性清理临时目录,重启后首次编译要冷启动。 想持久化就改成工作区内路径(如 cacheRoot: .dsh/cache,记得加 .gitignore), 或者直接切到 in-place。

provider(sandbox-tune-provider)

- id: sandbox-tune-provider
  name: 'dsh-sandbox-tune/provider'
  config:
    enabled: true
    autoDetect: true          # 自动探测真实路径
    presets: [go, npm, bun, cargo]
    sandboxRoots: []          # 手工追加的额外目录
    userDirs: []              # 用户级目录预设,默认全不授权
字段默认含义
enabledtruefalse 时完全不改沙箱
autoDetecttrue跑 go env / npm config get cache 等探测真实路径
presets['go']要探测哪些工具
sandboxRoots[]手工追加的额外目录,支持 ~
userDirs[]用户级目录预设,见下

自动探测为什么必要

本机用 gvm 管理 Go,GOMODCACHE 在 ~/.gvm/pkgsets/go1.26.0/global/pkg/mod, 而静态默认值会猜 ~/go/pkg/mod —— 猜错就会授权一个不存在的目录,等于没授权。

探测命令(都是只读,Host 侧执行,不受沙箱限制):

预设探测命令
gogo env GOCACHE / go env GOMODCACHE
npmnpm config get cache
bunbun pm cache
pip / cargo无探测,用静态默认值

探测失败时回落到静态默认值,单个工具失败不影响其他工具。

用户级目录预设

预设目录收益风险
user-config~/.configCLI 配置目录,最常见来源可读写所有 CLI 配置
user-cache~/.cache通用缓存(npm/pip/bun)较低,缓存可被篡改
user-data~/.local/share应用数据可修改应用数据,含数据库
user-state~/.local/state运行状态与日志较低,可被伪造

默认全部关闭:放宽文件写入边界是有代价的,必须显式勾选。

user-cache 刻意不用 macOS 的 ~/Library/Caches:那是系统级缓存 (本机实测 5.9GB,含 Docker / AndroidStudio / CloudKit),远超「工具缓存」 的范围。只用 XDG 风格的 ~/.cache。

会话级配置

同一台机器上不同会话可以有不同设置:做实验时放宽,动生产代码时收紧。

入口一:会话头部按钮

会话标题右侧有「沙箱调优」按钮:

会话头部的「沙箱调优」按钮

点击后弹出 Modal,只影响当前会话:

会话级设置弹窗

弹窗顶部的说明写清了作用域规则:

只影响当前会话。列表字段(预设 / 目录 / 环境变量)会叠加到全局之上, 不会替换;标量字段(模式 / 缓存目录)会替换全局值。清除后回到全局。

8 个可改字段,两列布局,每个字段旁标注了对应的配置键:

字段控件形态说明
模式 mode二选一按钮redirect / in-place
启用 enabled二选一按钮开 / 关
缓存根目录 cacheRoot文本框回车提交
工具预设 presets列表 + 添加逗号分隔也支持
用户级目录 userDirs列表 + 添加同上
额外授权目录 sandboxRoots列表 + 添加绝对路径
额外环境变量 env列表 + 添加KEY=VALUE
自动探测路径 autoDetect二选一按钮开 / 关

列表字段的交互:输入框输入后回车(或点「添加」)→ 已添加项显示为表格行, 每行右侧 × 删除。重复项自动忽略。空列表显示「暂无条目」。

底部操作:

按钮作用
查看当前在会话流里打印生效配置,标出每个值来自会话还是全局
清除覆盖清空本会话的全部覆盖,回到全局配置

弹窗底部还有一条「仅全局」提示,说明命令授权两项不支持会话级设置。

关闭方式:右上角 ×、点击遮罩、按 Escape。

每次增删都立即提交给 Host,没有「保存」按钮。这样界面显示与真实配置 始终一致 —— 本地暂存会让你以为改了,实际没生效。

入口二:斜杠命令

/sandbox-tune                                  查看本会话生效配置
/sandbox-tune set mode in-place                改模式
/sandbox-tune set presets go,npm               改预设
/sandbox-tune set cacheRoot /tmp/my-cache      改缓存目录
/sandbox-tune set env A=1,B=2                  追加环境变量
/sandbox-tune reset                            清除本会话覆盖

命令语法

值的类型语法例子
字符串原样(支持空格)set cacheRoot /Users/k/Library/Application Support/x
布尔true / falseset enabled false
数组逗号分隔set presets go,npm,bun
对象KEY=VALUE,逗号分隔set env A=1,B=2

非法值会在写入前被拒绝,不会留下半条配置。

会话级不能改的字段

autoApproveMode 与 autoApproveCommands 只能全局设置:

它们放宽的是 danger-full-access 升权。如果允许按会话设置、并随 fork 或 恢复历史会话带过去,你可能几天后完全不记得那个会话开着宽松规则。

写入时会直接报错(不是「存进去但不生效」——后者更难排查)。

覆盖存在哪

进程内 Map(按 sessionId 键)。进程重启后回到全局配置 —— 这个退化方向是安全的,不会意外保留宽松设置。

界面显示值的来源

会话弹窗里的值来自 sessionStorage 按会话 id 的缓存,不是从 Host 读回来的。

原因:Host 侧没有查询会话覆盖的接口 —— command() 只返回 {matched}, 结果以 flow 节点渲染;settings.describe 返回的是全局值。而组件在切换会话时 会重新挂载,状态重置为空,看起来就像配置丢了。

场景界面显示实际生效
同一浏览器内切换会话✅ 正确(读缓存)✅ 正确
刷新页面✅ 保留✅ 保留
换设备 / 清缓存❌ 显示为空✅ 仍生效
重启 DSH⚠️ 仍显示旧值❌ 已失效

最后一行是已知局限:重启后 Host 的覆盖已清空,但浏览器缓存还在,界面会 显示过期的值。以「查看当前」打印的结果为准 —— 那个值来自 Host,是真实的。

命令授权规则

有些弹窗靠放宽路径解决不了(命令要写工作区外的任意位置)。本插件提供 命令级自动批准,但安全边界必须说清楚。

它放行的是什么

自动批准的是 danger-full-access 升权 —— 放行后整条命令不受文件沙箱限制。 而 bash 命令是任意 shell 程序:

go build ./...                        ← 你想放行的
go build ./... && rm -rf ~/Documents  ← 危险,但前缀一模一样

所以不提供前缀通配,只提供两种模式:

模式判定规则安全性实用性
off(默认)一律不放行——
exact命令与规则逐字符相同(忽略首尾空白)最高低
safe-args按 && || ; | 拆段,每段都须匹配规则;重定向与命令替换整体拒绝中高较高

safe-args 与 exact 的差别:前者允许命令带不同参数 (go build 规则能匹配 go build ./...),后者必须完全相同。

safe-args 如何判定

分两步:

第一步:拒绝不可拆分的构造

构造用途为什么拆分也救不了
> <重定向(写文件 / 读文件)cmd > ~/.zshrc 拆开看 cmd 放行,但实际改写配置
` $(命令替换内容无法静态校验,可塞任意命令
${变量展开同上,可注入
&后台执行脱离等待,输出与退出码都拿不到

例外:不写文件的重定向放行

以下两类重定向不碰文件系统,自动放行:

形态例子为什么安全
fd 复制2>&1、1>&2、>&2只把 stderr 并到 stdout(或反之),改变的是输出流向
写 /dev/null2>/dev/null、> /dev/null、>>/dev/null/dev/null 是黑洞设备,写入即丢弃

所以 git push 2>&1 | tail -3 可以自动放行。

仍然拒绝的(会碰文件系统):

形态风险
> file / >> file覆盖或追加任意文件(> ~/.zshrc 改写 shell 配置)
> /dev/sda写入设备,会破坏磁盘
< file读取任意文件
2>&1 > out.txt前半安全、后半危险,整体拒绝

判定顺序是先剥离安全重定向,再看剩余部分 —— 否则 2>&1 > out.txt 里的 > 会被笼统判成危险,而 2>&1 单独出现时也会被误拒。

第二步:按 && || ; | 拆分,逐段匹配规则

每一段都必须在规则里,整条命令才放行:

命令规则 go build, go test结果
go build ./...✅ 命中放行
go build && go test✅ 两段都命中放行
go build || go test✅ 两段都命中放行
go build ; go test✅ 两段都命中放行
go build && rm -rf /❌ 第二段未命中弹窗
go build | sh❌ sh 未命中弹窗

管道两段都要放行:go build \| tee log 需要规则里有 go build 和 tee。

拒绝时会指出具体哪一段没通过,便于判断是漏配规则还是命令本身有问题。

引号内的分隔符会被误拆(echo "a && b" 会拆成两段)。这个误判方向是 更严格(多一段要匹配规则,可能被拒),不会放行本该拒绝的命令。 保守方向正确,就不需要写一个容易出错的 shell 词法分析器。

重定向为什么不看目标路径

> < 一律不放行,即使目标目录可写:

go build > ./out.log      → 不放行
go build > /tmp/out.log   → 不放行
go build < ./input.txt    → 不放行

原因是两个层面的事不能混:

层面判断依据由谁负责
能不能写这个路径沙箱白名单sandboxRoots / userDirs
要不要升权命令内容命令授权规则

如果规则要判断“目标目录有没有权限”,就得解析命令并解析出目标路径 —— 而重定向 路径可以写成任意形式,静态分析无法可靠判断:

go build > ./out.log
go build > ../out.log
go build > /tmp/../etc/passwd
go build > $(echo /tmp)/out.log

判错就是放行任意文件写入。

这不会带来额外弹窗

重定向本身不触发审批。只有目标真的被沙箱拒绝时,才会产生审批请求:

go build > /tmp/r1.log    → 直接成功,无审批请求(/tmp 本来可写)
go build > ~/x.log        → 被拒 → 产生审批 → 规则不放行 → 弹窗

所以规则不放行重定向,不会让「本来能过的命令」多弹一次。

想让某个目录可写,正确做法是授权目录

做法效果粒度
把目录加到 sandboxRoots / userDirs写入不再被拒 → 根本不弹窗该目录可写
在规则里放行重定向命令仍需逐条匹配,且路径判不准该命令可执行

授权目录才是正解 —— 粒度是「这个目录可写」,比「这条命令可执行」精确得多, 而且不会因为命令写法变化而失效。

规则匹配细节

规则能匹配不能匹配
go buildgo build、go build ./...、go build -vgo buildx、gofmt
go test ./internal/...完全相同的命令go test ./other/...

规则后面必须跟空白才算命中,避免 go 误匹配 google-chrome。

配置示例

- id: sandbox-tune-bash
  config:
    autoApproveMode: safe-args
    autoApproveCommands:
      - go build
      - go vet
      - go test

只作用于沙箱升权

判定条件之一是审批原因以 escalate sandbox to 开头。其他审批(hook 的权限 请求等)一律交回原链,不擅自放行。

每次放行都留日志

sandbox-tune: 自动批准升权 "go build ./..."(规则 "go build")

审计需要,不是可选项。

作用域与优先级

优先级

会话覆盖  >  全局配置  >  内置默认

按字段独立判断,不是整块覆盖。例如会话只改了 mode,其余字段仍用全局值。

叠加还是替换

字段会话级行为
presets / userDirs / sandboxRoots叠加(合并去重,全局在前)
env按 key 合并(同名 key 会话优先)
mode / enabled / cacheRoot / autoDetect替换(标量无叠加概念)

例如全局 sandboxRoots: [/A],会话加 /B,最终生效是 [/A, /B] —— 两个都可写。

叠加的固有限制:会话无法「移除」某个全局项。想移除只能去改全局配置。 这是选择叠加语义时接受的取舍(相比整体替换,叠加更符合直觉,也不会让 全局授权静默失效)。

字段作用域总表

字段全局会话级会话级语义
mode✅✅替换
enabled✅✅替换
cacheRoot✅✅替换
autoDetect✅✅替换
presets✅✅叠加
userDirs✅✅叠加
sandboxRoots✅✅叠加
env✅✅合并
autoApproveMode✅❌—
autoApproveCommands✅❌—

配置热更新

改配置文件后不需要重启 DSH。插件会监听 cordis.patch.yml,变化时自动 重读本插件的 config 段并重建配置对象。

生效范围

改动类型是否热更新说明
规则(autoApproveCommands)✅下一条命令立即生效
模式(mode)✅
预设、目录(presets / sandboxRoots / userDirs)✅
开关(enabled / autoDetect)✅
启用/禁用插件、增删 loader 行❌ 需重启涉及加载新模块,本质无法热替换
插件源码(.js)❌ 需重启同上

与设置页的关系

热更新只改执行器实际使用的配置,不推送界面更新。设置页的显示值来自 entry.fiber.config,而 describe() 每次调用都实时读该对象(无缓存), 所以下次打开设置页看到的就是最新值。

两者互不干扰:

操作作用
改文件触发热更新,执行器立即用新值
设置页保存写入文件 → 同样触发热更新

注意并发操作:如果设置页打开期间有人在外部改了文件,保存时会因 revision 不匹配而失败(SettingsConflictError)。重新打开设置页再改即可。 这不是缺陷,是乐观并发控制。

失败时保留旧配置

配置写错不会让插件失效:

情况行为
YAML 语法错误保留上一次有效配置,日志告警
字段值非法(如 mode: bogus)同上
该行被删除或禁用不动配置(这种情况本来需要重启)

这个设计是有意的:一次手误不该让你在不知情的情况下失去沙箱保护。

为什么不直接用 DSH 的 HMR

DSH 的 dsh-hmr 确实会监听 profile 的 patch 文件,但它构造时有硬性检查:

if (!this.ctx.loader.internal) throw new Error("--expose-internals is required for HMR service")

而 dsh web 默认不带 --expose-internals(该参数也不能经 NODE_OPTIONS 传入, Node 会拒绝)。实测即使手动用 node --expose-internals bin.js 启动,配置改动 仍不触发重载。

与其依赖一条需要改启动参数、且用到 Node 内部 API 的链路,不如插件自己监听: 只需 fs.watch,不碰内部 API,失败也只影响本插件。

实现要点

  • 监听父目录而非文件本身:编辑器「原子保存」会 unlink 再 create, 盯着文件会丢事件。
  • 内容去重:fs.watch 可能对未改动的内容触发事件,用上次文本比对过滤。
  • 防抖 300ms:编辑器保存常触发多次事件,合并成一次。
  • 不引入 YAML 库:link: 安装的插件在 Node 模块解析时不回溯到 profile 的 node_modules,import 'yaml' 在真实运行时是 ERR_MODULE_NOT_FOUND (实测踩过,启动日志报 failed to import 导致插件完全不激活)。 改为按缩进扫描 + 自己解析标量,config 段的取值形态有限,够用且零依赖。
  • 不用 fiber.update() 同步配置:它会走到 this.restart() 重启插件 (见 cordis 的 Fiber.update,waterfall 终态是 restart),而 restart 会 销毁并重建 watcher,可能引发重复触发。改为直接赋值 fiber.config —— 只换引用,state 保持 active,不触发任何生命周期。

安全回退

in-place 依赖后端能追加写白名单:

后端支持原因
macOS Seatbelt✅追加 (allow file-write* (subpath ...))
Linux bwrap✅追加 --bind
Linux Landlock❌授权由带版本的 launcher 内部拼装,无法外部注入
Windows ACL❌授权是 SID(--write-sid),由 provider 内部物化 ACE

遇到不支持的后端时,执行器自动回退到 redirect,而不是停在 「既不授权也不重定向」的中间状态 —— 那种状态下缓存仍写不进去,构建直接失败, 比弹窗更糟。

平台支持

能力macOSLinuxWindows
redirect 模式✅✅✅
in-place 模式✅✅❌ 自动回退
命令授权✅✅✅
用户目录预设✅✅✅

平台相关的路径约定

用途macOSLinuxWindows
缓存根$TMPDIR/dsh/tmp/dsh%TEMP%\dsh
配置目录预设~/.config~/.config%APPDATA%
缓存目录预设~/.cache~/.cache%LOCALAPPDATA%
应用数据预设~/.local/share~/.local/share%LOCALAPPDATA%

平台分支如何选择

cordis.patch.yml 同时禁用 bash-sandbox 与 pwsh-sandbox(两者都注册 ctx.shell),再由本插件按平台接管:

const SandboxExecutorBase = process.platform === 'win32'
  ? SandboxPwshExecutor   // Windows 跑 PowerShell
  : SandboxBashExecutor   // 其余平台跑 bash

如果一律继承 bash 版本,Windows 上的 shell 会去跑 bash -c,而 Windows 通常没有 bash —— 那会把功能弄坏,比不支持更糟。

验证

# 单元测试(纯函数,任何环境可跑)
node --test test/*.test.mjs

# 集成测试(最后一项需要进程本身未被沙箱限制)
node test/integration.mjs

集成测试最后一项会在真实沙箱里跑 go build。如果在 DSH 会话里执行, 测试进程自身已被 Seatbelt 限制,无法嵌套 sandbox-exec,该项会标记 SKIP 并说明原因 —— 请在普通终端重跑。

已知限制

  • 只处理文件写入。gh 读钥匙串是 mach 端口访问,open 是 LaunchServices, 这些不是文件路径问题,白名单解决不了。
  • redirect 模式下 go mod tidy / go get 仍会因要写 GOMODCACHE 失败; 需要切到 in-place,或单独放行一次。
  • in-place 的授权是整目录可写。授权 GOMODCACHE 意味着沙箱内进程可以 修改你的模块缓存,请自行权衡。
  • 会话级覆盖不跨进程重启保留。
  • 叠加语义下会话无法移除全局项。
  • 注入对所有命令生效(不按命令内容判断),因为判断一段 shell 语法到底 调用哪个工具并不可靠(make test、./build.sh 里都可能有 go)。

许可

MIT

Comments

Loading…

Similar plugins

dsh-sandbox-arg-guard

Keeps a redundant sandbox-escalation argument from failing a tool call. Escalating tools (pwsh, bash, write, edit) advertise the full sandbox_permissions enum, but DSH only accepts a level strictly wi

Security & AuditManifest valid

★ 0

dsh plugin --profile web add dsh-sandbox-arg-guard

dsh 插件:把复制粘贴的链接、图片、文本、账密收进本地仓库,自动分类,能在对话里检索取回

Terminal & ClientsMemory & ContextManifest valid

★ 1

MIT

TypeScript

dsh plugin --profile web add @chance722/dsh-inbox

by Arlenman

Backup, restore and machine-migration for DSH: packs skills (which live outside ~/.dsh, in ~/.agents/skills), plugin manifests, core config, memories, todos and optionally sessions into a tar.gz with

Tools & CapabilitiesDevelopment & InfrastructureTerminal & ClientsManifest valid

★ 2

MIT

JavaScript

Oct 18, 2026

dsh plugin --profile web add dsh-backup-sync

by JoblessJoe

One restart button for the DeepSeek Harness (dsh) web GUI that works with your setup: systemd, Docker, Kubernetes, pm2, launchd, terminal, Windows. Restarts in place, zero dependencies.

Development & InfrastructureManifest valid

★ 0

MIT

JavaScript

Oct 25, 2026

dsh plugin --profile web add dsh-restart-anywhere

by zzstar101

DSH plugin: drop sandbox_permissions that cannot widen the current sandbox mode, fixing 'not strictly wider' errors from GPT models

Security & AuditManifest valid

★ 0

MIT

JavaScript

Oct 6, 2026

dsh plugin --profile web add dsh-sandbox-noop-escalation

by lpf20200901

给 AI 编码助手的跨会话长期记忆:自动注入、只推变化。DSH 插件 + 零依赖 CLI。

Development & InfrastructureMemory & ContextManifest valid

★ 0

MIT

JavaScript

Oct 15, 2026

dsh plugin --profile web add dsh-memory-delta