@morlay/dsh-sandbox-local 0.0.2
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/LICENSE +21 -0
- package/README.md +160 -0
- package/cordis.patch.yml +22 -0
- package/dist/index.d.mts +29 -0
- package/dist/index.mjs +585 -0
- package/package.json +56 -0
- package/src/config.ts +31 -0
- package/src/containment.ts +72 -0
- package/src/dialects.ts +158 -0
- package/src/fs.ts +190 -0
- package/src/index.ts +72 -0
- package/src/rules.ts +305 -0
- package/src/sandbox.ts +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 morlay
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# @morlay/dsh-sandbox-local
|
|
2
|
+
|
|
3
|
+
可配置沙箱 bundle:替换官方 `ctx.sandbox`(进程沙箱)与 `ctx.fs`(文件系统围栏),
|
|
4
|
+
在官方语义之上叠加 `access` 规则——`rw <path>` 追加工作区之外的可写根,
|
|
5
|
+
`r- <path>` 只读(读放行、写拒绝),`-- <pattern>` 拒绝访问(读与写都拒)。
|
|
6
|
+
|
|
7
|
+
## 为什么
|
|
8
|
+
|
|
9
|
+
上游沙箱策略只有两个字段:`mode`(`read-only` / `workspace-write` /
|
|
10
|
+
`danger-full-access`)与 `workspaceRoot`;`workspace-write` 的可写路径是硬编码的
|
|
11
|
+
`[工作区, /tmp, os.tmpdir()]`(`packages/sandbox/sandbox/src/roots.ts:52-55`),
|
|
12
|
+
没有任何追加可写根或拒绝项的配置面。于是「让 agent 能写 `$XDG_CACHE_HOME`,
|
|
13
|
+
但永远不许碰项目里的 `mise.*.toml`」这类诉求只能整块放弃隔离。上游自己实现过拒绝项
|
|
14
|
+
(`readDenyPaths`)又撤回:bwrap 要在已置只读的树里创建挂载点、Landlock 无法从自己的
|
|
15
|
+
`/` 读授权里减除,一个「在能生效的地方破坏隔离、在不能生效的地方谎报」的保护被判为
|
|
16
|
+
不如明确的缺失(`.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md:29`)。
|
|
17
|
+
|
|
18
|
+
本包把「能表达多少就说多少」明确下来:Seatbelt 完整生效,其余平台按方言降级,
|
|
19
|
+
并在加载期告警,而不是静默失效。
|
|
20
|
+
|
|
21
|
+
## 行为
|
|
22
|
+
|
|
23
|
+
替换两个服务,规则在两个入口保持同一语义:
|
|
24
|
+
|
|
25
|
+
| 条目 | `ctx.fs`(read / write / edit / list 工具) | `ctx.sandbox`(bash 等子进程) |
|
|
26
|
+
| ----------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `-- <path>` | 任何模式下读与写都拒(`resolve` 入口即拦截) | Seatbelt:读 + 写;bwrap:退化为只读;Landlock / Windows ACL:无表达(加载期告警) |
|
|
28
|
+
| `r- <path>` | 任何模式下读放行、写拒绝(优先于可写根) | Seatbelt:`(deny file-write* …)`;bwrap:`--ro-bind-try`(只读挂载);Landlock / Windows:无表达 |
|
|
29
|
+
| `rw <path>` | `workspace-write` 下计入可写根 | Seatbelt:`(allow file-write* (subpath …))`;bwrap:`--bind-try`;Landlock:`--rw`;Windows:无表达 |
|
|
30
|
+
| 无条目 | 与官方 `fs-sandbox` 行为一致 | 与官方 argv 逐字一致(不做任何改写) |
|
|
31
|
+
|
|
32
|
+
- **进程沙箱侧是复用,不是重写**:`ConfigurableSandboxProvider` 继承官方
|
|
33
|
+
`LocalSandboxProvider`,`confine` 先走 `super.confine()`(runner 探测与选择、
|
|
34
|
+
Windows ACL 私有 temp、拒绝方言与 runner 失败规则全部保留),再按方言把规则追加到
|
|
35
|
+
返回的 argv 上。
|
|
36
|
+
- **Seatbelt 规则追加在 profile 末尾**:SBPL 的后置规则覆盖先置规则,因此
|
|
37
|
+
`(deny file-read* file-write* …)` 能压过官方已写入的 `(allow file-write* (subpath …))`;
|
|
38
|
+
已用真实 `sandbox-exec` 验证(`src/__tests__/seatbelt.e2e.spec.ts`)。
|
|
39
|
+
- **三类条目在两个入口同步**:上游把 `writableRoots` 同时喂给 Seatbelt profile 与
|
|
40
|
+
进程内 fs 围栏,只改一侧会造出「bash 能写、write 工具不能写」的裂缝。
|
|
41
|
+
- **命中优先级 `--` > `r-` > `rw` / 平台可写根**:显式拒绝覆盖只读声明,只读声明覆盖
|
|
42
|
+
更宽的可写授予(例如 `rw {{ env.XDG_DATA_HOME }}` 与 `r- {{ env.XDG_DATA_HOME }}/secrets`
|
|
43
|
+
同时存在时,后者胜)。
|
|
44
|
+
- **`r-` / `--` 条目在 `danger-full-access` 下仍然生效**(`ctx.fs` 侧):它们是显式写下的
|
|
45
|
+
用户规则,不是模式的推论;进程沙箱侧在 `danger-full-access` 下不经过沙箱,本包也无从施加。
|
|
46
|
+
|
|
47
|
+
## 配置
|
|
48
|
+
|
|
49
|
+
| 字段 | 默认 | 含义 |
|
|
50
|
+
| ------------------------- | --------------- | --------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `access` | `[]` | 规则条目:`rw <path>` 可写根 / `r- <path>` 只读 / `-- <pattern>` 拒绝访问;数组或一段多行文本 |
|
|
52
|
+
| `runnerCommand` | `[]` | 透传官方 `sandbox-local`:替换 runner argv(配置了它就不能用规则) |
|
|
53
|
+
| `runnerFailureSignatures` | `[]` | 透传官方 `sandbox-local`:自定义 runner 的失败签名 |
|
|
54
|
+
| `probeTimeoutMs` | `5000` | 透传官方 `sandbox-local`:候选 runner 的探测超时 |
|
|
55
|
+
| `cwd` | `process.cwd()` | 透传官方 `fs-local`:相对路径的解析基准 |
|
|
56
|
+
| `diffBasisMaxBytes` | `10485760` | 透传官方 `fs-local`:overwrite diff 单侧字节上限 |
|
|
57
|
+
|
|
58
|
+
两种写法等价(数组每项一条,或多行文本每行一条;多行文本的空行忽略):
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
- id: sandbox-local
|
|
62
|
+
config:
|
|
63
|
+
access:
|
|
64
|
+
- "rw {{ env.XDG_CACHE_HOME }}"
|
|
65
|
+
- "r- {{ env.XDG_CONFIG_HOME }}"
|
|
66
|
+
- "-- mise.*.toml"
|
|
67
|
+
- "-- **/*.pem"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- id: sandbox-local
|
|
72
|
+
config:
|
|
73
|
+
access: |-
|
|
74
|
+
rw {{ env.XDG_CACHE_HOME }}
|
|
75
|
+
r- {{ env.XDG_CONFIG_HOME }}
|
|
76
|
+
-- mise.*.toml
|
|
77
|
+
-- **/*.pem
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
条目语法:
|
|
81
|
+
|
|
82
|
+
- 每条必须以 `rw ` / `r- ` / `-- ` 开头;缺前缀、或前缀后没有路径,加载即失败(规则
|
|
83
|
+
不因写法歧义而变形)。
|
|
84
|
+
- 语义:`rw` 允许读写;`r-` 只允许读;`--` 读与写都拒绝。优先级 `--` > `r-` > `rw`。
|
|
85
|
+
- `{{ env.NAME }}` 在加载期按进程环境展开;变量未设置或为空时插件加载失败。
|
|
86
|
+
- 相对路径相对**会话工作区**(不是 `cwd` 配置项)解析。
|
|
87
|
+
- `r-` 与 `--` 条目接受 glob:`*` 与 `?` 不跨 `/`,`**` 跨层级(`**/` 也匹配零层),
|
|
88
|
+
`[!ab]` 取反;生成的正则同时用于进程内匹配与 SBPL 的 `(regex #"…")`,因此只用两者
|
|
89
|
+
共有的语法。
|
|
90
|
+
- 字面(无通配)的 `r-` / `--` 条目命中自身**及其全部后代**;`rw` 条目必须是具体路径
|
|
91
|
+
(可写根没有「通配」语义)。
|
|
92
|
+
|
|
93
|
+
## 装配
|
|
94
|
+
|
|
95
|
+
本包自带 `cordis.patch.yml`(禁用官方两行 + 插入自己的一行),把本包作为独立 bundle 采用
|
|
96
|
+
的部署直接列进 `dsh.profile.bundles` 即可;行不带 config(schema 默认是空规则):
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
- id: sandbox
|
|
100
|
+
disabled: true
|
|
101
|
+
|
|
102
|
+
- id: fs-sandbox
|
|
103
|
+
disabled: true
|
|
104
|
+
|
|
105
|
+
- insert:
|
|
106
|
+
- id: sandbox-local
|
|
107
|
+
name: "@morlay/dsh-sandbox-local"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**本部署(`@morlay/dsh-preset`)不走这条路径**:它的 patch 自己禁用官方两行、插入
|
|
111
|
+
`- id: sandbox-local` 行并写上规则,因此示例 app 的 `dsh.profile.bundles` 不需要列出本包,
|
|
112
|
+
只需要 profile 的依赖树能解析模块名(`@morlay/dsh-preset` 已在 `dependencies` 声明本包)。
|
|
113
|
+
两种采用方式互斥:同时上线会重复插入同一行。
|
|
114
|
+
|
|
115
|
+
**规则写在哪里才会生效**(patch 层按 `[bundle patches, profile patches, home patches,
|
|
116
|
+
overlays]` 合并,后应用者整块替换同一行的 `config`):
|
|
117
|
+
|
|
118
|
+
| 载体 | 生效范围 |
|
|
119
|
+
| ----------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
120
|
+
| profile 的 `cordis.patch.yml`(`$DSH_HOME/profiles/<name>/cordis.patch.yml`) | dev 与打包形态都生效,但只属于本机 home |
|
|
121
|
+
| app 的 `cordis.patch.yml` | 打包(`bundle`)形态:作为 seed 的 profile patch 生效 |
|
|
122
|
+
| 一个自有 bundle 的 patch | 所有形态(随包分发) |
|
|
123
|
+
|
|
124
|
+
本部署采用最后一种:**装配与规则在 `@morlay/dsh-preset` 的 bundle patch 里一起维护**
|
|
125
|
+
(禁用官方两行 + 插入本包行 + `access` 规则,跨 dev / 打包形态一致),不依赖 app 的
|
|
126
|
+
`dsh.profile.bundles` 再列一层。
|
|
127
|
+
|
|
128
|
+
## 前提
|
|
129
|
+
|
|
130
|
+
- 官方 `sandbox` 与 `fs-sandbox` 行必须禁用:同一 scope 内重复注册同名服务会 fail loud
|
|
131
|
+
(`service "sandbox" has been registered at …`),而不是覆盖。
|
|
132
|
+
- 启用规则的层必须同时做三件事——禁用官方两行、插入本包行、写规则:只做后两件时官方
|
|
133
|
+
实现仍在提供 `ctx.sandbox` / `ctx.fs`,规则没有生效点,沙箱静默退回「只有工作区 +
|
|
134
|
+
`/tmp` 可写」(命令照常跑,没有报错)。装配守卫见 `@morlay/dsh-preset` 的
|
|
135
|
+
`patch.spec.ts`。
|
|
136
|
+
- 规则与 `runnerCommand` 互斥:自定义 runner 的 argv 方言无法识别,此时配了规则会在
|
|
137
|
+
`confine` 抛错(宁可失败也不让规则静默失效)。
|
|
138
|
+
- `read-only` 模式不追加 `rw` 条目(显式选定的只读边界不因额外可写根放松),但 `r-`
|
|
139
|
+
与 `--` 条目仍然生效。
|
|
140
|
+
|
|
141
|
+
## 已知限制
|
|
142
|
+
|
|
143
|
+
- **Linux / Windows 的子进程侧降级**:bwrap 把 `r-` 与 `--` 都表达成只读挂载
|
|
144
|
+
(`--ro-bind-try`,所以 `--` 在 bwrap 上退化为「只拒写入」),Landlock 无法表达任何
|
|
145
|
+
子路径规则,Windows ACL runner 的 argv 没有承载额外 grant 的入口(`rw` 同样不生效)。
|
|
146
|
+
加载期对每种降级都打 warn,`ctx.fs` 侧(read / write / edit 工具)在所有平台保持完整
|
|
147
|
+
语义。
|
|
148
|
+
- **bwrap 参数顺序未实测**:`--bind-try` / `--ro-bind-try` 的「后挂载覆盖先挂载」与
|
|
149
|
+
`-try` 缺路径语义来自 bwrap 文档而非本仓库的测试证据(本机为 macOS)。
|
|
150
|
+
- **`r-` / `--` 条目不隐藏目录项**:`ls` 仍能看到被保护文件的名字,被拦的是内容读取
|
|
151
|
+
(仅 `--`)与写入。
|
|
152
|
+
- **`ctx.fs` 侧是策略检查,不是内核边界**:与它替换掉的官方 `fs-sandbox` 同一威胁模型
|
|
153
|
+
(受信代码 + 模型可控路径);内核级隔离仍是 `ctx.sandbox` 的职责。
|
|
154
|
+
- **Windows 额外授权未实现**:官方 `AclWriteGrant` 可以做预授权,但没有把 `AclWriteGrant`
|
|
155
|
+
接进 `confine` 的现成路径,本版只告警。
|
|
156
|
+
|
|
157
|
+
## 本地开发
|
|
158
|
+
|
|
159
|
+
根目录 `just test`(vitest,含 `seatbelt.e2e.spec.ts`——非 macOS 或被更外层 Seatbelt
|
|
160
|
+
拦住时自动跳过)、`just lint`(oxlint typeAware)、`just build`(tsdown 构建本包)。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @morlay/dsh-sandbox-local bundle patch:把官方沙箱栈换成可配置实现。
|
|
2
|
+
#
|
|
3
|
+
# - `sandbox`:官方 `@deepseek-ai/dsh-sandbox-local` 注册 `ctx.sandbox`;本包用它的
|
|
4
|
+
# 子类注册同名服务(复用官方 probe / runner 选择 / Windows ACL 流程,只在
|
|
5
|
+
# 选中的 runner 参数上追加 `access` 规则)。
|
|
6
|
+
# - `fs-sandbox`:官方 `@deepseek-ai/dsh-fs-sandbox` 注册 `ctx.fs`;本包用继承
|
|
7
|
+
# `LocalFileSystem` 的实现注册同名服务(复用同一套写围栏,并让 `--` 条目同时
|
|
8
|
+
# 拦住读与写、`r-` / `rw` 条目参与写判定)。
|
|
9
|
+
#
|
|
10
|
+
# 这组 patch 供把本包作为独立 bundle 采用的部署使用:行不带 config,schema 默认是空规则。
|
|
11
|
+
# 本部署(`@morlay/dsh-preset`)不走这条路径——它的 patch 自己禁用官方两行、插入本行
|
|
12
|
+
# 并写上规则,因此不会加载本 bundle;两种采用方式同时上线会重复插入同一行。
|
|
13
|
+
|
|
14
|
+
- id: sandbox
|
|
15
|
+
disabled: true
|
|
16
|
+
|
|
17
|
+
- id: fs-sandbox
|
|
18
|
+
disabled: true
|
|
19
|
+
|
|
20
|
+
- insert:
|
|
21
|
+
- id: sandbox-local
|
|
22
|
+
name: "@morlay/dsh-sandbox-local"
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Config as Config$1 } from "@deepseek-ai/dsh-fs-local";
|
|
2
|
+
import { Config as Config$2 } from "@deepseek-ai/dsh-sandbox-local";
|
|
3
|
+
import z from "@deepseek-ai/schemastery";
|
|
4
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
5
|
+
//#region src/config.d.ts
|
|
6
|
+
/** 插件配置。 */
|
|
7
|
+
interface Config extends Config$2, Config$1 {
|
|
8
|
+
/**
|
|
9
|
+
* 规则条目:`rw <path>`(额外可写根)、`r- <path>`(只读)、`-- <pattern>`(拒绝访问)。
|
|
10
|
+
* 数组每项一条,或写一段多行文本(每行一条);`{{ env.NAME }}` 按进程环境展开。
|
|
11
|
+
*/
|
|
12
|
+
access?: string | string[];
|
|
13
|
+
}
|
|
14
|
+
/** 运行时配置 schema。 */
|
|
15
|
+
declare const Config: z<Config>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/index.d.ts
|
|
18
|
+
/** Cordis 插件名。 */
|
|
19
|
+
declare const name = "sandbox-local";
|
|
20
|
+
/** fs 侧从策略服务取默认模式与工作区回退根,所以先等 `ctx.sandboxPolicy`。 */
|
|
21
|
+
declare const inject: string[];
|
|
22
|
+
/**
|
|
23
|
+
* 注册两个替换实现。
|
|
24
|
+
* @param ctx - 插件上下文(官方 `sandbox` / `fs-sandbox` 行已禁用)。
|
|
25
|
+
* @param config - 已由 schema 填好默认值的配置。
|
|
26
|
+
*/
|
|
27
|
+
declare function apply(ctx: Context, config: Config): void;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Config, apply, inject, name };
|