@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.
- 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/index.js +0 -0
- 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/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 +7 -3
- 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/linux/mapsParser.d.ts +16 -0
- package/dist/modules/process/memory/linux/mapsParser.js +28 -0
- 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 +225 -24
- 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/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/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/workflow/handlers.impl.workflow-api.js +14 -4
- 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 +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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
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
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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:
|
|
155
|
+
title: pageTitle,
|
|
155
156
|
});
|
|
156
157
|
this.registry.bindAlias(alias, pageId);
|
|
157
|
-
return this.ok({ alias, index: idx, pageId, url: newPage.url(), 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:
|
|
171
|
+
title: pageTitle,
|
|
170
172
|
});
|
|
171
173
|
this.registry.bindAlias(alias, pageId);
|
|
172
|
-
return this.ok({ alias, index: idx, pageId, url: newPage.url(), 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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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 =
|
|
314
|
-
result.pluginSource =
|
|
315
|
-
stale = stale ||
|
|
333
|
+
result.pluginCount = plugins.length;
|
|
334
|
+
result.pluginSource = pluginIndex.source;
|
|
335
|
+
stale = stale || pluginIndex.stale;
|
|
316
336
|
}
|
|
317
|
-
if (
|
|
318
|
-
const
|
|
319
|
-
result.workflows =
|
|
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 =
|
|
330
|
-
result.workflowSource =
|
|
331
|
-
stale = 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
161
|
+
text: JSON.stringify({ success: false, error: errorMessage }, null, 2),
|
|
53
162
|
},
|
|
54
163
|
],
|
|
55
164
|
};
|