@iasiv5/dsh-skip-browser-auth 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,193 +1,74 @@
1
1
  # @iasiv5/dsh-skip-browser-auth
2
2
 
3
- 个人自用的 DSH Web 插件:在兼容版本上替换官方 Connection 提供方,跳过 DSH 官方
4
- BrowserAuth 浏览器身份层,同时完整保留请求信任栅栏(request trust fence)。
5
- 不修改 DSH 本体,不创建任何新的监听服务或端口。
6
-
7
- ## 这是什么
8
-
9
- DSH 官方的 BrowserAuth 在进程启动 token 与浏览器之间换取 authority 绑定的签名
10
- cookie,保护全部 Host API 与 Remote WebSocket。本插件安装后在任意 DSH 版本上都能
11
- 被 `dsh plugin add` 接受;但只有当运行环境中
12
- `@deepseek-ai/dsh-client-connection` 的**精确版本为 `0.1.2-rc.1`** 时才激活:
13
-
14
- - 组合期(Loader 装配阶段)由 bundle patch(`cordis.patch.yml`)自门控:
15
- - 第一行把官方 `connection` 行的 `disabled` 绑定到版本探针(`!!js` 表达式,
16
- 从 pnpm store 路径段提取精确版本并与白名单比对);
17
- - 第二行插入本插件的 `trusted-connection` 行,其 `disabled` 为行绑定表达式的
18
- 否定(自身探针为真 + 同组官方行存在、名字匹配、确被禁用)。
19
- - 激活后由本插件提供 trusted-network(可信网络)等效 Connection:复用官方导出的
20
- `HostConnectionService`,以三方法 stub(trusted-network 语义)替代 BrowserAuth,
21
- `/api` 路由、请求体上限、image capacity 断言照抄官方 apply 语义。
22
- - 任一门控条件不成立(探针为假、行不存在、名字漂移、官方行未禁用)→ 插件保持
23
- dormant(休眠):官方行为分毫不变,插件不注册路由、不提供服务、不加载浏览器
24
- bundle。注意:在探针为假(非白名单版本)时,即使后续 patch 层把官方行强制设为
25
- `disabled: true`,行绑定内嵌的版本探针仍会阻止 Replacement 激活。
26
- - `apply()` 内另有 backstop(最后防线):读官方 manifest 比对白名单、校验行绑定
27
- 状态,任一不满足即 fail loud 拒绝运行;产品上下文中 loader / internal /
28
- 当前 entry 任一缺失(最后防线无法执行)同样 fail loud。
29
-
30
- 本插件包含或移植 DeepSeek Harness 的 MIT 许可代码,详见 LICENSE。
31
-
32
- ## 安全警告
33
-
34
- Replacement 激活时进程会输出以下固定警告(逐字):
3
+ [![npm](https://img.shields.io/npm/v/@iasiv5/dsh-skip-browser-auth?label=npm&color=cb3837)](https://www.npmjs.com/package/@iasiv5/dsh-skip-browser-auth)
4
+ [![DSH Web](https://img.shields.io/badge/DSH_Web-0.1.2--rc.1_verified-blue)](#兼容版本)
5
+ [![License](https://img.shields.io/github/license/iasiv5/dsh-skip-browser-auth?color=green)](./LICENSE)
35
6
 
36
- ```
37
- @iasiv5/dsh-skip-browser-auth: BrowserAuth has been skipped. DSH Web is using the trusted-network behavior; this plugin does not verify any upstream proxy.
38
- ```
7
+ 个人自用的 DSH Web 插件:在 **DSH 0.1.2-rc.1** 上自动跳过 BrowserAuth——打开 Web 地址即可直接使用,**不必每次把启动 URL 里那串随机 token 抄进浏览器**。
39
8
 
40
- trusted-network mode 的判据只有一个:**请求来源是否通过信任栅栏**
41
- (Host/Origin/Fetch Metadata/trustedHosts)。**网络可达即身份**——能通过栅栏的
42
- 请求一律被接受,不存在身份层,本插件也不校验任何上游反代。仅在与外界隔离、或由
43
- 可信网络层(如具备身份认证的反代)完全包住的网络中使用;`trustedHosts` 只声明
44
- 「这些主机名解析到的请求被接受」,不构成认证。
9
+ - **只在适配版本激活**:当前仅 `0.1.2-rc.1`。在 `0.1.1-rc.2` 及其它所有未测试版本上**自动休眠**,官方行为分毫不变。
10
+ - **不修改 DSH 本体与任何机制**:仅通过 DSH 官方插件 patch 机制替换 Connection 行;卸载或禁用插件并重启,即完全恢复官方行为。
11
+ - **提醒**:跳过认证后,Web 对本机/可信网络内的访问不再有身份层,请勿把 DSH 端口暴露给不可信网络。
45
12
 
46
- ## 安装与卸载
13
+ ## 安装
47
14
 
48
- ```sh
49
- # 从本地 checkout 安装(scope 是包名的一部分,不是目录路径)
50
- dsh plugin --profile web add /path/to/dsh-skip-browser-auth
15
+ ### 方式一:从插件市场一键安装(推荐)
16
+
17
+ 本插件已收录进我的 DSH 插件市场 **[dsh-m](https://github.com/iasiv5/dsh-m)**(收录 id:`dsh-skip-browser-auth`)。装好 dsh-m 后,在 Web 侧栏「插件市场」搜索 **Skip Browser Auth**,即可**一键安装 / 更新 / 卸载**;也可以直接对 agent 说:
51
18
 
52
- # 发布 npm 后,从 registry 安装
19
+ > 从 dsh-m 插件市场安装 dsh-skip-browser-auth,装完重启 DSH Web。
20
+
21
+ ### 方式二:直接命令安装
22
+
23
+ ```sh
53
24
  dsh plugin --profile web add @iasiv5/dsh-skip-browser-auth
25
+ sudo systemctl restart deepseek-harness.service # 按你的部署方式重启 DSH Web
26
+ ```
54
27
 
55
- sudo systemctl restart deepseek-harness.service
28
+ ### 复制给 agent 的安装提示词
29
+
30
+ 把下面整段贴给你机器上的 agent,它就能完成安装与验证:
31
+
32
+ ```text
33
+ 请给我的 DSH Web 安装 @iasiv5/dsh-skip-browser-auth 插件(DSH 0.1.2-rc.1 专用:
34
+ 自动跳过 BrowserAuth,免每次在启动 URL 后输入随机 token;其它版本会自动休眠,
35
+ 不影响任何行为)。
36
+ 1. 先执行 dsh --version 告诉我当前 DSH 版本,然后照常安装:
37
+ dsh plugin --profile web add @iasiv5/dsh-skip-browser-auth
38
+ 2. 重启 DSH Web 并轮询 http://127.0.0.1:3080 直到恢复 200
39
+ (systemd 系统级部署:sudo systemctl restart deepseek-harness.service;
40
+ user 级部署:systemctl --user restart dsh-web.service;端口以实际部署为准)。
41
+ 3. 若版本是 0.1.2-rc.1:确认 journal/日志中出现固定警告
42
+ "BrowserAuth has been skipped",且浏览器直接打开 Web 地址不再要求 token,
43
+ 即激活成功。
44
+ 4. 若版本不是 0.1.2-rc.1:插件会自动休眠(官方行为不变),直接告诉我即可,
45
+ 不要做任何强行处理。
46
+ ```
47
+
48
+ ## 卸载
56
49
 
57
- # 卸载(恢复官方行为;统一使用 manifest 包名)
50
+ ```sh
58
51
  dsh plugin --profile web remove @iasiv5/dsh-skip-browser-auth
59
52
  sudo systemctl restart deepseek-harness.service
60
53
  ```
61
54
 
62
- 没有 `skipBrowserAuth` 之类的配置项——始终 skip;卸载或禁用插件并重启即恢复官方
63
- 行为。激活状态由「行为 + 警告」共同证明(见下方升级清单),配置证据
64
- (dump-config 出现 `trusted-connection` 行)仅作安装证据。
55
+ 市场安装的用户在「插件市场 → 已装」里点卸载即可。卸载或禁用并重启后,官方 BrowserAuth 完全恢复。
65
56
 
66
- ## 版本白名单与升级策略
57
+ ## 兼容版本
67
58
 
68
- - 唯一白名单版本:字符串精确值 `'0.1.2-rc.1'`。禁止 `>=`、范围、`latest` 判断;
69
- 探测失败一律 fail-closed 到 dormant。
70
- - 升级后 `@deepseek-ai/dsh-client-connection` 版本不在精确白名单内时,本插件
71
- 自动回到 dormant(官方行为分毫不变),不影响使用;升级到白名单版本
72
- (`0.1.2-rc.1`)则会激活。
73
- - 新版本需要逐个测试验证后把精确版本加入白名单(`src/gate.ts` 的
74
- `GATE_VERSION` 与白名单判断、`src/index.ts` backstop 的白名单文案);
75
- 无法安全跟随时对插件做版本分叉。
59
+ | DSH 版本 | 状态 |
60
+ | --- | --- |
61
+ | `0.1.2-rc.1` | ✅ 激活:跳过 BrowserAuth(pnpm 与 npm 扁平安装布局均可识别) |
62
+ | `0.1.1-rc.2` 及其它未测试版本 | 😴 自动休眠:官方行为分毫不变 |
76
63
 
77
- ## 行为矩阵
64
+ 新版本经实测验证后逐个加入白名单;版本探测失败一律自动休眠(fail-closed)。
78
65
 
79
- | 组合 | 官方 `connection` 行 | 本插件 `trusted-connection` 行 | 行为 |
80
- | --- | --- | --- | --- |
81
- | rc.1 + 探针真 + 绑定完好 | `disabled: true`(被 patch 禁用,无 fiber) | `disabled: false`(活跃 fiber) | Replacement 激活:trusted-network 行为 + 固定警告 |
82
- | rc.1 + 探针假(如 9.9.9 段) | 活跃 | dormant | 官方 BrowserAuth 照常(401/cookie 换取) |
83
- | rc.2(0.1.1-rc.2,真实产物) | 活跃(rc.2 无 BrowserAuth) | dormant | 官方 rc.2 基线:`GET /` 200、`/api` 无认证层 |
84
- | 官方行名字漂移 | 活跃(patch 被 name-guard 跳过) | dormant | 官方行为,无冲突 |
85
- | 非白名单版本 + 后续 patch 层强制禁用官方行 | `disabled: true`(探针仍为假) | dormant(行绑定内嵌探针拦截) | 无 Connection 服务(无身份层也无替代) |
86
- | 白名单 rc.1 + 后续 patch 层强制禁用官方行 | `disabled: true`(与本插件自身禁用一致) | 激活(行绑定要求的正是官方行已禁用) | Replacement 照常激活 |
87
- | 路径段真、manifest 版本漂移 | 禁用 | 进入 apply 后被 backstop 拒绝 | fail loud:whitelist 诊断,`loader.await()` reject |
66
+ ## 机制与安全(简述)
88
67
 
89
- ## 测试说明
68
+ - 组合期版本探针采用**双锚点**(宿主 runtime 本体 + 官方 connection 包)与**双分支**版本确认(pnpm 路径段 / manifest 读取,兼容两种安装布局),精确匹配白名单才激活;任何解析失败、版本漂移、结构漂移都保持休眠。
69
+ - 激活后复用官方导出的 `HostConnectionService` 提供 trusted-network Connection:Host/Origin/trustedHosts 请求信任栅栏完整保留,仅移除身份层;激活时进程逐字输出固定警告 `@iasiv5/dsh-skip-browser-auth: BrowserAuth has been skipped. ...`。
70
+ - 运行前 backstop 复核 manifest 白名单与行绑定状态,任何不满足即拒绝运行。
90
71
 
91
- ```sh
92
- npm run test:all
93
- # 等价于:npm run test:rc2-fixture && npm run build && npm run typecheck && npm test
94
- ```
72
+ 详细设计、行为矩阵与实机验证记录见 [docs/DESIGN.md](./docs/DESIGN.md)。
95
73
 
96
- - `test:rc2-fixture` 需要 npm 网络访问:以别名安装 rc.2 dormant 组合所需产物。
97
- `--no-save` 会改动 `node_modules`(以及为满足 rc.2 peer 链所需的嵌套安装),
98
- 但不改 package manifest 与 lockfile(脚本内以 `git diff` 证明)。
99
- - 本机 npm 11.19 的 allow-scripts 策略与 project-scoped 安装存在三处适配
100
- (`--ignore-scripts`、`--force` 取代 `--legacy-peer-deps`、仅剥离
101
- `npm_config_allow_scripts` / `NPM_CONFIG_ALLOW_SCRIPTS` 两个确证冲突键),
102
- 详见 `scripts/install-rc2-fixture.mjs` 头注释。
103
- - 组合测试(`tests/composition/`)用真实 Loader + Include + 真实官方产物端到端
104
- 覆盖 active / gate-false / 真实 rc.2 dormant / gate 绑定负向四条路径;探针
105
- fixture 是真实文件(临时目录内构造 pnpm 风格路径段 + manifest)。
106
- - 测试使用 Node 内置 test runner(无 vitest)。
107
-
108
- ## 已知限制
109
-
110
- - npm 布局(`node_modules/@deepseek-ai/dsh-client-connection`,路径无版本段)下
111
- 探针无法确认版本 → 不激活(dormant)。pnpm 布局与真实部署(pnpm store 路径段)
112
- 可确认版本。
113
- - 组合测试的探针 fixture 是受控构造(真实文件但路径段版本可指定),真实布局的
114
- 版本提取由 Task 11 式实机验证与 Task 5 的 pnpm 真实路径断言兜底。
115
- - 完整浏览器组图 e2e(client-modules 组图、浏览器半区真实加载)未覆盖;
116
- 由 client bundle materialization 测试(单次注册、factory 可执行、external
117
- 全满足)与下方升级 active 清单兜底。
118
- - 激活要求 DSH 升级到 `0.1.2-rc.1` 后执行实机清单验证。
119
-
120
- ## 实机 dormant 前后对照记录
121
-
122
- 本机 DSH runtime 为 `0.1.1-rc.2`(2026-09-05 实测)。验证协议:安装前基线 →
123
- 安装 + 重启 → 行为逐项对照 → 卸载 + 重启 → 复验基线;journal 检查统一使用
124
- 安装前固化的时间锚;任何失败立即回滚(本次回滚未被触发)。
125
-
126
- | 检查项 | 安装前基线 | 安装后(dormant) | 卸载恢复后 |
127
- | --- | --- | --- | --- |
128
- | `GET /` | `200` `text/html; charset=utf-8` | `200` `text/html; charset=utf-8` | `200` `text/html; charset=utf-8` |
129
- | `POST /api/session.list`(未认证) | `200` `application/json` | `200` `application/json` | `200` `application/json` |
130
- | envelope `type` / `rpcId` / `resultOk` | `server-response` / `probe` / `true` | `server-response` / `probe` / `true` | `server-response` / `probe` / `true` |
131
- | 首页静态 shell marker(首个 `<script src=`) | 在 | 在 | 在 |
132
- | 首页认证文案 `dsh web authentication required` | 0 次出现 | 0 次出现 | 0 次出现 |
133
-
134
- 三组行为文件逐字节 diff 均为空;安装后与卸载后 journal(同一时间锚窗口内)
135
- 均无固定警告 `BrowserAuth has been skipped`、无 err 级日志。配置证据:安装后
136
- `dsh --profile web --dump-config` 出现 `trusted-connection` 行(仅作安装证据,
137
- 不作为未激活证明);卸载后该行消失、bundles 中无本插件。回滚未被触发。
138
-
139
- ### scoped rename 后重新验证(2026-09-05,`@iasiv5/dsh-skip-browser-auth@0.1.0`)
140
-
141
- 包名改为 scoped 后按同一协议重新执行(安装 `/home/ubuntu/workspace/dsh-skip-browser-auth`
142
- → 重启 → 对照 → `dsh plugin --profile web remove @iasiv5/dsh-skip-browser-auth`
143
- → 重启 → 复验):安装证据为 bundles 出现 `@iasiv5/dsh-skip-browser-auth`、
144
- dump-config 第 612 行 `trusted-connection` 行(行名 `@iasiv5/dsh-skip-browser-auth`);
145
- 三组行为文件逐字节 diff 均为空(`root 200 text/html`、`api 200 application/json`、
146
- `type=server-response` / `rpcId=probe` / `resultOk=true`、authCount=0);
147
- 安装后与卸载后 journal 均无固定警告、无 err 级日志;scoped 卸载无残留;
148
- 回滚未被触发。本节替代首次记录,作为 scoped 包身份的实机 dormant 结论。
149
-
150
- ## 升级到 0.1.2-rc.1 后的 active 验证清单
151
-
152
- DSH 升级到 `0.1.2-rc.1` 并安装本插件、重启后,依次执行:
153
-
154
- 1. 配置证据(仅安装证据,不证明激活):
155
-
156
- ```sh
157
- dsh --profile web --dump-config | grep -n -A2 'id: trusted-connection'
158
- # 预期:出现 trusted-connection 行
159
- ```
160
-
161
- 2. 行为验证(与固定警告共同证明激活):
162
-
163
- ```sh
164
- # GET / 免 token → 200(无 401、无认证文案)
165
- curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3080/
166
- # 预期:200
167
-
168
- # GET /?token=x → 303 location: /(token 清理)
169
- curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' 'http://127.0.0.1:3080/?token=x'
170
- # 预期:303 http://127.0.0.1:3080/
171
-
172
- # 无 cookie POST /api → 非 401/403(无认证层;无 api-gateway 挂载时 404)
173
- curl -s -o /dev/null -w '%{http_code}\n' -X POST \
174
- -H 'content-type: application/json' \
175
- -d '{"type":"client-request","rpcId":"probe","method":"session.list","payload":{}}' \
176
- http://127.0.0.1:3080/api/session.list
177
- # 预期:非 401/403
178
- ```
179
-
180
- 3. journal 固定警告:
181
-
182
- ```sh
183
- sudo journalctl -u deepseek-harness.service --since '<重启时间点>' \
184
- | grep -F 'BrowserAuth has been skipped'
185
- # 预期:出现固定警告文案
186
- ```
187
-
188
- 4. 回滚路径(任何时候行为异常):
189
-
190
- ```sh
191
- dsh plugin --profile web remove @iasiv5/dsh-skip-browser-auth
192
- sudo systemctl restart deepseek-harness.service
193
- ```
74
+ 本插件包含或移植 DeepSeek Harness 的 MIT 许可代码,详见 [LICENSE](./LICENSE)。
package/cordis.patch.yml CHANGED
@@ -1,13 +1,17 @@
1
1
  # 本文件由 scripts/write-patch.mjs 从 lib/gate.js 常量机械生成,请勿手改。
2
2
  # 自门控 bundle patch:第一行把官方 connection 行的 disabled 绑定到版本探针
3
- # (仅 @deepseek-ai/dsh-client-connection@0.1.2-rc.1 为真);第二行插入本插件的
4
- # trusted-connection 行,其 disabled 为行绑定表达式的否定(行绑定内嵌同一探针)。
5
- # 任何门控条件不成立(探针为假、行不存在、名字漂移、官方行未禁用),
6
- # Replacement 一律保持 dormant(fail-closed)。探针为假时,即使后续 patch 层
7
- # 把官方行强制 disabled,行绑定内嵌探针仍保持 dormant。
3
+ # (双锚点:宿主 runtime 本体 @deepseek-ai/dsh 与官方 connection 包的 pnpm
4
+ # 路径段版本必须同为 0.1.2-rc.1;runtime 锚点只可能来自 runtime fallback,
5
+ # 不会被插件自身依赖污染);第二行插入本插件的 trusted-connection 行,其
6
+ # disabled 为行绑定表达式的否定(行绑定内嵌同一探针)。
7
+ # 任何门控条件不成立(锚点解析失败、版本漂移、行不存在、名字漂移、
8
+ # 官方行未禁用),Replacement 一律保持 dormant(fail-closed)。探针为假时,
9
+ # 即使后续 patch 层把官方行强制 disabled,行绑定内嵌探针仍保持 dormant。
8
10
  - id: connection
9
11
  name: '@deepseek-ai/dsh-client-connection'
10
- disabled: !!js '(() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== ''function'') return false; let url; try { url = internal.version === ''v2'' ? internal.resolveSync(ctx.baseUrl, { specifier: ''@deepseek-ai/dsh-client-connection/package.json'', attributes: {} }).url : internal.resolveSync(''@deepseek-ai/dsh-client-connection/package.json'', ctx.baseUrl, {}).url; } catch (gateError) { return false; } if (typeof url !== ''string'') return false; const match = decodeURIComponent(url).match(/[+]dsh-client-connection@([^/_]+)[_/]/); return match !== null && match[1] === ''0.1.2-rc.1''; } catch (gateError) { return false; } })()'
12
+ disabled: !!js >-
13
+ (() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return false; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof
14
+ manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return false; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return false; } if (anchorVersion !== '0.1.2-rc.1') return false; } return true; } catch (gateError) { return false; } })()
11
15
  - insert:
12
16
  - id: trusted-connection
13
17
  name: '@iasiv5/dsh-skip-browser-auth'
@@ -16,5 +20,6 @@
16
20
  config:
17
21
  trustedHosts: !!js ctx.webRuntime.trustedHosts
18
22
  disabled: !!js >-
19
- !((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const probeAllowed = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return false; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: '@deepseek-ai/dsh-client-connection/package.json', attributes: {} }).url : internal.resolveSync('@deepseek-ai/dsh-client-connection/package.json', ctx.baseUrl, {}).url; } catch (gateError) { return false; } if (typeof url !== 'string') return false; const match = decodeURIComponent(url).match(/[+]dsh-client-connection@([^/_]+)[_/]/); return match !== null &&
20
- match[1] === '0.1.2-rc.1'; } catch (gateError) { return false; } })()); if (probeAllowed !== true) return false; return official.disabled === true; } catch (gateError) { return false; } })())
23
+ !((() => { try { const self = ctx[Symbol.for('cordis.entry')]; if (!self || !self.parent || !self.parent.tree) return false; const official = self.parent.tree.resolve('connection'); if (!official || official.parent !== self.parent) return false; if (official.options.id !== 'connection' || official.options.name !== '@deepseek-ai/dsh-client-connection') return false; const probeAllowed = ((() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return false; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule =
24
+ processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = [["@deepseek-ai/dsh","[+]dsh@([^/_]+)[_/]"],["@deepseek-ai/dsh-client-connection","[+]dsh-client-connection@([^/_]+)[_/]"]]; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return false; } let anchorVersion; try { anchorVersion = versionOfUrl(url,
25
+ patternSource); } catch (gateError) { return false; } if (anchorVersion !== '0.1.2-rc.1') return false; } return true; } catch (gateError) { return false; } })()); if (probeAllowed !== true) return false; return official.disabled === true; } catch (gateError) { return false; } })())
package/lib/gate.js CHANGED
@@ -5,11 +5,55 @@
5
5
  * 见 vendor/loader/src/config/utils.ts),失败一律落入 dormant(fail-closed)。
6
6
  * sibling 解析走 Symbol.for('cordis.entry') 当前 entry 所属子树,
7
7
  * 不依赖根 Loader 的 resolve——业务行位于 Include 子树,根上看不到。
8
+ *
9
+ * 探针解析基准(2026-09-05 事故修正):锚定宿主 runtime 本体,而不是
10
+ * connection 包自身。`ctx.baseUrl` 是 profile 目录,Node 解析自 profile
11
+ * 向上走 node_modules;旧版插件把 connection 包声明进 dependencies,发布
12
+ * 安装(profile `nodeLinker: hoisted`)把 0.1.2-rc.1 副本抬升进 profile
13
+ * node_modules,遮蔽 `~/.dsh/profiles/node_modules/` 下
14
+ * healProfilesModuleFallback 维护的 runtime fallback 符号链——探针随之
15
+ * 「自我满足」,在不兼容宿主上误激活(正是本次事故)。因此:
16
+ * 1. 第一锚点是 `@deepseek-ai/dsh/package.json`(runtime 应用包本体)。
17
+ * 它只可能来自 runtime fallback,不可能被任何 profile 插件的依赖
18
+ * 抬升污染;其版本即宿主 runtime 版本。
19
+ * 2. 第二锚点仍是官方 connection 包(被替换对象本体);双锚点必须同时
20
+ * 命中白名单版本,任一解析失败或版本漂移一律 dormant。
21
+ * 配套必要条件:本插件的 dependencies 不得声明任何 `@deepseek-ai` 运行时
22
+ * 包(connection 仅作 devDependency 供编译期类型与构建锚点),由
23
+ * tests/manifest.test.mjs 作为不变量看护。
24
+ *
25
+ * 单锚点版本判定(2026-09-05 第二修正,npm 扁平布局):
26
+ * 1. pnpm store 路径段(快路径):URL 含 `+包名@版本[_/]` 段即取段值。
27
+ * 保留既有文档化行为——「段真、manifest 版本漂移」时探针仍放行,
28
+ * 由 apply 内 backstop 读 manifest 兜底 fail loud。
29
+ * 2. manifest 回退:URL 为 `file:` 且无版本段(npm 扁平布局,如 dshm
30
+ * 安装)时,经 `process.getBuiltinModule('node:fs'/'node:url')` 同步读
31
+ * manifest 比对精确版本(Node ≥22.3,插件 engines 覆盖;`!!js` 求值
32
+ * 环境无 require/模块导入,这是唯一受支持的同步内建模块获取方式)。
33
+ * 两分支都拿不到精确版本 → dormant。任何异常一律 dormant(组合期绝不
34
+ * fail loud;最后防线在 apply 内 backstop)。
8
35
  */
9
36
  export const CONNECTION_PACKAGE = '@deepseek-ai/dsh-client-connection';
37
+ /** 宿主 runtime 本体包:探针的第一锚点,版本即宿主 dsh 版本。 */
38
+ export const GATE_RUNTIME_PACKAGE = '@deepseek-ai/dsh';
10
39
  export const GATE_VERSION = '0.1.2-rc.1';
40
+ /** 探针锚点(顺序即求值顺序):宿主 runtime 本体在前,被替换对象在后。 */
41
+ export const GATE_ANCHOR_PACKAGES = [GATE_RUNTIME_PACKAGE, CONNECTION_PACKAGE];
42
+ /**
43
+ * 从已解析 URL 提取 pnpm store 版本段的正则源(`+name@VER_` / `+name@VER/`)。
44
+ * 探针表达式内嵌的正则由此单一实现生成;仅匹配 pnpm 布局,非 pnpm 布局
45
+ * 落入 manifest 回退分支。
46
+ */
47
+ export function versionSegmentPatternSource(pkg) {
48
+ const shortName = pkg.slice(pkg.indexOf('/') + 1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
49
+ return `[+]${shortName}@([^/_]+)[_/]`;
50
+ }
51
+ /** 探针内嵌的锚点表:`[包名, 版本段正则源]` 对,全部由上方常量派生。 */
52
+ const GATE_ANCHOR_TABLE = GATE_ANCHOR_PACKAGES.map((pkg) => [pkg, versionSegmentPatternSource(pkg)]);
11
53
  // 版本探针:单行;生成 cordis.patch.yml 官方行 disabled(scripts/write-patch.mjs)。
12
- export const GATE_PROBE_EXPRESSION = `(() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return false; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier: '${CONNECTION_PACKAGE}/package.json', attributes: {} }).url : internal.resolveSync('${CONNECTION_PACKAGE}/package.json', ctx.baseUrl, {}).url; } catch (gateError) { return false; } if (typeof url !== 'string') return false; const match = decodeURIComponent(url).match(/[+]dsh-client-connection@([^/_]+)[_/]/); return match !== null && match[1] === '${GATE_VERSION}'; } catch (gateError) { return false; } })()`;
54
+ // 逐锚点解析 `锚点包/package.json`,版本(路径段快路径或 manifest 回退)全部
55
+ // 精确等于 GATE_VERSION 才放行。
56
+ export const GATE_PROBE_EXPRESSION = `(() => { try { const internal = ctx.loader && ctx.loader.internal; if (!internal || typeof internal.resolveSync !== 'function') return false; const versionOfUrl = (url, patternSource) => { if (typeof url !== 'string') return null; const decoded = decodeURIComponent(url); const segmented = decoded.match(new RegExp(patternSource)); if (segmented !== null) return segmented[1]; if (decoded.slice(0, 5) === 'file:') { const processRef = globalThis.process; if (processRef === null || typeof processRef !== 'object' || typeof processRef.getBuiltinModule !== 'function') return null; const fsModule = processRef.getBuiltinModule('node:fs'); const urlModule = processRef.getBuiltinModule('node:url'); if (!fsModule || typeof fsModule.readFileSync !== 'function' || !urlModule || typeof urlModule.fileURLToPath !== 'function') return null; const manifest = JSON.parse(fsModule.readFileSync(urlModule.fileURLToPath(url), 'utf8')); return manifest !== null && typeof manifest === 'object' && typeof manifest.version === 'string' ? manifest.version : null; } return null; }; const anchors = ${JSON.stringify(GATE_ANCHOR_TABLE)}; for (const [anchorPackage, patternSource] of anchors) { const specifier = anchorPackage + '/package.json'; let url; try { url = internal.version === 'v2' ? internal.resolveSync(ctx.baseUrl, { specifier, attributes: {} }).url : internal.resolveSync(specifier, ctx.baseUrl, {}).url; } catch (gateError) { return false; } let anchorVersion; try { anchorVersion = versionOfUrl(url, patternSource); } catch (gateError) { return false; } if (anchorVersion !== '${GATE_VERSION}') return false; } return true; } catch (gateError) { return false; } })()`;
13
57
  // 行绑定放行:自身探针为真 + 同组 sibling 存在且名字匹配且其 effective disabled 为真。
14
58
  // sibling 必须从当前 entry(Symbol.for('cordis.entry'))所属子树解析,
15
59
  // 根 Loader 的 resolve 看不到 Include 子树内的行。
@@ -17,7 +61,7 @@ export const GATE_ROW_ALLOWED_EXPRESSION = `(() => { try { const self = ctx[Symb
17
61
  /**
18
62
  * Evaluate the activation probe against a loader context scope.
19
63
  * @param ctx - the `!!js` evaluation scope (same object the Loader passes).
20
- * @returns true only when the resolved connection package is exactly the whitelisted version.
64
+ * @returns true only when every gate anchor resolves to exactly the whitelisted version.
21
65
  */
22
66
  export function evaluateGate(ctx) {
23
67
  // 与 vendor/loader/src/config/utils.ts 的 evaluate 同语义:with(ctx) + eval。
package/lib/host.js CHANGED
@@ -5,9 +5,9 @@
5
5
  * isAuthenticated / authorizeIndex / authenticatedUrl 三个方法,由
6
6
  * trusted-auth 的 stub 提供),复刻官方 apply 的 /api 前缀路由、
7
7
  * 请求体上限与 image capacity 断言语义,并在启动时输出固定警告。
8
- * apply 内 backstop 是最后防线:版本白名单(读官方 manifest)与
9
- * 门控绑定状态(同组官方行确被禁用且无活跃 fiber)任一不满足即
10
- * fail loud 拒绝运行。
8
+ * apply 内 backstop 是最后防线:版本白名单(读全部门控锚点的 manifest,
9
+ * 与探针同源常量)与门控绑定状态(同组官方行确被禁用且无活跃 fiber)
10
+ * 任一不满足即 fail loud 拒绝运行。
11
11
  */
12
12
  import { readFileSync } from 'node:fs';
13
13
  import { fileURLToPath } from 'node:url';
@@ -16,7 +16,7 @@ import { HostConnectionService, API_PATH } from '@deepseek-ai/dsh-client-connect
16
16
  import { assertTrustedAuthority } from './trust-fence.js';
17
17
  import { createTrustedAuth } from './trusted-auth.js';
18
18
  import { bridge, DEFAULT_MAX_REQUEST_BODY_BYTES } from './bridge.js';
19
- import { CONNECTION_PACKAGE, GATE_VERSION } from './gate.js';
19
+ import { CONNECTION_PACKAGE, GATE_ANCHOR_PACKAGES, GATE_VERSION } from './gate.js';
20
20
  /** Stable Cordis plugin name. */
21
21
  export const name = '@iasiv5/dsh-skip-browser-auth';
22
22
  /** Services required before providing the replacement Connection. */
@@ -36,10 +36,16 @@ function assertImageBodyCapacity(ctx, maxRequestBodyBytes) {
36
36
  }
37
37
  }
38
38
  /**
39
- * apply 内 backstop(最后防线,fail loud):版本白名单断言读取官方 manifest
40
- * 本体(组合期探针只读 pnpm 路径段,会被「路径真、manifest 假」的漂移骗过);
41
- * 绑定断言确认当前 entry 子树内的官方 connection 行确实被本插件禁用、
42
- * 同组同名且无活跃 fiber。
39
+ * apply 内 backstop(最后防线,fail loud):版本白名单断言读取全部门控锚点
40
+ * (gate.ts 的 GATE_ANCHOR_PACKAGES:宿主 runtime 本体 + 官方 connection 包)
41
+ * 的 manifest 本体比对白名单(组合期探针只读 pnpm 路径段,会被
42
+ * 「路径真、manifest 假」的漂移骗过);绑定断言确认当前 entry 子树内的
43
+ * 官方 connection 行确实被本插件禁用、同组同名且无活跃 fiber。
44
+ *
45
+ * 锚点解析与探针同基准(ctx.baseUrl,即 profile 根):发布安装的插件不携带
46
+ * 任何 @deepseek-ai 运行时依赖(见 tests/manifest.test.mjs 不变量),解析只
47
+ * 可能命中 runtime fallback;探针基准污染的历史事故由此不变量 + 双锚点共同
48
+ * 封死。
43
49
  *
44
50
  * 跳过 seam 仅限「完全没有 Loader、也没有 Loader entry」的直接单元测试调用;
45
51
  * 产品组合中 loader / internal / current 任一缺失都意味着最后防线无法执行,
@@ -60,16 +66,23 @@ function assertGateBackstop(ctx) {
60
66
  if (current === undefined) {
61
67
  throw new Error('@iasiv5/dsh-skip-browser-auth: gate backstop unavailable: current loader entry is missing');
62
68
  }
63
- // (a) 版本断言:与官方 row 同一解析锚点(loader.internal),读 manifest 比对白名单。
69
+ // (a) 版本断言:与探针同一解析锚点集合(loader.internal + ctx.baseUrl),
70
+ // 逐锚点读 manifest 比对白名单。
64
71
  const baseUrl = ctx.baseUrl ?? '';
65
- const specifier = `${CONNECTION_PACKAGE}/package.json`;
66
72
  const internal = loader.internal;
67
- const resolved = internal.version === 'v2'
68
- ? internal.resolveSync(baseUrl, { specifier, attributes: {} })
69
- : internal.resolveSync(specifier, baseUrl, {});
70
- const manifest = JSON.parse(readFileSync(fileURLToPath(resolved?.url), 'utf8'));
71
- if (manifest.version !== GATE_VERSION) {
72
- throw new Error(`@iasiv5/dsh-skip-browser-auth: official connection manifest is ${String(manifest.version)}, whitelist is ['0.1.2-rc.1']; disable or uninstall the plugin`);
73
+ for (const anchorPackage of GATE_ANCHOR_PACKAGES) {
74
+ const specifier = `${anchorPackage}/package.json`;
75
+ const resolved = internal.version === 'v2'
76
+ ? internal.resolveSync(baseUrl, { specifier, attributes: {} })
77
+ : internal.resolveSync(specifier, baseUrl, {});
78
+ const manifestUrl = resolved?.url;
79
+ if (typeof manifestUrl !== 'string') {
80
+ throw new Error(`@iasiv5/dsh-skip-browser-auth: gate anchor ${anchorPackage} did not resolve; whitelist is ['${GATE_VERSION}']; disable or uninstall the plugin`);
81
+ }
82
+ const manifest = JSON.parse(readFileSync(fileURLToPath(manifestUrl), 'utf8'));
83
+ if (manifest.version !== GATE_VERSION) {
84
+ throw new Error(`@iasiv5/dsh-skip-browser-auth: gate anchor ${anchorPackage} manifest is ${String(manifest.version)}, whitelist is ['${GATE_VERSION}']; disable or uninstall the plugin`);
85
+ }
73
86
  }
74
87
  // (b) 绑定断言:官方行被禁用是 Replacement 放行的前提,且不得有活跃 fiber。
75
88
  const official = current.parent.tree.resolve('connection');
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "@iasiv5/dsh-skip-browser-auth",
3
- "version": "0.1.0",
4
- "description": "个人自用:在 DSH 0.1.2-rc.1 上跳过 BrowserAuth 的 trusted-network Connection 替换插件",
3
+ "version": "0.1.2",
4
+ "description": "DSH 0.1.2-rc.1 专用:自动跳过 BrowserAuth,免每次在启动 URL 后输入随机 token;其它版本(含 0.1.1-rc.2)自动休眠,不修改任何 DSH 本体与机制",
5
+ "keywords": [
6
+ "dsh",
7
+ "deepseek-harness",
8
+ "browserauth",
9
+ "cordis"
10
+ ],
5
11
  "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/iasiv5/dsh-skip-browser-auth.git"
15
+ },
6
16
  "type": "module",
7
17
  "main": "lib/host.js",
8
18
  "exports": {
@@ -42,9 +52,6 @@
42
52
  "immediately": true
43
53
  }
44
54
  },
45
- "dependencies": {
46
- "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1"
47
- },
48
55
  "peerDependencies": {
49
56
  "@deepseek-ai/cordis": "*",
50
57
  "@deepseek-ai/schemastery": "*"
@@ -53,6 +60,7 @@
53
60
  "@deepseek-ai/cordis": "4.0.2",
54
61
  "@deepseek-ai/cordis-plugin-include": "1.0.7",
55
62
  "@deepseek-ai/cordis-plugin-loader": "1.0.3",
63
+ "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1",
56
64
  "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
57
65
  "@deepseek-ai/dsh-credentials-local": "0.1.2-rc.1",
58
66
  "@deepseek-ai/dsh-host-frontend-static": "0.1.2-rc.1",