yunti-browser-runtime 0.1.0 → 0.1.1
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 +12 -7
- package/docs/EXECUTION_PLAN.md +78 -29
- package/docs/INSTALL.md +9 -8
- package/docs/PROJECT_STATUS.md +35 -30
- package/docs/PUBLISHING_BLOCKERS.md +16 -13
- package/docs/SECURITY.md +6 -4
- package/extension/manifest.json +1 -1
- package/mcp/http-server.js +38 -13
- package/mcp/server.js +18 -8
- package/package.json +1 -1
- package/scripts/doctor.js +6 -3
- package/scripts/print-config.js +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,8 @@ yunti-browser-runtime package-extension
|
|
|
59
59
|
http://127.0.0.1:48887
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
bridge
|
|
62
|
+
默认本地 bridge 只监听 `127.0.0.1`,不需要配置 token。需要额外加固本机访问时,
|
|
63
|
+
可以显式启用 token:
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
66
|
YUNTI_BROWSER_BRIDGE_TOKEN="$(openssl rand -hex 24)" npm run bridge
|
|
@@ -87,7 +88,7 @@ npm run doctor:json
|
|
|
87
88
|
3. 点击“加载已解压的扩展程序”。
|
|
88
89
|
4. 选择本项目的 `extension/` 目录。
|
|
89
90
|
5. 打开任意 `http` 或 `https` 页面。
|
|
90
|
-
6.
|
|
91
|
+
6. 点击扩展图标,确认 bridge 地址为 `http://127.0.0.1:48887`,保存设置。
|
|
91
92
|
7. 刷新目标页面,确认页面已连接。
|
|
92
93
|
|
|
93
94
|
扩展只负责把页面注册到本地 bridge,并执行 Agent 发来的浏览器动作。
|
|
@@ -239,7 +240,10 @@ YUNTI_E2E=1 npm run test:e2e
|
|
|
239
240
|
|
|
240
241
|
### bridge token 是什么?
|
|
241
242
|
|
|
242
|
-
bridge token
|
|
243
|
+
默认本地安装不需要 bridge token。bridge 只监听 `127.0.0.1`,扩展和 MCP server
|
|
244
|
+
可以直接连接,减少首次使用心智。需要加固共享机器或自定义部署时,可设置
|
|
245
|
+
`YUNTI_BROWSER_BRIDGE_TOKEN`,这时 MCP server、extension 和 doctor 必须使用同一个
|
|
246
|
+
token。
|
|
243
247
|
|
|
244
248
|
### 会读取 cookie 或密码吗?
|
|
245
249
|
|
|
@@ -249,8 +253,9 @@ bridge token 是本地 HTTP bridge 的访问凭证。MCP server、extension 和
|
|
|
249
253
|
|
|
250
254
|
调用 `yunti_list_browser_targets` 获取最新浏览器路由,再用返回的 `browserSessionId` 继续操作。
|
|
251
255
|
|
|
252
|
-
##
|
|
256
|
+
## 发布状态与后续事项
|
|
253
257
|
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
-
|
|
258
|
+
- `yunti-browser-runtime@0.1.1` 已发布到官方 npm registry。
|
|
259
|
+
- `0.1.1` 将本地默认使用路径改为免 bridge token。
|
|
260
|
+
- 发布后验证命令:`npm run release:verify-published`。
|
|
261
|
+
- 如要上架浏览器扩展商店,需要再次审查 broad host permissions。
|
package/docs/EXECUTION_PLAN.md
CHANGED
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
| P4.16 | 已完成 | 正式发布脚本内置门禁 |
|
|
55
55
|
| P4.17 | 已完成 | npm 登录预检门禁 |
|
|
56
56
|
| P4.18 | 已完成 | npm 发布后验证命令 |
|
|
57
|
-
| P4.19 |
|
|
57
|
+
| P4.19 | 已完成 | npm 正式发布执行 |
|
|
58
|
+
| P5.1 | 已完成 | 本地默认免 bridge token |
|
|
58
59
|
|
|
59
60
|
## P0.1 bridge token + CORS 收紧
|
|
60
61
|
|
|
@@ -1151,8 +1152,9 @@ release gate、npm package 内容检查和 extension zip 内容检查。
|
|
|
1151
1152
|
|
|
1152
1153
|
### 验收记录
|
|
1153
1154
|
|
|
1154
|
-
-
|
|
1155
|
-
|
|
1155
|
+
- 发布前,`npm run release:whoami` 曾在未登录状态返回 `ENEEDAUTH`,验证缺少 npm
|
|
1156
|
+
登录时会提前失败。
|
|
1157
|
+
- 登录后,`npm run release:whoami` 返回 `xuanzhu`,并在正式发布前通过预检。
|
|
1156
1158
|
- `release:publish` 已内置登录预检;登录前不会进入正式 npm publish。
|
|
1157
1159
|
|
|
1158
1160
|
## P4.18 npm 发布后验证命令
|
|
@@ -1186,9 +1188,10 @@ metadata 与当前 `package.json` 一致。
|
|
|
1186
1188
|
|
|
1187
1189
|
### 验收记录
|
|
1188
1190
|
|
|
1189
|
-
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1191
|
+
- 发布前,`npm run release:verify-published` 曾返回非零退出码并输出结构化未发布报告,
|
|
1192
|
+
且失败路径不包含 npm 本机 debug log 路径。
|
|
1193
|
+
- 发布后,`npm run release:verify-published` 返回 0,确认
|
|
1194
|
+
`yunti-browser-runtime@0.1.0` 已发布到 `https://registry.npmjs.org/`。
|
|
1192
1195
|
- `node --check scripts/check-published-package.js` 通过。
|
|
1193
1196
|
- `npm run release:check` 通过:`npm run check` 已覆盖
|
|
1194
1197
|
`scripts/check-published-package.js`,npm package contents check 确认 tarball
|
|
@@ -1198,7 +1201,7 @@ metadata 与当前 `package.json` 一致。
|
|
|
1198
1201
|
|
|
1199
1202
|
## P4.19 npm 正式发布执行
|
|
1200
1203
|
|
|
1201
|
-
|
|
1204
|
+
状态:已完成(2026-07-03)
|
|
1202
1205
|
|
|
1203
1206
|
执行摘要:
|
|
1204
1207
|
|
|
@@ -1206,8 +1209,8 @@ metadata 与当前 `package.json` 一致。
|
|
|
1206
1209
|
- 已按用户确认执行 `npm run release:publish`。
|
|
1207
1210
|
- `release:publish` 已通过内置 `release:prepublish`、`release:whoami` 和全部本地
|
|
1208
1211
|
发布门禁;本次测试覆盖 59 项,58 项通过,1 项真实浏览器 smoke 按配置跳过。
|
|
1209
|
-
-
|
|
1210
|
-
|
|
1212
|
+
- 正式发布进入 `npm publish --registry=https://registry.npmjs.org/`,tarball 包含
|
|
1213
|
+
40 个文件。
|
|
1211
1214
|
- npm registry 返回 `E403`:当前账号发布包需要双因素认证 OTP,或使用开启
|
|
1212
1215
|
bypass 2FA 的 granular access token。
|
|
1213
1216
|
- 已使用临时 npm token 重试 `npm run release:publish`;本地发布门禁再次通过,但
|
|
@@ -1217,7 +1220,11 @@ metadata 与当前 `package.json` 一致。
|
|
|
1217
1220
|
但重新执行 `npm run release:publish` 仍在 `npm publish` 阶段返回相同 `E403`。
|
|
1218
1221
|
- 已用新的 npm token 覆盖项目级 `.npmrc` 后再次确认 `npm whoami` 返回 `xuanzhu`;
|
|
1219
1222
|
`npm run release:publish` 仍通过本地门禁并在 `npm publish` 阶段返回相同 `E403`。
|
|
1220
|
-
-
|
|
1223
|
+
- 已用具备发布权限的 npm token 覆盖项目级 `.npmrc` 后再次执行
|
|
1224
|
+
`npm run release:publish`,正式发布成功:`yunti-browser-runtime@0.1.0` 已发布到
|
|
1225
|
+
`https://registry.npmjs.org/`。
|
|
1226
|
+
- `npm run release:verify-published` 通过,确认 registry 上的 name、version、
|
|
1227
|
+
repository、homepage、bugs 和 tarball URL 与本地 `package.json` 匹配。
|
|
1221
1228
|
|
|
1222
1229
|
### 目标
|
|
1223
1230
|
|
|
@@ -1234,11 +1241,61 @@ metadata 与当前 `package.json` 一致。
|
|
|
1234
1241
|
repository、homepage、bugs 和 tarball URL 与本地 `package.json` 匹配。
|
|
1235
1242
|
- 发布结果同步到 `docs/PROJECT_STATUS.md`。
|
|
1236
1243
|
|
|
1237
|
-
###
|
|
1244
|
+
### 验收记录
|
|
1245
|
+
|
|
1246
|
+
- `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.0`。
|
|
1247
|
+
- 发布前门禁通过:metadata URL 检查、公开文档残留检查、Markdown 链接检查、版本一致性、
|
|
1248
|
+
CLI smoke、print-config smoke、doctor smoke、`npm run check`、`npm test`、npm
|
|
1249
|
+
package contents check 和 extension zip contents check。
|
|
1250
|
+
- `npm test` 覆盖 59 项测试:58 项通过,1 项真实浏览器 smoke 按配置跳过。
|
|
1251
|
+
- `npm run release:verify-published` 返回 0。
|
|
1252
|
+
- `npm view yunti-browser-runtime@0.1.0 ... --registry=https://registry.npmjs.org/`
|
|
1253
|
+
返回已发布版本和正确 metadata。
|
|
1238
1254
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1255
|
+
## P5.1 本地默认免 bridge token
|
|
1256
|
+
|
|
1257
|
+
状态:已完成(2026-07-03)
|
|
1258
|
+
|
|
1259
|
+
实现摘要:
|
|
1260
|
+
|
|
1261
|
+
- 默认 `127.0.0.1` 本地 bridge 不再自动生成随机 token,也不要求 extension、doctor
|
|
1262
|
+
或 MCP proxy 携带 `x-yunti-browser-token`。
|
|
1263
|
+
- 用户显式设置 `YUNTI_BROWSER_BRIDGE_TOKEN` 时,bridge 继续强制校验
|
|
1264
|
+
`x-yunti-browser-token` 或 `Authorization: Bearer <token>`。
|
|
1265
|
+
- bridge 绑定到非 loopback host 时必须设置 `YUNTI_BROWSER_BRIDGE_TOKEN`,避免把
|
|
1266
|
+
本地免 token 默认值误用于远程或局域网部署。
|
|
1267
|
+
- `npm run doctor` 默认显示 token not required;只有 bridge 明确要求 token 且请求
|
|
1268
|
+
未授权时才提示设置 token。
|
|
1269
|
+
- `npm run print-config` 默认不再要求用户配置 token,仅保留需要加固时的可选说明。
|
|
1270
|
+
- 真实浏览器 smoke test 改为覆盖默认免 token 路径。
|
|
1271
|
+
- package 和 extension 版本同步提升到 `0.1.1`。
|
|
1272
|
+
|
|
1273
|
+
### 目标
|
|
1274
|
+
|
|
1275
|
+
降低本地安装和首次使用心智:用户只需要启动 bridge、加载扩展、确认 bridge URL,
|
|
1276
|
+
不需要复制或理解 bridge token。安全边界仍保持本地 loopback 默认,显式 token 用于
|
|
1277
|
+
共享机器、非本地绑定或额外加固场景。
|
|
1278
|
+
|
|
1279
|
+
### 验收标准
|
|
1280
|
+
|
|
1281
|
+
- 默认 `npm run bridge` 启动的 `127.0.0.1` bridge 不要求 token。
|
|
1282
|
+
- 未配置 token 时,extension 可以注册页面,doctor 可以读取完整 health。
|
|
1283
|
+
- 配置 `YUNTI_BROWSER_BRIDGE_TOKEN` 后,未携带 token 的受保护 HTTP route 仍返回 401。
|
|
1284
|
+
- `YUNTI_BROWSER_BRIDGE_HOST` 绑定非 loopback 地址且未配置 token 时启动失败。
|
|
1285
|
+
- README、INSTALL、SECURITY、PROJECT_STATUS 同步新的默认使用路径。
|
|
1286
|
+
- `npm run release:check` 通过。
|
|
1287
|
+
- `npm run release:publish` 发布 `yunti-browser-runtime@0.1.1`,并通过
|
|
1288
|
+
`npm run release:verify-published` 验证。
|
|
1289
|
+
|
|
1290
|
+
### 验收记录
|
|
1291
|
+
|
|
1292
|
+
- `npm test` 通过:61 项测试,60 项通过,1 项真实浏览器 smoke 按配置跳过。
|
|
1293
|
+
- 新增测试覆盖本地默认免 token、显式 token 仍强制校验、非 loopback 无 token
|
|
1294
|
+
启动失败。
|
|
1295
|
+
- `npm run check` 通过。
|
|
1296
|
+
- `npm run release:check` 通过。
|
|
1297
|
+
- `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.1`。
|
|
1298
|
+
- `npm run release:verify-published` 返回 0。
|
|
1242
1299
|
|
|
1243
1300
|
## 每阶段完成后的固定检查
|
|
1244
1301
|
|
|
@@ -1261,18 +1318,10 @@ rg "/U[s]ers|C[o]deg|x[y]y|y[b]m100" README.md docs skills package.json
|
|
|
1261
1318
|
|
|
1262
1319
|
## 当前下一步
|
|
1263
1320
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
-
|
|
1267
|
-
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
-
|
|
1271
|
-
- 已创建项目级 `.npmrc` 并使用实际 token 复验,`npm whoami` 成功但 publish 仍返回
|
|
1272
|
-
相同 `E403`。
|
|
1273
|
-
- 已用新的 npm token 覆盖项目级 `.npmrc` 复验,认证仍成功但 publish 仍返回相同
|
|
1274
|
-
`E403`。
|
|
1275
|
-
- 下一步使用当前 npm OTP 执行 `npm run release:publish -- --otp=<6-digit-code>`,
|
|
1276
|
-
或改用具备 publish 权限且允许 bypass 2FA 的 granular access token。
|
|
1277
|
-
- 发布后执行 `npm run release:verify-published` 确认 npm registry 版本和 metadata。
|
|
1278
|
-
- 浏览器扩展如需上架商店,发布前还需重新审查 broad host permissions。
|
|
1321
|
+
P0.1-P5.1 已完成,`yunti-browser-runtime@0.1.1` 已发布到官方 npm registry。
|
|
1322
|
+
|
|
1323
|
+
- 发布后验证已通过:`npm run release:verify-published`。
|
|
1324
|
+
- 本地默认使用不再需要 bridge token;需要加固时可显式设置
|
|
1325
|
+
`YUNTI_BROWSER_BRIDGE_TOKEN`。
|
|
1326
|
+
- 后续如要上架浏览器扩展商店,发布前还需重新审查 broad host permissions。
|
|
1327
|
+
- 后续版本开发前,先在本文档新增下一阶段目标、范围和验收标准。
|
package/docs/INSTALL.md
CHANGED
|
@@ -27,8 +27,8 @@ Use a custom port when needed:
|
|
|
27
27
|
YUNTI_BROWSER_BRIDGE_PORT=48999 npm run bridge
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
bridge
|
|
31
|
-
|
|
30
|
+
默认本地 bridge 只监听 `127.0.0.1`,不需要配置 token。需要额外加固本机访问、
|
|
31
|
+
或自定义部署时,可以显式启用 token:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
YUNTI_BROWSER_BRIDGE_TOKEN="$(openssl rand -hex 24)" npm run bridge
|
|
@@ -54,7 +54,7 @@ Development loading:
|
|
|
54
54
|
2. Enable developer mode.
|
|
55
55
|
3. Choose "Load unpacked".
|
|
56
56
|
4. Select the project `extension/` directory.
|
|
57
|
-
5. Click the extension icon and
|
|
57
|
+
5. Click the extension icon and confirm the bridge URL is `http://127.0.0.1:48887`.
|
|
58
58
|
6. Open any `http` or `https` page and click the extension icon to confirm the
|
|
59
59
|
page is connected.
|
|
60
60
|
|
|
@@ -117,9 +117,10 @@ npm run print-config -- --agent claude-code --human
|
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Add the printed `mcpServers.yunti-browser-runtime` block to Claude Code's MCP
|
|
120
|
-
configuration.
|
|
121
|
-
|
|
122
|
-
token in the
|
|
120
|
+
configuration. The default local loopback setup does not require a bridge token.
|
|
121
|
+
If using a bridge token, set `YUNTI_BROWSER_BRIDGE_TOKEN` in the same environment
|
|
122
|
+
Claude Code uses to launch the MCP server, and save the same token in the
|
|
123
|
+
extension popup.
|
|
123
124
|
|
|
124
125
|
#### Cursor
|
|
125
126
|
|
|
@@ -176,8 +177,8 @@ runs the unit test suite, and verifies the npm package contents with
|
|
|
176
177
|
## Common Recovery
|
|
177
178
|
|
|
178
179
|
- If tools say no browser tab is connected, refresh the target page.
|
|
179
|
-
- If doctor reports `authorized: false
|
|
180
|
-
save the same token in the extension popup.
|
|
180
|
+
- If doctor reports `authorized: false` with `authRequired: true`, set
|
|
181
|
+
`YUNTI_BROWSER_BRIDGE_TOKEN` and save the same token in the extension popup.
|
|
181
182
|
- If an old `browserSessionId` fails, call `yunti_list_browser_targets` again
|
|
182
183
|
and use the latest returned session.
|
|
183
184
|
- If a parameter error appears, call `yunti_get_tool_usage_hints` with the
|
package/docs/PROJECT_STATUS.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Current Phase
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Patch release `yunti-browser-runtime@0.1.1` is complete. The local loopback
|
|
6
|
+
bridge no longer requires a token by default, and the published package is
|
|
7
|
+
verified with `npm run release:verify-published`.
|
|
8
8
|
|
|
9
9
|
## Current State
|
|
10
10
|
|
|
@@ -20,15 +20,18 @@ authentication OTP.
|
|
|
20
20
|
- A staged execution plan exists at `docs/EXECUTION_PLAN.md`.
|
|
21
21
|
- Tool prefix is `yunti_*`.
|
|
22
22
|
- MCP local mode defaults to `userId=local`.
|
|
23
|
-
-
|
|
24
|
-
|
|
23
|
+
- Local loopback bridge HTTP routes do not require a token by default; setting
|
|
24
|
+
`YUNTI_BROWSER_BRIDGE_TOKEN` re-enables `x-yunti-browser-token` checks.
|
|
25
|
+
- Binding the bridge to a non-loopback host requires
|
|
26
|
+
`YUNTI_BROWSER_BRIDGE_TOKEN`.
|
|
25
27
|
- Bridge CORS now echoes only local debug origins and browser extension origins
|
|
26
28
|
by default, with `YUNTI_BROWSER_BRIDGE_ALLOW_ORIGINS` as an override.
|
|
27
|
-
- Extension popup can save the local bridge URL, token, and page match
|
|
29
|
+
- Extension popup can save the local bridge URL, optional token, and page match
|
|
30
|
+
patterns.
|
|
28
31
|
- Browser sessions now track `lastSeenAt`, `lastActivatedAt`, `expiresAt`, and
|
|
29
32
|
`staleReason`; extension polling refreshes heartbeat and expired sessions are
|
|
30
33
|
cleaned up with recovery guidance.
|
|
31
|
-
- `npm run doctor` now checks Node version, bridge reachability,
|
|
34
|
+
- `npm run doctor` now checks Node version, bridge reachability, auth mode,
|
|
32
35
|
registered sessions, active session, extension presence, MCP server path, and
|
|
33
36
|
skill path; it emits JSON plus a human-readable summary.
|
|
34
37
|
- `npm run print-config` prints MCP configuration for Codex, Claude Code,
|
|
@@ -39,6 +42,7 @@ authentication OTP.
|
|
|
39
42
|
- `npm run release:dry-run` and `npm run release:publish` pin the official npm
|
|
40
43
|
registry at `https://registry.npmjs.org/`, avoiding accidental publication to
|
|
41
44
|
a locally configured mirror registry.
|
|
45
|
+
- `yunti-browser-runtime@0.1.1` is published on the official npm registry.
|
|
42
46
|
- `npm run test:e2e` provides an opt-in real-browser Playwright smoke test for
|
|
43
47
|
extension loading, page registration, MCP target listing, snapshot, click/fill,
|
|
44
48
|
and CDP `Runtime.evaluate`.
|
|
@@ -98,16 +102,15 @@ authentication OTP.
|
|
|
98
102
|
|
|
99
103
|
## Open Work
|
|
100
104
|
|
|
101
|
-
- Follow `docs/EXECUTION_PLAN.md`
|
|
105
|
+
- Follow `docs/EXECUTION_PLAN.md` when defining the next post-0.1.1 phase.
|
|
102
106
|
- P4.1 release-readiness docs and permission review is complete.
|
|
103
107
|
- P4.2 Agent integration examples are complete.
|
|
104
108
|
- P4.3 npm publishing URL confirmation is complete with the GitHub repository
|
|
105
109
|
`https://github.com/dingguangyi0/yunti-browser-runtime`; package metadata
|
|
106
110
|
points to that repository/homepage/issues URL set and all three URLs return
|
|
107
111
|
HTTP 200.
|
|
108
|
-
- `npm view yunti-browser-runtime`
|
|
109
|
-
|
|
110
|
-
name.
|
|
112
|
+
- `npm view yunti-browser-runtime@0.1.0` returns the published `0.1.0` package
|
|
113
|
+
metadata from `https://registry.npmjs.org/`.
|
|
111
114
|
- Latest release gate checks passed after documenting P4.3: public-doc residue
|
|
112
115
|
check, `npm run check`, `npm test`, and `npm pack --dry-run`.
|
|
113
116
|
- P4.4 release gate scripting is complete: `npm run release:check` now runs
|
|
@@ -217,19 +220,16 @@ authentication OTP.
|
|
|
217
220
|
- P4.18 post-publish verification command is complete:
|
|
218
221
|
`release:verify-published` checks the published npm package name, version,
|
|
219
222
|
repository, homepage, bugs URL, and tarball URL against local `package.json`.
|
|
220
|
-
- Latest P4.18 validation: `release:verify-published`
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
verifier.
|
|
225
|
-
- P4.19 formal npm publish execution is in progress: `npm run release:publish`
|
|
223
|
+
- Latest P4.18 validation: after publish, `npm run release:verify-published`
|
|
224
|
+
returns 0 and confirms the published name, version, repository, homepage,
|
|
225
|
+
bugs URL, and tarball URL.
|
|
226
|
+
- P4.19 formal npm publish execution is complete: `npm run release:publish`
|
|
226
227
|
passed metadata checks, release gates, `npm run release:whoami`, `npm run
|
|
227
|
-
check`, `npm test`, npm package contents validation,
|
|
228
|
-
|
|
229
|
-
- Latest P4.19 validation: official npm publish
|
|
230
|
-
`
|
|
231
|
-
`
|
|
232
|
-
granular access token with bypass 2FA enabled.
|
|
228
|
+
check`, `npm test`, npm package contents validation, extension zip validation,
|
|
229
|
+
and official npm publish.
|
|
230
|
+
- Latest P4.19 validation: official npm publish succeeded for
|
|
231
|
+
`yunti-browser-runtime@0.1.0`; the published tarball URL is
|
|
232
|
+
`https://registry.npmjs.org/yunti-browser-runtime/-/yunti-browser-runtime-0.1.0.tgz`.
|
|
233
233
|
- A temporary npm token publish retry also reached official npm publish after
|
|
234
234
|
passing the full local release gate, but npm returned the same `E403`; the
|
|
235
235
|
token does not satisfy npm's publish-time bypass 2FA requirement.
|
|
@@ -238,10 +238,16 @@ authentication OTP.
|
|
|
238
238
|
`.npmrc` is ignored by git to avoid committing local npm credentials.
|
|
239
239
|
- A second npm token written to the project-local `.npmrc` also authenticated
|
|
240
240
|
successfully as `xuanzhu`, then failed at `npm publish` with the same `E403`.
|
|
241
|
-
-
|
|
242
|
-
`npm run release:publish
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
- A publish-capable npm token written to the project-local `.npmrc` authenticated
|
|
242
|
+
successfully as `xuanzhu`; `npm run release:publish` then succeeded, followed
|
|
243
|
+
by successful `npm run release:verify-published`.
|
|
244
|
+
- P5.1 local default auth simplification is complete: default `127.0.0.1`
|
|
245
|
+
bridge usage no longer needs a token; explicit `YUNTI_BROWSER_BRIDGE_TOKEN`
|
|
246
|
+
still enforces token checks; non-loopback binding without a token fails fast.
|
|
247
|
+
- Latest P5.1 validation: `npm test` passed 61 tests with 60 passing and 1
|
|
248
|
+
real-browser smoke skipped by configuration; `npm run check` and
|
|
249
|
+
`npm run release:check` passed; `npm run release:publish` published
|
|
250
|
+
`yunti-browser-runtime@0.1.1`; `npm run release:verify-published` returned 0.
|
|
245
251
|
|
|
246
252
|
## Known Risks
|
|
247
253
|
|
|
@@ -251,9 +257,8 @@ authentication OTP.
|
|
|
251
257
|
long-tail tools can still receive the same treatment before a later release.
|
|
252
258
|
- Extension broad host permissions are now documented, but should still be
|
|
253
259
|
re-reviewed before any store-distributed release.
|
|
254
|
-
- Package repository/homepage/bugs metadata is publicly reachable
|
|
255
|
-
|
|
256
|
-
access token.
|
|
260
|
+
- Package repository/homepage/bugs metadata is publicly reachable and published
|
|
261
|
+
npm metadata now matches local `package.json`.
|
|
257
262
|
- Tool names and descriptions must stay clear enough for agents to choose the
|
|
258
263
|
right route without relying on hidden model knowledge.
|
|
259
264
|
|
|
@@ -3,13 +3,20 @@
|
|
|
3
3
|
This document tracks release blockers that require external confirmation before
|
|
4
4
|
publishing `yunti-browser-runtime`.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## Current Status
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
No active npm publish blockers as of 2026-07-03.
|
|
9
|
+
|
|
10
|
+
`yunti-browser-runtime@0.1.0` is published on the official npm registry and
|
|
11
|
+
verified with `npm run release:verify-published`.
|
|
12
|
+
|
|
13
|
+
## Resolved Blocker: npm 2FA Publish Token
|
|
14
|
+
|
|
15
|
+
Status: resolved on 2026-07-03.
|
|
9
16
|
|
|
10
17
|
`npm run release:publish` now passes local release gates and npm login preflight,
|
|
11
18
|
then reaches `npm publish --registry=https://registry.npmjs.org/`. The registry
|
|
12
|
-
|
|
19
|
+
initially rejected the publish with `E403` because the logged-in account requires
|
|
13
20
|
two-factor authentication for package publishing.
|
|
14
21
|
|
|
15
22
|
A temporary npm token retry also passed the local release gate and reached
|
|
@@ -23,22 +30,18 @@ The local `.npmrc` is ignored by git and must not be committed.
|
|
|
23
30
|
A second token written to the project-root `.npmrc` also authenticated
|
|
24
31
|
successfully as `xuanzhu`, then failed at `npm publish` with the same `E403`.
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
npm run release:publish -- --otp=<6-digit-code>
|
|
30
|
-
```
|
|
33
|
+
A publish-capable npm token written to the project-root `.npmrc` authenticated
|
|
34
|
+
successfully as `xuanzhu`, and `npm run release:publish` then succeeded for
|
|
35
|
+
`yunti-browser-runtime@0.1.0`.
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
alternative, configure a granular npm access token with package publish
|
|
34
|
-
permission and bypass 2FA enabled, then rerun `npm run release:publish`.
|
|
35
|
-
|
|
36
|
-
After publish succeeds, run:
|
|
37
|
+
Verification:
|
|
37
38
|
|
|
38
39
|
```bash
|
|
39
40
|
npm run release:verify-published
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
The verification command returns 0 and confirms the published package metadata.
|
|
44
|
+
|
|
42
45
|
## Resolved Blocker: P4.3 Package URLs
|
|
43
46
|
|
|
44
47
|
Status: resolved on 2026-07-02 with
|
package/docs/SECURITY.md
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
The first release is local single-user software:
|
|
6
6
|
|
|
7
7
|
- bridge binds to `127.0.0.1` by default;
|
|
8
|
-
- bridge routes require
|
|
8
|
+
- local loopback bridge routes do not require a token by default;
|
|
9
|
+
- setting `YUNTI_BROWSER_BRIDGE_TOKEN` enables `x-yunti-browser-token` checks
|
|
10
|
+
for bridge routes, and binding to a non-loopback host requires a token;
|
|
9
11
|
- CORS only echoes local debug origins and browser-extension origins by default;
|
|
10
12
|
- extension talks to the local bridge;
|
|
11
13
|
- MCP tools default to `userId=local`;
|
|
@@ -24,7 +26,7 @@ The extension uses browser permissions required for automation:
|
|
|
24
26
|
|
|
25
27
|
- `tabs` for tab inventory and activation;
|
|
26
28
|
- `debugger` for CDP commands;
|
|
27
|
-
- `storage` for local bridge URL, token, user, and page-match settings;
|
|
29
|
+
- `storage` for local bridge URL, optional token, user, and page-match settings;
|
|
28
30
|
- `webRequest` for sanitized network observations;
|
|
29
31
|
- `activeTab` for visible-tab capture fallback paths.
|
|
30
32
|
|
|
@@ -52,5 +54,5 @@ Users should only load the extension from a trusted local checkout.
|
|
|
52
54
|
- Remote multi-user isolation layer.
|
|
53
55
|
- Per-tool allow/deny policy.
|
|
54
56
|
|
|
55
|
-
Remote deployment must be designed separately and
|
|
56
|
-
|
|
57
|
+
Remote deployment must be designed separately and must not use the local
|
|
58
|
+
no-token loopback default without an authentication and ownership model.
|
package/extension/manifest.json
CHANGED
package/mcp/http-server.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import http from "node:http"
|
|
2
|
-
import { randomUUID } from "node:crypto"
|
|
3
2
|
import {
|
|
4
3
|
BridgeHub,
|
|
5
4
|
DEFAULT_SESSION_TTL_MS,
|
|
@@ -88,6 +87,11 @@ function isAllowedOrigin(origin, allowOrigins) {
|
|
|
88
87
|
return allowOrigins.some((pattern) => originMatchesPattern(origin, pattern))
|
|
89
88
|
}
|
|
90
89
|
|
|
90
|
+
function isLoopbackHost(host) {
|
|
91
|
+
const value = String(host || "").trim().toLowerCase()
|
|
92
|
+
return value === "127.0.0.1" || value === "localhost" || value === "::1" || value === "[::1]"
|
|
93
|
+
}
|
|
94
|
+
|
|
91
95
|
function bridgeCorsHeaders(req, allowOrigins) {
|
|
92
96
|
const origin = String(req.headers.origin || "")
|
|
93
97
|
const headers = {
|
|
@@ -117,20 +121,33 @@ function bridgeRequestToken(req) {
|
|
|
117
121
|
return match ? normalizeBridgeToken(match[1]) : ""
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
function isAuthorizedBridgeRequest(req, bridgeToken) {
|
|
124
|
+
function isAuthorizedBridgeRequest(req, bridgeToken, authRequired = true) {
|
|
125
|
+
if (!authRequired) return true
|
|
121
126
|
const token = normalizeBridgeToken(bridgeToken)
|
|
122
127
|
return Boolean(token && bridgeRequestToken(req) === token)
|
|
123
128
|
}
|
|
124
129
|
|
|
125
|
-
function
|
|
130
|
+
function authInfo(authRequired) {
|
|
131
|
+
return {
|
|
132
|
+
required: Boolean(authRequired),
|
|
133
|
+
header: BRIDGE_TOKEN_HEADER,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function healthWithAuth(value, authRequired) {
|
|
138
|
+
return {
|
|
139
|
+
...value,
|
|
140
|
+
authorized: true,
|
|
141
|
+
auth: authInfo(authRequired),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function limitedHealth(authRequired) {
|
|
126
146
|
return {
|
|
127
147
|
ok: true,
|
|
128
148
|
name: "yunti-browser-runtime-bridge",
|
|
129
149
|
authorized: false,
|
|
130
|
-
auth:
|
|
131
|
-
required: true,
|
|
132
|
-
header: BRIDGE_TOKEN_HEADER,
|
|
133
|
-
},
|
|
150
|
+
auth: authInfo(authRequired),
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
153
|
|
|
@@ -173,7 +190,13 @@ export async function startBridgeServer({
|
|
|
173
190
|
const configuredBridgeToken = normalizeBridgeToken(
|
|
174
191
|
bridgeToken ?? process.env.YUNTI_BROWSER_BRIDGE_TOKEN
|
|
175
192
|
)
|
|
176
|
-
const
|
|
193
|
+
const authRequired = Boolean(configuredBridgeToken)
|
|
194
|
+
if (!authRequired && !isLoopbackHost(host)) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
"YUNTI_BROWSER_BRIDGE_TOKEN is required when binding the bridge to a non-loopback host"
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
const activeBridgeToken = configuredBridgeToken
|
|
177
200
|
const hub = new BridgeHub({ sessionTtlMs })
|
|
178
201
|
const cleanupTimer = setInterval(() => {
|
|
179
202
|
hub.cleanupExpiredSessions()
|
|
@@ -182,7 +205,7 @@ export async function startBridgeServer({
|
|
|
182
205
|
const server = http.createServer(async (req, res) => {
|
|
183
206
|
try {
|
|
184
207
|
const url = new URL(req.url || "/", `http://${host}:${port}`)
|
|
185
|
-
const authorized = isAuthorizedBridgeRequest(req, activeBridgeToken)
|
|
208
|
+
const authorized = isAuthorizedBridgeRequest(req, activeBridgeToken, authRequired)
|
|
186
209
|
|
|
187
210
|
if (req.method === "OPTIONS") {
|
|
188
211
|
const origin = String(req.headers.origin || "")
|
|
@@ -195,7 +218,9 @@ export async function startBridgeServer({
|
|
|
195
218
|
req,
|
|
196
219
|
res,
|
|
197
220
|
200,
|
|
198
|
-
authorized
|
|
221
|
+
authorized
|
|
222
|
+
? healthWithAuth(hub.health({ userId: url.searchParams.get("userId") }), authRequired)
|
|
223
|
+
: limitedHealth(authRequired),
|
|
199
224
|
{ allowOrigins }
|
|
200
225
|
)
|
|
201
226
|
return
|
|
@@ -209,7 +234,7 @@ export async function startBridgeServer({
|
|
|
209
234
|
{
|
|
210
235
|
ok: false,
|
|
211
236
|
error: `missing or invalid ${BRIDGE_TOKEN_HEADER}`,
|
|
212
|
-
auth:
|
|
237
|
+
auth: authInfo(authRequired),
|
|
213
238
|
},
|
|
214
239
|
{ allowOrigins }
|
|
215
240
|
)
|
|
@@ -320,7 +345,7 @@ export async function startBridgeServer({
|
|
|
320
345
|
if (mode === "proxy") {
|
|
321
346
|
clearInterval(cleanupTimer)
|
|
322
347
|
server.close()
|
|
323
|
-
return { mode: "proxy", host, port, bridgeToken: configuredBridgeToken, allowOrigins, hub: null, server: null }
|
|
348
|
+
return { mode: "proxy", host, port, bridgeToken: configuredBridgeToken, authRequired, allowOrigins, hub: null, server: null }
|
|
324
349
|
}
|
|
325
|
-
return { mode: "owner", host, port, bridgeToken: activeBridgeToken, allowOrigins, hub, server }
|
|
350
|
+
return { mode: "owner", host, port, bridgeToken: activeBridgeToken, authRequired, allowOrigins, hub, server }
|
|
326
351
|
}
|
package/mcp/server.js
CHANGED
|
@@ -215,12 +215,17 @@ export async function runStdio() {
|
|
|
215
215
|
`[yunti-browser-runtime] bridge ${bridge.mode} on http://${bridge.host}:${bridge.port}`
|
|
216
216
|
)
|
|
217
217
|
if (bridge.mode === "owner") {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
if (bridge.authRequired) {
|
|
219
|
+
console.error(
|
|
220
|
+
`[yunti-browser-runtime] bridge token header ${BRIDGE_TOKEN_HEADER}: ${bridge.bridgeToken}`
|
|
221
|
+
)
|
|
222
|
+
} else {
|
|
223
|
+
console.error("[yunti-browser-runtime] bridge auth disabled for local loopback")
|
|
224
|
+
console.error("[yunti-browser-runtime] set YUNTI_BROWSER_BRIDGE_TOKEN to require a local token")
|
|
225
|
+
}
|
|
221
226
|
} else if (!bridge.bridgeToken) {
|
|
222
227
|
console.error(
|
|
223
|
-
`[yunti-browser-runtime] proxy mode
|
|
228
|
+
`[yunti-browser-runtime] proxy mode will connect without a bridge token; set YUNTI_BROWSER_BRIDGE_TOKEN if the running bridge requires one`
|
|
224
229
|
)
|
|
225
230
|
}
|
|
226
231
|
}
|
|
@@ -249,12 +254,17 @@ export async function runBridgeOnly() {
|
|
|
249
254
|
`[yunti-browser-runtime] bridge ${bridge.mode} on http://${bridge.host}:${bridge.port}`
|
|
250
255
|
)
|
|
251
256
|
if (bridge.mode === "owner") {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
if (bridge.authRequired) {
|
|
258
|
+
console.error(
|
|
259
|
+
`[yunti-browser-runtime] bridge token header ${BRIDGE_TOKEN_HEADER}: ${bridge.bridgeToken}`
|
|
260
|
+
)
|
|
261
|
+
} else {
|
|
262
|
+
console.error("[yunti-browser-runtime] bridge auth disabled for local loopback")
|
|
263
|
+
console.error("[yunti-browser-runtime] set YUNTI_BROWSER_BRIDGE_TOKEN to require a local token")
|
|
264
|
+
}
|
|
255
265
|
} else if (!bridge.bridgeToken) {
|
|
256
266
|
console.error(
|
|
257
|
-
`[yunti-browser-runtime] proxy mode
|
|
267
|
+
`[yunti-browser-runtime] proxy mode will connect without a bridge token; set YUNTI_BROWSER_BRIDGE_TOKEN if the running bridge requires one`
|
|
258
268
|
)
|
|
259
269
|
}
|
|
260
270
|
await new Promise(() => {})
|
package/package.json
CHANGED
package/scripts/doctor.js
CHANGED
|
@@ -58,12 +58,14 @@ async function checkBridge() {
|
|
|
58
58
|
`${bridgeUrl}/health?userId=${encodeURIComponent(routeUserId)}`
|
|
59
59
|
)
|
|
60
60
|
const authorized = health.json?.authorized !== false
|
|
61
|
+
const authRequired = health.json?.auth?.required !== false && Boolean(health.json?.auth?.required)
|
|
61
62
|
const sessions = Array.isArray(health.json?.sessions) ? health.json.sessions : []
|
|
62
63
|
const activeSessionId = health.json?.activeSessionId || null
|
|
63
64
|
return {
|
|
64
65
|
ok: health.status === 200 && authorized,
|
|
65
66
|
reachable: health.status === 200,
|
|
66
67
|
authorized,
|
|
68
|
+
authRequired,
|
|
67
69
|
status: health.status,
|
|
68
70
|
url: bridgeUrl,
|
|
69
71
|
tokenConfigured: Boolean(bridgeToken),
|
|
@@ -81,6 +83,7 @@ async function checkBridge() {
|
|
|
81
83
|
ok: false,
|
|
82
84
|
reachable: false,
|
|
83
85
|
authorized: false,
|
|
86
|
+
authRequired: Boolean(bridgeToken),
|
|
84
87
|
url: bridgeUrl,
|
|
85
88
|
tokenConfigured: Boolean(bridgeToken),
|
|
86
89
|
tokenHeader: bridgeTokenHeader,
|
|
@@ -110,8 +113,8 @@ function buildNextSteps(checks) {
|
|
|
110
113
|
if (!checks.bridge.reachable) {
|
|
111
114
|
steps.push("Start the local bridge with npm run bridge.")
|
|
112
115
|
}
|
|
113
|
-
if (checks.bridge.reachable && !checks.bridge.authorized) {
|
|
114
|
-
steps.push("Set YUNTI_BROWSER_BRIDGE_TOKEN to the
|
|
116
|
+
if (checks.bridge.reachable && checks.bridge.authRequired && !checks.bridge.authorized) {
|
|
117
|
+
steps.push("Set YUNTI_BROWSER_BRIDGE_TOKEN to the token used by the running bridge.")
|
|
115
118
|
steps.push("Save the same token in the extension popup.")
|
|
116
119
|
}
|
|
117
120
|
if (checks.bridge.ok && !checks.bridge.extensionConnected) {
|
|
@@ -131,7 +134,7 @@ function humanSummary(report) {
|
|
|
131
134
|
`Yunti Browser Runtime doctor: ${report.ok ? "OK" : "needs attention"}`,
|
|
132
135
|
`- Node: ${report.checks.node.version} (${report.checks.node.ok ? "ok" : "requires >=22"})`,
|
|
133
136
|
`- Bridge: ${report.checks.bridge.reachable ? report.checks.bridge.url : "not reachable"}`,
|
|
134
|
-
`- Token: ${report.checks.bridge.authorized ? "valid" : "missing or invalid"}`,
|
|
137
|
+
`- Token: ${report.checks.bridge.authRequired ? report.checks.bridge.authorized ? "valid" : "missing or invalid" : "not required for local loopback"}`,
|
|
135
138
|
`- Sessions: ${report.checks.bridge.visibleSessionCount} visible, active ${report.checks.bridge.activeSessionId || "none"}`,
|
|
136
139
|
`- Extension: ${report.checks.bridge.extensionConnected ? "connected" : "not detected"}`,
|
|
137
140
|
`- MCP server: ${report.checks.mcpServer.ok ? "found" : "missing"}`,
|
package/scripts/print-config.js
CHANGED
|
@@ -77,7 +77,7 @@ function buildConfig(agent) {
|
|
|
77
77
|
tokenConfigured: bridgeTokenConfigured,
|
|
78
78
|
tokenInstruction: bridgeTokenConfigured
|
|
79
79
|
? "Token is configured in the current environment; copy the same secret into your agent and extension settings without committing it."
|
|
80
|
-
: "
|
|
80
|
+
: "Local loopback bridge auth is disabled by default; set YUNTI_BROWSER_BRIDGE_TOKEN only when you want to require a local token.",
|
|
81
81
|
},
|
|
82
82
|
skill: {
|
|
83
83
|
sourcePath: skillPath,
|