cicy-desktop 2.1.363 → 2.1.365

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.363",
3
+ "version": "2.1.365",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -0,0 +1,41 @@
1
+ // Copyright 2026 CiCy AI
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ // document-start 注入:让页面认为这台机器没有可用的通行密钥验证器,从而不弹
5
+ // Windows 的「请将安全密钥插入 USB 端口」系统模态框(会卡死矩阵里的自动登录)。
6
+ //
7
+ // 关键:必须改**主世界**的 navigator.credentials / PublicKeyCredential。
8
+ // 这个文件既作普通窗口的 session 预加载,也作 <webview> 客机的 preload,两种场景
9
+ // 大多 contextIsolation=true —— 预加载跑在隔离世界,直接改 navigator 改的是隔离世界的副本,
10
+ // 页面(主世界)根本不受影响(实测 2.1.364:webview 里 credentials.get 仍是原生,照弹框)。
11
+ // 所以统一走「往文档里插一个 <script> 让补丁在主世界执行」,隔离/主世界都能生效,
12
+ // 且 document-start 时插入 = 先于页面脚本。
13
+ (function () {
14
+ const patch = function () {
15
+ try {
16
+ if (window.PublicKeyCredential) {
17
+ const no = function () { return Promise.resolve(false); };
18
+ try { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = no; } catch (e) {}
19
+ try { PublicKeyCredential.isConditionalMediationAvailable = no; } catch (e) {}
20
+ try { if (PublicKeyCredential.getClientCapabilities) PublicKeyCredential.getClientCapabilities = function () { return Promise.resolve({}); }; } catch (e) {}
21
+ }
22
+ if (navigator.credentials) {
23
+ const get = navigator.credentials.get ? navigator.credentials.get.bind(navigator.credentials) : null;
24
+ const create = navigator.credentials.create ? navigator.credentials.create.bind(navigator.credentials) : null;
25
+ const deny = function () { return Promise.reject(new DOMException("The operation either timed out or was not allowed.", "NotAllowedError")); };
26
+ if (get) navigator.credentials.get = function (o) { return (o && o.publicKey) ? deny() : get(o); };
27
+ if (create) navigator.credentials.create = function (o) { return (o && o.publicKey) ? deny() : create(o); };
28
+ }
29
+ } catch (e) { /* 不能影响页面 */ }
30
+ };
31
+ try {
32
+ // 主世界执行:把补丁函数体塞进一个 <script>,插到 documentElement 上同步运行,随即移除。
33
+ const el = document.createElement("script");
34
+ el.textContent = "(" + patch.toString() + ")();";
35
+ (document.documentElement || document.head || document).appendChild(el);
36
+ el.remove();
37
+ } catch (e) {
38
+ // 万一 DOM 还没有(极早期),退回直接在当前世界打一遍(contextIsolation=false 时这就够了)
39
+ try { patch(); } catch (e2) {}
40
+ }
41
+ })();
package/src/main.js CHANGED
@@ -24,6 +24,39 @@ const appUpdater = require("./app-updater");
24
24
  // and guests fell back to the OS-native menu; this unifies them and adds 重新加载
25
25
  // + 切换开发者工具 + 检查元素 everywhere (see utils/context-menu-options.js).
26
26
  const { attachContextMenu } = require("./utils/context-menu-options");
27
+ // 每个 session 一建出来就挂上 no-passkey:document-start 运行,先于页面脚本,
28
+ // 这样站点探测通行密钥时拿到的就是「没有验证器」,Windows 那个系统模态框不会弹。
29
+ // 放在 session-created 而不是逐个分区手动注册 —— 矩阵有上百个 persist:sandbox-N,
30
+ // 而且新建 profile 随时会多出来,手动注册必然漏。
31
+ const NO_PASSKEY = require("path").join(__dirname, "inject", "no-passkey.js");
32
+ function armNoPasskey(ses) {
33
+ try {
34
+ const has = (ses.getPreloadScripts ? ses.getPreloadScripts() : [])
35
+ .some((x) => String((x && x.filePath) || "").endsWith("no-passkey.js"));
36
+ if (!has) ses.registerPreloadScript({ type: "frame", filePath: NO_PASSKEY });
37
+ } catch (_) {}
38
+ }
39
+ electronApp.on("session-created", armNoPasskey);
40
+ // <webview> 客机不吃 session 级预加载(Electron 机制:那只对普通窗口/BrowserView 生效),
41
+ // 得在客机 attach 时把 no-passkey 设成它自己的 preload,否则矩阵格子(全是 webview)里
42
+ // 的 FB 页照样能调 navigator.credentials.get → Windows 弹安全密钥框(实测 2.1.364)。
43
+ // preload 内部用「插 <script> 改主世界」,所以不用动客机的 contextIsolation。
44
+ electronApp.on("web-contents-created", (_e, wc) => {
45
+ try {
46
+ wc.on("will-attach-webview", (_ev, webPreferences) => {
47
+ try {
48
+ const cur = webPreferences.preload;
49
+ // 客机原本没有 preload 才设;真有的话不覆盖别人的,改到 preloadScripts 里追加
50
+ if (!cur) webPreferences.preload = NO_PASSKEY;
51
+ } catch (_) {}
52
+ });
53
+ } catch (_) {}
54
+ });
55
+ // 默认会话可能在上面这个监听器挂上之前就已经建好了(main.js 里别处也踩过同一个坑,
56
+ // 见启动时那段「默认会话强制直连」的保险),所以 ready 之后再补一次。幂等。
57
+ electronApp.whenReady().then(() => {
58
+ try { armNoPasskey(require("electron").session.defaultSession); } catch (_) {}
59
+ });
27
60
  electronApp.on("web-contents-created", (_e, wc) => {
28
61
  attachContextMenu(wc);
29
62
  // Security backstop for window.open. createWindow (setupWindowHandlers) and the
@@ -101,7 +134,9 @@ electronApp.commandLine.appendSwitch("remote-allow-origins", "*");
101
134
  // 「使用密钥登录 · 请将安全密钥插入 USB 端口」这个系统模态框 —— 机器上根本没有安全密钥,
102
135
  // 框又是系统级的、页面脚本关不掉,自动登录就卡死在那儿(实测 2026-09-21,FB #96)。
103
136
  // 关掉这个特性后 navigator.credentials / PublicKeyCredential 直接不存在,站点自动回落到密码。
104
- electronApp.commandLine.appendSwitch("disable-features", "WebAuthentication,WebAuthenticationRemoteDesktopSupport");
137
+ // 注:这里**不要**再写 disable-features=WebAuthentication —— 那个 feature 名在现在的
138
+ // Chromium 里已经没有了,开关传得进去但毫无作用(2.1.362 实测)。真正起作用的是下面
139
+ // session-created 里注册的 no-passkey 注入脚本。
105
140
  if (process.platform === "linux") {
106
141
  process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
107
142
  // electronApp.commandLine.appendSwitch("disable-setuid-sandbox");