@jshookmcp/jshook 0.1.5 → 0.1.6
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/LICENSE +661 -661
- package/README.md +72 -40
- package/README.zh.md +77 -40
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +13 -1
- package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
- package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
- package/dist/modules/browser/BrowserModeManager.js +96 -10
- package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
- package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
- package/dist/modules/browser/TabRegistry.js +3 -2
- package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
- package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
- package/dist/modules/captcha/AICaptchaDetector.js +185 -185
- package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
- package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
- package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
- package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
- package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
- package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
- package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
- package/dist/modules/monitor/PerformanceMonitor.js +64 -32
- package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
- package/dist/modules/process/LinuxProcessManager.js +7 -3
- package/dist/modules/process/MacProcessManager.d.ts +3 -1
- package/dist/modules/process/MacProcessManager.js +32 -28
- package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
- package/dist/modules/process/ProcessManager.impl.js +54 -13
- package/dist/modules/process/index.d.ts +3 -1
- package/dist/modules/process/index.js +2 -2
- package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
- package/dist/modules/process/memory/AuditTrail.js +44 -0
- package/dist/modules/process/memory/availability.js +49 -49
- package/dist/modules/process/memory/injector.js +185 -185
- package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
- package/dist/modules/process/memory/linux/mapsParser.js +28 -0
- package/dist/modules/process/memory/reader.js +50 -50
- package/dist/modules/process/memory/regions.enumerate.js +45 -1
- package/dist/modules/process/memory/regions.protection.js +48 -2
- package/dist/modules/process/memory/scanner.d.ts +4 -1
- package/dist/modules/process/memory/scanner.js +383 -182
- package/dist/modules/process/memory/writer.js +54 -54
- package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
- package/dist/native/NativeMemoryManager.impl.js +72 -24
- package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
- package/dist/native/NativeMemoryManager.utils.js +44 -1
- package/dist/native/scripts/linux/enum-windows.sh +12 -12
- package/dist/native/scripts/macos/enum-windows.applescript +22 -22
- package/dist/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
- package/dist/native/scripts/windows/enum-windows.ps1 +44 -44
- package/dist/native/scripts/windows/inject-dll.ps1 +21 -21
- package/dist/server/MCPServer.search.d.ts +3 -0
- package/dist/server/MCPServer.search.js +21 -2
- package/dist/server/ToolCallContextGuard.d.ts +2 -0
- package/dist/server/ToolCallContextGuard.js +29 -14
- package/dist/server/ToolSearch.js +11 -5
- package/dist/server/domains/browser/definitions.tools.page-core.js +53 -53
- package/dist/server/domains/browser/definitions.tools.runtime.js +40 -40
- package/dist/server/domains/browser/definitions.tools.security.js +76 -76
- package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
- package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
- package/dist/server/domains/process/definitions.js +20 -7
- package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
- package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
- package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
- package/dist/server/domains/process/manifest.js +1 -0
- package/dist/server/domains/transform/handlers.impl.transform-base.js +102 -102
- package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
- package/dist/server/domains/workflow/handlers.impl.workflow-base.js +51 -51
- package/dist/server/registry/discovery.js +17 -12
- package/dist/server/registry/index.js +10 -2
- package/dist/utils/TokenBudgetManager.d.ts +1 -0
- package/dist/utils/TokenBudgetManager.js +22 -0
- package/package.json +5 -1
- package/src/native/scripts/linux/enum-windows.sh +12 -12
- package/src/native/scripts/macos/enum-windows.applescript +22 -22
- package/src/native/scripts/windows/enum-windows-by-class.ps1 +51 -51
- package/src/native/scripts/windows/enum-windows.ps1 +44 -44
- package/src/native/scripts/windows/inject-dll.ps1 +21 -21
|
@@ -55,6 +55,7 @@ export class NetworkMonitor {
|
|
|
55
55
|
responses = new Map();
|
|
56
56
|
MAX_NETWORK_RECORDS = 500;
|
|
57
57
|
MAX_INJECTED_RECORDS = 500;
|
|
58
|
+
JS_RESPONSE_CONCURRENCY = 6;
|
|
58
59
|
responseBodyCache = new Map();
|
|
59
60
|
MAX_BODY_CACHE_ENTRIES = 200;
|
|
60
61
|
networkListeners = {};
|
|
@@ -313,24 +314,30 @@ export class NetworkMonitor {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
async getAllJavaScriptResponses() {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
if (response.mimeType.includes('javascript') ||
|
|
317
|
+
const candidates = Array.from(this.responses.entries()).filter(([, response]) => {
|
|
318
|
+
return (response.mimeType.includes('javascript') ||
|
|
319
319
|
response.url.endsWith('.js') ||
|
|
320
|
-
response.url.includes('.js?'))
|
|
320
|
+
response.url.includes('.js?'));
|
|
321
|
+
});
|
|
322
|
+
const jsResponses = [];
|
|
323
|
+
for (let i = 0; i < candidates.length; i += this.JS_RESPONSE_CONCURRENCY) {
|
|
324
|
+
const batch = candidates.slice(i, i + this.JS_RESPONSE_CONCURRENCY);
|
|
325
|
+
const batchResults = await Promise.all(batch.map(async ([requestId, response]) => {
|
|
321
326
|
const bodyResult = await this.getResponseBody(requestId);
|
|
322
|
-
if (bodyResult) {
|
|
323
|
-
|
|
324
|
-
? Buffer.from(bodyResult.body, 'base64').toString('utf-8')
|
|
325
|
-
: bodyResult.body;
|
|
326
|
-
jsResponses.push({
|
|
327
|
-
url: response.url,
|
|
328
|
-
content,
|
|
329
|
-
size: content.length,
|
|
330
|
-
requestId,
|
|
331
|
-
});
|
|
327
|
+
if (!bodyResult) {
|
|
328
|
+
return null;
|
|
332
329
|
}
|
|
333
|
-
|
|
330
|
+
const content = bodyResult.base64Encoded
|
|
331
|
+
? Buffer.from(bodyResult.body, 'base64').toString('utf-8')
|
|
332
|
+
: bodyResult.body;
|
|
333
|
+
return {
|
|
334
|
+
url: response.url,
|
|
335
|
+
content,
|
|
336
|
+
size: content.length,
|
|
337
|
+
requestId,
|
|
338
|
+
};
|
|
339
|
+
}));
|
|
340
|
+
jsResponses.push(...batchResults.filter((value) => value !== null));
|
|
334
341
|
}
|
|
335
342
|
logger.info(`Collected ${jsResponses.length} JavaScript responses`);
|
|
336
343
|
return jsResponses;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import { setImmediate as waitForImmediate } from 'node:timers/promises';
|
|
2
3
|
import { logger } from '../../utils/logger.js';
|
|
3
4
|
import { PrerequisiteError } from '../../errors/PrerequisiteError.js';
|
|
4
5
|
import { cdpLimit } from '../../utils/concurrency.js';
|
|
@@ -65,6 +66,62 @@ function isCDPHeapSamplingNode(value) {
|
|
|
65
66
|
function isCDPHeapSamplingPayload(value) {
|
|
66
67
|
return isRecord(value) && isRecord(value.profile) && isCDPHeapSamplingNode(value.profile.head);
|
|
67
68
|
}
|
|
69
|
+
async function yieldToEventLoop() {
|
|
70
|
+
await waitForImmediate();
|
|
71
|
+
}
|
|
72
|
+
function countTraceEvents(traceData) {
|
|
73
|
+
const eventPattern = /"ph"\s*:/g;
|
|
74
|
+
let count = 0;
|
|
75
|
+
while (eventPattern.exec(traceData) !== null) {
|
|
76
|
+
count++;
|
|
77
|
+
}
|
|
78
|
+
return count;
|
|
79
|
+
}
|
|
80
|
+
function insertTopAllocation(topAllocations, candidate, topN) {
|
|
81
|
+
if (topN <= 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (topAllocations.length === topN &&
|
|
85
|
+
candidate.selfSize <= topAllocations[topAllocations.length - 1].selfSize) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let insertIndex = topAllocations.findIndex((entry) => candidate.selfSize > entry.selfSize);
|
|
89
|
+
if (insertIndex === -1) {
|
|
90
|
+
insertIndex = topAllocations.length;
|
|
91
|
+
}
|
|
92
|
+
topAllocations.splice(insertIndex, 0, candidate);
|
|
93
|
+
if (topAllocations.length > topN) {
|
|
94
|
+
topAllocations.length = topN;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function collectTopHeapAllocations(root, topN) {
|
|
98
|
+
const stack = [root];
|
|
99
|
+
const topAllocations = [];
|
|
100
|
+
let sampleCount = 0;
|
|
101
|
+
while (stack.length > 0) {
|
|
102
|
+
const node = stack.pop();
|
|
103
|
+
if (!node) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (node.callFrame) {
|
|
107
|
+
sampleCount++;
|
|
108
|
+
insertTopAllocation(topAllocations, {
|
|
109
|
+
functionName: node.callFrame.functionName || '(anonymous)',
|
|
110
|
+
url: node.callFrame.url || '',
|
|
111
|
+
selfSize: node.selfSize || 0,
|
|
112
|
+
}, topN);
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(node.children)) {
|
|
115
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
116
|
+
const child = node.children[i];
|
|
117
|
+
if (child) {
|
|
118
|
+
stack.push(child);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { sampleCount, topAllocations };
|
|
124
|
+
}
|
|
68
125
|
export class PerformanceMonitor {
|
|
69
126
|
collector;
|
|
70
127
|
cdpSession = null;
|
|
@@ -306,14 +363,7 @@ export class PerformanceMonitor {
|
|
|
306
363
|
traceData = traceChunks.join('');
|
|
307
364
|
}
|
|
308
365
|
this.tracingEnabled = false;
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
const parsed = JSON.parse(traceData);
|
|
312
|
-
eventCount = Array.isArray(parsed) ? parsed.length : (parsed.traceEvents?.length ?? 0);
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
eventCount = (traceData.match(/"ph":/g) || []).length;
|
|
316
|
-
}
|
|
366
|
+
const eventCount = countTraceEvents(traceData);
|
|
317
367
|
let savedPath;
|
|
318
368
|
if (options?.artifactPath) {
|
|
319
369
|
await writeFile(options.artifactPath, traceData, 'utf-8');
|
|
@@ -364,28 +414,10 @@ export class PerformanceMonitor {
|
|
|
364
414
|
await cdp.send('HeapProfiler.disable');
|
|
365
415
|
this.heapSamplingEnabled = false;
|
|
366
416
|
const topN = options?.topN ?? 20;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
if (node.callFrame) {
|
|
373
|
-
allNodes.push({
|
|
374
|
-
functionName: node.callFrame.functionName || '(anonymous)',
|
|
375
|
-
url: node.callFrame.url || '',
|
|
376
|
-
selfSize: node.selfSize || 0,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
if (node.children) {
|
|
380
|
-
for (const child of node.children) {
|
|
381
|
-
walkNodes(child);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
walkNodes(profile.head);
|
|
386
|
-
allNodes.sort((a, b) => b.selfSize - a.selfSize);
|
|
387
|
-
const topAllocations = allNodes.slice(0, topN);
|
|
388
|
-
const profileJson = JSON.stringify(profile, null, 2);
|
|
417
|
+
await yieldToEventLoop();
|
|
418
|
+
const { sampleCount, topAllocations } = collectTopHeapAllocations(profile.head, topN);
|
|
419
|
+
await yieldToEventLoop();
|
|
420
|
+
const profileJson = JSON.stringify(profile);
|
|
389
421
|
let savedPath;
|
|
390
422
|
if (options?.artifactPath) {
|
|
391
423
|
await writeFile(options.artifactPath, profileJson, 'utf-8');
|
|
@@ -400,10 +432,10 @@ export class PerformanceMonitor {
|
|
|
400
432
|
await writeFile(absolutePath, profileJson, 'utf-8');
|
|
401
433
|
savedPath = displayPath;
|
|
402
434
|
}
|
|
403
|
-
logger.success('Heap sampling profile saved', { sampleCount
|
|
435
|
+
logger.success('Heap sampling profile saved', { sampleCount, path: savedPath });
|
|
404
436
|
return {
|
|
405
437
|
artifactPath: savedPath,
|
|
406
|
-
sampleCount
|
|
438
|
+
sampleCount,
|
|
407
439
|
topAllocations,
|
|
408
440
|
};
|
|
409
441
|
});
|
|
@@ -19,7 +19,9 @@ export declare class LinuxProcessManager {
|
|
|
19
19
|
commandLine?: string;
|
|
20
20
|
parentPid?: number;
|
|
21
21
|
}>;
|
|
22
|
-
checkDebugPort(pid: number
|
|
22
|
+
checkDebugPort(pid: number, options?: {
|
|
23
|
+
commandLine?: string;
|
|
24
|
+
}): Promise<number | null>;
|
|
23
25
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<ProcessInfo | null>;
|
|
24
26
|
killProcess(pid: number): Promise<boolean>;
|
|
25
27
|
isRunningOnWayland(): boolean;
|
|
@@ -212,10 +212,10 @@ export class LinuxProcessManager {
|
|
|
212
212
|
return {};
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
async checkDebugPort(pid) {
|
|
215
|
+
async checkDebugPort(pid, options) {
|
|
216
216
|
try {
|
|
217
217
|
pid = safePid(pid);
|
|
218
|
-
const
|
|
218
|
+
const commandLine = options?.commandLine ?? (await this.getProcessCommandLine(pid)).commandLine;
|
|
219
219
|
if (commandLine) {
|
|
220
220
|
const match = commandLine.match(/--remote-debugging-port=(\d+)/);
|
|
221
221
|
if (match && match[1]) {
|
|
@@ -244,7 +244,11 @@ export class LinuxProcessManager {
|
|
|
244
244
|
});
|
|
245
245
|
child.unref();
|
|
246
246
|
await new Promise(resolve => setTimeout(resolve, PROCESS_LAUNCH_WAIT_MS));
|
|
247
|
-
|
|
247
|
+
if (!child.pid) {
|
|
248
|
+
logger.error(`Failed to spawn process: PID is undefined for ${executablePath}`);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const process = await this.getProcessByPid(child.pid);
|
|
248
252
|
logger.info(`Launched process with debug port ${debugPort}:`, {
|
|
249
253
|
pid: child.pid,
|
|
250
254
|
executable: executablePath,
|
|
@@ -18,7 +18,9 @@ export declare class MacProcessManager {
|
|
|
18
18
|
commandLine?: string;
|
|
19
19
|
parentPid?: number;
|
|
20
20
|
}>;
|
|
21
|
-
checkDebugPort(pid: number
|
|
21
|
+
checkDebugPort(pid: number, options?: {
|
|
22
|
+
commandLine?: string;
|
|
23
|
+
}): Promise<number | null>;
|
|
22
24
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<ProcessInfo | null>;
|
|
23
25
|
killProcess(pid: number): Promise<boolean>;
|
|
24
26
|
}
|
|
@@ -103,31 +103,31 @@ export class MacProcessManager {
|
|
|
103
103
|
if (!process) {
|
|
104
104
|
return [];
|
|
105
105
|
}
|
|
106
|
-
const appleScript = `
|
|
107
|
-
tell application "System Events"
|
|
108
|
-
set processList to {}
|
|
109
|
-
try
|
|
110
|
-
set targetProcess to first process whose unix id is ${pid}
|
|
111
|
-
set procName to name of targetProcess
|
|
112
|
-
set windowList to {}
|
|
113
|
-
|
|
114
|
-
tell targetProcess
|
|
115
|
-
repeat with win in windows
|
|
116
|
-
set winInfo to {|
|
|
117
|
-
title:name of win,
|
|
118
|
-
className:procName,
|
|
119
|
-
processId:${pid},
|
|
120
|
-
handle:"applescript-window"
|
|
121
|
-
|}
|
|
122
|
-
set end of windowList to winInfo
|
|
123
|
-
end repeat
|
|
124
|
-
end tell
|
|
125
|
-
|
|
126
|
-
return windowList
|
|
127
|
-
on error
|
|
128
|
-
return {}
|
|
129
|
-
end try
|
|
130
|
-
end tell
|
|
106
|
+
const appleScript = `
|
|
107
|
+
tell application "System Events"
|
|
108
|
+
set processList to {}
|
|
109
|
+
try
|
|
110
|
+
set targetProcess to first process whose unix id is ${pid}
|
|
111
|
+
set procName to name of targetProcess
|
|
112
|
+
set windowList to {}
|
|
113
|
+
|
|
114
|
+
tell targetProcess
|
|
115
|
+
repeat with win in windows
|
|
116
|
+
set winInfo to {|
|
|
117
|
+
title:name of win,
|
|
118
|
+
className:procName,
|
|
119
|
+
processId:${pid},
|
|
120
|
+
handle:"applescript-window"
|
|
121
|
+
|}
|
|
122
|
+
set end of windowList to winInfo
|
|
123
|
+
end repeat
|
|
124
|
+
end tell
|
|
125
|
+
|
|
126
|
+
return windowList
|
|
127
|
+
on error
|
|
128
|
+
return {}
|
|
129
|
+
end try
|
|
130
|
+
end tell
|
|
131
131
|
`;
|
|
132
132
|
const { stdout } = await execAsync(`osascript -e '${appleScript.replace(/'/g, "'\"'\"'")}' 2>/dev/null || echo "[]"`, { timeout: 5000 });
|
|
133
133
|
const windows = [];
|
|
@@ -261,10 +261,10 @@ export class MacProcessManager {
|
|
|
261
261
|
return {};
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
|
-
async checkDebugPort(pid) {
|
|
264
|
+
async checkDebugPort(pid, options) {
|
|
265
265
|
try {
|
|
266
266
|
pid = safePid(pid);
|
|
267
|
-
const
|
|
267
|
+
const commandLine = options?.commandLine ?? (await this.getProcessCommandLine(pid)).commandLine;
|
|
268
268
|
if (commandLine) {
|
|
269
269
|
const match = commandLine.match(/--remote-debugging-port=(\d+)/);
|
|
270
270
|
if (match && match[1]) {
|
|
@@ -293,7 +293,11 @@ export class MacProcessManager {
|
|
|
293
293
|
});
|
|
294
294
|
child.unref();
|
|
295
295
|
await new Promise(resolve => setTimeout(resolve, PROCESS_LAUNCH_WAIT_MS));
|
|
296
|
-
|
|
296
|
+
if (!child.pid) {
|
|
297
|
+
logger.error(`Failed to spawn process: PID is undefined for ${executablePath}`);
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const process = await this.getProcessByPid(child.pid);
|
|
297
301
|
logger.info(`Launched process with debug port ${debugPort}:`, {
|
|
298
302
|
pid: child.pid,
|
|
299
303
|
executable: executablePath,
|
|
@@ -5,8 +5,10 @@ export declare class ProcessManager {
|
|
|
5
5
|
private powershellPath;
|
|
6
6
|
private scriptLoader;
|
|
7
7
|
private browserDiscovery;
|
|
8
|
+
private processCache;
|
|
8
9
|
constructor();
|
|
9
10
|
findProcesses(pattern: string): Promise<ProcessInfo[]>;
|
|
11
|
+
private computeProcessDiff;
|
|
10
12
|
getProcessByPid(pid: number): Promise<ProcessInfo | null>;
|
|
11
13
|
getProcessWindows(pid: number): Promise<WindowInfo[]>;
|
|
12
14
|
findChromiumProcesses(config?: TargetAppConfig): Promise<ChromiumProcess>;
|
|
@@ -15,7 +17,9 @@ export declare class ProcessManager {
|
|
|
15
17
|
commandLine?: string;
|
|
16
18
|
parentPid?: number;
|
|
17
19
|
}>;
|
|
18
|
-
checkDebugPort(pid: number
|
|
20
|
+
checkDebugPort(pid: number, options?: {
|
|
21
|
+
commandLine?: string;
|
|
22
|
+
}): Promise<number | null>;
|
|
19
23
|
private findPidByListeningPort;
|
|
20
24
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<ProcessInfo | null>;
|
|
21
25
|
injectDll(_pid: number, _dllPath: string): Promise<boolean>;
|
|
@@ -8,6 +8,7 @@ import { findChromiumProcessesWithConfig } from '../process/ProcessManager.chrom
|
|
|
8
8
|
import { DEFAULT_CHROMIUM_CONFIG, } from '../process/ProcessManager.types.js';
|
|
9
9
|
export { DEFAULT_CHROMIUM_CONFIG, } from '../process/ProcessManager.types.js';
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
|
+
const PROCESS_SNAPSHOT_CACHE_TTL_MS = 3000;
|
|
11
12
|
function sanitizePsPattern(s) {
|
|
12
13
|
return String(s || '').replace(/[`$"'{}();|<>@#%!\\\n\r]/g, '');
|
|
13
14
|
}
|
|
@@ -21,6 +22,7 @@ export class ProcessManager {
|
|
|
21
22
|
powershellPath = 'powershell.exe';
|
|
22
23
|
scriptLoader;
|
|
23
24
|
browserDiscovery;
|
|
25
|
+
processCache = new Map();
|
|
24
26
|
constructor() {
|
|
25
27
|
this.scriptLoader = new ScriptLoader();
|
|
26
28
|
this.browserDiscovery = new BrowserDiscovery();
|
|
@@ -29,6 +31,12 @@ export class ProcessManager {
|
|
|
29
31
|
async findProcesses(pattern) {
|
|
30
32
|
try {
|
|
31
33
|
const normalizedPattern = sanitizePsPattern(String(pattern || '').trim());
|
|
34
|
+
const cacheKey = normalizedPattern.toLowerCase() || '*';
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const cachedEntry = this.processCache.get(cacheKey);
|
|
37
|
+
if (cachedEntry && cachedEntry.expiresAt > now) {
|
|
38
|
+
return cachedEntry.snapshot;
|
|
39
|
+
}
|
|
32
40
|
let psCommand;
|
|
33
41
|
if (normalizedPattern) {
|
|
34
42
|
psCommand = `Get-Process -Name "*${normalizedPattern.replace(/"/g, '""')}*" -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path, MainWindowTitle, MainWindowHandle, CPU, WorkingSet64 | ConvertTo-Json -Compress`;
|
|
@@ -37,20 +45,30 @@ export class ProcessManager {
|
|
|
37
45
|
psCommand = `Get-Process -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path, MainWindowTitle, MainWindowHandle, CPU, WorkingSet64 | ConvertTo-Json -Compress`;
|
|
38
46
|
}
|
|
39
47
|
const { stdout } = await execAsync(`${this.powershellPath} -NoProfile -Command "${psCommand}"`, { maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES });
|
|
40
|
-
const processes = [];
|
|
41
48
|
const lines = stdout.trim();
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
const processes = [];
|
|
50
|
+
if (lines && lines !== 'null') {
|
|
51
|
+
const data = JSON.parse(lines);
|
|
52
|
+
const procList = Array.isArray(data) ? data : [data];
|
|
53
|
+
for (const proc of procList) {
|
|
54
|
+
processes.push({
|
|
55
|
+
pid: proc.Id,
|
|
56
|
+
name: proc.ProcessName,
|
|
57
|
+
executablePath: proc.Path,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
44
60
|
}
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
processes.push({
|
|
49
|
-
pid: proc.Id,
|
|
50
|
-
name: proc.ProcessName,
|
|
51
|
-
executablePath: proc.Path,
|
|
52
|
-
});
|
|
61
|
+
const byPid = new Map();
|
|
62
|
+
for (const process of processes) {
|
|
63
|
+
byPid.set(process.pid, process);
|
|
53
64
|
}
|
|
65
|
+
const lastDelta = this.computeProcessDiff(cachedEntry?.byPid ?? new Map(), byPid);
|
|
66
|
+
this.processCache.set(cacheKey, {
|
|
67
|
+
expiresAt: now + PROCESS_SNAPSHOT_CACHE_TTL_MS,
|
|
68
|
+
snapshot: processes,
|
|
69
|
+
byPid,
|
|
70
|
+
lastDelta,
|
|
71
|
+
});
|
|
54
72
|
const patternStr = normalizedPattern.length > 0 ? `'${normalizedPattern}'` : 'all';
|
|
55
73
|
logger.info(`Found ${processes.length} processes matching ${patternStr}`);
|
|
56
74
|
return processes;
|
|
@@ -60,6 +78,29 @@ export class ProcessManager {
|
|
|
60
78
|
return [];
|
|
61
79
|
}
|
|
62
80
|
}
|
|
81
|
+
computeProcessDiff(previousByPid, nextByPid) {
|
|
82
|
+
const added = [];
|
|
83
|
+
const removed = [];
|
|
84
|
+
const changed = [];
|
|
85
|
+
for (const [pid, nextProcess] of nextByPid) {
|
|
86
|
+
const previousProcess = previousByPid.get(pid);
|
|
87
|
+
if (!previousProcess) {
|
|
88
|
+
added.push(nextProcess);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (previousProcess.pid !== nextProcess.pid ||
|
|
92
|
+
previousProcess.name !== nextProcess.name ||
|
|
93
|
+
previousProcess.executablePath !== nextProcess.executablePath) {
|
|
94
|
+
changed.push({ before: previousProcess, after: nextProcess });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const [pid, previousProcess] of previousByPid) {
|
|
98
|
+
if (!nextByPid.has(pid)) {
|
|
99
|
+
removed.push(previousProcess);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { added, removed, changed };
|
|
103
|
+
}
|
|
63
104
|
async getProcessByPid(pid) {
|
|
64
105
|
try {
|
|
65
106
|
pid = safePid(pid);
|
|
@@ -142,10 +183,10 @@ export class ProcessManager {
|
|
|
142
183
|
return {};
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
|
-
async checkDebugPort(pid) {
|
|
186
|
+
async checkDebugPort(pid, options) {
|
|
146
187
|
try {
|
|
147
188
|
pid = safePid(pid);
|
|
148
|
-
const
|
|
189
|
+
const commandLine = options?.commandLine ?? (await this.getProcessCommandLine(pid)).commandLine;
|
|
149
190
|
if (commandLine) {
|
|
150
191
|
const match = commandLine.match(/--remote-debugging-port=(\d+)/);
|
|
151
192
|
if (match && match[1]) {
|
|
@@ -22,7 +22,9 @@ export declare class UnifiedProcessManager {
|
|
|
22
22
|
findProcesses(pattern: string): Promise<import("../process/ProcessManager.js").ProcessInfo[]>;
|
|
23
23
|
getProcessByPid(pid: number): Promise<import("../process/ProcessManager.js").ProcessInfo | null>;
|
|
24
24
|
getProcessWindows(pid: number): Promise<import("../process/ProcessManager.js").WindowInfo[]>;
|
|
25
|
-
checkDebugPort(pid: number
|
|
25
|
+
checkDebugPort(pid: number, options?: {
|
|
26
|
+
commandLine?: string;
|
|
27
|
+
}): Promise<number | null>;
|
|
26
28
|
launchWithDebug(executablePath: string, debugPort?: number, args?: string[]): Promise<import("../process/ProcessManager.js").ProcessInfo | null>;
|
|
27
29
|
killProcess(pid: number): Promise<boolean>;
|
|
28
30
|
getProcessCommandLine(pid: number): Promise<{
|
|
@@ -58,8 +58,8 @@ export class UnifiedProcessManager {
|
|
|
58
58
|
async getProcessWindows(pid) {
|
|
59
59
|
return this.manager.getProcessWindows(pid);
|
|
60
60
|
}
|
|
61
|
-
async checkDebugPort(pid) {
|
|
62
|
-
return this.manager.checkDebugPort(pid);
|
|
61
|
+
async checkDebugPort(pid, options) {
|
|
62
|
+
return this.manager.checkDebugPort(pid, options);
|
|
63
63
|
}
|
|
64
64
|
async launchWithDebug(executablePath, debugPort, args) {
|
|
65
65
|
return this.manager.launchWithDebug(executablePath, debugPort, args);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface AuditEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
operation: string;
|
|
4
|
+
pid: number | null;
|
|
5
|
+
address: string | null;
|
|
6
|
+
size: number | null;
|
|
7
|
+
result: 'success' | 'failure';
|
|
8
|
+
error?: string;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
user: string;
|
|
11
|
+
pattern?: string;
|
|
12
|
+
resultsCount?: number;
|
|
13
|
+
dllPath?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class MemoryAuditTrail {
|
|
16
|
+
private buffer;
|
|
17
|
+
private head;
|
|
18
|
+
private count;
|
|
19
|
+
private readonly capacity;
|
|
20
|
+
constructor(capacity?: number);
|
|
21
|
+
record(entry: Omit<AuditEntry, 'timestamp' | 'user'>): void;
|
|
22
|
+
exportJson(): string;
|
|
23
|
+
clear(): void;
|
|
24
|
+
size(): number;
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class MemoryAuditTrail {
|
|
2
|
+
buffer;
|
|
3
|
+
head = 0;
|
|
4
|
+
count = 0;
|
|
5
|
+
capacity;
|
|
6
|
+
constructor(capacity = 5000) {
|
|
7
|
+
this.capacity = Number.isInteger(capacity) && capacity > 0 ? capacity : 5000;
|
|
8
|
+
this.buffer = [];
|
|
9
|
+
}
|
|
10
|
+
record(entry) {
|
|
11
|
+
const fullEntry = {
|
|
12
|
+
...entry,
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
user: process.env.USERNAME || process.env.USER || 'unknown',
|
|
15
|
+
};
|
|
16
|
+
if (this.count < this.capacity) {
|
|
17
|
+
const writeIndex = (this.head + this.count) % this.capacity;
|
|
18
|
+
this.buffer[writeIndex] = fullEntry;
|
|
19
|
+
this.count += 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.buffer[this.head] = fullEntry;
|
|
23
|
+
this.head = (this.head + 1) % this.capacity;
|
|
24
|
+
}
|
|
25
|
+
exportJson() {
|
|
26
|
+
const entries = [];
|
|
27
|
+
for (let i = 0; i < this.count; i += 1) {
|
|
28
|
+
const index = (this.head + i) % this.capacity;
|
|
29
|
+
const entry = this.buffer[index];
|
|
30
|
+
if (entry) {
|
|
31
|
+
entries.push(entry);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return JSON.stringify(entries, null, 2);
|
|
35
|
+
}
|
|
36
|
+
clear() {
|
|
37
|
+
this.buffer = [];
|
|
38
|
+
this.head = 0;
|
|
39
|
+
this.count = 0;
|
|
40
|
+
}
|
|
41
|
+
size() {
|
|
42
|
+
return this.count;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -103,55 +103,55 @@ export async function checkDebugPort(platform, pid) {
|
|
|
103
103
|
return { success: false, error: 'Debug port check currently only implemented for Windows' };
|
|
104
104
|
}
|
|
105
105
|
try {
|
|
106
|
-
const psScript = `
|
|
107
|
-
Add-Type @"
|
|
108
|
-
using System;
|
|
109
|
-
using System.Runtime.InteropServices;
|
|
110
|
-
using System.ComponentModel;
|
|
111
|
-
|
|
112
|
-
public class DebugChecker {
|
|
113
|
-
[DllImport("ntdll.dll")]
|
|
114
|
-
public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, out IntPtr processInformation, int processInformationLength, out int returnLength);
|
|
115
|
-
|
|
116
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
117
|
-
public static extern IntPtr OpenProcess(int access, bool inherit, int pid);
|
|
118
|
-
|
|
119
|
-
[DllImport("kernel32.dll", SetLastError = true)]
|
|
120
|
-
public static extern bool CloseHandle(IntPtr handle);
|
|
121
|
-
|
|
122
|
-
const int PROCESS_QUERY_INFORMATION = 0x0400;
|
|
123
|
-
const int ProcessDebugPort = 7;
|
|
124
|
-
|
|
125
|
-
public static object Check(int pid) {
|
|
126
|
-
IntPtr hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
|
|
127
|
-
if (hProcess == IntPtr.Zero) {
|
|
128
|
-
int error = Marshal.GetLastWin32Error();
|
|
129
|
-
throw new Win32Exception(error, "Failed to open process");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
IntPtr debugPort;
|
|
134
|
-
int returnLength;
|
|
135
|
-
int status = NtQueryInformationProcess(hProcess, ProcessDebugPort, out debugPort, IntPtr.Size, out returnLength);
|
|
136
|
-
|
|
137
|
-
if (status != 0) {
|
|
138
|
-
return new { success = false, error = "NtQueryInformationProcess failed with status: 0x" + status.ToString("X") };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return new { success = true, isDebugged = debugPort != IntPtr.Zero };
|
|
142
|
-
} finally {
|
|
143
|
-
CloseHandle(hProcess);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
"@
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
$result = [DebugChecker]::Check(${pid})
|
|
151
|
-
$result | ConvertTo-Json -Compress
|
|
152
|
-
} catch {
|
|
153
|
-
@{ success = $false; error = $_.Exception.Message } | ConvertTo-Json -Compress
|
|
154
|
-
}
|
|
106
|
+
const psScript = `
|
|
107
|
+
Add-Type @"
|
|
108
|
+
using System;
|
|
109
|
+
using System.Runtime.InteropServices;
|
|
110
|
+
using System.ComponentModel;
|
|
111
|
+
|
|
112
|
+
public class DebugChecker {
|
|
113
|
+
[DllImport("ntdll.dll")]
|
|
114
|
+
public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, out IntPtr processInformation, int processInformationLength, out int returnLength);
|
|
115
|
+
|
|
116
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
117
|
+
public static extern IntPtr OpenProcess(int access, bool inherit, int pid);
|
|
118
|
+
|
|
119
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
120
|
+
public static extern bool CloseHandle(IntPtr handle);
|
|
121
|
+
|
|
122
|
+
const int PROCESS_QUERY_INFORMATION = 0x0400;
|
|
123
|
+
const int ProcessDebugPort = 7;
|
|
124
|
+
|
|
125
|
+
public static object Check(int pid) {
|
|
126
|
+
IntPtr hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
|
|
127
|
+
if (hProcess == IntPtr.Zero) {
|
|
128
|
+
int error = Marshal.GetLastWin32Error();
|
|
129
|
+
throw new Win32Exception(error, "Failed to open process");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
IntPtr debugPort;
|
|
134
|
+
int returnLength;
|
|
135
|
+
int status = NtQueryInformationProcess(hProcess, ProcessDebugPort, out debugPort, IntPtr.Size, out returnLength);
|
|
136
|
+
|
|
137
|
+
if (status != 0) {
|
|
138
|
+
return new { success = false, error = "NtQueryInformationProcess failed with status: 0x" + status.ToString("X") };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return new { success = true, isDebugged = debugPort != IntPtr.Zero };
|
|
142
|
+
} finally {
|
|
143
|
+
CloseHandle(hProcess);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
"@
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
$result = [DebugChecker]::Check(${pid})
|
|
151
|
+
$result | ConvertTo-Json -Compress
|
|
152
|
+
} catch {
|
|
153
|
+
@{ success = $false; error = $_.Exception.Message } | ConvertTo-Json -Compress
|
|
154
|
+
}
|
|
155
155
|
`;
|
|
156
156
|
const { stdout } = await executePowerShellScript(psScript, { maxBuffer: 1024 * 1024, timeout: 10000 });
|
|
157
157
|
const _trimmed = stdout.trim();
|