@mseep/anything-analyzer 3.6.50

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 (172) hide show
  1. package/.codeartsdoer/.codebaseignore +0 -0
  2. package/.codeartsdoer/AGENTS.md +12 -0
  3. package/.github/workflows/build.yml +146 -0
  4. package/README.en.md +264 -0
  5. package/README.md +276 -0
  6. package/RELEASE_NOTES.md +16 -0
  7. package/USAGE.md +490 -0
  8. package/color-preview-r3.html +414 -0
  9. package/color-preview.html +414 -0
  10. package/dev-app-update.yml +3 -0
  11. package/electron-builder.yml +36 -0
  12. package/electron.vite.config.ts +40 -0
  13. package/package.json +53 -0
  14. package/report-2026-04-13-copilot-claude-sonnet-4.6.md +955 -0
  15. package/resources/doloffer-logo.png +0 -0
  16. package/resources/entitlements.mac.plist +12 -0
  17. package/resources/icon.ico +0 -0
  18. package/resources/icon.png +0 -0
  19. package/src/main/ai/ai-analyzer.ts +517 -0
  20. package/src/main/ai/crypto-script-extractor.ts +206 -0
  21. package/src/main/ai/data-assembler.ts +205 -0
  22. package/src/main/ai/llm-router.ts +1120 -0
  23. package/src/main/ai/prompt-builder.ts +349 -0
  24. package/src/main/ai/scene-detector.ts +302 -0
  25. package/src/main/capture/capture-engine.ts +130 -0
  26. package/src/main/capture/interaction-recorder.ts +171 -0
  27. package/src/main/capture/js-injector.ts +57 -0
  28. package/src/main/capture/replay-engine.ts +256 -0
  29. package/src/main/capture/storage-collector.ts +76 -0
  30. package/src/main/cdp/cdp-manager.ts +233 -0
  31. package/src/main/db/database.ts +41 -0
  32. package/src/main/db/migrations.ts +235 -0
  33. package/src/main/db/repositories.ts +574 -0
  34. package/src/main/fingerprint/http-spoofing.ts +48 -0
  35. package/src/main/fingerprint/presets.ts +173 -0
  36. package/src/main/fingerprint/profile-generator.ts +115 -0
  37. package/src/main/fingerprint/profile-store.ts +52 -0
  38. package/src/main/index.ts +260 -0
  39. package/src/main/ipc.ts +856 -0
  40. package/src/main/logger.ts +42 -0
  41. package/src/main/mcp/mcp-config.ts +66 -0
  42. package/src/main/mcp/mcp-manager.ts +155 -0
  43. package/src/main/mcp/mcp-server.ts +1038 -0
  44. package/src/main/prompt-templates.ts +170 -0
  45. package/src/main/proxy/ca-manager.ts +204 -0
  46. package/src/main/proxy/cert-download-page.ts +171 -0
  47. package/src/main/proxy/cert-installer.ts +242 -0
  48. package/src/main/proxy/mitm-proxy-config.ts +37 -0
  49. package/src/main/proxy/mitm-proxy-server.ts +1085 -0
  50. package/src/main/proxy/system-proxy.ts +248 -0
  51. package/src/main/session/session-manager.ts +724 -0
  52. package/src/main/tab-manager.ts +582 -0
  53. package/src/main/updater.ts +111 -0
  54. package/src/main/window.ts +235 -0
  55. package/src/preload/hook-script.ts +270 -0
  56. package/src/preload/index.ts +211 -0
  57. package/src/preload/interaction-hook.ts +286 -0
  58. package/src/preload/stealth-script.ts +302 -0
  59. package/src/preload/target-preload.ts +15 -0
  60. package/src/renderer/App.tsx +656 -0
  61. package/src/renderer/components/AiLogDetail.tsx +173 -0
  62. package/src/renderer/components/AiLogList.tsx +101 -0
  63. package/src/renderer/components/AiLogView.module.css +364 -0
  64. package/src/renderer/components/AiLogView.tsx +86 -0
  65. package/src/renderer/components/AnalyzeBar.module.css +79 -0
  66. package/src/renderer/components/AnalyzeBar.tsx +104 -0
  67. package/src/renderer/components/BrowserPanel.module.css +67 -0
  68. package/src/renderer/components/BrowserPanel.tsx +90 -0
  69. package/src/renderer/components/ControlBar.module.css +47 -0
  70. package/src/renderer/components/ControlBar.tsx +205 -0
  71. package/src/renderer/components/HookLog.tsx +132 -0
  72. package/src/renderer/components/InteractionLog.tsx +183 -0
  73. package/src/renderer/components/MCPServerModal.tsx +427 -0
  74. package/src/renderer/components/PromptTemplateModal.tsx +254 -0
  75. package/src/renderer/components/ReportView.module.css +413 -0
  76. package/src/renderer/components/ReportView.tsx +429 -0
  77. package/src/renderer/components/RequestDetail.module.css +191 -0
  78. package/src/renderer/components/RequestDetail.tsx +202 -0
  79. package/src/renderer/components/RequestLog.module.css +69 -0
  80. package/src/renderer/components/RequestLog.tsx +208 -0
  81. package/src/renderer/components/SessionList.module.css +245 -0
  82. package/src/renderer/components/SessionList.tsx +247 -0
  83. package/src/renderer/components/SettingsModal.tsx +100 -0
  84. package/src/renderer/components/StatusBar.module.css +44 -0
  85. package/src/renderer/components/StatusBar.tsx +102 -0
  86. package/src/renderer/components/StorageView.module.css +41 -0
  87. package/src/renderer/components/StorageView.tsx +178 -0
  88. package/src/renderer/components/TabBar.module.css +88 -0
  89. package/src/renderer/components/TabBar.tsx +70 -0
  90. package/src/renderer/components/Titlebar.module.css +254 -0
  91. package/src/renderer/components/Titlebar.tsx +169 -0
  92. package/src/renderer/components/settings/FingerprintSection.tsx +198 -0
  93. package/src/renderer/components/settings/GeneralSection.tsx +164 -0
  94. package/src/renderer/components/settings/LLMSection.tsx +148 -0
  95. package/src/renderer/components/settings/MCPServerSection.tsx +136 -0
  96. package/src/renderer/components/settings/MitmProxySection.tsx +320 -0
  97. package/src/renderer/components/settings/ProxySection.tsx +110 -0
  98. package/src/renderer/css-modules.d.ts +4 -0
  99. package/src/renderer/hooks/useCapture.ts +383 -0
  100. package/src/renderer/hooks/useConfirm.tsx +91 -0
  101. package/src/renderer/hooks/useSession.ts +136 -0
  102. package/src/renderer/hooks/useTabs.ts +103 -0
  103. package/src/renderer/i18n/en.ts +167 -0
  104. package/src/renderer/i18n/index.ts +47 -0
  105. package/src/renderer/i18n/zh.ts +170 -0
  106. package/src/renderer/index.html +12 -0
  107. package/src/renderer/main.tsx +15 -0
  108. package/src/renderer/styles/global.css +144 -0
  109. package/src/renderer/styles/themes/ayu-dark.css +59 -0
  110. package/src/renderer/styles/themes/catppuccin.css +59 -0
  111. package/src/renderer/styles/themes/discord.css +59 -0
  112. package/src/renderer/styles/themes/dracula.css +59 -0
  113. package/src/renderer/styles/themes/github-dark.css +59 -0
  114. package/src/renderer/styles/themes/gruvbox.css +59 -0
  115. package/src/renderer/styles/themes/index.css +11 -0
  116. package/src/renderer/styles/themes/light.css +59 -0
  117. package/src/renderer/styles/themes/nord.css +59 -0
  118. package/src/renderer/styles/themes/one-dark.css +59 -0
  119. package/src/renderer/styles/themes/tokyo-night.css +59 -0
  120. package/src/renderer/styles/tokens.css +137 -0
  121. package/src/renderer/theme.ts +31 -0
  122. package/src/renderer/ui/Badge.module.css +38 -0
  123. package/src/renderer/ui/Badge.tsx +36 -0
  124. package/src/renderer/ui/Button.module.css +142 -0
  125. package/src/renderer/ui/Button.tsx +46 -0
  126. package/src/renderer/ui/Collapse.module.css +49 -0
  127. package/src/renderer/ui/Collapse.tsx +57 -0
  128. package/src/renderer/ui/CopyableBlock.module.css +56 -0
  129. package/src/renderer/ui/CopyableBlock.tsx +42 -0
  130. package/src/renderer/ui/Empty.module.css +19 -0
  131. package/src/renderer/ui/Empty.tsx +34 -0
  132. package/src/renderer/ui/Icons.tsx +346 -0
  133. package/src/renderer/ui/Input.module.css +103 -0
  134. package/src/renderer/ui/Input.tsx +94 -0
  135. package/src/renderer/ui/InputNumber.module.css +68 -0
  136. package/src/renderer/ui/InputNumber.tsx +104 -0
  137. package/src/renderer/ui/Modal.module.css +83 -0
  138. package/src/renderer/ui/Modal.tsx +67 -0
  139. package/src/renderer/ui/Popconfirm.module.css +73 -0
  140. package/src/renderer/ui/Popconfirm.tsx +74 -0
  141. package/src/renderer/ui/Progress.module.css +35 -0
  142. package/src/renderer/ui/Progress.tsx +30 -0
  143. package/src/renderer/ui/Select.module.css +91 -0
  144. package/src/renderer/ui/Select.tsx +100 -0
  145. package/src/renderer/ui/Spinner.module.css +44 -0
  146. package/src/renderer/ui/Spinner.tsx +27 -0
  147. package/src/renderer/ui/Switch.module.css +39 -0
  148. package/src/renderer/ui/Switch.tsx +43 -0
  149. package/src/renderer/ui/Tabs.module.css +76 -0
  150. package/src/renderer/ui/Tabs.tsx +53 -0
  151. package/src/renderer/ui/Tag.module.css +66 -0
  152. package/src/renderer/ui/Tag.tsx +47 -0
  153. package/src/renderer/ui/Timeline.module.css +42 -0
  154. package/src/renderer/ui/Timeline.tsx +29 -0
  155. package/src/renderer/ui/Toast.module.css +99 -0
  156. package/src/renderer/ui/Toast.tsx +90 -0
  157. package/src/renderer/ui/Tooltip.module.css +26 -0
  158. package/src/renderer/ui/Tooltip.tsx +23 -0
  159. package/src/renderer/ui/VirtualTable.module.css +230 -0
  160. package/src/renderer/ui/VirtualTable.tsx +416 -0
  161. package/src/renderer/ui/index.ts +55 -0
  162. package/src/shared/types.ts +695 -0
  163. package/tests/main/ai/crypto-script-extractor.test.ts +281 -0
  164. package/tests/main/ai/llm-router.test.ts +1537 -0
  165. package/tests/main/ai/prompt-builder.test.ts +178 -0
  166. package/tests/main/ai/scene-detector.test.ts +212 -0
  167. package/tests/main/db/migrations.test.ts +134 -0
  168. package/tests/main/release-workflow.test.ts +59 -0
  169. package/tsconfig.json +7 -0
  170. package/tsconfig.node.json +23 -0
  171. package/tsconfig.web.json +24 -0
  172. package/vitest.config.ts +13 -0
@@ -0,0 +1,235 @@
1
+ import { BrowserWindow, nativeImage } from "electron";
2
+ import { join } from "path";
3
+ import { TabManager } from "./tab-manager";
4
+
5
+ /** Custom titlebar height in renderer (px) */
6
+ const TITLEBAR_HEIGHT = 40;
7
+ /** Tab bar height in renderer (px) */
8
+ const TAB_BAR_HEIGHT = 33; // 32px height + 1px border-bottom
9
+
10
+ /**
11
+ * WindowManager — Creates and manages the main BrowserWindow
12
+ * and delegates embedded browser tabs to TabManager.
13
+ */
14
+ export class WindowManager {
15
+ private mainWindow: BrowserWindow | null = null;
16
+ private tabManager: TabManager | null = null;
17
+ /** Browser area height ratio (0.0 ~ 1.0), default 70% */
18
+ private browserRatio = 0.7;
19
+ /** Whether the browser view should be visible */
20
+ private targetViewVisible = true;
21
+
22
+ /**
23
+ * Create the main application window.
24
+ */
25
+ createMainWindow(): BrowserWindow {
26
+ this.mainWindow = new BrowserWindow({
27
+ width: 1400,
28
+ height: 900,
29
+ minWidth: 1024,
30
+ minHeight: 700,
31
+ title: "Anything Analyzer",
32
+ icon: nativeImage.createFromPath(join(__dirname, "../../resources/icon.png")),
33
+ frame: false,
34
+ autoHideMenuBar: true,
35
+ webPreferences: {
36
+ preload: join(__dirname, "../preload/index.js"),
37
+ sandbox: false,
38
+ contextIsolation: true,
39
+ nodeIntegration: false,
40
+ },
41
+ });
42
+
43
+ if (process.env["ELECTRON_RENDERER_URL"]) {
44
+ this.mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch(() => {});
45
+ } else {
46
+ this.mainWindow.loadFile(join(__dirname, "../renderer/index.html")).catch(() => {});
47
+ }
48
+
49
+ return this.mainWindow;
50
+ }
51
+
52
+ /**
53
+ * Initialize the tab manager and create the first (default) tab.
54
+ */
55
+ initTabs(): TabManager {
56
+ if (!this.mainWindow) throw new Error("Main window not created");
57
+
58
+ this.tabManager = new TabManager();
59
+ this.tabManager.init(
60
+ this.mainWindow,
61
+ () => this.calculateTargetBounds(),
62
+ () => this.targetViewVisible,
63
+ );
64
+
65
+ // Create the first tab
66
+ this.tabManager.createTab();
67
+
68
+ // Update bounds when window resizes
69
+ this.mainWindow.on("resize", () => {
70
+ this.tabManager?.updateBounds();
71
+ });
72
+
73
+ return this.tabManager;
74
+ }
75
+
76
+ /**
77
+ * Navigate the active tab to a URL.
78
+ */
79
+ async navigateTo(url: string): Promise<void> {
80
+ const wc = this.tabManager?.getActiveWebContents();
81
+ if (!wc || wc.isDestroyed()) return;
82
+ let normalizedUrl = url;
83
+ if (!/^https?:\/\//i.test(url)) {
84
+ normalizedUrl = `https://${url}`;
85
+ }
86
+ try {
87
+ await wc.loadURL(normalizedUrl);
88
+ } catch (err) {
89
+ console.warn('[WindowManager] navigateTo failed:', (err as Error).message);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Go back in the active tab.
95
+ */
96
+ goBack(): void {
97
+ const wc = this.tabManager?.getActiveWebContents();
98
+ if (wc && !wc.isDestroyed() && wc.canGoBack()) wc.goBack();
99
+ }
100
+
101
+ /**
102
+ * Go forward in the active tab.
103
+ */
104
+ goForward(): void {
105
+ const wc = this.tabManager?.getActiveWebContents();
106
+ if (wc && !wc.isDestroyed() && wc.canGoForward()) wc.goForward();
107
+ }
108
+
109
+ /**
110
+ * Reload the active tab.
111
+ */
112
+ reload(): void {
113
+ const wc = this.tabManager?.getActiveWebContents();
114
+ if (wc && !wc.isDestroyed()) wc.reload();
115
+ }
116
+
117
+ /**
118
+ * Get the main window instance.
119
+ */
120
+ getMainWindow(): BrowserWindow | null {
121
+ return this.mainWindow;
122
+ }
123
+
124
+ /**
125
+ * Get the TabManager instance.
126
+ */
127
+ getTabManager(): TabManager | null {
128
+ return this.tabManager;
129
+ }
130
+
131
+ /** Propagate app shutdown state to tab manager. */
132
+ setShuttingDown(shuttingDown: boolean): void {
133
+ this.tabManager?.setShuttingDown(shuttingDown);
134
+ }
135
+
136
+ /**
137
+ * Get the active tab's WebContents (for backward compatibility).
138
+ */
139
+ getTargetWebContents() {
140
+ return this.tabManager?.getActiveWebContents() || null;
141
+ }
142
+
143
+ /**
144
+ * Show or hide the active tab's browser view using bounds (not add/remove).
145
+ */
146
+ setTargetViewVisible(visible: boolean): void {
147
+ this.targetViewVisible = visible;
148
+ if (!this.mainWindow || !this.tabManager) return;
149
+ const activeTab = this.tabManager.getActiveTab();
150
+ if (!activeTab) return;
151
+
152
+ try {
153
+ if (activeTab.view.webContents.isDestroyed()) return;
154
+ if (visible) {
155
+ this.tabManager.updateBounds();
156
+ } else {
157
+ activeTab.view.setBounds({ x: 0, y: 0, width: 0, height: 0 });
158
+ }
159
+ } catch {
160
+ /* View may have been destroyed during operation — safe to ignore */
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Whether the browser view is currently meant to be visible.
166
+ */
167
+ isTargetViewVisible(): boolean {
168
+ return this.targetViewVisible;
169
+ }
170
+
171
+ /**
172
+ * Calculate bounds for the target browser view area.
173
+ * Browser view fills all remaining space below the tab bar + browser panel.
174
+ * Sidebar (221px) is on the left.
175
+ */
176
+ private calculateTargetBounds(): Electron.Rectangle {
177
+ if (!this.mainWindow) return { x: 0, y: 0, width: 0, height: 0 };
178
+
179
+ const contentBounds = this.mainWindow.getContentBounds();
180
+ const width = contentBounds.width;
181
+ const height = contentBounds.height;
182
+ const sidebarWidth = 221; // 220px sidebar + 1px border-right
183
+ const browserPanelHeight = 49; // padding 8+8 + Input 32 + borderBottom 1
184
+ const statusBarHeight = 26;
185
+ const topOffset = TITLEBAR_HEIGHT + TAB_BAR_HEIGHT + browserPanelHeight;
186
+ const browserHeight = Math.max(0, height - topOffset - statusBarHeight);
187
+
188
+ return {
189
+ x: sidebarWidth,
190
+ y: topOffset,
191
+ width: width - sidebarWidth,
192
+ height: browserHeight,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Set the browser area height ratio and update bounds.
198
+ * @param ratio 0.0 ~ 1.0
199
+ */
200
+ setBrowserRatio(ratio: number): void {
201
+ this.browserRatio = Math.max(0.15, Math.min(0.85, ratio));
202
+ // Don't call updateBounds here — the renderer will report exact bounds
203
+ // via syncBrowserBounds after its layout updates.
204
+ }
205
+
206
+ /**
207
+ * Set exact bounds for the active browser tab view.
208
+ * Called by the renderer which measures the actual placeholder position.
209
+ */
210
+ syncBrowserBounds(bounds: Electron.Rectangle): void {
211
+ const tab = this.tabManager?.getActiveTab();
212
+ if (tab) {
213
+ try {
214
+ if (!tab.view.webContents.isDestroyed()) {
215
+ tab.view.setBounds(bounds);
216
+ }
217
+ } catch { /* view destroyed */ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Get current browser area height ratio.
223
+ */
224
+ getBrowserRatio(): number {
225
+ return this.browserRatio;
226
+ }
227
+
228
+ /**
229
+ * Destroy all tabs and clean up.
230
+ */
231
+ destroyTargetView(): void {
232
+ this.tabManager?.destroyEverything();
233
+ this.tabManager = null;
234
+ }
235
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Hook script injected into the target browser page context.
3
+ * Intercepts fetch, XMLHttpRequest, crypto.subtle, and document.cookie.
4
+ */
5
+ ;(function () {
6
+ const HOOK_MSG_TYPE = 'ar-hook'
7
+
8
+ function sendHookData(hookType: string, functionName: string, args: unknown, result: unknown, callStack: string | null): void {
9
+ try {
10
+ window.postMessage({ type: HOOK_MSG_TYPE, hookType, functionName, arguments: JSON.stringify(args), result: result != null ? JSON.stringify(result) : null, callStack, timestamp: Date.now() }, '*')
11
+ } catch { /* ignore serialization errors */ }
12
+ }
13
+
14
+ function getCallStack(): string {
15
+ return new Error().stack?.split('\n').slice(2).join('\n') || ''
16
+ }
17
+
18
+ function arrayBufferToHex(buffer: ArrayBuffer): string {
19
+ return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join('')
20
+ }
21
+
22
+ // Hook: window.fetch
23
+ const originalFetch = window.fetch
24
+ const hookedFetch = function(this: typeof globalThis, input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
25
+ const stack = getCallStack()
26
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.href : (input as Request).url
27
+ const method = init?.method || (input instanceof Request ? input.method : 'GET')
28
+ sendHookData('fetch', 'window.fetch', { url, method, body: init?.body?.toString() }, null, stack)
29
+ return originalFetch.call(this, input, init).then(response => {
30
+ sendHookData('fetch', 'window.fetch.response', { url, method }, { status: response.status, statusText: response.statusText }, null)
31
+ return response
32
+ })
33
+ }
34
+ try { Object.defineProperty(window, 'fetch', { value: hookedFetch, writable: false, configurable: false }) } catch { (window as any).fetch = hookedFetch }
35
+
36
+ // Hook: XMLHttpRequest
37
+ const XHRProto = XMLHttpRequest.prototype
38
+ const originalOpen = XHRProto.open
39
+ const originalSend = XHRProto.send
40
+ const originalSetHeader = XHRProto.setRequestHeader
41
+
42
+ XHRProto.open = function(method: string, url: string | URL, ...args: any[]) {
43
+ (this as any)._arMethod = method;
44
+ (this as any)._arUrl = typeof url === 'string' ? url : url.href;
45
+ (this as any)._arHeaders = {}
46
+ return (originalOpen as Function).call(this, method, url, ...args)
47
+ }
48
+
49
+ XHRProto.setRequestHeader = function(name: string, value: string) {
50
+ if ((this as any)._arHeaders) (this as any)._arHeaders[name] = value
51
+ return originalSetHeader.call(this, name, value)
52
+ }
53
+
54
+ XHRProto.send = function(body?: Document | XMLHttpRequestBodyInit | null) {
55
+ const xhr = this as any
56
+ const stack = getCallStack()
57
+ sendHookData('xhr', 'XMLHttpRequest.send', { method: xhr._arMethod, url: xhr._arUrl, headers: xhr._arHeaders, body: body?.toString() || null }, null, stack)
58
+ this.addEventListener('load', function() {
59
+ sendHookData('xhr', 'XMLHttpRequest.response', { method: xhr._arMethod, url: xhr._arUrl }, { status: this.status, statusText: this.statusText }, null)
60
+ })
61
+ return originalSend.call(this, body)
62
+ }
63
+
64
+ // Hook: crypto.subtle
65
+ if (window.crypto?.subtle) {
66
+ const subtle = window.crypto.subtle
67
+ for (const methodName of ['sign', 'digest', 'encrypt', 'decrypt'] as const) {
68
+ const original = subtle[methodName].bind(subtle)
69
+ ;(subtle as any)[methodName] = async function(...args: any[]) {
70
+ const stack = getCallStack()
71
+ const serializedArgs = args.map(arg => {
72
+ if (arg instanceof ArrayBuffer) return arrayBufferToHex(arg)
73
+ if (ArrayBuffer.isView(arg)) return arrayBufferToHex(arg.buffer)
74
+ return arg
75
+ })
76
+ sendHookData('crypto', `crypto.subtle.${methodName}`, serializedArgs, null, stack)
77
+ const result = await (original as Function)(...args)
78
+ sendHookData('crypto', `crypto.subtle.${methodName}.result`, serializedArgs, result instanceof ArrayBuffer ? arrayBufferToHex(result) : result, null)
79
+ return result
80
+ }
81
+ }
82
+ }
83
+
84
+ // ---- Third-party crypto library hooks ----
85
+
86
+ function truncateArg(val: unknown): string {
87
+ const s = typeof val === 'string' ? val : JSON.stringify(val)
88
+ return s && s.length > 500 ? s.substring(0, 500) + '...' : (s || '')
89
+ }
90
+
91
+ function wrapMethod(obj: any, methodName: string, libLabel: string): void {
92
+ if (typeof obj[methodName] !== 'function') return
93
+ const original = obj[methodName]
94
+ obj[methodName] = function(this: any, ...args: any[]) {
95
+ const stack = getCallStack()
96
+ const truncatedArgs = args.map(a => truncateArg(a))
97
+ sendHookData('crypto_lib', `${libLabel}.${methodName}`, truncatedArgs, null, stack)
98
+ try {
99
+ const result = original.apply(this, args)
100
+ if (result && typeof result === 'object' && typeof result.toString === 'function') {
101
+ sendHookData('crypto_lib', `${libLabel}.${methodName}.result`, truncatedArgs, truncateArg(result.toString()), null)
102
+ }
103
+ return result
104
+ } catch (e) {
105
+ throw e
106
+ }
107
+ }
108
+ }
109
+
110
+ function hookCryptoJS(CryptoJS: any): void {
111
+ if (!CryptoJS || CryptoJS._arHooked) return
112
+ CryptoJS._arHooked = true
113
+
114
+ // AES / DES / TripleDES / Rabbit / RC4
115
+ for (const cipher of ['AES', 'DES', 'TripleDES', 'Rabbit', 'RC4']) {
116
+ if (CryptoJS[cipher]) {
117
+ wrapMethod(CryptoJS[cipher], 'encrypt', `CryptoJS.${cipher}`)
118
+ wrapMethod(CryptoJS[cipher], 'decrypt', `CryptoJS.${cipher}`)
119
+ }
120
+ }
121
+
122
+ // Hash functions
123
+ for (const hash of ['MD5', 'SHA1', 'SHA256', 'SHA512', 'SHA3', 'RIPEMD160']) {
124
+ if (typeof CryptoJS[hash] === 'function') {
125
+ const original = CryptoJS[hash]
126
+ CryptoJS[hash] = function(...args: any[]) {
127
+ const stack = getCallStack()
128
+ sendHookData('crypto_lib', `CryptoJS.${hash}`, args.map(a => truncateArg(a)), null, stack)
129
+ const result = original.apply(this, args)
130
+ sendHookData('crypto_lib', `CryptoJS.${hash}.result`, [], truncateArg(result?.toString()), null)
131
+ return result
132
+ }
133
+ }
134
+ }
135
+
136
+ // HMAC functions
137
+ for (const hmac of ['HmacSHA1', 'HmacSHA256', 'HmacSHA512', 'HmacMD5']) {
138
+ if (typeof CryptoJS[hmac] === 'function') {
139
+ const original = CryptoJS[hmac]
140
+ CryptoJS[hmac] = function(...args: any[]) {
141
+ const stack = getCallStack()
142
+ sendHookData('crypto_lib', `CryptoJS.${hmac}`, args.map(a => truncateArg(a)), null, stack)
143
+ const result = original.apply(this, args)
144
+ sendHookData('crypto_lib', `CryptoJS.${hmac}.result`, [], truncateArg(result?.toString()), null)
145
+ return result
146
+ }
147
+ }
148
+ }
149
+
150
+ // PBKDF2
151
+ if (typeof CryptoJS.PBKDF2 === 'function') {
152
+ const original = CryptoJS.PBKDF2
153
+ CryptoJS.PBKDF2 = function(...args: any[]) {
154
+ const stack = getCallStack()
155
+ sendHookData('crypto_lib', 'CryptoJS.PBKDF2', args.map(a => truncateArg(a)), null, stack)
156
+ const result = original.apply(this, args)
157
+ sendHookData('crypto_lib', 'CryptoJS.PBKDF2.result', [], truncateArg(result?.toString()), null)
158
+ return result
159
+ }
160
+ }
161
+
162
+ // enc.Base64 / enc.Hex
163
+ if (CryptoJS.enc) {
164
+ for (const enc of ['Base64', 'Hex', 'Utf8', 'Latin1']) {
165
+ if (CryptoJS.enc[enc]) {
166
+ wrapMethod(CryptoJS.enc[enc], 'stringify', `CryptoJS.enc.${enc}`)
167
+ wrapMethod(CryptoJS.enc[enc], 'parse', `CryptoJS.enc.${enc}`)
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ function hookJSEncrypt(JSEncryptClass: any): void {
174
+ if (!JSEncryptClass || JSEncryptClass._arHooked) return
175
+ JSEncryptClass._arHooked = true
176
+ const proto = JSEncryptClass.prototype
177
+ if (proto) {
178
+ for (const method of ['encrypt', 'decrypt', 'sign', 'verify', 'setPublicKey', 'setPrivateKey']) {
179
+ wrapMethod(proto, method, 'JSEncrypt')
180
+ }
181
+ }
182
+ }
183
+
184
+ function hookForge(forge: any): void {
185
+ if (!forge || forge._arHooked) return
186
+ forge._arHooked = true
187
+ if (forge.pki) {
188
+ wrapMethod(forge.pki, 'publicKeyFromPem', 'forge.pki')
189
+ wrapMethod(forge.pki, 'privateKeyFromPem', 'forge.pki')
190
+ wrapMethod(forge.pki, 'certificateFromPem', 'forge.pki')
191
+ }
192
+ if (forge.cipher) {
193
+ wrapMethod(forge.cipher, 'createCipher', 'forge.cipher')
194
+ wrapMethod(forge.cipher, 'createDecipher', 'forge.cipher')
195
+ }
196
+ if (forge.md) {
197
+ for (const alg of ['sha256', 'sha1', 'sha512', 'md5']) {
198
+ if (forge.md[alg]) wrapMethod(forge.md[alg], 'create', `forge.md.${alg}`)
199
+ }
200
+ }
201
+ if (forge.util) {
202
+ wrapMethod(forge.util, 'encode64', 'forge.util')
203
+ wrapMethod(forge.util, 'decode64', 'forge.util')
204
+ }
205
+ if (forge.hmac) {
206
+ wrapMethod(forge.hmac, 'create', 'forge.hmac')
207
+ }
208
+ }
209
+
210
+ function hookSmCrypto(name: string, obj: any): void {
211
+ if (!obj || obj._arHooked) return
212
+ obj._arHooked = true
213
+ for (const method of ['doEncrypt', 'doDecrypt', 'doSignature', 'doVerifySignature', 'encrypt', 'decrypt']) {
214
+ wrapMethod(obj, method, name)
215
+ }
216
+ }
217
+
218
+ // Trap library globals: fires when library is assigned to window
219
+ function trapGlobal(name: string, hookFn: (lib: any) => void): void {
220
+ // If already present, hook immediately
221
+ if ((window as any)[name]) {
222
+ try { hookFn((window as any)[name]) } catch { /* ignore */ }
223
+ return
224
+ }
225
+ // Set a defineProperty trap for lazy loading
226
+ let _val: any = undefined
227
+ try {
228
+ Object.defineProperty(window, name, {
229
+ get() { return _val },
230
+ set(v) {
231
+ _val = v
232
+ if (v) { try { hookFn(v) } catch { /* ignore */ } }
233
+ },
234
+ configurable: true,
235
+ enumerable: true,
236
+ })
237
+ } catch { /* CSP or frozen global */ }
238
+ }
239
+
240
+ trapGlobal('CryptoJS', hookCryptoJS)
241
+ trapGlobal('JSEncrypt', hookJSEncrypt)
242
+ trapGlobal('forge', hookForge)
243
+ trapGlobal('sm2', (obj) => hookSmCrypto('sm2', obj))
244
+ trapGlobal('sm3', (obj) => hookSmCrypto('sm3', obj))
245
+ trapGlobal('sm4', (obj) => hookSmCrypto('sm4', obj))
246
+
247
+ // Hook native btoa/atob
248
+ const originalBtoa = window.btoa
249
+ const originalAtob = window.atob
250
+ window.btoa = function(data: string): string {
251
+ sendHookData('crypto_lib', 'btoa', { data: truncateArg(data) }, null, getCallStack())
252
+ return originalBtoa.call(window, data)
253
+ }
254
+ window.atob = function(data: string): string {
255
+ sendHookData('crypto_lib', 'atob', { data: truncateArg(data) }, null, getCallStack())
256
+ return originalAtob.call(window, data)
257
+ }
258
+
259
+ // Hook: document.cookie setter
260
+ const cookieDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie')
261
+ if (cookieDesc) {
262
+ try {
263
+ Object.defineProperty(document, 'cookie', {
264
+ get: function() { return cookieDesc.get?.call(this) },
265
+ set: function(value: string) { sendHookData('cookie_set', 'document.cookie.set', { value }, null, getCallStack()); return cookieDesc.set?.call(this, value) },
266
+ configurable: false
267
+ })
268
+ } catch { /* CSP or already locked */ }
269
+ }
270
+ })()