@jshookmcp/jshook 0.1.5 → 0.1.7

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/README.md +72 -40
  2. package/README.zh.md +77 -40
  3. package/dist/constants.d.ts +1 -0
  4. package/dist/constants.js +13 -1
  5. package/dist/index.js +0 -0
  6. package/dist/modules/analyzer/IntelligentAnalyzer.js +19 -11
  7. package/dist/modules/browser/BrowserModeManager.d.ts +5 -0
  8. package/dist/modules/browser/BrowserModeManager.js +96 -10
  9. package/dist/modules/browser/CamoufoxBrowserManager.d.ts +4 -0
  10. package/dist/modules/browser/CamoufoxBrowserManager.js +64 -3
  11. package/dist/modules/browser/TabRegistry.js +3 -2
  12. package/dist/modules/browser/UnifiedBrowserManager.d.ts +5 -0
  13. package/dist/modules/browser/UnifiedBrowserManager.js +62 -9
  14. package/dist/modules/debugger/DebuggerSessionManager.d.ts +4 -0
  15. package/dist/modules/debugger/DebuggerSessionManager.js +29 -19
  16. package/dist/modules/debugger/ScriptManager.impl.class.d.ts +4 -0
  17. package/dist/modules/debugger/ScriptManager.impl.class.js +46 -21
  18. package/dist/modules/emulator/EnvironmentEmulator.js +2 -2
  19. package/dist/modules/monitor/NetworkMonitor.impl.d.ts +1 -0
  20. package/dist/modules/monitor/NetworkMonitor.impl.js +22 -15
  21. package/dist/modules/monitor/PerformanceMonitor.js +64 -32
  22. package/dist/modules/process/LinuxProcessManager.d.ts +3 -1
  23. package/dist/modules/process/LinuxProcessManager.js +7 -3
  24. package/dist/modules/process/MacProcessManager.d.ts +3 -1
  25. package/dist/modules/process/MacProcessManager.js +7 -3
  26. package/dist/modules/process/ProcessManager.impl.d.ts +5 -1
  27. package/dist/modules/process/ProcessManager.impl.js +54 -13
  28. package/dist/modules/process/index.d.ts +3 -1
  29. package/dist/modules/process/index.js +2 -2
  30. package/dist/modules/process/memory/AuditTrail.d.ts +25 -0
  31. package/dist/modules/process/memory/AuditTrail.js +44 -0
  32. package/dist/modules/process/memory/linux/mapsParser.d.ts +16 -0
  33. package/dist/modules/process/memory/linux/mapsParser.js +28 -0
  34. package/dist/modules/process/memory/regions.enumerate.js +45 -1
  35. package/dist/modules/process/memory/regions.protection.js +48 -2
  36. package/dist/modules/process/memory/scanner.d.ts +4 -1
  37. package/dist/modules/process/memory/scanner.js +225 -24
  38. package/dist/native/NativeMemoryManager.impl.d.ts +4 -0
  39. package/dist/native/NativeMemoryManager.impl.js +72 -24
  40. package/dist/native/NativeMemoryManager.utils.d.ts +1 -0
  41. package/dist/native/NativeMemoryManager.utils.js +44 -1
  42. package/dist/server/MCPServer.search.d.ts +3 -0
  43. package/dist/server/MCPServer.search.js +21 -2
  44. package/dist/server/ToolCallContextGuard.d.ts +2 -0
  45. package/dist/server/ToolCallContextGuard.js +29 -14
  46. package/dist/server/ToolSearch.js +11 -5
  47. package/dist/server/domains/browser/handlers/tab-workflow.js +6 -4
  48. package/dist/server/domains/maintenance/handlers.extensions.js +46 -26
  49. package/dist/server/domains/process/definitions.js +20 -7
  50. package/dist/server/domains/process/handlers.impl.core.runtime.base.d.ts +35 -0
  51. package/dist/server/domains/process/handlers.impl.core.runtime.base.js +107 -1
  52. package/dist/server/domains/process/handlers.impl.core.runtime.inject.js +111 -2
  53. package/dist/server/domains/process/handlers.impl.core.runtime.memory.d.ts +9 -0
  54. package/dist/server/domains/process/handlers.impl.core.runtime.memory.js +282 -31
  55. package/dist/server/domains/process/manifest.js +1 -0
  56. package/dist/server/domains/workflow/handlers.impl.workflow-api.js +14 -4
  57. package/dist/server/registry/discovery.js +17 -12
  58. package/dist/server/registry/index.js +10 -2
  59. package/dist/utils/TokenBudgetManager.d.ts +1 -0
  60. package/dist/utils/TokenBudgetManager.js +22 -0
  61. package/package.json +28 -41
@@ -8,6 +8,7 @@ export declare class NetworkMonitor {
8
8
  private responses;
9
9
  private readonly MAX_NETWORK_RECORDS;
10
10
  private readonly MAX_INJECTED_RECORDS;
11
+ private readonly JS_RESPONSE_CONCURRENCY;
11
12
  private responseBodyCache;
12
13
  private readonly MAX_BODY_CACHE_ENTRIES;
13
14
  private networkListeners;
@@ -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 jsResponses = [];
317
- for (const [requestId, response] of this.responses.entries()) {
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
- const content = bodyResult.base64Encoded
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
- let eventCount = 0;
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
- const allNodes = [];
368
- function walkNodes(node) {
369
- if (!isCDPHeapSamplingNode(node)) {
370
- return;
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: allNodes.length, path: savedPath });
435
+ logger.success('Heap sampling profile saved', { sampleCount, path: savedPath });
404
436
  return {
405
437
  artifactPath: savedPath,
406
- sampleCount: allNodes.length,
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): Promise<number | null>;
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 { commandLine } = await this.getProcessCommandLine(pid);
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
- const process = await this.getProcessByPid(child.pid || 0);
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): Promise<number | null>;
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
  }
@@ -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 { commandLine } = await this.getProcessCommandLine(pid);
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
- const process = await this.getProcessByPid(child.pid || 0);
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): Promise<number | null>;
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
- if (!lines || lines === 'null' || lines === '') {
43
- return processes;
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 data = JSON.parse(lines);
46
- const procList = Array.isArray(data) ? data : [data];
47
- for (const proc of procList) {
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 { commandLine } = await this.getProcessCommandLine(pid);
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): Promise<number | null>;
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
+ }
@@ -0,0 +1,16 @@
1
+ export interface LinuxMemoryRegion {
2
+ start: bigint;
3
+ end: bigint;
4
+ permissions: {
5
+ read: boolean;
6
+ write: boolean;
7
+ exec: boolean;
8
+ private: boolean;
9
+ };
10
+ offset: bigint;
11
+ dev: string;
12
+ inode: number;
13
+ pathname: string;
14
+ }
15
+ export declare function parseProcMaps(content: string): LinuxMemoryRegion[];
16
+ export declare function formatLinuxProtection(perms: LinuxMemoryRegion['permissions']): string;
@@ -0,0 +1,28 @@
1
+ const PROC_MAPS_LINE_RE = /^([0-9a-f]+)-([0-9a-f]+)\s+([r-][w-][x-][ps])\s+([0-9a-f]+)\s+(\S+)\s+(\d+)\s*(.*)$/i;
2
+ export function parseProcMaps(content) {
3
+ const regions = [];
4
+ for (const line of content.split(/\r?\n/)) {
5
+ const match = line.trimEnd().match(PROC_MAPS_LINE_RE);
6
+ if (!match)
7
+ continue;
8
+ const perms = match[3];
9
+ regions.push({
10
+ start: BigInt(`0x${match[1]}`),
11
+ end: BigInt(`0x${match[2]}`),
12
+ permissions: {
13
+ read: perms[0] === 'r',
14
+ write: perms[1] === 'w',
15
+ exec: perms[2] === 'x',
16
+ private: perms[3] === 'p',
17
+ },
18
+ offset: BigInt(`0x${match[4]}`),
19
+ dev: match[5],
20
+ inode: parseInt(match[6], 10),
21
+ pathname: match[7]?.trim() ?? '',
22
+ });
23
+ }
24
+ return regions;
25
+ }
26
+ export function formatLinuxProtection(perms) {
27
+ return `${perms.read ? 'r' : '-'}${perms.write ? 'w' : '-'}${perms.exec ? 'x' : '-'}`;
28
+ }
@@ -1,5 +1,9 @@
1
+ import { readFileSync } from 'fs';
1
2
  import { logger } from '../../../utils/logger.js';
2
3
  import { execAsync, executePowerShellScript } from '../../process/memory/types.js';
4
+ import { parseProcMaps, formatLinuxProtection } from './linux/mapsParser.js';
5
+ import { nativeMemoryManager } from '../../../native/NativeMemoryManager.js';
6
+ import { isKoffiAvailable } from '../../../native/NativeMemoryManager.utils.js';
3
7
  function buildEnumerateRegionsScript(pid) {
4
8
  return `
5
9
  Add-Type @"
@@ -111,8 +115,28 @@ try {
111
115
  `.trim();
112
116
  }
113
117
  export async function enumerateRegions(platform, pid) {
118
+ if (platform === 'linux') {
119
+ try {
120
+ const mapsContent = readFileSync(`/proc/${pid}/maps`, 'utf-8');
121
+ const readableRegions = parseProcMaps(mapsContent).filter(region => region.permissions.read);
122
+ const regions = readableRegions
123
+ .map(region => ({
124
+ baseAddress: `0x${region.start.toString(16)}`,
125
+ size: Number(region.end - region.start),
126
+ state: 'COMMIT',
127
+ protection: formatLinuxProtection(region.permissions),
128
+ isReadable: true,
129
+ type: region.pathname || 'anonymous',
130
+ }));
131
+ return { success: true, regions };
132
+ }
133
+ catch (error) {
134
+ logger.error('Linux region enumeration failed:', error);
135
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
136
+ }
137
+ }
114
138
  if (platform !== 'win32' && platform !== 'darwin') {
115
- return { success: false, error: 'Region enumeration currently only implemented for Windows and macOS' };
139
+ return { success: false, error: 'Region enumeration currently only implemented for Windows, Linux, and macOS' };
116
140
  }
117
141
  if (platform === 'darwin') {
118
142
  try {
@@ -148,6 +172,26 @@ export async function enumerateRegions(platform, pid) {
148
172
  return { success: false, error: error instanceof Error ? error.message : String(error) };
149
173
  }
150
174
  }
175
+ if (isKoffiAvailable()) {
176
+ try {
177
+ const nativeResult = await nativeMemoryManager.enumerateRegions(pid);
178
+ if (nativeResult.success) {
179
+ return nativeResult;
180
+ }
181
+ logger.warn('Native Windows region enumeration failed, falling back to PowerShell', {
182
+ pid,
183
+ error: nativeResult.error,
184
+ nativeAvailable: isKoffiAvailable(),
185
+ });
186
+ }
187
+ catch (error) {
188
+ logger.warn('Native Windows region enumeration threw, falling back to PowerShell', {
189
+ pid,
190
+ error: error instanceof Error ? error.message : String(error),
191
+ nativeAvailable: isKoffiAvailable(),
192
+ });
193
+ }
194
+ }
151
195
  try {
152
196
  const psScript = buildEnumerateRegionsScript(pid);
153
197
  const { stdout } = await executePowerShellScript(psScript, {