Skip to content

fix(upstream #110): 回环免 token 例外钉到扩展的稳定 Origin(安全修复) - #12

Merged
graydovee merged 4 commits into
mainfrom
upstream/110-extension-origin-pin
Oct 19, 2026
Merged

graydovee merged 4 commits into
mainfrom
upstream/110-extension-origin-pin

Conversation

@graydovee

Copy link
Copy Markdown
Owner

原始 PR / Upstream PR

概述 / Summary

修复本地权限提升:回环免 token 的例外不再按 chrome-extension:// 前缀放行,而是精确匹配本扩展由 manifest key 派生的稳定 Origin。

动机 / Motivation

桥原来对回环连接使用 origin.startsWith('chrome-extension://') 判断是否免 token。于是任何已安装的扩展都能用自己的 Origin 免 token 连上本机 dsh 的桥,调用全套 browser_* 工具——这是一个本地权限提升面。要让「按扩展身份信任」成立,扩展 id 必须先稳定:加固定 key 后 Chromium 从它派生 id(ampcoeplakeelcoengijbfbhjnlbdign),跨机器、跨重装一致。

改动内容 / Changes

  • extensions/dsh-browser/manifest.json 增加 stable key(公开 SPKI),扩展 id 从此固定。
  • 新增 src/extension-origins.ts:固定 id 常量 + 由 key 派生的辅助函数(非法 DER 显式失败,不"凑"一个看似合理的 id)。
  • Config.trustedExtensionOrigins:默认只含随附扩展 Origin;条目必须是精确的 chrome-extension://<32 位 a–p>,[] 为加固模式(全部强制 token),非法条目启动即失败;挂载时把生效白名单写入日志。
  • server.ts:免 token 判定改为 loopback && 白名单精确匹配。
  • 文档:桥 README 中英安全模型重写——manifest key 是公开的,因此该钉住只防误连、不防刻意复制 key 的冒充;回环上终结连接的反向代理(tailscale serve、ssh -L、nginx)需用加固模式。
  • 适配:manifest.firefox.json、extensions/dsh-browser/package.json、manifest.json 的版本号保留 fork 的 0.1.6(版本 bump 不进 fork);install.sh / install.ps1 与根 README 的「从 0.1.5 之前的版本升级」改为「从旧构建升级」,与本 fork 的版本线对齐;桥 README 冲突保留 fork 的 engines 段落与信标/CORS 安全条目,并并入 trustedExtensionOrigins 配置行。

测试 / Testing

  • pnpm run typecheck / pnpm run check:runtime / pnpm run build 通过。
  • pnpm run test:桥插件 220 用例、扩展 586 用例全过。其中
    • tests/extension-origins.spec.ts 新增漂移守卫(manifest key 必须哈希出固定 id,畸形 key 被拒绝);
    • tests/index.spec.ts / tests/server.spec.ts 覆盖默认值、非法条目、空名单、未列出扩展必须带 token;
    • e2e(tests/e2e/bridge-extension.e2e.spec.ts)在本机真实 Chromium 上跑通 2 个用例:构建出的扩展确实派生出固定 id 并仅凭 Origin 完成免 token hello,另一例走配对 token。
  • node scripts/smoke-runtime.mjs:通过(真机 dsh web,全链路)。

影响范围 / Impact

  • 安全边界变化:免 token 只授予本扩展;其它扩展必须带 token。非回环连接一律需要 token。
  • 升级影响:key 落地后扩展 id 会变一次,按 id 存的设置(远程桥地址、token)需重新填写——README 与两个安装脚本已注明;重跑 scripts/install.sh 并重启 dsh 生效。
  • 平台:Chrome(id 派生);Firefox 的 moz-extension:// Origin 一直是每次安装的 UUID,本来就必须带 token,行为不变。

changelog

  • 中文:* 2026-10-19 [Fix] 安全修复:本机免 token 连接现在只授予本应用自己的扩展(扩展 id 随安装包固定),其它已安装扩展必须携带 token;从旧构建升级时扩展 id 会变更一次(感谢 @chintoleung)。
  • English:* 2026-10-19 [Fix] Security fix: tokenless loopback access is now granted only to this extension (its id is pinned by the installation), so other installed extensions must present the token; upgrading from an older build changes the id once (thanks @chintoleung).

Checklist

  • 已在相关平台实测通过 / Tested on the relevant platforms(单测 + 真实 Chromium e2e + 真机冒烟,macOS)
  • 已通过 pnpm run build 等基础检查 / Passed basic checks
  • 文档已同步更新(如需要)/ Docs updated if needed(桥 README 中英 + 安装脚本提示 + CHANGELOG 中英)
  • 不包含无关改动 / No unrelated changes included

chintoleung and others added 4 commits October 5, 2026 16:25
…le origin

Any installed extension could bypass bearer-token auth by presenting its
own chrome-extension:// Origin (prefix check). The exemption is now an
exact-match allowlist: the shipped extension pins a manifest key so its
Chromium id (ampcoeplakeelcoengijbfbhjnlbdign) is stable across machines
and updates, and that single origin is trusted by default. Every other
origin must present the token; origin trust never applies off loopback.

Hardening from adversarial review:
- trustedExtensionOrigins entries are validated (chrome-extension://<a-p
  32> only) and the effective allowlist is logged at mount; [] is the
  explicit hardened mode. A web-origin entry would have re-granted the
  trust this change removes.
- Docs state the real boundary: the manifest key is public, so the pin
  stops incidental cross-extension access, not deliberate key-cloning;
  hardened deployments pair the token. Loopback-terminating proxies
  (tailscale serve, ssh -L) documented.
- Updating from pre-0.1.5 changes the extension id once (settings are
  per id): READMEs and both installers now say to re-enter a configured
  remote bridge address/token. Extension version bumped to 0.1.5.
- Drift guard: a test fails if the manifest key ever hashes to anything
  but the pinned id; malformed keys are rejected, not munged.
- e2e now proves the pin: real Chromium must derive the pinned id and
  accept a tokenless hello against the default allowlist (plus the
  paired-token path), with a locale-independent harness and loud skips.
…e extension's stable origin

Upstream-PR: omdsh-dev#110
Source: origin/pr/110 @ a118d3a
Author: Toto Leung <to.watery8t@icloud.com>

Conflicts(全部按 fork 现状解决):
- manifest.json / manifest.firefox.json / extensions/dsh-browser/package.json:保留 fork 的 0.1.6,只接受 manifest.json 的 stable `key`(版本 bump 不进 fork)
- packages/browser/bridge-browser/README*:保留 fork 的 engines 段落与信标/CORS 安全条目,并入 trustedExtensionOrigins 配置行与安全模型条目
- src/index.ts:fork 的信标/实例元信息与 PR 的 trustedExtensionOrigins 配置两侧都保留
- tests/index.spec.ts:beacon 与 trustedExtensionOrigins 的断言合并共存
上游按自身版本线写了「从 0.1.5 之前的版本升级」,与本 fork 的版本号(当前 0.1.6,且已发过
0.1.5)对不上。改为「从旧构建升级」,中英与两个安装脚本保持一致,行为不变。
@graydovee
graydovee merged commit a7da076 into main Oct 19, 2026
1 check passed
@graydovee
graydovee deleted the upstream/110-extension-origin-pin branch October 19, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants