@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
@@ -14,11 +14,17 @@ const CONTEXT_SENSITIVE_PREFIXES = [
14
14
  ];
15
15
  export class ToolCallContextGuard {
16
16
  getProvider;
17
+ contextSensitiveCache = new Map();
17
18
  constructor(getProvider) {
18
19
  this.getProvider = getProvider;
19
20
  }
20
21
  isContextSensitive(toolName) {
21
- return CONTEXT_SENSITIVE_PREFIXES.some((p) => toolName.startsWith(p));
22
+ const cached = this.contextSensitiveCache.get(toolName);
23
+ if (cached !== undefined)
24
+ return cached;
25
+ const result = CONTEXT_SENSITIVE_PREFIXES.some((p) => toolName.startsWith(p));
26
+ this.contextSensitiveCache.set(toolName, result);
27
+ return result;
22
28
  }
23
29
  enrichResponse(toolName, response) {
24
30
  if (!this.isContextSensitive(toolName))
@@ -40,21 +46,30 @@ export class ToolCallContextGuard {
40
46
  typeof c.text === 'string');
41
47
  if (!firstText)
42
48
  return response;
43
- try {
44
- const parsed = JSON.parse(firstText.text);
45
- if (typeof parsed === 'object' && parsed !== null) {
46
- parsed._tabContext = {
47
- url: meta.url,
48
- title: meta.title,
49
- tabIndex: meta.tabIndex,
50
- pageId: meta.pageId,
51
- };
52
- firstText.text = JSON.stringify(parsed, null, 2);
49
+ const raw = firstText.text;
50
+ const trimmedStart = raw.trimStart();
51
+ if (trimmedStart.startsWith('{') && trimmedStart.trimEnd().endsWith('}')) {
52
+ try {
53
+ const parsed = JSON.parse(raw);
54
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
55
+ firstText.text = this.spliceTabContext(raw, meta);
56
+ return response;
57
+ }
58
+ }
59
+ catch {
60
+ logger.debug(`[ContextGuard] Skipped non-JSON response enrichment for ${toolName}`);
61
+ return response;
53
62
  }
54
- }
55
- catch {
56
- logger.debug(`[ContextGuard] Skipped non-JSON response enrichment for ${toolName}`);
57
63
  }
58
64
  return response;
59
65
  }
66
+ spliceTabContext(raw, meta) {
67
+ const tabContext = { url: meta.url, title: meta.title, tabIndex: meta.tabIndex, pageId: meta.pageId };
68
+ if (/\n\}\s*$/.test(raw)) {
69
+ const prettyJson = JSON.stringify(tabContext, null, 2).replace(/\n/g, '\n ');
70
+ return raw.replace(/\n\}\s*$/, `,\n "_tabContext": ${prettyJson}\n}`);
71
+ }
72
+ const compactJson = JSON.stringify(tabContext);
73
+ return raw.replace(/\}\s*$/, `,"_tabContext":${compactJson}}`);
74
+ }
60
75
  }
@@ -170,6 +170,7 @@ export class ToolSearchEngine {
170
170
  const description = tool.description ?? '';
171
171
  const shortDescription = extractShortDescription(description);
172
172
  const nameTokens = tokenise(tool.name);
173
+ const nameTokenSet = new Set(nameTokens);
173
174
  const domainTokens = domain ? tokenise(domain) : [];
174
175
  const descTokens = tokenise(description);
175
176
  const allTokens = [...nameTokens, ...domainTokens, ...descTokens];
@@ -180,6 +181,9 @@ export class ToolSearchEngine {
180
181
  shortDescription,
181
182
  tokens: allTokens,
182
183
  length: allTokens.length,
184
+ nameTokens,
185
+ nameTokenSet,
186
+ nameTokenCount: nameTokenSet.size,
183
187
  };
184
188
  this.docs.push(doc);
185
189
  totalLength += doc.length;
@@ -249,11 +253,13 @@ export class ToolSearchEngine {
249
253
  }
250
254
  continue;
251
255
  }
252
- const nameTokens = tokenise(doc.name);
253
- const nameTokenSet = new Set(nameTokens);
254
- const matchedCount = queryTokens.filter((qt) => nameTokenSet.has(qt)).length;
255
- if (matchedCount > 0 && nameTokenSet.size > 0 && queryTokenSet.size > 0) {
256
- const coverage = matchedCount / nameTokenSet.size;
256
+ let matchedCount = 0;
257
+ for (const qt of queryTokens) {
258
+ if (doc.nameTokenSet.has(qt))
259
+ matchedCount++;
260
+ }
261
+ if (matchedCount > 0 && doc.nameTokenCount > 0 && queryTokenSet.size > 0) {
262
+ const coverage = matchedCount / doc.nameTokenCount;
257
263
  const precision = matchedCount / queryTokenSet.size;
258
264
  scores[i] *= 1 + 0.5 * coverage * precision;
259
265
  }
@@ -148,13 +148,14 @@ export class TabWorkflowHandlers {
148
148
  await newPage.goto(url, { waitUntil: 'domcontentloaded' });
149
149
  const pages = context.pages();
150
150
  const idx = pages.indexOf(newPage);
151
+ const pageTitle = await newPage.title();
151
152
  const pageId = this.registry.registerPage(newPage, {
152
153
  index: idx,
153
154
  url: newPage.url(),
154
- title: await newPage.title(),
155
+ title: pageTitle,
155
156
  });
156
157
  this.registry.bindAlias(alias, pageId);
157
- return this.ok({ alias, index: idx, pageId, url: newPage.url(), title: await newPage.title() });
158
+ return this.ok({ alias, index: idx, pageId, url: newPage.url(), title: pageTitle });
158
159
  }
159
160
  const browser = await this.getBrowserFromController();
160
161
  if (!browser)
@@ -163,13 +164,14 @@ export class TabWorkflowHandlers {
163
164
  await newPage.goto(url, { waitUntil: 'domcontentloaded' });
164
165
  const pages = await browser.pages();
165
166
  const idx = pages.indexOf(newPage);
167
+ const pageTitle = await newPage.title();
166
168
  const pageId = this.registry.registerPage(newPage, {
167
169
  index: idx,
168
170
  url: newPage.url(),
169
- title: await newPage.title(),
171
+ title: pageTitle,
170
172
  });
171
173
  this.registry.bindAlias(alias, pageId);
172
- return this.ok({ alias, index: idx, pageId, url: newPage.url(), title: await newPage.title() });
174
+ return this.ok({ alias, index: idx, pageId, url: newPage.url(), title: pageTitle });
173
175
  }
174
176
  async navigateAlias(args) {
175
177
  const alias = readRequiredString(args.alias);
@@ -233,28 +233,38 @@ async function rewriteLocalExtensionSdkDependency(installDir) {
233
233
  }
234
234
  }
235
235
  async function findRegistryEntryBySlug(registryBase, slug) {
236
- let workflowFetchError;
237
- try {
238
- const workflowIndex = await fetchJson(`${registryBase}/workflows.index.json`, { cacheKey: 'workflows' });
239
- const workflowEntry = workflowIndex.data.workflows.find((item) => item.slug === slug);
236
+ const [workflowResult, pluginResult] = await Promise.allSettled([
237
+ fetchJson(`${registryBase}/workflows.index.json`, { cacheKey: 'workflows' }),
238
+ fetchJson(`${registryBase}/plugins.index.json`, { cacheKey: 'plugins' }),
239
+ ]);
240
+ if (workflowResult.status === 'fulfilled') {
241
+ const workflows = Array.isArray(workflowResult.value.data.workflows)
242
+ ? workflowResult.value.data.workflows
243
+ : [];
244
+ const workflowEntry = workflows.find((item) => item.slug === slug);
240
245
  if (workflowEntry) {
241
246
  return { entry: workflowEntry, kind: 'workflow' };
242
247
  }
243
248
  }
244
- catch (error) {
245
- workflowFetchError = error instanceof Error ? error : new Error(String(error));
246
- }
247
- let pluginFetchError;
248
- try {
249
- const pluginIndex = await fetchJson(`${registryBase}/plugins.index.json`, { cacheKey: 'plugins' });
250
- const pluginEntry = pluginIndex.data.plugins.find((item) => item.slug === slug);
249
+ if (pluginResult.status === 'fulfilled') {
250
+ const plugins = Array.isArray(pluginResult.value.data.plugins)
251
+ ? pluginResult.value.data.plugins
252
+ : [];
253
+ const pluginEntry = plugins.find((item) => item.slug === slug);
251
254
  if (pluginEntry) {
252
255
  return { entry: pluginEntry, kind: 'plugin' };
253
256
  }
254
257
  }
255
- catch (error) {
256
- pluginFetchError = error instanceof Error ? error : new Error(String(error));
257
- }
258
+ const workflowFetchError = workflowResult.status === 'rejected'
259
+ ? workflowResult.reason instanceof Error
260
+ ? workflowResult.reason
261
+ : new Error(String(workflowResult.reason))
262
+ : undefined;
263
+ const pluginFetchError = pluginResult.status === 'rejected'
264
+ ? pluginResult.reason instanceof Error
265
+ ? pluginResult.reason
266
+ : new Error(String(pluginResult.reason))
267
+ : undefined;
258
268
  if (workflowFetchError && pluginFetchError) {
259
269
  throw new Error(`Failed to resolve extension slug "${slug}": workflow registry error: ${workflowFetchError.message}; plugin registry error: ${pluginFetchError.message}`);
260
270
  }
@@ -298,9 +308,19 @@ export class ExtensionManagementHandlers {
298
308
  const showWorkflows = kind === 'all' || kind === 'workflow';
299
309
  const result = { success: true };
300
310
  let stale = false;
301
- if (showPlugins) {
302
- const index = await fetchJson(`${registryBase}/plugins.index.json`, { cacheKey: 'plugins' });
303
- result.plugins = index.data.plugins.map((p) => ({
311
+ const pluginPromise = showPlugins
312
+ ? fetchJson(`${registryBase}/plugins.index.json`, { cacheKey: 'plugins' })
313
+ : undefined;
314
+ const workflowPromise = showWorkflows
315
+ ? fetchJson(`${registryBase}/workflows.index.json`, { cacheKey: 'workflows' })
316
+ : undefined;
317
+ const [pluginIndex, workflowIndex] = await Promise.all([
318
+ pluginPromise ?? Promise.resolve(undefined),
319
+ workflowPromise ?? Promise.resolve(undefined),
320
+ ]);
321
+ if (pluginIndex) {
322
+ const plugins = Array.isArray(pluginIndex.data.plugins) ? pluginIndex.data.plugins : [];
323
+ result.plugins = plugins.map((p) => ({
304
324
  slug: p.slug,
305
325
  id: p.id,
306
326
  name: p.meta.name,
@@ -310,13 +330,13 @@ export class ExtensionManagementHandlers {
310
330
  commit: p.source.commit,
311
331
  entry: p.source.entry,
312
332
  }));
313
- result.pluginCount = index.data.plugins.length;
314
- result.pluginSource = index.source;
315
- stale = stale || index.stale;
333
+ result.pluginCount = plugins.length;
334
+ result.pluginSource = pluginIndex.source;
335
+ stale = stale || pluginIndex.stale;
316
336
  }
317
- if (showWorkflows) {
318
- const index = await fetchJson(`${registryBase}/workflows.index.json`, { cacheKey: 'workflows' });
319
- result.workflows = index.data.workflows.map((w) => ({
337
+ if (workflowIndex) {
338
+ const workflows = Array.isArray(workflowIndex.data.workflows) ? workflowIndex.data.workflows : [];
339
+ result.workflows = workflows.map((w) => ({
320
340
  slug: w.slug,
321
341
  id: w.id,
322
342
  name: w.meta.name,
@@ -326,9 +346,9 @@ export class ExtensionManagementHandlers {
326
346
  commit: w.source.commit,
327
347
  entry: w.source.entry,
328
348
  }));
329
- result.workflowCount = index.data.workflows.length;
330
- result.workflowSource = index.source;
331
- stale = stale || index.stale;
349
+ result.workflowCount = workflows.length;
350
+ result.workflowSource = workflowIndex.source;
351
+ stale = stale || workflowIndex.stale;
332
352
  }
333
353
  if (stale) {
334
354
  result.stale = true;
@@ -121,7 +121,7 @@ export const processToolDefinitions = [
121
121
  },
122
122
  {
123
123
  name: 'memory_read',
124
- description: 'Read memory from a process at a specific address. Requires process to be attached.',
124
+ description: 'Read memory from a process at a specific address. Failures include structured diagnostics for permissions, region checks, and ASLR guidance.',
125
125
  inputSchema: {
126
126
  type: 'object',
127
127
  properties: {
@@ -143,7 +143,7 @@ export const processToolDefinitions = [
143
143
  },
144
144
  {
145
145
  name: 'memory_write',
146
- description: 'Write data to process memory at a specific address. Requires process to be attached.',
146
+ description: 'Write data to process memory at a specific address. Failures include structured diagnostics for permissions, region checks, and ASLR guidance.',
147
147
  inputSchema: {
148
148
  type: 'object',
149
149
  properties: {
@@ -171,7 +171,7 @@ export const processToolDefinitions = [
171
171
  },
172
172
  {
173
173
  name: 'memory_scan',
174
- description: 'Scan process memory for a pattern or value. Useful for finding game values.',
174
+ description: 'Scan process memory for a pattern or value. Failures include structured diagnostics for permissions, region checks, and ASLR guidance.',
175
175
  inputSchema: {
176
176
  type: 'object',
177
177
  properties: {
@@ -325,9 +325,22 @@ export const processToolDefinitions = [
325
325
  required: ['pid'],
326
326
  },
327
327
  },
328
+ {
329
+ name: 'memory_audit_export',
330
+ description: 'Export the in-memory audit trail for memory operations as JSON. Supports clear=true to flush the buffer after export.',
331
+ inputSchema: {
332
+ type: 'object',
333
+ properties: {
334
+ clear: {
335
+ type: 'boolean',
336
+ description: 'Clear audit trail after export',
337
+ },
338
+ },
339
+ },
340
+ },
328
341
  {
329
342
  name: 'inject_dll',
330
- description: 'Inject a DLL into a target process using CreateRemoteThread + LoadLibraryA. Requires administrator privileges.',
343
+ description: 'Inject a DLL into a target process using CreateRemoteThread + LoadLibraryA. Disabled by default; set ENABLE_INJECTION_TOOLS=true to enable. Requires administrator privileges.',
331
344
  inputSchema: {
332
345
  type: 'object',
333
346
  properties: {
@@ -345,7 +358,7 @@ export const processToolDefinitions = [
345
358
  },
346
359
  {
347
360
  name: 'module_inject_dll',
348
- description: 'Alias of inject_dll. Inject a DLL into a target process.',
361
+ description: 'Alias of inject_dll. Disabled by default; set ENABLE_INJECTION_TOOLS=true to enable.',
349
362
  inputSchema: {
350
363
  type: 'object',
351
364
  properties: {
@@ -363,7 +376,7 @@ export const processToolDefinitions = [
363
376
  },
364
377
  {
365
378
  name: 'inject_shellcode',
366
- description: 'Inject and execute shellcode in a target process. Uses VirtualAllocEx + WriteProcessMemory + CreateRemoteThread.',
379
+ description: 'Inject and execute shellcode in a target process. Accepts hex or base64. Disabled by default; set ENABLE_INJECTION_TOOLS=true to enable.',
367
380
  inputSchema: {
368
381
  type: 'object',
369
382
  properties: {
@@ -387,7 +400,7 @@ export const processToolDefinitions = [
387
400
  },
388
401
  {
389
402
  name: 'module_inject_shellcode',
390
- description: 'Alias of inject_shellcode. Inject and execute shellcode in a target process.',
403
+ description: 'Alias of inject_shellcode. Disabled by default; set ENABLE_INJECTION_TOOLS=true to enable.',
391
404
  inputSchema: {
392
405
  type: 'object',
393
406
  properties: {
@@ -1,4 +1,36 @@
1
1
  import { UnifiedProcessManager, MemoryManager } from '../../domains/shared/modules.js';
2
+ import { MemoryAuditTrail } from '../../../modules/process/memory/AuditTrail.js';
3
+ interface MemoryDiagnosticsInput {
4
+ pid?: number;
5
+ address?: string;
6
+ size?: number;
7
+ operation: string;
8
+ error?: string;
9
+ }
10
+ interface MemoryDiagnostics {
11
+ permission: {
12
+ available: boolean;
13
+ reason?: string;
14
+ platform: string;
15
+ };
16
+ process: {
17
+ exists: boolean | null;
18
+ pid: number | null;
19
+ name: string | null;
20
+ };
21
+ address: {
22
+ queried: boolean;
23
+ valid: boolean | null;
24
+ protection: string | null;
25
+ regionStart: string | null;
26
+ regionSize: number | null;
27
+ };
28
+ aslr: {
29
+ heuristic: true;
30
+ note: string;
31
+ };
32
+ recommendedActions: string[];
33
+ }
2
34
  export declare function validatePid(value: unknown): number;
3
35
  export declare function requireString(value: unknown, name: string): string;
4
36
  export declare function requirePositiveNumber(value: unknown, name: string): number;
@@ -6,7 +38,9 @@ export declare class ProcessToolHandlersBase {
6
38
  protected processManager: UnifiedProcessManager;
7
39
  protected memoryManager: MemoryManager;
8
40
  protected platform: string;
41
+ protected auditTrail: MemoryAuditTrail;
9
42
  constructor();
43
+ protected buildMemoryDiagnostics(input: MemoryDiagnosticsInput): Promise<MemoryDiagnostics>;
10
44
  handleProcessFind(args: Record<string, unknown>): Promise<{
11
45
  content: {
12
46
  type: string;
@@ -50,3 +84,4 @@ export declare class ProcessToolHandlersBase {
50
84
  }[];
51
85
  }>;
52
86
  }
87
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { UnifiedProcessManager, MemoryManager } from '../../domains/shared/modules.js';
2
+ import { MemoryAuditTrail } from '../../../modules/process/memory/AuditTrail.js';
2
3
  import { logger } from '../../../utils/logger.js';
3
4
  export function validatePid(value) {
4
5
  const n = Number(value);
@@ -22,12 +23,115 @@ export class ProcessToolHandlersBase {
22
23
  processManager;
23
24
  memoryManager;
24
25
  platform;
26
+ auditTrail = new MemoryAuditTrail();
25
27
  constructor() {
26
28
  this.processManager = new UnifiedProcessManager();
27
29
  this.memoryManager = new MemoryManager();
28
30
  this.platform = this.processManager.getPlatform();
29
31
  logger.info(`ProcessToolHandlers initialized for platform: ${this.platform}`);
30
32
  }
33
+ async buildMemoryDiagnostics(input) {
34
+ const recommendedActions = new Set();
35
+ const permission = await this.memoryManager.checkAvailability();
36
+ if (!permission.available) {
37
+ recommendedActions.add('Run as administrator');
38
+ }
39
+ let processInfo = null;
40
+ if (input.pid != null) {
41
+ try {
42
+ const resolvedProcess = await this.processManager.getProcessByPid(input.pid);
43
+ processInfo = resolvedProcess
44
+ ? {
45
+ pid: resolvedProcess.pid,
46
+ name: resolvedProcess.name,
47
+ executablePath: resolvedProcess.executablePath,
48
+ windowTitle: resolvedProcess.windowTitle,
49
+ windowHandle: resolvedProcess.windowHandle,
50
+ memoryUsage: resolvedProcess.memoryUsage,
51
+ }
52
+ : null;
53
+ }
54
+ catch {
55
+ processInfo = null;
56
+ }
57
+ if (!processInfo) {
58
+ recommendedActions.add('Check if process is still running');
59
+ }
60
+ }
61
+ let protectionInfo = null;
62
+ let protectionQueryFailed = false;
63
+ if (input.pid != null && input.address) {
64
+ try {
65
+ protectionInfo = await this.memoryManager.checkMemoryProtection(input.pid, input.address);
66
+ }
67
+ catch {
68
+ protectionQueryFailed = true;
69
+ }
70
+ if (protectionQueryFailed || protectionInfo?.success === false) {
71
+ recommendedActions.add('Verify address is within valid memory region');
72
+ }
73
+ }
74
+ if (input.size != null && protectionInfo?.regionSize != null && input.size > protectionInfo.regionSize) {
75
+ recommendedActions.add('Reduce the requested size to fit the target memory region');
76
+ }
77
+ if (input.operation === 'memory_read' && protectionInfo?.success && protectionInfo.isReadable === false) {
78
+ recommendedActions.add('Ensure target memory region is readable');
79
+ }
80
+ if (input.operation === 'memory_write' && protectionInfo?.success && protectionInfo.isWritable === false) {
81
+ recommendedActions.add('Ensure target memory region is writable');
82
+ }
83
+ let modulesEnumerated = false;
84
+ let moduleCount = null;
85
+ if (input.pid != null) {
86
+ try {
87
+ const modulesResult = await this.memoryManager.enumerateModules(input.pid);
88
+ modulesEnumerated = modulesResult.success;
89
+ moduleCount = modulesResult.modules?.length ?? null;
90
+ }
91
+ catch {
92
+ modulesEnumerated = false;
93
+ }
94
+ }
95
+ if (input.pid != null && input.address) {
96
+ recommendedActions.add('Re-resolve the address after the process restarts because ASLR can shift module addresses');
97
+ }
98
+ const normalizedError = input.error?.toLowerCase() ?? '';
99
+ if (normalizedError.includes('access denied') ||
100
+ normalizedError.includes('permission') ||
101
+ normalizedError.includes('privilege') ||
102
+ normalizedError.includes('administrator')) {
103
+ recommendedActions.add('Run as administrator');
104
+ }
105
+ const aslrNote = modulesEnumerated
106
+ ? moduleCount && moduleCount > 0
107
+ ? `Enumerated ${moduleCount} module(s). Treat absolute addresses as session-specific because ASLR can shift module bases between launches.`
108
+ : 'Module enumeration succeeded but returned no modules. Absolute addresses may still change across process launches because of ASLR.'
109
+ : 'Module enumeration was unavailable. Assume ASLR may shift absolute addresses between launches and re-resolve addresses after restarts.';
110
+ return {
111
+ permission: {
112
+ available: permission.available,
113
+ reason: permission.reason,
114
+ platform: this.platform,
115
+ },
116
+ process: {
117
+ exists: input.pid != null ? Boolean(processInfo) : null,
118
+ pid: input.pid ?? null,
119
+ name: processInfo?.name ?? null,
120
+ },
121
+ address: {
122
+ queried: input.pid != null && Boolean(input.address),
123
+ valid: input.pid != null && input.address ? protectionInfo?.success ?? null : null,
124
+ protection: protectionInfo?.protection ?? null,
125
+ regionStart: protectionInfo?.regionStart ?? null,
126
+ regionSize: protectionInfo?.regionSize ?? null,
127
+ },
128
+ aslr: {
129
+ heuristic: true,
130
+ note: aslrNote,
131
+ },
132
+ recommendedActions: Array.from(recommendedActions),
133
+ };
134
+ }
31
135
  async handleProcessFind(args) {
32
136
  try {
33
137
  const pattern = requireString(args.pattern, 'pattern');
@@ -86,7 +190,9 @@ export class ProcessToolHandlersBase {
86
190
  };
87
191
  }
88
192
  const cmdLine = await this.processManager.getProcessCommandLine(pid);
89
- const debugPort = await this.processManager.checkDebugPort(pid);
193
+ const debugPort = await this.processManager.checkDebugPort(pid, {
194
+ commandLine: cmdLine.commandLine,
195
+ });
90
196
  return {
91
197
  content: [
92
198
  {
@@ -1,12 +1,67 @@
1
1
  import { logger } from '../../../utils/logger.js';
2
+ import { ENABLE_INJECTION_TOOLS } from '../../../constants.js';
2
3
  import { ProcessToolHandlersMemory } from '../../domains/process/handlers.impl.core.runtime.memory.js';
3
4
  import { requireString, validatePid } from '../../domains/process/handlers.impl.core.runtime.base.js';
5
+ const INJECTION_TOOLS_DISABLED_ERROR = 'Injection tools are disabled by default for safety. Set ENABLE_INJECTION_TOOLS=true before starting the server to enable DLL and shellcode injection.';
6
+ const INJECTION_TOOLS_ENABLE_GUIDANCE = 'Set ENABLE_INJECTION_TOOLS=true before starting the server.';
7
+ const INJECTION_TOOLS_SECURITY_NOTICE = 'Only enable injection tools in an authorized debugging, lab, or CTF environment.';
8
+ function buildInjectionDisabledPayload() {
9
+ return {
10
+ success: false,
11
+ error: INJECTION_TOOLS_DISABLED_ERROR,
12
+ howToEnable: INJECTION_TOOLS_ENABLE_GUIDANCE,
13
+ securityNotice: INJECTION_TOOLS_SECURITY_NOTICE,
14
+ };
15
+ }
16
+ function getOptionalPid(value) {
17
+ const pid = Number(value);
18
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
19
+ }
20
+ function getOptionalString(value) {
21
+ return typeof value === 'string' && value.length > 0 ? value : null;
22
+ }
23
+ function getShellcodeSize(shellcode, encoding) {
24
+ if (encoding === 'hex') {
25
+ const normalized = shellcode.replace(/\s+/g, '');
26
+ return Math.ceil(normalized.length / 2);
27
+ }
28
+ return Buffer.from(shellcode, 'base64').length;
29
+ }
4
30
  export class ProcessToolHandlersRuntime extends ProcessToolHandlersMemory {
5
31
  async handleInjectDll(args) {
32
+ const startedAt = Date.now();
33
+ if (!ENABLE_INJECTION_TOOLS) {
34
+ this.recordMemoryAudit({
35
+ operation: 'inject_dll',
36
+ pid: getOptionalPid(args.pid),
37
+ address: getOptionalString(args.dllPath),
38
+ size: null,
39
+ result: 'failure',
40
+ error: INJECTION_TOOLS_DISABLED_ERROR,
41
+ durationMs: Date.now() - startedAt,
42
+ });
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text',
47
+ text: JSON.stringify(buildInjectionDisabledPayload(), null, 2),
48
+ },
49
+ ],
50
+ };
51
+ }
6
52
  try {
7
53
  const pid = validatePid(args.pid);
8
54
  const dllPath = requireString(args.dllPath, 'dllPath');
9
55
  const result = await this.memoryManager.injectDll(pid, dllPath);
56
+ this.recordMemoryAudit({
57
+ operation: 'inject_dll',
58
+ pid,
59
+ address: dllPath,
60
+ size: null,
61
+ result: result.success ? 'success' : 'failure',
62
+ error: result.error,
63
+ durationMs: Date.now() - startedAt,
64
+ });
10
65
  return {
11
66
  content: [
12
67
  {
@@ -18,22 +73,64 @@ export class ProcessToolHandlersRuntime extends ProcessToolHandlersMemory {
18
73
  }
19
74
  catch (error) {
20
75
  logger.error('DLL injection failed:', error);
76
+ const errorMessage = error instanceof Error ? error.message : String(error);
77
+ this.recordMemoryAudit({
78
+ operation: 'inject_dll',
79
+ pid: getOptionalPid(args.pid),
80
+ address: getOptionalString(args.dllPath),
81
+ size: null,
82
+ result: 'failure',
83
+ error: errorMessage,
84
+ durationMs: Date.now() - startedAt,
85
+ });
21
86
  return {
22
87
  content: [
23
88
  {
24
89
  type: 'text',
25
- text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }, null, 2),
90
+ text: JSON.stringify({ success: false, error: errorMessage }, null, 2),
26
91
  },
27
92
  ],
28
93
  };
29
94
  }
30
95
  }
31
96
  async handleInjectShellcode(args) {
97
+ const startedAt = Date.now();
98
+ if (!ENABLE_INJECTION_TOOLS) {
99
+ const shellcode = getOptionalString(args.shellcode);
100
+ const encoding = args.encoding || 'hex';
101
+ this.recordMemoryAudit({
102
+ operation: 'inject_shellcode',
103
+ pid: getOptionalPid(args.pid),
104
+ address: null,
105
+ size: shellcode ? getShellcodeSize(shellcode, encoding) : null,
106
+ result: 'failure',
107
+ error: INJECTION_TOOLS_DISABLED_ERROR,
108
+ durationMs: Date.now() - startedAt,
109
+ });
110
+ return {
111
+ content: [
112
+ {
113
+ type: 'text',
114
+ text: JSON.stringify(buildInjectionDisabledPayload(), null, 2),
115
+ },
116
+ ],
117
+ };
118
+ }
32
119
  try {
33
120
  const pid = validatePid(args.pid);
34
121
  const shellcode = requireString(args.shellcode, 'shellcode');
35
122
  const encoding = args.encoding || 'hex';
123
+ const size = getShellcodeSize(shellcode, encoding);
36
124
  const result = await this.memoryManager.injectShellcode(pid, shellcode, encoding);
125
+ this.recordMemoryAudit({
126
+ operation: 'inject_shellcode',
127
+ pid,
128
+ address: null,
129
+ size,
130
+ result: result.success ? 'success' : 'failure',
131
+ error: result.error,
132
+ durationMs: Date.now() - startedAt,
133
+ });
37
134
  return {
38
135
  content: [
39
136
  {
@@ -45,11 +142,23 @@ export class ProcessToolHandlersRuntime extends ProcessToolHandlersMemory {
45
142
  }
46
143
  catch (error) {
47
144
  logger.error('Shellcode injection failed:', error);
145
+ const errorMessage = error instanceof Error ? error.message : String(error);
146
+ const shellcode = getOptionalString(args.shellcode);
147
+ const encoding = args.encoding || 'hex';
148
+ this.recordMemoryAudit({
149
+ operation: 'inject_shellcode',
150
+ pid: getOptionalPid(args.pid),
151
+ address: null,
152
+ size: shellcode ? getShellcodeSize(shellcode, encoding) : null,
153
+ result: 'failure',
154
+ error: errorMessage,
155
+ durationMs: Date.now() - startedAt,
156
+ });
48
157
  return {
49
158
  content: [
50
159
  {
51
160
  type: 'text',
52
- text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }, null, 2),
161
+ text: JSON.stringify({ success: false, error: errorMessage }, null, 2),
53
162
  },
54
163
  ],
55
164
  };