@lightharu/krouter 1.8.0

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.
Files changed (61) hide show
  1. package/LICENSE +679 -0
  2. package/README.md +238 -0
  3. package/dist-web/assets/index-CM4-0adf.css +1 -0
  4. package/dist-web/assets/index-DCslvfUR.js +139 -0
  5. package/dist-web/favicon.svg +9 -0
  6. package/dist-web/icon.svg +9 -0
  7. package/dist-web/index.html +19 -0
  8. package/out-server/main/kiroAuthSync.js +249 -0
  9. package/out-server/main/kproxy/certManager.js +262 -0
  10. package/out-server/main/kproxy/index.js +254 -0
  11. package/out-server/main/kproxy/mitmProxy.js +475 -0
  12. package/out-server/main/kproxy/types.js +23 -0
  13. package/out-server/main/proxy/accountPool.js +543 -0
  14. package/out-server/main/proxy/clientConfig.js +596 -0
  15. package/out-server/main/proxy/index.js +25 -0
  16. package/out-server/main/proxy/kiroApi.js +1996 -0
  17. package/out-server/main/proxy/logger.js +407 -0
  18. package/out-server/main/proxy/modelCatalog.js +75 -0
  19. package/out-server/main/proxy/promptCacheTracker.js +301 -0
  20. package/out-server/main/proxy/proxyServer.js +3543 -0
  21. package/out-server/main/proxy/selfSignedCert.js +179 -0
  22. package/out-server/main/proxy/systemProxy.js +250 -0
  23. package/out-server/main/proxy/tokenCounter.js +164 -0
  24. package/out-server/main/proxy/toolNameRegistry.js +57 -0
  25. package/out-server/main/proxy/translator.js +1084 -0
  26. package/out-server/main/proxy/types.js +3 -0
  27. package/out-server/main/registration/browser-identity.js +184 -0
  28. package/out-server/main/registration/chainProxy.js +349 -0
  29. package/out-server/main/registration/config.js +58 -0
  30. package/out-server/main/registration/email-service.js +801 -0
  31. package/out-server/main/registration/fingerprint.js +352 -0
  32. package/out-server/main/registration/http-utils.js +148 -0
  33. package/out-server/main/registration/jwe.js +74 -0
  34. package/out-server/main/registration/names.js +142 -0
  35. package/out-server/main/registration/proton-mail-window.js +339 -0
  36. package/out-server/main/registration/registrar.js +1715 -0
  37. package/out-server/main/registration/tlsClientPool.js +70 -0
  38. package/out-server/main/registration/xxtea.js +161 -0
  39. package/out-server/main/runtimePaths.js +19 -0
  40. package/out-server/main/utils/redact.js +95 -0
  41. package/out-server/server/index.js +1272 -0
  42. package/out-server/server/services/accountExtras.js +105 -0
  43. package/out-server/server/services/accountProfileHydration.js +95 -0
  44. package/out-server/server/services/authFlows.js +509 -0
  45. package/out-server/server/services/dashboardTunnel.js +315 -0
  46. package/out-server/server/services/diagnostics.js +326 -0
  47. package/out-server/server/services/kiroAccounts.js +431 -0
  48. package/out-server/server/services/kiroSettings.js +260 -0
  49. package/out-server/server/services/kproxyRuntime.js +264 -0
  50. package/out-server/server/services/localKiroCredentials.js +320 -0
  51. package/out-server/server/services/machineIdRuntime.js +327 -0
  52. package/out-server/server/services/protonBrowserRuntime.js +724 -0
  53. package/out-server/server/services/proxyRuntime.js +523 -0
  54. package/out-server/server/services/registrationRuntime.js +106 -0
  55. package/out-server/server/store.js +266 -0
  56. package/package.json +113 -0
  57. package/resources/tls-client-xgo-1.14.0-windows-amd64.dll +0 -0
  58. package/scripts/kiro-manager-cli.cjs +3 -0
  59. package/scripts/krouter-cli.cjs +509 -0
  60. package/src/renderer/src/assets/krouter-logo.svg +11 -0
  61. package/src/renderer/src/assets/krouter-mark.svg +9 -0
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ // TLS 客户端进程级共享池
3
+ //
4
+ // 背景:tlsclientwrapper 的 ModuleClient 内部是 piscina worker pool + 加载 DLL,
5
+ // 每次新建 + open() 大约要 1-3 秒,批量注册时累计可达数十秒。
6
+ // 由于 DLL 路径 / customLibraryPath 在整个应用生命周期内是稳定的,所有 Registrar 完全可以共用同一个 ModuleClient
7
+ // (SessionClient 才是真正"按注册"独立的 TLS 会话/指纹层)。
8
+ //
9
+ // 设计:
10
+ // - 首次 acquireModuleClient() 时 open,之后所有调用直接拿同一个实例
11
+ // - 用 openPromise 防止并发首调导致重复 open
12
+ // - 主进程退出前统一 shutdownTlsClientPool() 释放 worker pool
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.acquireModuleClient = acquireModuleClient;
15
+ exports.isModuleClientReady = isModuleClientReady;
16
+ exports.getModuleClientPoolStats = getModuleClientPoolStats;
17
+ exports.shutdownTlsClientPool = shutdownTlsClientPool;
18
+ const tlsclientwrapper_1 = require("tlsclientwrapper");
19
+ let shared = null;
20
+ let openPromise = null;
21
+ /**
22
+ * 获取共享 ModuleClient。首次调用 open(),之后所有调用都拿同一个实例。
23
+ * 注意:传入的 opts 仅在首次有效;后续调用会忽略 opts 直接复用。
24
+ */
25
+ async function acquireModuleClient(opts) {
26
+ if (shared)
27
+ return shared;
28
+ if (openPromise)
29
+ return openPromise;
30
+ openPromise = (async () => {
31
+ const mc = new tlsclientwrapper_1.ModuleClient(opts);
32
+ await mc.open();
33
+ shared = mc;
34
+ openPromise = null;
35
+ return mc;
36
+ })();
37
+ try {
38
+ return await openPromise;
39
+ }
40
+ catch (err) {
41
+ openPromise = null;
42
+ throw err;
43
+ }
44
+ }
45
+ /** 是否已经开启了共享池(用于诊断 / 日志) */
46
+ function isModuleClientReady() {
47
+ return shared !== null;
48
+ }
49
+ /** 调试用:拿到 piscina 池统计 */
50
+ function getModuleClientPoolStats() {
51
+ return shared ? shared.getPoolStats() : null;
52
+ }
53
+ /**
54
+ * 应用退出前统一清理:terminate 共享 ModuleClient。
55
+ * 带超时保护(5s),避免 DLL 内残留请求把退出卡住。
56
+ */
57
+ async function shutdownTlsClientPool() {
58
+ const mc = shared;
59
+ shared = null;
60
+ openPromise = null;
61
+ if (!mc)
62
+ return;
63
+ try {
64
+ await Promise.race([
65
+ mc.terminate(),
66
+ new Promise((_, reject) => setTimeout(() => reject(new Error('terminate timeout')), 5000))
67
+ ]);
68
+ }
69
+ catch { /* 超时 / piscina 终止错误均忽略 */ }
70
+ }
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshAppJSConfig = refreshAppJSConfig;
4
+ exports.getTESVersion = getTESVersion;
5
+ exports.getIdentifier = getIdentifier;
6
+ exports.getActiveKey = getActiveKey;
7
+ exports.encryptFingerprint = encryptFingerprint;
8
+ const DELTA = 0x9e3779b9 >>> 0;
9
+ const FALLBACK_KEY = [1888420705, 2576816180, 2347232058, 874813317];
10
+ const FALLBACK_VER = '4.0.0';
11
+ const FALLBACK_IDENTIFIER = 'ECdITeCs';
12
+ let cachedKey = null;
13
+ let cachedVersion = '';
14
+ let cachedIdentifier = '';
15
+ let refreshPromise = null;
16
+ function extractFromAppJS(js) {
17
+ let key = null;
18
+ let identifier = '';
19
+ let version = '';
20
+ const keyMatch = js.match(/var\s+\w+\s*=\s*\[(\d+),\s*"([A-Za-z0-9]+)",\s*(\d+),\s*(\d+),\s*(\d+)\]/);
21
+ if (keyMatch) {
22
+ const nums = [keyMatch[1], keyMatch[3], keyMatch[4], keyMatch[5]].map(Number);
23
+ key = [nums[2], nums[0], nums[3], nums[1]];
24
+ identifier = keyMatch[2];
25
+ }
26
+ const verMatch = js.match(/FWCIM_VERSION\s*=\s*"(\d+\.\d+\.\d+)"/);
27
+ if (verMatch) {
28
+ version = verMatch[1];
29
+ }
30
+ return { key, identifier, version };
31
+ }
32
+ async function refreshAppJSConfig(fetchFn) {
33
+ if (cachedKey)
34
+ return;
35
+ if (refreshPromise)
36
+ return refreshPromise;
37
+ refreshPromise = (async () => {
38
+ if (cachedKey)
39
+ return;
40
+ try {
41
+ const resp = await fetchFn('https://us-east-1.signin.aws/assets/js/app.js', {
42
+ headers: {
43
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
44
+ Accept: '*/*',
45
+ Referer: 'https://us-east-1.signin.aws/'
46
+ }
47
+ });
48
+ const js = await resp.text();
49
+ if (js) {
50
+ const result = extractFromAppJS(js);
51
+ if (result.key)
52
+ cachedKey = result.key;
53
+ if (result.identifier)
54
+ cachedIdentifier = result.identifier;
55
+ if (result.version)
56
+ cachedVersion = result.version;
57
+ }
58
+ }
59
+ catch (err) {
60
+ console.log('[xxtea] 下载 app.js 失败:', err);
61
+ }
62
+ if (!cachedKey) {
63
+ console.log('[xxtea] 使用 fallback 密钥');
64
+ cachedKey = [...FALLBACK_KEY];
65
+ }
66
+ if (!cachedVersion)
67
+ cachedVersion = FALLBACK_VER;
68
+ if (!cachedIdentifier)
69
+ cachedIdentifier = FALLBACK_IDENTIFIER;
70
+ })();
71
+ return refreshPromise;
72
+ }
73
+ function getTESVersion() {
74
+ return cachedVersion || FALLBACK_VER;
75
+ }
76
+ function getIdentifier() {
77
+ return cachedIdentifier || FALLBACK_IDENTIFIER;
78
+ }
79
+ function getActiveKey() {
80
+ return cachedKey
81
+ ? [...cachedKey]
82
+ : [...FALLBACK_KEY];
83
+ }
84
+ function xxteaEncryptCore(plaintext, key) {
85
+ if (!plaintext.length)
86
+ return Buffer.alloc(0);
87
+ const n = Math.ceil(plaintext.length / 4);
88
+ const v = new Uint32Array(n);
89
+ for (let i = 0; i < n; i++) {
90
+ let b0 = 0, b1 = 0, b2 = 0, b3 = 0;
91
+ if (4 * i < plaintext.length)
92
+ b0 = plaintext.charCodeAt(4 * i);
93
+ if (4 * i + 1 < plaintext.length)
94
+ b1 = plaintext.charCodeAt(4 * i + 1);
95
+ if (4 * i + 2 < plaintext.length)
96
+ b2 = plaintext.charCodeAt(4 * i + 2);
97
+ if (4 * i + 3 < plaintext.length)
98
+ b3 = plaintext.charCodeAt(4 * i + 3);
99
+ v[i] = (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;
100
+ }
101
+ const rounds = 6 + Math.floor(52 / n);
102
+ let z = v[n - 1];
103
+ let total = 0;
104
+ for (let r = 0; r < rounds; r++) {
105
+ total = (total + DELTA) >>> 0;
106
+ const e = (total >>> 2) & 3;
107
+ for (let p = 0; p < n; p++) {
108
+ const y = v[(p + 1) % n];
109
+ const part1 = ((z >>> 5) ^ (y << 2)) >>> 0;
110
+ const part2 = ((y >>> 3) ^ (z << 4)) >>> 0;
111
+ const group1 = (part1 + part2) >>> 0;
112
+ const part3 = (total ^ y) >>> 0;
113
+ const part4 = (key[(p & 3) ^ e] ^ z) >>> 0;
114
+ const group2 = (part3 + part4) >>> 0;
115
+ const mx = (group1 ^ group2) >>> 0;
116
+ v[p] = (v[p] + mx) >>> 0;
117
+ z = v[p];
118
+ }
119
+ }
120
+ const result = Buffer.alloc(n * 4);
121
+ for (let i = 0; i < n; i++) {
122
+ result[4 * i] = v[i] & 0xff;
123
+ result[4 * i + 1] = (v[i] >>> 8) & 0xff;
124
+ result[4 * i + 2] = (v[i] >>> 16) & 0xff;
125
+ result[4 * i + 3] = (v[i] >>> 24) & 0xff;
126
+ }
127
+ return result;
128
+ }
129
+ /** 加密指纹 JSON: JSON -> CRC32前缀 -> XXTEA加密 -> base64 -> identifier:结果 */
130
+ function encryptFingerprint(jsonStr) {
131
+ const crc = crc32(jsonStr);
132
+ const crcHex = crc.toString(16).toUpperCase().padStart(8, '0');
133
+ const plaintext = crcHex + '#' + jsonStr;
134
+ const key = getActiveKey();
135
+ const encrypted = xxteaEncryptCore(plaintext, key);
136
+ const encoded = encrypted.toString('base64');
137
+ return getIdentifier() + ':' + encoded;
138
+ }
139
+ /** CRC32 (IEEE) */
140
+ function crc32(str) {
141
+ const table = crc32Table();
142
+ let crc = 0xffffffff >>> 0;
143
+ for (let i = 0; i < str.length; i++) {
144
+ crc = ((crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xff]) >>> 0;
145
+ }
146
+ return (crc ^ 0xffffffff) >>> 0;
147
+ }
148
+ let _crc32Table = null;
149
+ function crc32Table() {
150
+ if (_crc32Table)
151
+ return _crc32Table;
152
+ _crc32Table = new Uint32Array(256);
153
+ for (let i = 0; i < 256; i++) {
154
+ let c = i >>> 0;
155
+ for (let j = 0; j < 8; j++) {
156
+ c = c & 1 ? (0xedb88320 ^ (c >>> 1)) >>> 0 : c >>> 1;
157
+ }
158
+ _crc32Table[i] = c;
159
+ }
160
+ return _crc32Table;
161
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getRuntimeUserDataPath = getRuntimeUserDataPath;
7
+ const path_1 = __importDefault(require("path"));
8
+ function getRuntimeUserDataPath() {
9
+ try {
10
+ const electron = require('electron');
11
+ const electronPath = electron.app?.getPath?.('userData');
12
+ if (electronPath)
13
+ return electronPath;
14
+ }
15
+ catch {
16
+ // Running as the VPS web backend without Electron installed.
17
+ }
18
+ return path_1.default.resolve(process.env.KROUTER_DATA_DIR || process.env.KAM_DATA_DIR || process.env.KIRO_RUNTIME_DATA_DIR || process.env.KIRO_WEB_DATA_DIR || '.web-data');
19
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ // 统一敏感信息脱敏工具
3
+ //
4
+ // 用途:在写日志 / 推送日志到 UI 前,把代理账密、token、password、apiKey 等敏感字段脱敏,
5
+ // 避免凭据明文落到磁盘日志或在界面/控制台暴露。
6
+ //
7
+ // 设计:
8
+ // - redactString:处理裸字符串里的代理 URL 账密、Bearer token、长 JWT 串
9
+ // - redactValue:递归处理对象/数组,对"敏感键名"整体打码,并对字符串值套用 redactString
10
+ // - 防御递归深度与循环引用,避免日志路径上出问题
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.redactString = redactString;
13
+ exports.redactValue = redactValue;
14
+ /** 命中即整体打码的敏感键名(小写匹配) */
15
+ const SENSITIVE_KEYS = [
16
+ 'password', 'passwd', 'pwd',
17
+ 'accesstoken', 'access_token',
18
+ 'refreshtoken', 'refresh_token',
19
+ 'idtoken', 'id_token',
20
+ 'bearertoken', 'bearer',
21
+ 'authorization', 'auth',
22
+ 'apikey', 'api_key', 'x-api-key',
23
+ 'clientsecret', 'client_secret',
24
+ 'secret', 'epin', 'cookie', 'set-cookie',
25
+ 'proxyauthorization', 'proxy-authorization'
26
+ ];
27
+ /** 白名单:这些键虽然包含敏感子串但本身是安全的计量/统计字段 */
28
+ const SAFE_KEYS = new Set([
29
+ 'inputtokens', 'outputtokens', 'cachetokens',
30
+ 'cachereadtokens', 'cachewritetokens', 'reasoningtokens',
31
+ 'totaltokens', 'maxtokens', 'tokensused', 'tokencount'
32
+ ]);
33
+ /** 仅保留头尾少量字符,中间打码;过短直接全打码 */
34
+ function maskMiddle(value, head = 3, tail = 2) {
35
+ if (!value)
36
+ return value;
37
+ if (value.length <= head + tail + 2)
38
+ return '***';
39
+ return `${value.slice(0, head)}***${value.slice(-tail)}`;
40
+ }
41
+ /** 脱敏裸字符串中的常见敏感片段 */
42
+ function redactString(input) {
43
+ if (!input)
44
+ return input;
45
+ let out = input;
46
+ // 1) 代理 / 任意 URL 里的 user:pass@host → user:***@host
47
+ out = out.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)([^/\s:@]+):([^/\s@]+)@/gi, (_m, scheme, user) => {
48
+ return `${scheme}${user}:***@`;
49
+ });
50
+ // 2) Authorization: Bearer xxx / Basic xxx
51
+ out = out.replace(/(authorization\s*[:=]\s*)(bearer|basic)\s+([A-Za-z0-9._\-+/=]+)/gi, (_m, p, scheme) => {
52
+ return `${p}${scheme} ***`;
53
+ });
54
+ // 3) JWT(三段 base64url)整体打码
55
+ out = out.replace(/\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\b/g, (m) => maskMiddle(m, 6, 4));
56
+ // 4) 形如 accessToken=xxx / "refreshToken":"xxx" 的内联键值(兜底,针对已被 JSON.stringify 的串)
57
+ out = out.replace(/("?(?:access_?token|refresh_?token|id_?token|password|api_?key|client_?secret|secret|epin)"?\s*[:=]\s*"?)([^",}\s]+)("?)/gi, (_m, prefix, val, suffix) => `${prefix}${maskMiddle(String(val))}${suffix}`);
58
+ return out;
59
+ }
60
+ function isSensitiveKey(key) {
61
+ const k = key.toLowerCase().replace(/[_-]/g, '');
62
+ if (SAFE_KEYS.has(k))
63
+ return false;
64
+ return SENSITIVE_KEYS.some((s) => k === s.replace(/[_-]/g, '') || k.includes(s.replace(/[_-]/g, '')));
65
+ }
66
+ /** 递归脱敏任意值(对象/数组/字符串)。maxDepth 防御过深结构与循环引用 */
67
+ function redactValue(value, maxDepth = 6, seen = new WeakSet()) {
68
+ if (value == null)
69
+ return value;
70
+ if (typeof value === 'string')
71
+ return redactString(value);
72
+ if (typeof value === 'number' || typeof value === 'boolean')
73
+ return value;
74
+ if (maxDepth <= 0)
75
+ return '[depth-limit]';
76
+ if (Array.isArray(value)) {
77
+ return value.map((v) => redactValue(v, maxDepth - 1, seen));
78
+ }
79
+ if (typeof value === 'object') {
80
+ if (seen.has(value))
81
+ return '[circular]';
82
+ seen.add(value);
83
+ const out = {};
84
+ for (const [k, v] of Object.entries(value)) {
85
+ if (isSensitiveKey(k)) {
86
+ out[k] = typeof v === 'string' ? maskMiddle(v) : '***';
87
+ }
88
+ else {
89
+ out[k] = redactValue(v, maxDepth - 1, seen);
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+ return value;
95
+ }