bug: Windows 启动时重复注册深链协议触发 360 文件关联拦截
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 401
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## 问题描述 / What happened
Windows 打包版 Cindy 启动时,360 安全卫士的“注册表防护”会提示:
> 有程序正在修改文件关联
被拦截的目标为:
```text
HKEY_CURRENT_USER\Software\Classes\CINDY\SHELL\OPEN\COMMAND
```
写入值形如:
```text
"<当前 Cindy.exe 的绝对路径>" "%1"
```
该告警与 #215 中启动阶段调用 PowerShell 的告警不是同一条链路。即使移除 Claude orphan scan 和 Start Menu shortcut cleanup 的 PowerShell 实现,该注册表防护仍会出现。
### 实际行为
- 每次启动 packaged `Cindy.exe` 都会尝试注册 `cindy://` 和历史兼容的 `xdt-maker://`。
- 当测试包位于不同目录时,`Software\Classes\\shell\open\command` 会被改写为最新启动的 exe 路径。
- 360 将自定义 URL scheme 注册归类为“修改文件关联”,从而在正常启动阶段拦截。
- 如果用户选择阻止,Cindy 主程序仍可启动,但系统级 `cindy://` / `xdt-maker://` 深链可能无法唤起当前版本。
### 期望行为
- 保留 `cindy://` 与 `xdt-maker://` 深链能力。
- 正常启动 Cindy 时不修改文件关联,不触发 360 注册表防护。
- 协议注册应由安装/升级流程一次性完成,运行时只进行只读检查;关联损坏时再由用户主动触发修复。
## 根本原因 / Root cause
当前调用链如下:
```text
bootstrap-electron.ts 模块初始化
-> registerDeepLinkProtocol()
-> 遍历 DEEP_LINK_SCHEMES(cindy、xdt-maker)
-> packaged 分支无条件调用 app.setAsDefaultProtocolClient(scheme)
-> Electron 写入 HKCU\Software\Classes\\shell\open\command
-> 360 判定为文件关联修改
```
相关源码:
- `apps/desktop/src/main/bootstrap-electron.ts`
- 在 `app.whenReady()` 之前无条件调用 `registerDeepLinkProtocol()`。
- `apps/desktop/src/main/deepLink.ts`
- `registerDeepLinkProtocol()` 对 `DEEP_LINK_SCHEMES` 中的每个 scheme 调用 `app.setAsDefaultProtocolClient()`。
- packaged 分支没有先调用 `app.isDefaultProtocolClient()`,也没有区分安装版与直接运行的 package 目录。
- `apps/desktop/src/shared/deepLinkSchemes.ts`
- scheme 来源为主协议 `cindy` 加历史协议 `xdt-maker`。
- `apps/desktop/forge.config.ts`
- `packagerConfig.protocols` 主要用于 macOS `Info.plist`;Windows 仍依赖运行时注册。
- `apps/desktop/resources/installer.nsh`
- 当前 `customInstall` 会写文件夹右键菜单及 `.cshare` / `.xdtshare` shell verb,但没有注册 `Software\Classes\cindy`、`Software\Classes\xdt-maker` 或 `URL Protocol`。
Electron 官方文档也说明 `app.setAsDefaultProtocolClient()` 在 Windows 上通过注册表实现:
https://www.electronjs.org/docs/latest/api/app#appsetasdefaultprotocolclientprotocol-path-args
## 环境 / Environment
- Cindy 版本或 commit / version or commit: 在验证 PR #291、commit `b17715872fd1370143cea146775e71ba46103ce7` 的 Windows package 时确认;相关深链注册代码为既有逻辑,不由该 PR 引入
- 平台与版本 / platform & OS version: Windows 11 x64
- 安全软件: 360 安全卫士,注册表防护开启
- 安装方式 / install method: `pnpm --filter desktop package --platform=win32 --arch=x64` 产生的 unpacked packaged directory
- 签名状态: 本地测试产物 `Cindy.exe` 为 `NotSigned`
## 复现步骤 / Steps to reproduce
1. 在 Windows 上执行:
```text
pnpm --filter desktop package --platform=win32 --arch=x64
```
2. 保持 360 安全卫士的注册表防护开启。
3. 从 package 输出目录启动 `Cindy.exe`。
4. 观察 360 提示 Cindy 正在修改文件关联,目标为:
```text
HKCU\Software\Classes\CINDY\SHELL\OPEN\COMMAND
```
5. 将 package 复制到另一个目录并再次启动;注册表 command 会改指向新的 `Cindy.exe` 路径,再次触发告警。
6. 可使用以下只读命令验证两个 handler 当前指向:
```text
reg query "HKCU\Software\Classes\cindy\shell\open\command" /ve
reg query "HKCU\Software\Classes\xdt-maker\shell\open\command" /ve
```
## 影响 / Impact
- 用户会认为 Cindy 启动时正在劫持文件关联,与 #215 要解决的安全软件误报体验目标相冲突。
- 点击“阻止”后,系统深链可能失效。
- 直接运行不同目录下的 portable/package 测试包时,各副本会反复争抢协议 handler。
- 因为同时注册主协议和历史协议,安全软件可能对两个 registry tree 分别观察到写入。
- 未签名的本地 packaged executable 更容易被安全软件按启发式规则拦截,但签名本身不能消除“每次启动写关联”的设计问题。
## 建议修复方向 / Proposed direction
1. 在 NSIS `customInstall` 中为 `cindy` 和 `xdt-maker` 一次性注册 URL protocol,command 指向稳定的 `$INSTDIR\${APP_EXECUTABLE_FILENAME}`。
2. 在 `customUnInstall` 中仅清理由当前安装拥有的协议键,避免误删被其他并存安装接管的 handler。
3. Windows packaged 应用正常启动时不再调用 `setAsDefaultProtocolClient()`:
- 只用 `isDefaultProtocolClient()` 做只读检查;或
- 完全信任安装器注册。
4. 如果关联缺失,在设置中提供用户主动触发的“修复深链关联”,不要在每次启动时静默修复。
5. 正式发布时为 installer、uninstaller 和 `Cindy.exe` 添加 Authenticode 签名,降低安装阶段的启发式误报。
6. 对直接运行的 `electron-forge package` 目录默认不抢占系统协议;需要验证深链解析时可直接传 argv:
```text
Cindy.exe "cindy://session/"
```
## 验收建议 / Acceptance criteria
- 安装正式 NSIS 包后,`cindy://` 和 `xdt-maker://` 均能唤起 Cindy。
- 连续正常启动 Cindy 多次,不再写入协议关联注册表项。
- 升级且安装目录不变时,无需运行时重新注册。
- 用户阻止/删除关联后,应用不会在下次启动时静默抢回;可通过明确的用户操作修复。
- 卸载只删除仍指向当前安装目录的 handler,不影响同机其他 Cindy 区域版本或历史安装。
- Windows 启动时不再触发 360 的“有程序正在修改文件关联”提示。
## 日志与截图 / Logs & screenshots
360 告警截图显示的核心信息已脱敏转录如下:
```text
风险程序: \Cindy.exe
目标: HKEY_CURRENT_USER\Software\Classes\CINDY\SHELL\OPEN\COMMAND
新值: "\Cindy.exe" "%1"
分类: 注册表防护 / 修改文件关联
```
Contributor guide
Research direction
Start with apps/desktop/src/main/bootstrap-electron.ts and deepLink.ts, then compare shared/deepLinkSchemes.ts with resources/installer.nsh and forge.config.ts. Reproduce the packaged Windows launch and inspect both handlers with the supplied reg query commands. Done means the installer owns both protocols, repeated launches perform no registry writes, and uninstall preserves handlers owned by other installations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron, typescript
- Domain
- desktop, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100