dsh-pwsh-plugin 0.2.16 → 0.2.18
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/FINDINGS.md +35 -0
- package/README.md +54 -21
- package/README.zh-CN.md +45 -16
- package/cordis.patch.yml +4 -0
- package/docs/dsh-windows-schannel.zh-CN.md +118 -0
- package/docs/llrun.md +21 -1
- package/docs/pwsh-run-audit.zh-CN.md +183 -0
- package/docs/runtime-notes.md +23 -19
- package/docs/runtime-notes.zh-CN.md +17 -15
- package/package.json +3 -3
- package/plugin/index.mjs +155 -193
- package/plugin/session-server.ps1 +166 -202
- package/plugin/session.mjs +160 -152
- package/wsl/custom-bash.mjs +47 -2
package/FINDINGS.md
CHANGED
|
@@ -1877,4 +1877,39 @@ No authorization header was set for the request.
|
|
|
1877
1877
|
|
|
1878
1878
|
十七份 → 八份,305 项 → **117 项**(itest 17 / plugintest 15 / e2e 8 / sandbox 7 / bash 25 / bash-e2e 11 /
|
|
1879
1879
|
denial 11 / meta 23)。
|
|
1880
|
+
## 三十五、WSL 里的 SSH agent:非交互 shell 不读 .bashrc(2026-10-30)
|
|
1881
|
+
|
|
1882
|
+
`bash` 工具跑的是 `bash -s`,非交互。Ubuntu 的默认 `~/.bashrc` 第 8-12 行有一句提前返回
|
|
1883
|
+
(`case $- in *i*) ;; *) return;; esac`),所以文件末尾那句 `eval $(wsl2-ssh-agent)` 永远执行不到,
|
|
1884
|
+
`SSH_AUTH_SOCK` 保持为空。在首行插一个 `echo` 标记即可确认:`bash -s` 的输出里没有它,
|
|
1885
|
+
`bash -i -s` 有。三条调用对照:`bash -i -s` 与 `bash -l -s`(经 `.profile`)有钥匙,`bash -s` 没有。
|
|
1886
|
+
|
|
1887
|
+
围栏与此无关。python 直连 socket 的探测在裸 shell 和 llrun 内都返回 `CONNECTED`,`SO_PEERCRED` 的
|
|
1888
|
+
pid/uid/gid 相同,identities 请求回 541 字节的 type 12。四格矩阵(有无围栏 × 有无显式
|
|
1889
|
+
`SSH_AUTH_SOCK`)说明成败只取决于变量有没有给出来。
|
|
1890
|
+
|
|
1891
|
+
围栏确实挡了一件事:它屏蔽 `/init`,于是 `wsl2-ssh-agent` 在围栏内起不来 —— 那个桥要调
|
|
1892
|
+
`powershell.exe` 去读 Windows 侧的 `\\.\pipe\openssh-ssh-agent`,在围栏内是 `Permission denied`。
|
|
1893
|
+
所以守护进程只能在围栏外活,靠一个 systemd 用户单元持续拉起:
|
|
1894
|
+
`/home/lihao/.config/systemd/user/skadi-ssh-agent.service`,内容是
|
|
1895
|
+
`ExecStart=%h/.local/bin/wsl2-ssh-agent -foreground`、`ExecStartPre` 先删残留 socket、
|
|
1896
|
+
`Restart=on-failure`。实测四种保活方式里只有它稳:`eval "$(wsl2-ssh-agent)"` 与
|
|
1897
|
+
`setsid nohup ... &` 都在父 shell 退出后几十秒内消失,`systemd-run --user --scope` 直接不返回
|
|
1898
|
+
(占满超时),`--unit=... --collect` 的 PPID 是 1。
|
|
1899
|
+
|
|
1900
|
+
插件侧的做法是给每条命令前置三行:
|
|
1901
|
+
|
|
1902
|
+
```sh
|
|
1903
|
+
if [ -z "$SSH_AUTH_SOCK" ] && [ -S "$HOME/.ssh/wsl2-ssh-agent.sock" ]; then
|
|
1904
|
+
export SSH_AUTH_SOCK="$HOME/.ssh/wsl2-ssh-agent.sock"
|
|
1905
|
+
fi
|
|
1906
|
+
```
|
|
1907
|
+
|
|
1908
|
+
判据是 `-S`(真 socket)而不是 `-e`,且不覆盖用户自己设的值 —— 脚本内 `export` 仍然胜出,这条有断言
|
|
1909
|
+
钉住。`sshAgentSocket: false` 关掉注入,其他字符串替换路径。端到端实测在围栏内拿到 8 把钥匙。
|
|
1910
|
+
|
|
1911
|
+
顺带修掉一个实现缺陷:无围栏 + `workdir` 的分支原本用 `args.command` 重建脚本,会把前置探测丢掉;
|
|
1912
|
+
现在基于已带前置的 `script` 再包 `cd`。断言 `agent-survives-workdir` 钉住这一条。
|
|
1913
|
+
|
|
1914
|
+
计数因此从 117 涨到 **123**(bash 25 → 31)。`docs/llrun.md` 里的 25 与两份 README 的 117 同批改过。
|
|
1880
1915
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# dsh-pwsh-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**English** · [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-pwsh-plugin) [](LICENSE) [](https://github.com/topics/dsh-plugin)
|
|
4
6
|
|
|
5
7
|
A `pwsh_run` tool for DeepSeek Harness: PowerShell script text goes to a **persistent** pwsh 7 process
|
|
6
8
|
over stdin, so nothing is escaped on the way in, there is no 32K command-line cap, the ~400 ms cold start
|
|
@@ -119,19 +121,20 @@ The three files in `plugin/` (`index.mjs`, `session.mjs`, `session-server.ps1`)
|
|
|
119
121
|
|
|
120
122
|
## The tools
|
|
121
123
|
|
|
122
|
-
`pwsh_run` runs a script. Versions are managed by DSH itself — see [
|
|
124
|
+
`pwsh_run` runs a script. Versions are managed by DSH itself — see [Updating](#updating).
|
|
123
125
|
|
|
124
126
|
| Argument | Required | Meaning |
|
|
125
127
|
|---|---|---|
|
|
126
128
|
| `script` | yes | PowerShell script text. Reaches the server **verbatim** — do not escape `$`, do not wrap it in `pwsh -c`, do not add outer quotes. |
|
|
127
129
|
| `workdir` | no | `Set-Location` first. The session is stateful, so later calls inherit it. An invalid path fails this call without running the script. |
|
|
128
|
-
| `timeoutMs` | no |
|
|
130
|
+
| `timeoutMs` | no | Execution timeout, excluding queue waiting; default 120000. Integer milliseconds from 1 to 2147478647. |
|
|
129
131
|
|
|
130
|
-
Output
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
Output uses PowerShell formatting at width 200; objects appear as tables or lists. Use `ConvertTo-Json`
|
|
133
|
+
when structured fields are needed. Warning, Verbose, Information and Debug have separate labels; errors
|
|
134
|
+
and innermost exceptions follow ordinary output. Streams are grouped by type, so cross-stream order is
|
|
135
|
+
not preserved; merge them in the script with `2>&1` or `*>&1` when needed. The script stays at top level
|
|
136
|
+
for `using` and `param`. `#requires` is checked before the body; unsupported `-ShellId` / `-Assembly`
|
|
137
|
+
requirements are explicitly refused.
|
|
135
138
|
|
|
136
139
|
## Configuration
|
|
137
140
|
|
|
@@ -140,8 +143,8 @@ When order matters, merge them yourself: `2>&1` or `*>&1`.
|
|
|
140
143
|
| `pwshPath` | `pwsh` | Executable; resolved through `PATH` by default. |
|
|
141
144
|
| `timeoutMs` | `120000` | Default per-call timeout. |
|
|
142
145
|
| `idleMs` | `1800000` | Recycle an idle session (30 minutes). |
|
|
143
|
-
| `maxSessions` | `4` |
|
|
144
|
-
| `maxOutputBytes` | `8388608` |
|
|
146
|
+
| `maxSessions` | `4` | Per-plugin process limit, isolated by DSH session ID. If all are busy, new sessions are refused with a retry message. |
|
|
147
|
+
| `maxOutputBytes` | `8388608` | UTF-8 payload budget per call, including record separators; errors have a separate 65536-byte budget. Framing and display labels are excluded. Truncation is reported. |
|
|
145
148
|
| `allowUnconfined` | `false` | `true` = start a session even when the sandbox policy service is missing. |
|
|
146
149
|
|
|
147
150
|
`allowUnconfined` is the one deliberate escape hatch. In a normal deployment `ctx.sandboxPolicy` is always
|
|
@@ -158,9 +161,12 @@ returns. The Windows ACL sandbox uses a `WRITE_RESTRICTED` token, and a token is
|
|
|
158
161
|
after that single confine, every later read and write in the session obeys the same workspace boundary as
|
|
159
162
|
the built-in `pwsh` tool.
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
`sandboxPolicy.resolve`)
|
|
163
|
-
|
|
164
|
+
Every call resolves policy against the **current real session** (`session` must reach
|
|
165
|
+
`sandboxPolicy.resolve`) and checks the effective policy, workspace and sandbox backend. A change
|
|
166
|
+
disposes the old process before any new script; read-only refuses the body. Permission or workspace
|
|
167
|
+
changes therefore clear persistent state too. Working-directory service errors are surfaced instead
|
|
168
|
+
of falling back to stale paths. Creation and execution share one queue per ID; only sessions with no
|
|
169
|
+
executing, queued or starting requests can be evicted.
|
|
164
170
|
|
|
165
171
|
A trap worth knowing: passing the ToolExecution instead (`agent` lives on `exec.agent`) means `resolve`
|
|
166
172
|
never sees a session and **silently falls back to the deployment default** (mode + configured
|
|
@@ -176,7 +182,7 @@ Under `danger-full-access` nothing is confined, exactly like the built-in tool.
|
|
|
176
182
|
**`read-only` does not start.** In that mode pwsh runs in ConstrainedLanguage and cannot even create a
|
|
177
183
|
runspace. The tool reports that clearly and points at the built-in `pwsh` tool, which works there.
|
|
178
184
|
|
|
179
|
-
##
|
|
185
|
+
## Updating
|
|
180
186
|
|
|
181
187
|
This plugin carries no updater of its own. It is an ordinary registry package, so **DSH manages its
|
|
182
188
|
version**: the Plugins page lists it, shows when a newer release exists, and installs it — the same
|
|
@@ -218,6 +224,13 @@ It needs no manual install: the binary ships inside the package and is the last
|
|
|
218
224
|
`plugin add` is enough. Since releases are packed on Windows, that copy arrives as `0644` — the probe
|
|
219
225
|
restores the executable bit itself before giving up, as long as the file belongs to the current user.
|
|
220
226
|
|
|
227
|
+
The same row also injects `SSH_AUTH_SOCK`. `bash -s` is non-interactive, so the `eval $(wsl2-ssh-agent)`
|
|
228
|
+
line at the bottom of `~/.bashrc` never runs and the variable stays empty — the fence is not the cause, a
|
|
229
|
+
socket named explicitly works inside it just as well. A three-line probe exports
|
|
230
|
+
`$HOME/.ssh/wsl2-ssh-agent.sock` when it is a real socket and nothing is set yet; `sshAgentSocket: false`
|
|
231
|
+
turns it off. The agent daemon itself must live **outside** the fence (it needs `/init`), typically as a
|
|
232
|
+
systemd user unit.
|
|
233
|
+
|
|
221
234
|
Full description, the `-r` semantics table, the 54-assertion acceptance matrix and the wiring:
|
|
222
235
|
**[docs/llrun.md](docs/llrun.md)**. Run `bash wsl/acceptance.sh` from a plain WSL shell (not from inside
|
|
223
236
|
`custom-bash`, where nesting a second fence makes four assertions fail for the wrong reason) to re-verify on
|
|
@@ -237,9 +250,12 @@ your own machine — it derives every path from its own location and re-checks t
|
|
|
237
250
|
| Output looks reordered | stdout and stderr are merged by type, not by time. Merge them yourself with `2>&1`. |
|
|
238
251
|
| `exit N` code is not reported | Runspace semantics — use `Write-Output` or read `$LASTEXITCODE`. |
|
|
239
252
|
| A child process keeps a file locked after the call | The session holds the `Process` object. Wait for `idleMs`, or force a rebuild with a timeout. |
|
|
240
|
-
| `Program 'node.exe' failed to run: Access is denied.` (or `拒绝访问`) |
|
|
253
|
+
| `Program 'node.exe' failed to run: Access is denied.` (or `拒绝访问`) | The current Windows restricted token cannot create some child pipes, so `& exe` can fail. For output and an exit code, use `[Diagnostics.Process]::Start` on `cmd.exe /c "<exe> <args> > out.txt 2> err.txt"` with `UseShellExecute=$false`; or `Start-Process -NoNewWindow -PassThru -Wait -RedirectStandardOutput/-RedirectStandardError <file>` (the exit code may be empty). Avoid a new console; directly inherited output goes to `NUL`. |
|
|
241
254
|
|
|
242
|
-
## Known
|
|
255
|
+
## Known limitations
|
|
256
|
+
|
|
257
|
+
The [audit and local fix record](docs/pwsh-run-audit.zh-CN.md) covers the nine issue groups.
|
|
258
|
+
The host TLS failure has a separate [Schannel investigation](docs/dsh-windows-schannel.zh-CN.md).
|
|
243
259
|
|
|
244
260
|
The full list — session semantics, detached child std handles, UAC differences, and what an update does to a
|
|
245
261
|
session — lives in **[docs/runtime-notes.md](docs/runtime-notes.md)**. The short version:
|
|
@@ -259,6 +275,8 @@ plugin/
|
|
|
259
275
|
session-server.ps1 the PowerShell side: runspace, stream tagging, sentinels
|
|
260
276
|
test/
|
|
261
277
|
support.mjs locates a DSH install; converts Windows paths to /mnt form
|
|
278
|
+
protocoltest.mjs frame limits, UTF-8 budgets, startup cleanup
|
|
279
|
+
lifecycletest.mjs policy changes, session pool, cancellation
|
|
262
280
|
itest.mjs protocol and session core
|
|
263
281
|
plugintest.mjs the whole plugin path against a mock context
|
|
264
282
|
e2e-real-subprocess.mjs real DSH subprocess runtime
|
|
@@ -278,17 +296,19 @@ FINDINGS.md every measurement and the reasoning behind the design
|
|
|
278
296
|
|
|
279
297
|
```bash
|
|
280
298
|
cd <checkout>
|
|
281
|
-
|
|
299
|
+
node test/protocoltest.mjs # 6: frame limits, UTF-8 budgets, startup cleanup
|
|
300
|
+
node test/lifecycletest.mjs # 16: policy, pool, concurrency, cancellation, unload
|
|
301
|
+
PWSH_BIN='pwsh' node test/itest.mjs # 30: protocol and session core
|
|
282
302
|
PWSH_BIN='pwsh' node test/plugintest.mjs # 15: full plugin path
|
|
283
303
|
node test/e2e-real-subprocess.mjs # 8: real DSH subprocess
|
|
284
|
-
node test/sandboxtest.mjs #
|
|
285
|
-
node test/bashtest.mjs #
|
|
304
|
+
node test/sandboxtest.mjs # 12: real sandbox, downgrade, root switch, first concurrency
|
|
305
|
+
node test/bashtest.mjs # 31: custom-bash fence triage, bash fallback, exec-bit repair
|
|
286
306
|
node test/bash-e2e.mjs # 11: custom-bash end to end
|
|
287
307
|
node test/denialtest.mjs # 11: sandbox-denial hint (no spawn, runs anywhere)
|
|
288
308
|
node test/metatest.mjs # 23: plugin-page metadata (no spawn, runs anywhere)
|
|
289
309
|
```
|
|
290
310
|
|
|
291
|
-
|
|
311
|
+
Ten scripts, **163 assertions**, none of which needs DSH to be running — just node 24, PowerShell 7 and a
|
|
292
312
|
DSH install to borrow `node_modules` from. Tests that need a DSH runtime locate one automatically
|
|
293
313
|
(`DSH_MODULES`, `DSH_MODULES_ASAR`, or by walking up from the working directory) and print a `SKIP` hint
|
|
294
314
|
instead of failing when none is found.
|
|
@@ -320,6 +340,19 @@ new content). With no tag on HEAD it prints the exact name to create.
|
|
|
320
340
|
Use the Windows-side npm. In WSL, `/usr/local/bin/npm` resolves to a bun symlink and `npm version` fails
|
|
321
341
|
with `Script not found "version"`.
|
|
322
342
|
|
|
343
|
+
## Related projects
|
|
344
|
+
|
|
345
|
+
More DeepSeek Harness (DSH) projects from [@citydirector](https://github.com/citydirector):
|
|
346
|
+
|
|
347
|
+
| Project | Kind | What it does |
|
|
348
|
+
|---|---|---|
|
|
349
|
+
| [DSH-build](https://github.com/citydirector/DSH-build) | Build | Portable and desktop Windows builds of DSH, patched, smoke-tested and released daily |
|
|
350
|
+
| [dsh-remote-access-proxy](https://github.com/citydirector/dsh-remote-access-proxy) | Plugin | Use the DSH Web GUI from your phone, tablet, or another computer |
|
|
351
|
+
| [deepinfra-fusion-proxy](https://github.com/citydirector/deepinfra-fusion-proxy) | Plugin | DeepInfra Standard / Flex switching with automatic fallback, per session |
|
|
352
|
+
| [dsh-loop-rescue](https://github.com/citydirector/dsh-loop-rescue) | Plugin | Cut model repetition loops mid-stream, prune them from the log, and resume |
|
|
353
|
+
| [ui-locate-mcp](https://github.com/citydirector/ui-locate-mcp) | MCP plugin | Pixel-exact coordinates for on-screen text and icons, for desktop automation |
|
|
354
|
+
| [dsh-session-manager](https://github.com/citydirector/dsh-session-manager) | Offline tool | Upgrade, archive, trash, loop cleanup and repair for DSH sessions |
|
|
355
|
+
|
|
323
356
|
## License
|
|
324
357
|
|
|
325
|
-
MIT
|
|
358
|
+
[MIT](LICENSE)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) · **简体中文**
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-pwsh-plugin) [](LICENSE) [](https://github.com/topics/dsh-plugin)
|
|
6
|
+
|
|
5
7
|
给 DeepSeek Harness 的 `pwsh_run` 工具:PowerShell 脚本正文**经 stdin** 送进一个常驻 pwsh 7 进程 ——
|
|
6
8
|
路上不做任何转义、没有 32K 命令行上限、~400ms 冷启动只付一次,变量 / 当前目录 / 已加载模块跨调用保留。
|
|
7
9
|
|
|
@@ -110,12 +112,12 @@ $env:DSH_HOME = '<DSH_HOME>' # 桌面 App 自己用的那个数据根
|
|
|
110
112
|
|---|---|---|
|
|
111
113
|
| `script` | 是 | PowerShell 脚本正文。**原样**送达 —— 不要转义 `$`,不要包 `pwsh -c`,不要加外层引号。 |
|
|
112
114
|
| `workdir` | 否 | 先 `Set-Location` 过去。会话有状态,后续调用会继承。路径无效则本次脚本不执行。 |
|
|
113
|
-
| `timeoutMs` | 否 |
|
|
115
|
+
| `timeoutMs` | 否 | 实际执行的超时,默认 120000;不含排队等待。必须为 1–2147478647 的整数毫秒。 |
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
输出使用 PowerShell 标准格式化(宽度 200),对象按表格或列表显示;需要结构化字段时显式 `ConvertTo-Json`。
|
|
118
|
+
Warning、Verbose、Information、Debug 分别带类型前缀,错误和最内层异常排在普通输出之后。各流按类型分组,
|
|
119
|
+
不保留跨流时序;需要合并时在脚本中使用 `2>&1` 或 `*>&1`。正文保持顶层执行,支持 `using`、`param`;
|
|
120
|
+
`#requires` 会在正文前核验,暂不支持的 `-ShellId` / `-Assembly` 会明确拒绝。
|
|
119
121
|
|
|
120
122
|
## 配置
|
|
121
123
|
|
|
@@ -124,8 +126,8 @@ $env:DSH_HOME = '<DSH_HOME>' # 桌面 App 自己用的那个数据根
|
|
|
124
126
|
| `pwshPath` | `pwsh` | 可执行文件;默认经 `PATH` 解析。 |
|
|
125
127
|
| `timeoutMs` | `120000` | 单次调用默认超时。 |
|
|
126
128
|
| `idleMs` | `1800000` | 空闲会话回收(30 分钟)。 |
|
|
127
|
-
| `maxSessions` | `4` |
|
|
128
|
-
| `maxOutputBytes` | `8388608` |
|
|
129
|
+
| `maxSessions` | `4` | 每个插件实例的会话上限,以 DSH session ID 隔离;全部忙时拒绝新会话并提示重试。 |
|
|
130
|
+
| `maxOutputBytes` | `8388608` | 每次调用普通输出正文的 UTF-8 字节预算(含记录换行);错误另留 65536 字节。协议与显示标签不计入,截断会明确提示。 |
|
|
129
131
|
| `allowUnconfined` | `false` | `true` = 沙箱策略服务缺席时也起会话。 |
|
|
130
132
|
|
|
131
133
|
`allowUnconfined` 是唯一一条「明知故犯」的开关。正常部署下 `ctx.sandboxPolicy` 一定在;缺席说明组合
|
|
@@ -140,8 +142,10 @@ $env:DSH_HOME = '<DSH_HOME>' # 桌面 App 自己用的那个数据根
|
|
|
140
142
|
`WRITE_RESTRICTED` 受限令牌,而令牌是**进程级**的 —— 所以 confine 一次之后,该会话的每一次读 / 写都
|
|
141
143
|
受同一个 workspace 边界约束,与内置 `pwsh` 工具等价。
|
|
142
144
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
每次调用先按**当前真实 session** 重新解析策略(`session` 必须传进 `sandboxPolicy.resolve`),并核对
|
|
146
|
+
有效策略、工作区和沙箱后端。任一项变化就销毁旧进程,再按新边界创建;切到只读时拒绝正文。
|
|
147
|
+
因此权限或工作区切换也会清空常驻状态。工作目录服务出错时直接报错,不回退到旧目录。
|
|
148
|
+
同一 ID 的创建与执行共用队列;只淘汰没有执行、排队或启动请求的空闲会话。
|
|
145
149
|
|
|
146
150
|
一个值得记住的坑:早先传的是 ToolExecution(`agent` 挂在 `exec.agent` 上),`resolve` 拿不到
|
|
147
151
|
session,**静默退回部署默认值**(mode + 配置里的 `workspaceRoot`),边界于是跑到 profile 目录去了 ——
|
|
@@ -191,6 +195,11 @@ $env:DSH_HOME = '<DSH_HOME>'
|
|
|
191
195
|
不需要手动装:二进制随包发布,探测候选的最后一个就是它,所以一次 `plugin add` 就够。由于发布包在
|
|
192
196
|
Windows 上打,那份文件到手时是 `0644` —— 探测发现它属于当前用户却不可执行时,会自己补上执行位再试。
|
|
193
197
|
|
|
198
|
+
同一行还会注入 `SSH_AUTH_SOCK`。`bash -s` 是非交互的,`~/.bashrc` 末尾那句 `eval $(wsl2-ssh-agent)`
|
|
199
|
+
永远执行不到,变量因此是空的 —— 围栏不是原因,显式给出的 socket 在围栏内一样能用。现在每条命令前
|
|
200
|
+
都带一段三行探测:`$HOME/.ssh/wsl2-ssh-agent.sock` 是真 socket 且当前没设过值时导出它;
|
|
201
|
+
`sshAgentSocket: false` 关掉。agent 守护进程本身必须在**围栏外**(它要 `/init`),通常做成 systemd 用户单元。
|
|
202
|
+
|
|
194
203
|
完整说明、`-r` 档语义表、54 项验收矩阵与接入方式:**[docs/llrun.md](docs/llrun.md)**。想在自己机器上
|
|
195
204
|
复验就 `bash wsl/acceptance.sh`(要在未被 `custom-bash` 包裹的 WSL 里跑:套第二层围栏会让四项断言
|
|
196
205
|
因错误的原因变红)—— 它的每个路径都从自身位置推导,并在结尾复核磁盘有没有泄漏。
|
|
@@ -209,10 +218,13 @@ Windows 上打,那份文件到手时是 `0644` —— 探测发现它属于当
|
|
|
209
218
|
| 输出顺序看着不对 | stdout 与 stderr 按类型合并,不按时间。自己用 `2>&1` 合。 |
|
|
210
219
|
| `exit N` 的退出码拿不到 | runspace 语义 —— 用 `Write-Output` 或读 `$LASTEXITCODE`。 |
|
|
211
220
|
| 调用结束后子进程还占着文件 | 会话持有 `Process` 对象。等 `idleMs`,或者用一次超时强制重建。 |
|
|
212
|
-
| `Program 'node.exe' failed to run: 拒绝访问。` |
|
|
221
|
+
| `Program 'node.exe' failed to run: 拒绝访问。` | 当前 Windows 受限令牌无法创建部分子进程管道,`& exe` 可能失败。需要输出和退出码时,用 `[Diagnostics.Process]::Start` 执行 `cmd.exe /c "<exe> <参数> > out.txt 2> err.txt"` 且 `UseShellExecute=$false`;或 `Start-Process -NoNewWindow -PassThru -Wait -RedirectStandardOutput/-RedirectStandardError <文件>`(退出码可能为空)。不要新建控制台;直接继承的输出会送往 `NUL`。 |
|
|
213
222
|
|
|
214
223
|
## 已知边界
|
|
215
224
|
|
|
225
|
+
九组问题及修复验收见[审计报告](docs/pwsh-run-audit.zh-CN.md);宿主 HTTPS 问题单独记在
|
|
226
|
+
[Schannel 兼容性记录](docs/dsh-windows-schannel.zh-CN.md)。
|
|
227
|
+
|
|
216
228
|
完整清单(会话语义、已断开的子进程 std handle、UAC 差异、升级对会话的影响)见
|
|
217
229
|
**[docs/runtime-notes.zh-CN.md](docs/runtime-notes.zh-CN.md)**。要点:
|
|
218
230
|
|
|
@@ -231,6 +243,8 @@ plugin/
|
|
|
231
243
|
session-server.ps1 PowerShell 侧:runspace、流打标、哨兵
|
|
232
244
|
test/
|
|
233
245
|
support.mjs 定位 DSH 安装;Windows 路径转 /mnt 形式
|
|
246
|
+
protocoltest.mjs 分帧、UTF-8 预算、启动清理
|
|
247
|
+
lifecycletest.mjs 权限切换、会话池与取消
|
|
234
248
|
itest.mjs 协议与会话本体
|
|
235
249
|
plugintest.mjs 插件完整路径(mock 上下文)
|
|
236
250
|
e2e-real-subprocess.mjs 真 DSH subprocess 运行时
|
|
@@ -250,17 +264,19 @@ FINDINGS.md 全部实测数据与设计理由
|
|
|
250
264
|
|
|
251
265
|
```bash
|
|
252
266
|
cd <检出目录>
|
|
253
|
-
|
|
267
|
+
node test/protocoltest.mjs # 6:帧边界、字节预算、启动清理
|
|
268
|
+
node test/lifecycletest.mjs # 16:权限切换、池、并发、取消、卸载
|
|
269
|
+
PWSH_BIN='pwsh' node test/itest.mjs # 30:协议与会话本体
|
|
254
270
|
PWSH_BIN='pwsh' node test/plugintest.mjs # 15:插件完整路径
|
|
255
271
|
node test/e2e-real-subprocess.mjs # 8:真 DSH subprocess
|
|
256
|
-
node test/sandboxtest.mjs #
|
|
257
|
-
node test/bashtest.mjs #
|
|
272
|
+
node test/sandboxtest.mjs # 12:真沙箱边界、降权、切根、首次并发
|
|
273
|
+
node test/bashtest.mjs # 31:custom-bash 围栏分诊、bash 后备、执行位自修复、SSH agent
|
|
258
274
|
node test/bash-e2e.mjs # 11:custom-bash 端到端
|
|
259
275
|
node test/denialtest.mjs # 11:沙箱拒绝提示(不起子进程,哪儿都能跑)
|
|
260
276
|
node test/metatest.mjs # 23:插件页元数据(不起子进程,哪儿都能跑)
|
|
261
277
|
```
|
|
262
278
|
|
|
263
|
-
|
|
279
|
+
十份脚本合计 **163 项**,都不需要 DSH 在跑 —— 只要 node 24、PowerShell 7,以及一个能借
|
|
264
280
|
`node_modules` 的 DSH 安装。需要 DSH 运行时的测试会自动找(`DSH_MODULES`、`DSH_MODULES_ASAR`,
|
|
265
281
|
或从工作目录逐级向上),找不到时打一行 `SKIP` 提示而不是失败。
|
|
266
282
|
|
|
@@ -288,6 +304,19 @@ HEAD 上还没有 tag 时,它把该打的标签名打印出来。
|
|
|
288
304
|
npm 要用 Windows 侧那份。WSL 里 `/usr/local/bin/npm` 指向 bun 的软链,`npm version` 会报
|
|
289
305
|
`Script not found "version"`。
|
|
290
306
|
|
|
291
|
-
##
|
|
307
|
+
## 相关项目
|
|
308
|
+
|
|
309
|
+
[@citydirector](https://github.com/citydirector) 的其它 DeepSeek Harness(DSH)项目:
|
|
310
|
+
|
|
311
|
+
| 项目 | 类型 | 用途 |
|
|
312
|
+
|---|---|---|
|
|
313
|
+
| [DSH-build](https://github.com/citydirector/DSH-build) | 构建 | DSH 的 Windows 便携版 / 桌面版绿色构建,每天跟随上游打补丁、冒烟、发布 |
|
|
314
|
+
| [dsh-remote-access-proxy](https://github.com/citydirector/dsh-remote-access-proxy) | 插件 | 在手机、平板或其它电脑上用浏览器访问 DSH Web GUI |
|
|
315
|
+
| [deepinfra-fusion-proxy](https://github.com/citydirector/deepinfra-fusion-proxy) | 插件 | DeepInfra Standard / Flex 按会话切换与自动回退 |
|
|
316
|
+
| [dsh-loop-rescue](https://github.com/citydirector/dsh-loop-rescue) | 插件 | 模型复读时从流内切断、剪掉循环并自动续跑 |
|
|
317
|
+
| [ui-locate-mcp](https://github.com/citydirector/ui-locate-mcp) | MCP 插件 | 按界面文字和图标给出精确像素坐标,用于桌面操作 |
|
|
318
|
+
| [dsh-session-manager](https://github.com/citydirector/dsh-session-manager) | 离线工具 | 离线升级、归档、回收站、清洗循环、修复烂尾 / 隐藏会话 |
|
|
319
|
+
|
|
320
|
+
## 许可证
|
|
292
321
|
|
|
293
|
-
MIT
|
|
322
|
+
[MIT](LICENSE)
|
package/cordis.patch.yml
CHANGED
|
@@ -25,3 +25,7 @@
|
|
|
25
25
|
disabled: !!js process.platform !== 'win32'
|
|
26
26
|
config:
|
|
27
27
|
timeoutMs: 300000
|
|
28
|
+
# 非交互 shell 不读 ~/.bashrc,用户终端里能用的 SSH_AUTH_SOCK 在这里是空的。
|
|
29
|
+
# 这段探测让 bash 工具也能用 Windows 侧的 ssh-agent(socket 由 wsl2-ssh-agent 提供,
|
|
30
|
+
# 守护进程要另行保活,例如一个 systemd 用户单元)。设 false 关掉。
|
|
31
|
+
sshAgentSocket: $HOME/.ssh/wsl2-ssh-agent.sock
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# DSH Windows 沙箱与 Schannel 兼容性记录
|
|
2
|
+
|
|
3
|
+
记录日期:2026-11-12。来源为本机隔离实验及上游代码追溯;与本插件九组修复分开维护。
|
|
4
|
+
|
|
5
|
+
## 补充核实:curl 联网与 Windows PATH(2026-11-11)
|
|
6
|
+
|
|
7
|
+
本段核实 DSH 沙箱里的 curl 联网与 Windows PATH 是否符合既定语义。本段所有网络请求均为公开站点的只读请求,未修改网络、凭据、PATH 或沙箱配置。
|
|
8
|
+
|
|
9
|
+
### 结论
|
|
10
|
+
|
|
11
|
+
1. **不是全面断网。** 当前 workspace-write 的 Windows 沙箱可以建立 TCP,也能使用 HTTP;HTTPS 故障发生在系统 curl / Invoke-WebRequest 使用的 Windows Schannel 凭据获取阶段。
|
|
12
|
+
2. **这是当前沙箱令牌与 Schannel 的兼容性问题,不是设计上禁止联网。** 已安装的 `@deepseek-ai/dsh-sandbox-windows-acl` 0.2.2-rc.1 的中文边界说明(第 127 行)明确写“写入与删除受限;读取、网络与进程可见性不受限”。
|
|
13
|
+
3. **当前 Windows PATH 正常。** 旧的 Machine PATH 整段丢失没有复现。
|
|
14
|
+
4. **WSL 围栏禁止执行 Windows exe 是预期限制。** 借 cmd.exe / pwsh.exe 从 WSL 查询 Windows 环境会被拦,与 Windows 侧读取 PATH 的问题不同。
|
|
15
|
+
|
|
16
|
+
### 本轮对照矩阵
|
|
17
|
+
|
|
18
|
+
| 环境 / 工具 | 请求 | 结果 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| workspace-write,pwsh_run,系统 curl 8.22.0 / Schannel | http://example.com/ | HTTP 200,exit 0 |
|
|
21
|
+
| 同上 | https://example.com/ | TCP 约 0.048s 建立;TLS 未建立,HTTP 000,exit 35 |
|
|
22
|
+
| 同上 | https://registry.npmjs.org/ | TCP 约 0.053s 建立;TLS 未建立,HTTP 000,exit 35 |
|
|
23
|
+
| workspace-write,原生 pwsh,系统 curl | https://example.com/ | 同样 exit 35,排除本插件独有故障 |
|
|
24
|
+
| workspace-write,pwsh_run,Invoke-WebRequest | https://example.com/ | 最内层 Win32Exception:安全包中没有可用的凭证 |
|
|
25
|
+
| 正常令牌,原生 pwsh,**同一系统 curl** | https://example.com/ | HTTP 200,exit 0 |
|
|
26
|
+
| 正常令牌,Invoke-WebRequest | https://example.com/ | HTTP 200 |
|
|
27
|
+
| workspace-write,Windows Node.js 的 https 模块 | https://example.com/ | HTTP 200,exit 0,未关闭证书校验 |
|
|
28
|
+
| 当前 WSL 围栏内,Linux curl 8.14.1 / OpenSSL 3.5.6 | https://example.com/ | HTTP 200,exit 0 |
|
|
29
|
+
|
|
30
|
+
Windows curl 的原始错误:
|
|
31
|
+
|
|
32
|
+
> curl: (35) schannel: AcquireCredentialsHandle failed: SEC_E_NO_CREDENTIALS (0x8009030e)
|
|
33
|
+
|
|
34
|
+
受限进程的完整性 SID 为 S-1-16-4096(Low),正常对照为 S-1-16-12288(High)。这个对照确认问题随当前 Windows 沙箱令牌出现;本轮没有进一步分离 Low 完整性和 WRITE_RESTRICTED 各自的贡献,也没有证明所有 Windows 主机必然如此。此前记忆中有类似结论,本段以本轮本机实测为依据。
|
|
35
|
+
|
|
36
|
+
### PATH 核对
|
|
37
|
+
|
|
38
|
+
- 当前 PowerShell 7.6.8。Process PATH 长 1087,Machine PATH 长 471,User PATH 长 588;三者可读取。
|
|
39
|
+
- 逐项展开 Machine PATH 中的变量,再与进程 PATH 忽略大小写和尾斜杠比对:**缺失项 0**,没有未展开的变量引用。
|
|
40
|
+
- System32 存在;Get-Command 能解析 curl.exe、cmd.exe、wsl.exe、git.exe。curl 落在 Windows System32,后端是 Schannel。
|
|
41
|
+
- WSL 自己的 PATH 也含 System32,但执行 Windows cmd.exe 返回 126 / Permission denied。这是 llrun 阻止 Windows interop 的既定行为,不能据此判定 PATH 缺失。
|
|
42
|
+
|
|
43
|
+
### 目前可用的路径与范围
|
|
44
|
+
|
|
45
|
+
Windows 沙箱内需要 HTTPS 时,Windows Node.js 的 https 已验证可用;纯 Linux 网络操作可使用 WSL curl;读取网页也可以用宿主提供的抓取工具。Windows 路径与文件操作仍走 Windows 工具。
|
|
46
|
+
|
|
47
|
+
如果程序必须依赖 Schannel,应作为 Windows 沙箱后端的兼容性问题单独处理,不能通过修改 PATH 解决,也不应默认放开全部文件权限来掩盖。只读模式的独立进程、本机其它 curl 构建、UAC 标准用户和其它主机未在本段补测;不把这张矩阵扩大成所有环境的保证。
|
|
48
|
+
|
|
49
|
+
## 归因追溯:Schannel 故障由谁引入(2026-11-12)
|
|
50
|
+
|
|
51
|
+
本段追溯预期外 HTTPS 故障的引入层与来源提交。本段只做归因、记录与一次性对照,不修改生产沙箱或插件实现。
|
|
52
|
+
|
|
53
|
+
### 归属结论
|
|
54
|
+
|
|
55
|
+
**故障触发点在 DSH 上游 Windows ACL 后端的受限令牌构造。** 当前系统 curl 使用 Schannel,在带 restricting SIDs 的 WRITE_RESTRICTED 令牌下无法取得出站 TLS 凭据。这是该安全机制在本机暴露的兼容性缺陷;不能解释成产品有意禁止联网。
|
|
56
|
+
|
|
57
|
+
- pwsh_run 只是委托宿主:plugin/index.mjs:204 解析策略,:226 调用 sandbox.confine(plain, policy, signal),:230 使用返回的 confined.argv 启动。它没有实现 CreateRestrictedToken。
|
|
58
|
+
- 原生 pwsh 在同一策略下同样失败,排除插件独有的会话协议或编码处理。
|
|
59
|
+
- 当前桌面归档的 dsh/node_modules/@deepseek-ai/dsh-sandbox-windows-acl/lib/types-Cl07j3lG.js 中,createRestrictedToken 函数与本轮测试使用的便携包逐字相同;两者包版本均为 0.2.2-rc.1,桌面 VERSION 为 fcc0ad462172522aaf5c93a7b2e01f6b9f0b0159。
|
|
60
|
+
- 本地 DSH-build 的 portable/patch-windows-acl-runner-console.mjs 只调整 ffi.ts / runner.ts 的控制台处理,没有改 token.ts。本轮直接调用 AclSandbox 的进程启动接口也能复现,未经过本插件的 PowerShell 服务端。
|
|
61
|
+
|
|
62
|
+
### 隔离变量,而非只比较高低完整性
|
|
63
|
+
|
|
64
|
+
同一台 Windows、同一个 System32 curl.exe、同一个 https://example.com/,均为公开页面只读请求。探针父进程经审批使用正常令牌;仅在该一次性进程中调整 Win32 API 的实参,测试后恢复。所有 AclSandbox 实例均设 manageDacls:false,未改变磁盘 ACL、注册表或生产配置。
|
|
65
|
+
|
|
66
|
+
| 对照令牌 | 完整性 | CreateRestrictedToken flags / SID 数 | curl 结果 |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| 正常父进程 | High | 不创建受限令牌 | HTTP 200 / exit 0 |
|
|
69
|
+
| 仅降完整性,不加 restricting SID | Low | 0 / 0 | HTTP 200 / exit 0 |
|
|
70
|
+
| 仅裁剪特权,不加 restricting SID | High | DISABLE_MAX_PRIVILEGE = 1 / 0 | HTTP 200 / exit 0 |
|
|
71
|
+
| 写限制与 restricting SID,不裁剪特权 | High | WRITE_RESTRICTED = 8 / 2 | HTTP 000 / exit 35 |
|
|
72
|
+
| 当前正式构造,仅保留 High | High | 9 / 2 | HTTP 000 / exit 35 |
|
|
73
|
+
| 当前正式构造 | Low | 9 / 2 | HTTP 000 / exit 35 |
|
|
74
|
+
| 恢复 Authenticated Users | High | 9 / 3 | HTTP 000 / exit 35 |
|
|
75
|
+
| 重建初版广 SID 列表和 flags | High | 13 / 6 | HTTP 000 / exit 35 |
|
|
76
|
+
|
|
77
|
+
flags 9 = DISABLE_MAX_PRIVILEGE | WRITE_RESTRICTED;flags 13 另含历史 LUA_TOKEN。当前 read-only 的两个 SID 是 logon SID、Everyone;初版广列表另含 Authenticated Users、INTERACTIVE、LOCAL 与一个写能力 SID。
|
|
78
|
+
|
|
79
|
+
所有 exit 35 的有效对照均返回:
|
|
80
|
+
|
|
81
|
+
> curl: (35) schannel: AcquireCredentialsHandle failed: SEC_E_NO_CREDENTIALS (0x8009030e)
|
|
82
|
+
|
|
83
|
+
由此排除“只要 Low 就不能用 Schannel”和“仅因裁剪特权而失败”;恢复 Authenticated Users 也不能修复。这里定位到的是**写限制令牌与 restricting SID 的组合**,没有声称限制 SID 中某一个成员是单独根因,也没有定位 Windows 内部哪个访问检查返回拒绝。
|
|
84
|
+
|
|
85
|
+
额外尝试进一步拆开该组合时,WRITE_RESTRICTED / 零限制 SID 的进程退出 3221225794(0xC0000142),flags 0 / 两个限制 SID 的进程退出 3221225781(0xC0000135)。这两组连 whoami / curl 都未正常启动,**属于无效 TLS 对照,不计为网络失败证据**;本轮不继续扩大这些变体。随后查询 System / Application Popup,最近 12 分钟没有新事件。
|
|
86
|
+
|
|
87
|
+
### 来源提交链
|
|
88
|
+
|
|
89
|
+
以下日期均为 UTC;作者为 Git 提交记录中的 author,不把提交元数据解释成人为故障责任。
|
|
90
|
+
|
|
91
|
+
| 上游提交 | 日期 | 内容及本轮判断 |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| [f64ba40](https://github.com/deepseek-ai/deepseek-harness/commit/f64ba40f43961fc65b7e1724132708a0b6d163cf) | 2026-08-07 17:24:33 | Huanqi Cao,feat(sandbox): Windows ACL write-restriction sandbox (restricted-token runner)。首次加入该后端及受限令牌构造;本机重建初版参数也复现同一 TLS 错误,是可追溯的机制来源。 |
|
|
94
|
+
| [441927c](https://github.com/deepseek-ai/deepseek-harness/commit/441927c5268e1abd2be024d3b5bb69e3869b6f06) | 2026-08-08 | 为封堵 C 盘根的越界写入而移除 Authenticated Users;本轮恢复该 SID 仍失败,不能把本次故障归给这一处收紧。 |
|
|
95
|
+
| [d5ad3ba](https://github.com/deepseek-ai/deepseek-harness/commit/d5ad3baeb5d531e31da5e586c3ec3ebedd825169) | 2026-09-18 | 增加 Low 完整性及目录标签,堵 FILE_DELETE_CHILD 删除旁路;仅 Low 的 TLS 对照成功,不能把它当作本次故障引入点。 |
|
|
96
|
+
| [bdd75ca](https://github.com/deepseek-ai/deepseek-harness/commit/bdd75ca1a0ce122a4801fa0e24d26f19159c071b) | 2026-10-26 | Huo Yaoyuan,去掉 LUA_TOKEN 以支持内置 Administrator / WDAGUtilityAccount;带/不带 LUA 的已测组合均失败,不是该修补造成。 |
|
|
97
|
+
|
|
98
|
+
首次提交的父提交为 4c2d20dbf7709ce88da5f01b163c8daaa1f087aa。初版 token.ts 的关键调用是 DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED,列表为 world / logonSid / authUser / interactive / local / writeSid。当前桌面编译件的调用为 flags 9,read-only 列表为 logonSid / world,workspace-write 另加写能力 SID。
|
|
99
|
+
|
|
100
|
+
这是一条**代码来源 + 本机参数复现**的归因链;未安装并运行全部历史 DSH 版本,不能宣称已证明每个旧版本在当时 Windows 系统上的表现。当前版本上游 runner.spec.ts 中未检出 curl / Schannel / HTTPS / Invoke-WebRequest 用例;这只能说明该测试文件未覆盖,不能扩大成全仓所有测试都没有。
|
|
101
|
+
|
|
102
|
+
### 记录更正与后续方向
|
|
103
|
+
|
|
104
|
+
此前记忆 dsh-sandbox-lowil-schannel-no-credentials 将完整沙箱与 High 的差异直接归因于 Low,证据不成立。本轮仅 Low 成功、High + 写限制失败,已据此更正;前段“未分离变量”的观察保留为历史记录,以本段为进一步结论。
|
|
105
|
+
|
|
106
|
+
后续应在 DSH Windows ACL 后端增加 Schannel / curl / Invoke-WebRequest 回归,并研究保留文件写入与删除边界的兼容方案。不能直接撤 Low(会回开删除旁路,且本轮 High + 写限制仍然失败),也不能恢复宽 SID 列表(本轮无效,历史上还会回开越界写入)。Node.js HTTPS / WSL OpenSSL 的可用性已在前段实测,可作为沙箱内现有的网络路径。
|
|
107
|
+
|
|
108
|
+
本轮没有修改上游、应用归档、生产权限或系统网络配置,没有创建公开 issue / PR;归因实验只覆盖本机 Windows 构建,其他系统及 UAC 标准用户仍需独立验证。
|
|
109
|
+
|
|
110
|
+
## 新版上游复核(2026-11-12)
|
|
111
|
+
|
|
112
|
+
联网核对时,最新标签为 [dsh-v0.2.2-rc.2](https://github.com/deepseek-ai/deepseek-harness/tree/dsh-v0.2.2-rc.2),commit ddac96760b6edf8fa0fe17cbdcf0b512f4c12b2c;master 为 e8f7b93a344964de3dca1ace9e72b6e4d6de87cb,提交时间2026-11-11T02:49:46Z。发布标签与master的Windows ACL后端src树相同。
|
|
113
|
+
|
|
114
|
+
与本机fcc0ad462172522aaf5c93a7b2e01f6b9f0b0159相比,token.ts、runner.ts、spawn.ts、ffi.ts、acl.ts、grant.ts均为相同Git blob;tests树也完全相同。src仅index.ts新增导出isTempRootInsideWorkspace,path-boundary.ts把既有临时目录包含关系判断抽成该函数;没有改变Schannel、令牌、进程启动或凭据处理。token.ts仍为DISABLE_MAX_PRIVILEGE | WRITE_RESTRICTED加logon SID / Everyone / 写能力SID。
|
|
115
|
+
|
|
116
|
+
结论:截至该次核对,最新发布与master中没有针对此故障的修复,不能指望升级到rc.2恢复系统curl/IWR。此为源码与历史差异核对,没有安装新版运行;仍不泛化所有Windows主机。API查询dev分支失败,未对不存在或不可取的dev分支作结论。
|
|
117
|
+
|
|
118
|
+
上游已有相同现象报告:[讨论3207](https://github.com/deepseek-ai/deepseek-harness/discussions/3207)(2026-08-18)及[讨论4163](https://github.com/deepseek-ai/deepseek-harness/discussions/4163)(2026-08-23;2026-10-02回复建议OpenSSL)。帖子是旁证,结论以实际源码差异为主;不沿用帖子里未经隔离的特权归因或把pip/git一概判定为Schannel客户端。
|
package/docs/llrun.md
CHANGED
|
@@ -184,6 +184,7 @@ Configuration:
|
|
|
184
184
|
config:
|
|
185
185
|
llrunPath: '/mnt/c/Users/<you>/.local/bin/llrun' # omit to probe the candidates
|
|
186
186
|
fence: true # false = no fence (not recommended)
|
|
187
|
+
sshAgentSocket: '$HOME/.ssh/wsl2-ssh-agent.sock' # false = do not inject
|
|
187
188
|
```
|
|
188
189
|
|
|
189
190
|
`llrunPath` is only for overriding. Left out, the plugin probes `$LLRUN` / `$DSH_LLRUN`,
|
|
@@ -192,8 +193,27 @@ to `custom-bash.mjs` inside the package — first executable wins, and the resul
|
|
|
192
193
|
install works without a restart. Yours come first: a system-level `llrun` may be newer, the packaged one only
|
|
193
194
|
guarantees that the fence is there at all.
|
|
194
195
|
|
|
195
|
-
Tests: `node test/bashtest.mjs` (
|
|
196
|
+
Tests: `node test/bashtest.mjs` (31, mock runtime) + `node test/bash-e2e.mjs` (11, real runtime + real llrun).
|
|
196
197
|
Three of the 25 pin the executable-bit repair: a `0644` copy is made executable and the call succeeds, an
|
|
197
198
|
unusable copy is named in the error, and an unusable candidate earlier in the list does not shadow a usable
|
|
198
199
|
one later. They only assert the mode on POSIX hosts — NTFS reports every file as `0666`, so there the
|
|
199
200
|
assertion is that the call still works.
|
|
201
|
+
|
|
202
|
+
### The SSH agent socket
|
|
203
|
+
|
|
204
|
+
`bash -s` is a **non-interactive** shell: it never reads `~/.bashrc`, so the usual
|
|
205
|
+
`eval $(wsl2-ssh-agent)` line at the bottom of that file never runs and `SSH_AUTH_SOCK` stays empty. The
|
|
206
|
+
fence is not the cause — a socket given explicitly works inside it just as well as outside.
|
|
207
|
+
|
|
208
|
+
`custom-bash` therefore prepends a three-line probe to every command:
|
|
209
|
+
|
|
210
|
+
```sh
|
|
211
|
+
if [ -z "$SSH_AUTH_SOCK" ] && [ -S "$HOME/.ssh/wsl2-ssh-agent.sock" ]; then
|
|
212
|
+
export SSH_AUTH_SOCK="$HOME/.ssh/wsl2-ssh-agent.sock"
|
|
213
|
+
fi
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
It tests `-S` (a real socket, not merely an existing path) and never overwrites a value you set yourself —
|
|
217
|
+
so a script that exports its own socket still wins. `sshAgentSocket: false` turns the injection off; any
|
|
218
|
+
other string replaces the path. The agent itself is still yours to keep running: the fence blocks
|
|
219
|
+
`/init`, so `wsl2-ssh-agent` cannot start **inside** it and must be a user service outside.
|