windows-exe-decompiler-mcp-server 0.1.0
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/CODEX_INSTALLATION.md +69 -0
- package/COPILOT_INSTALLATION.md +77 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/bin/windows-exe-decompiler-mcp-server.js +3 -0
- package/dist/analysis-provenance.d.ts +184 -0
- package/dist/analysis-provenance.js +74 -0
- package/dist/analysis-task-runner.d.ts +31 -0
- package/dist/analysis-task-runner.js +160 -0
- package/dist/artifact-inventory.d.ts +23 -0
- package/dist/artifact-inventory.js +175 -0
- package/dist/cache-manager.d.ts +128 -0
- package/dist/cache-manager.js +454 -0
- package/dist/confidence-semantics.d.ts +66 -0
- package/dist/confidence-semantics.js +122 -0
- package/dist/config.d.ts +335 -0
- package/dist/config.js +193 -0
- package/dist/database.d.ts +227 -0
- package/dist/database.js +601 -0
- package/dist/decompiler-worker.d.ts +441 -0
- package/dist/decompiler-worker.js +1962 -0
- package/dist/dynamic-trace.d.ts +95 -0
- package/dist/dynamic-trace.js +629 -0
- package/dist/env-validator.d.ts +15 -0
- package/dist/env-validator.js +249 -0
- package/dist/error-handler.d.ts +28 -0
- package/dist/error-handler.example.d.ts +22 -0
- package/dist/error-handler.example.js +141 -0
- package/dist/error-handler.js +139 -0
- package/dist/ghidra-analysis-status.d.ts +49 -0
- package/dist/ghidra-analysis-status.js +178 -0
- package/dist/ghidra-config.d.ts +134 -0
- package/dist/ghidra-config.js +464 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +200 -0
- package/dist/job-queue.d.ts +169 -0
- package/dist/job-queue.js +407 -0
- package/dist/logger.d.ts +106 -0
- package/dist/logger.js +176 -0
- package/dist/policy-guard.d.ts +115 -0
- package/dist/policy-guard.js +243 -0
- package/dist/process-output.d.ts +15 -0
- package/dist/process-output.js +90 -0
- package/dist/prompts/function-explanation-review.d.ts +5 -0
- package/dist/prompts/function-explanation-review.js +64 -0
- package/dist/prompts/semantic-name-review.d.ts +5 -0
- package/dist/prompts/semantic-name-review.js +63 -0
- package/dist/runtime-correlation.d.ts +34 -0
- package/dist/runtime-correlation.js +279 -0
- package/dist/runtime-paths.d.ts +3 -0
- package/dist/runtime-paths.js +11 -0
- package/dist/selection-diff.d.ts +667 -0
- package/dist/selection-diff.js +53 -0
- package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
- package/dist/semantic-name-suggestion-artifacts.js +314 -0
- package/dist/server.d.ts +129 -0
- package/dist/server.js +578 -0
- package/dist/tools/artifact-read.d.ts +235 -0
- package/dist/tools/artifact-read.js +317 -0
- package/dist/tools/artifacts-diff.d.ts +728 -0
- package/dist/tools/artifacts-diff.js +304 -0
- package/dist/tools/artifacts-list.d.ts +515 -0
- package/dist/tools/artifacts-list.js +389 -0
- package/dist/tools/attack-map.d.ts +290 -0
- package/dist/tools/attack-map.js +519 -0
- package/dist/tools/cache-observability.d.ts +4 -0
- package/dist/tools/cache-observability.js +36 -0
- package/dist/tools/code-function-cfg.d.ts +50 -0
- package/dist/tools/code-function-cfg.js +102 -0
- package/dist/tools/code-function-decompile.d.ts +55 -0
- package/dist/tools/code-function-decompile.js +103 -0
- package/dist/tools/code-function-disassemble.d.ts +43 -0
- package/dist/tools/code-function-disassemble.js +185 -0
- package/dist/tools/code-function-explain-apply.d.ts +255 -0
- package/dist/tools/code-function-explain-apply.js +225 -0
- package/dist/tools/code-function-explain-prepare.d.ts +535 -0
- package/dist/tools/code-function-explain-prepare.js +276 -0
- package/dist/tools/code-function-explain-review.d.ts +397 -0
- package/dist/tools/code-function-explain-review.js +589 -0
- package/dist/tools/code-function-rename-apply.d.ts +248 -0
- package/dist/tools/code-function-rename-apply.js +220 -0
- package/dist/tools/code-function-rename-prepare.d.ts +506 -0
- package/dist/tools/code-function-rename-prepare.js +279 -0
- package/dist/tools/code-function-rename-review.d.ts +574 -0
- package/dist/tools/code-function-rename-review.js +761 -0
- package/dist/tools/code-functions-list.d.ts +37 -0
- package/dist/tools/code-functions-list.js +91 -0
- package/dist/tools/code-functions-rank.d.ts +34 -0
- package/dist/tools/code-functions-rank.js +90 -0
- package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
- package/dist/tools/code-functions-reconstruct.js +2807 -0
- package/dist/tools/code-functions-search.d.ts +39 -0
- package/dist/tools/code-functions-search.js +90 -0
- package/dist/tools/code-reconstruct-export.d.ts +1212 -0
- package/dist/tools/code-reconstruct-export.js +4002 -0
- package/dist/tools/code-reconstruct-plan.d.ts +274 -0
- package/dist/tools/code-reconstruct-plan.js +342 -0
- package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
- package/dist/tools/dotnet-metadata-extract.js +355 -0
- package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
- package/dist/tools/dotnet-reconstruct-export.js +1151 -0
- package/dist/tools/dotnet-types-list.d.ts +325 -0
- package/dist/tools/dotnet-types-list.js +201 -0
- package/dist/tools/dynamic-dependencies.d.ts +115 -0
- package/dist/tools/dynamic-dependencies.js +213 -0
- package/dist/tools/dynamic-memory-import.d.ts +10 -0
- package/dist/tools/dynamic-memory-import.js +567 -0
- package/dist/tools/dynamic-trace-import.d.ts +10 -0
- package/dist/tools/dynamic-trace-import.js +235 -0
- package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
- package/dist/tools/entrypoint-fallback-disasm.js +89 -0
- package/dist/tools/ghidra-analyze.d.ts +88 -0
- package/dist/tools/ghidra-analyze.js +208 -0
- package/dist/tools/ghidra-health.d.ts +37 -0
- package/dist/tools/ghidra-health.js +212 -0
- package/dist/tools/ioc-export.d.ts +209 -0
- package/dist/tools/ioc-export.js +542 -0
- package/dist/tools/packer-detect.d.ts +165 -0
- package/dist/tools/packer-detect.js +284 -0
- package/dist/tools/pe-exports-extract.d.ts +175 -0
- package/dist/tools/pe-exports-extract.js +253 -0
- package/dist/tools/pe-fingerprint.d.ts +234 -0
- package/dist/tools/pe-fingerprint.js +269 -0
- package/dist/tools/pe-imports-extract.d.ts +105 -0
- package/dist/tools/pe-imports-extract.js +245 -0
- package/dist/tools/report-generate.d.ts +157 -0
- package/dist/tools/report-generate.js +457 -0
- package/dist/tools/report-summarize.d.ts +2131 -0
- package/dist/tools/report-summarize.js +596 -0
- package/dist/tools/runtime-detect.d.ts +135 -0
- package/dist/tools/runtime-detect.js +247 -0
- package/dist/tools/sample-ingest.d.ts +94 -0
- package/dist/tools/sample-ingest.js +327 -0
- package/dist/tools/sample-profile-get.d.ts +183 -0
- package/dist/tools/sample-profile-get.js +121 -0
- package/dist/tools/sandbox-execute.d.ts +441 -0
- package/dist/tools/sandbox-execute.js +392 -0
- package/dist/tools/strings-extract.d.ts +375 -0
- package/dist/tools/strings-extract.js +314 -0
- package/dist/tools/strings-floss-decode.d.ts +143 -0
- package/dist/tools/strings-floss-decode.js +259 -0
- package/dist/tools/system-health.d.ts +434 -0
- package/dist/tools/system-health.js +446 -0
- package/dist/tools/task-cancel.d.ts +21 -0
- package/dist/tools/task-cancel.js +70 -0
- package/dist/tools/task-status.d.ts +27 -0
- package/dist/tools/task-status.js +106 -0
- package/dist/tools/task-sweep.d.ts +22 -0
- package/dist/tools/task-sweep.js +77 -0
- package/dist/tools/tool-help.d.ts +340 -0
- package/dist/tools/tool-help.js +261 -0
- package/dist/tools/yara-scan.d.ts +554 -0
- package/dist/tools/yara-scan.js +313 -0
- package/dist/types.d.ts +266 -0
- package/dist/types.js +41 -0
- package/dist/worker-pool.d.ts +204 -0
- package/dist/worker-pool.js +650 -0
- package/dist/workflows/deep-static.d.ts +104 -0
- package/dist/workflows/deep-static.js +276 -0
- package/dist/workflows/function-explanation-review.d.ts +655 -0
- package/dist/workflows/function-explanation-review.js +440 -0
- package/dist/workflows/reconstruct.d.ts +2053 -0
- package/dist/workflows/reconstruct.js +666 -0
- package/dist/workflows/semantic-name-review.d.ts +2418 -0
- package/dist/workflows/semantic-name-review.js +521 -0
- package/dist/workflows/triage.d.ts +659 -0
- package/dist/workflows/triage.js +1374 -0
- package/dist/workspace-manager.d.ts +150 -0
- package/dist/workspace-manager.js +411 -0
- package/ghidra_scripts/DecompileFunction.java +487 -0
- package/ghidra_scripts/DecompileFunction.py +150 -0
- package/ghidra_scripts/ExtractCFG.java +256 -0
- package/ghidra_scripts/ExtractCFG.py +233 -0
- package/ghidra_scripts/ExtractFunctions.java +442 -0
- package/ghidra_scripts/ExtractFunctions.py +101 -0
- package/ghidra_scripts/README.md +125 -0
- package/ghidra_scripts/SearchFunctionReferences.java +380 -0
- package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
- package/helpers/DotNetMetadataProbe/Program.cs +566 -0
- package/install-to-codex.ps1 +178 -0
- package/install-to-copilot.ps1 +303 -0
- package/package.json +101 -0
- package/requirements.txt +9 -0
- package/workers/requirements-dynamic.txt +11 -0
- package/workers/requirements.txt +8 -0
- package/workers/speakeasy_compat.py +175 -0
- package/workers/static_worker.py +5183 -0
- package/workers/yara_rules/default.yar +33 -0
- package/workers/yara_rules/malware_families.yar +93 -0
- package/workers/yara_rules/packers.yar +80 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* system.health tool implementation
|
|
3
|
+
* Aggregated runtime health checks for high-availability operations.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { randomUUID } from 'crypto';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { checkGhidraHealth } from '../ghidra-config.js';
|
|
12
|
+
import { resolvePackagePath } from '../runtime-paths.js';
|
|
13
|
+
import { lookupCachedResult } from './cache-observability.js';
|
|
14
|
+
const TOOL_NAME = 'system.health';
|
|
15
|
+
const ComponentSchema = z.object({
|
|
16
|
+
status: z.enum(['healthy', 'degraded', 'unhealthy', 'skipped']),
|
|
17
|
+
ok: z.boolean(),
|
|
18
|
+
error: z.string().nullable().optional(),
|
|
19
|
+
details: z.record(z.any()).optional(),
|
|
20
|
+
});
|
|
21
|
+
const CacheObservabilitySchema = z.object({
|
|
22
|
+
key: z.string().nullable(),
|
|
23
|
+
tier: z.string().nullable(),
|
|
24
|
+
created_at: z.string().nullable(),
|
|
25
|
+
expires_at: z.string().nullable(),
|
|
26
|
+
hit_at: z.string().nullable(),
|
|
27
|
+
sample_sha256: z.string().nullable(),
|
|
28
|
+
sample_state_consistent: z.boolean().nullable(),
|
|
29
|
+
});
|
|
30
|
+
export const SystemHealthInputSchema = z.object({
|
|
31
|
+
sample_id: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Optional sample ID used to verify cache metadata consistency'),
|
|
35
|
+
timeout_ms: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.min(2000)
|
|
39
|
+
.max(120000)
|
|
40
|
+
.default(10000)
|
|
41
|
+
.describe('Timeout for each external probe in milliseconds'),
|
|
42
|
+
include_ghidra: z
|
|
43
|
+
.boolean()
|
|
44
|
+
.default(true)
|
|
45
|
+
.describe('Include ghidra.health probe'),
|
|
46
|
+
include_static_worker: z
|
|
47
|
+
.boolean()
|
|
48
|
+
.default(true)
|
|
49
|
+
.describe('Include Python static-worker dependency probe'),
|
|
50
|
+
include_cache_probe: z
|
|
51
|
+
.boolean()
|
|
52
|
+
.default(true)
|
|
53
|
+
.describe('Include cache observability probe (key/tier/timestamps/sample consistency)'),
|
|
54
|
+
});
|
|
55
|
+
export const SystemHealthOutputSchema = z.object({
|
|
56
|
+
ok: z.boolean(),
|
|
57
|
+
data: z
|
|
58
|
+
.object({
|
|
59
|
+
overall_status: z.enum(['healthy', 'degraded', 'unhealthy']),
|
|
60
|
+
checked_at: z.string(),
|
|
61
|
+
components: z.object({
|
|
62
|
+
workspace: ComponentSchema,
|
|
63
|
+
database: ComponentSchema,
|
|
64
|
+
ghidra: ComponentSchema,
|
|
65
|
+
static_worker: ComponentSchema,
|
|
66
|
+
cache: ComponentSchema,
|
|
67
|
+
}),
|
|
68
|
+
cache_observability: CacheObservabilitySchema,
|
|
69
|
+
recommendations: z.array(z.string()),
|
|
70
|
+
})
|
|
71
|
+
.optional(),
|
|
72
|
+
warnings: z.array(z.string()).optional(),
|
|
73
|
+
errors: z.array(z.string()).optional(),
|
|
74
|
+
metrics: z
|
|
75
|
+
.object({
|
|
76
|
+
elapsed_ms: z.number(),
|
|
77
|
+
tool: z.string(),
|
|
78
|
+
})
|
|
79
|
+
.optional(),
|
|
80
|
+
});
|
|
81
|
+
export const systemHealthToolDefinition = {
|
|
82
|
+
name: TOOL_NAME,
|
|
83
|
+
description: 'Run aggregated environment health checks (workspace, database, Ghidra, Python static worker dependencies).',
|
|
84
|
+
inputSchema: SystemHealthInputSchema,
|
|
85
|
+
outputSchema: SystemHealthOutputSchema,
|
|
86
|
+
};
|
|
87
|
+
function normalizeError(error) {
|
|
88
|
+
if (error instanceof Error) {
|
|
89
|
+
return error.message;
|
|
90
|
+
}
|
|
91
|
+
return String(error);
|
|
92
|
+
}
|
|
93
|
+
async function callStaticWorkerHealth(timeoutMs) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const workerPath = resolvePackagePath('workers', 'static_worker.py');
|
|
96
|
+
const pythonCommand = process.platform === 'win32' ? 'python' : 'python3';
|
|
97
|
+
const processTimeoutMs = Math.max(timeoutMs, 2000);
|
|
98
|
+
const pythonProcess = spawn(pythonCommand, [workerPath], {
|
|
99
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
100
|
+
});
|
|
101
|
+
let stdout = '';
|
|
102
|
+
let stderr = '';
|
|
103
|
+
let settled = false;
|
|
104
|
+
const onDone = (fn) => {
|
|
105
|
+
if (settled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
settled = true;
|
|
109
|
+
fn();
|
|
110
|
+
};
|
|
111
|
+
const timer = setTimeout(() => {
|
|
112
|
+
onDone(() => {
|
|
113
|
+
pythonProcess.kill();
|
|
114
|
+
reject(new Error(`Static worker health probe timed out after ${processTimeoutMs}ms`));
|
|
115
|
+
});
|
|
116
|
+
}, processTimeoutMs);
|
|
117
|
+
pythonProcess.stdout.on('data', (data) => {
|
|
118
|
+
stdout += data.toString();
|
|
119
|
+
});
|
|
120
|
+
pythonProcess.stderr.on('data', (data) => {
|
|
121
|
+
stderr += data.toString();
|
|
122
|
+
});
|
|
123
|
+
pythonProcess.on('error', (error) => {
|
|
124
|
+
onDone(() => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
reject(new Error(`Failed to spawn Python worker: ${error.message}`));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
pythonProcess.on('close', (code) => {
|
|
130
|
+
onDone(() => {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
if (code !== 0) {
|
|
133
|
+
reject(new Error(`Python worker exited with code ${code}. stderr: ${stderr}`));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const lines = stdout
|
|
138
|
+
.trim()
|
|
139
|
+
.split('\n')
|
|
140
|
+
.map((line) => line.trim())
|
|
141
|
+
.filter((line) => line.length > 0);
|
|
142
|
+
const lastLine = lines[lines.length - 1];
|
|
143
|
+
const response = JSON.parse(lastLine);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
reject(new Error((response.errors || []).join('; ') || 'Static worker probe failed'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
resolve((response.data || {}));
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
reject(new Error(`Failed to parse static worker health response: ${normalizeError(error)}. stdout: ${stdout}`));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
const request = {
|
|
157
|
+
job_id: randomUUID(),
|
|
158
|
+
tool: 'system.health',
|
|
159
|
+
sample: {
|
|
160
|
+
sample_id: 'health-check',
|
|
161
|
+
path: '',
|
|
162
|
+
},
|
|
163
|
+
args: {},
|
|
164
|
+
context: {
|
|
165
|
+
request_time_utc: new Date().toISOString(),
|
|
166
|
+
policy: {
|
|
167
|
+
allow_dynamic: false,
|
|
168
|
+
allow_network: false,
|
|
169
|
+
},
|
|
170
|
+
versions: {
|
|
171
|
+
tool_version: '1.0.0',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
pythonProcess.stdin.write(JSON.stringify(request) + '\n');
|
|
176
|
+
pythonProcess.stdin.end();
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
onDone(() => {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
reject(new Error(`Failed to write health request: ${normalizeError(error)}`));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async function checkWorkspaceWritable(root) {
|
|
187
|
+
const probeFile = path.join(root, `.health-probe-${process.pid}-${Date.now()}.tmp`);
|
|
188
|
+
await fs.mkdir(root, { recursive: true });
|
|
189
|
+
await fs.writeFile(probeFile, `probe:${Date.now()}:${os.hostname()}`, 'utf-8');
|
|
190
|
+
await fs.unlink(probeFile);
|
|
191
|
+
}
|
|
192
|
+
export function createSystemHealthHandler(workspaceManager, database, dependencies) {
|
|
193
|
+
const runGhidraCheck = dependencies?.checkGhidra || checkGhidraHealth;
|
|
194
|
+
const runStaticWorkerProbe = dependencies?.probeStaticWorker || callStaticWorkerHealth;
|
|
195
|
+
const cacheManager = dependencies?.cacheManager;
|
|
196
|
+
return async (args) => {
|
|
197
|
+
const input = SystemHealthInputSchema.parse(args);
|
|
198
|
+
const startTime = Date.now();
|
|
199
|
+
try {
|
|
200
|
+
const recommendations = [];
|
|
201
|
+
const warnings = [];
|
|
202
|
+
const workspaceRoot = workspaceManager.getWorkspaceRoot();
|
|
203
|
+
let workspaceComponent;
|
|
204
|
+
try {
|
|
205
|
+
await checkWorkspaceWritable(workspaceRoot);
|
|
206
|
+
workspaceComponent = {
|
|
207
|
+
status: 'healthy',
|
|
208
|
+
ok: true,
|
|
209
|
+
details: { root: workspaceRoot, writable: true },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
workspaceComponent = {
|
|
214
|
+
status: 'unhealthy',
|
|
215
|
+
ok: false,
|
|
216
|
+
error: normalizeError(error),
|
|
217
|
+
details: { root: workspaceRoot, writable: false },
|
|
218
|
+
};
|
|
219
|
+
recommendations.push('Fix workspace permissions/path to restore write access.');
|
|
220
|
+
}
|
|
221
|
+
let databaseComponent;
|
|
222
|
+
try {
|
|
223
|
+
const row = database.getDatabase().prepare('SELECT 1 as ok').get();
|
|
224
|
+
databaseComponent = {
|
|
225
|
+
status: row?.ok === 1 ? 'healthy' : 'degraded',
|
|
226
|
+
ok: row?.ok === 1,
|
|
227
|
+
details: { query_ok: row?.ok === 1 },
|
|
228
|
+
};
|
|
229
|
+
if (row?.ok !== 1) {
|
|
230
|
+
recommendations.push('Check database integrity and connectivity.');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
databaseComponent = {
|
|
235
|
+
status: 'unhealthy',
|
|
236
|
+
ok: false,
|
|
237
|
+
error: normalizeError(error),
|
|
238
|
+
};
|
|
239
|
+
recommendations.push('Repair SQLite database access before production use.');
|
|
240
|
+
}
|
|
241
|
+
let ghidraComponent = {
|
|
242
|
+
status: 'skipped',
|
|
243
|
+
ok: true,
|
|
244
|
+
};
|
|
245
|
+
if (input.include_ghidra) {
|
|
246
|
+
try {
|
|
247
|
+
const ghidraStatus = runGhidraCheck(input.timeout_ms);
|
|
248
|
+
ghidraComponent = {
|
|
249
|
+
status: ghidraStatus.ok ? 'healthy' : 'degraded',
|
|
250
|
+
ok: ghidraStatus.ok,
|
|
251
|
+
error: ghidraStatus.ok ? null : (ghidraStatus.errors || []).join('; '),
|
|
252
|
+
details: ghidraStatus,
|
|
253
|
+
};
|
|
254
|
+
if (ghidraStatus.checks?.pyghidra_available === false) {
|
|
255
|
+
warnings.push('PyGhidra is unavailable; Python post-scripts may fail and Java fallback will be used.');
|
|
256
|
+
recommendations.push('Install pyghidra in the active Python environment to improve script compatibility.');
|
|
257
|
+
}
|
|
258
|
+
if (!ghidraStatus.ok) {
|
|
259
|
+
recommendations.push('Fix Ghidra install path or launch check before decompiler workloads.');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
ghidraComponent = {
|
|
264
|
+
status: 'degraded',
|
|
265
|
+
ok: false,
|
|
266
|
+
error: normalizeError(error),
|
|
267
|
+
};
|
|
268
|
+
recommendations.push('Investigate ghidra.health probe failure.');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
let staticWorkerComponent = {
|
|
272
|
+
status: 'skipped',
|
|
273
|
+
ok: true,
|
|
274
|
+
};
|
|
275
|
+
if (input.include_static_worker) {
|
|
276
|
+
try {
|
|
277
|
+
const workerHealth = await runStaticWorkerProbe(input.timeout_ms);
|
|
278
|
+
const statusRaw = String(workerHealth.status || 'degraded').toLowerCase();
|
|
279
|
+
const status = statusRaw === 'healthy'
|
|
280
|
+
? 'healthy'
|
|
281
|
+
: statusRaw === 'unhealthy'
|
|
282
|
+
? 'unhealthy'
|
|
283
|
+
: 'degraded';
|
|
284
|
+
staticWorkerComponent = {
|
|
285
|
+
status,
|
|
286
|
+
ok: status === 'healthy',
|
|
287
|
+
error: status === 'healthy' ? null : `Static worker status=${statusRaw}`,
|
|
288
|
+
details: workerHealth,
|
|
289
|
+
};
|
|
290
|
+
if (status !== 'healthy') {
|
|
291
|
+
recommendations.push('Install/repair Python dependencies (yara-python, flare-floss, yara rules) for static analysis stability.');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
staticWorkerComponent = {
|
|
296
|
+
status: 'degraded',
|
|
297
|
+
ok: false,
|
|
298
|
+
error: normalizeError(error),
|
|
299
|
+
};
|
|
300
|
+
recommendations.push('Fix Python runtime or static worker startup to avoid analysis outages.');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
let cacheComponent = {
|
|
304
|
+
status: 'skipped',
|
|
305
|
+
ok: true,
|
|
306
|
+
details: { reason: 'cache probe disabled' },
|
|
307
|
+
};
|
|
308
|
+
let cacheObservability = {
|
|
309
|
+
key: null,
|
|
310
|
+
tier: null,
|
|
311
|
+
created_at: null,
|
|
312
|
+
expires_at: null,
|
|
313
|
+
hit_at: null,
|
|
314
|
+
sample_sha256: null,
|
|
315
|
+
sample_state_consistent: null,
|
|
316
|
+
};
|
|
317
|
+
if (input.include_cache_probe) {
|
|
318
|
+
if (!cacheManager) {
|
|
319
|
+
cacheComponent = {
|
|
320
|
+
status: 'skipped',
|
|
321
|
+
ok: true,
|
|
322
|
+
details: { reason: 'cache manager not configured' },
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
try {
|
|
327
|
+
let expectedSampleSha;
|
|
328
|
+
if (input.sample_id) {
|
|
329
|
+
const sample = database.findSample(input.sample_id);
|
|
330
|
+
if (sample) {
|
|
331
|
+
expectedSampleSha = sample.sha256;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
cacheComponent = {
|
|
335
|
+
status: 'degraded',
|
|
336
|
+
ok: false,
|
|
337
|
+
error: `Sample not found for cache consistency check: ${input.sample_id}`,
|
|
338
|
+
};
|
|
339
|
+
recommendations.push('Provide a valid sample_id to verify cache metadata consistency against current sample state.');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (cacheComponent.status !== 'degraded') {
|
|
343
|
+
const probeKey = `health_cache_probe_${Date.now()}_${randomUUID().replace(/-/g, '')}`;
|
|
344
|
+
const probePayload = {
|
|
345
|
+
tool: TOOL_NAME,
|
|
346
|
+
checked_at: new Date().toISOString(),
|
|
347
|
+
host: os.hostname(),
|
|
348
|
+
};
|
|
349
|
+
await cacheManager.setCachedResult(probeKey, probePayload, 60_000, expectedSampleSha);
|
|
350
|
+
const cachedLookup = await lookupCachedResult(cacheManager, probeKey);
|
|
351
|
+
if (!cachedLookup) {
|
|
352
|
+
cacheComponent = {
|
|
353
|
+
status: 'degraded',
|
|
354
|
+
ok: false,
|
|
355
|
+
error: 'cache probe write/read failed',
|
|
356
|
+
details: { probe_key: probeKey },
|
|
357
|
+
};
|
|
358
|
+
recommendations.push('Investigate cache read/write path; probe key was not retrievable.');
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
const metadata = cachedLookup.metadata;
|
|
362
|
+
const sampleStateConsistent = expectedSampleSha !== undefined
|
|
363
|
+
? metadata.sampleSha256 === expectedSampleSha
|
|
364
|
+
: null;
|
|
365
|
+
cacheObservability = {
|
|
366
|
+
key: metadata.key,
|
|
367
|
+
tier: metadata.tier,
|
|
368
|
+
created_at: metadata.createdAt || null,
|
|
369
|
+
expires_at: metadata.expiresAt || null,
|
|
370
|
+
hit_at: metadata.fetchedAt || null,
|
|
371
|
+
sample_sha256: metadata.sampleSha256 || null,
|
|
372
|
+
sample_state_consistent: sampleStateConsistent,
|
|
373
|
+
};
|
|
374
|
+
const cacheHealthy = sampleStateConsistent !== false && typeof metadata.key === 'string';
|
|
375
|
+
cacheComponent = {
|
|
376
|
+
status: cacheHealthy ? 'healthy' : 'degraded',
|
|
377
|
+
ok: cacheHealthy,
|
|
378
|
+
error: cacheHealthy ? null : 'cache metadata sample_sha256 mismatch',
|
|
379
|
+
details: {
|
|
380
|
+
probe_key: metadata.key,
|
|
381
|
+
tier: metadata.tier,
|
|
382
|
+
sample_sha256: metadata.sampleSha256 || null,
|
|
383
|
+
expected_sample_sha256: expectedSampleSha || null,
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
if (!cacheHealthy) {
|
|
387
|
+
recommendations.push('Align cache metadata sample_sha256 with active sample state to reduce stale-cache risk.');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
cacheComponent = {
|
|
394
|
+
status: 'degraded',
|
|
395
|
+
ok: false,
|
|
396
|
+
error: normalizeError(error),
|
|
397
|
+
};
|
|
398
|
+
recommendations.push('Fix cache probe failures to improve cache observability.');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const essentialUnhealthy = workspaceComponent.status === 'unhealthy' || databaseComponent.status === 'unhealthy';
|
|
403
|
+
const optionalIssues = [ghidraComponent, staticWorkerComponent, cacheComponent].some((item) => item.status === 'degraded' || item.status === 'unhealthy');
|
|
404
|
+
const overallStatus = essentialUnhealthy
|
|
405
|
+
? 'unhealthy'
|
|
406
|
+
: optionalIssues
|
|
407
|
+
? 'degraded'
|
|
408
|
+
: 'healthy';
|
|
409
|
+
if (overallStatus !== 'healthy') {
|
|
410
|
+
warnings.push(`System health is ${overallStatus}`);
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
ok: overallStatus !== 'unhealthy',
|
|
414
|
+
data: {
|
|
415
|
+
overall_status: overallStatus,
|
|
416
|
+
checked_at: new Date().toISOString(),
|
|
417
|
+
components: {
|
|
418
|
+
workspace: workspaceComponent,
|
|
419
|
+
database: databaseComponent,
|
|
420
|
+
ghidra: ghidraComponent,
|
|
421
|
+
static_worker: staticWorkerComponent,
|
|
422
|
+
cache: cacheComponent,
|
|
423
|
+
},
|
|
424
|
+
cache_observability: cacheObservability,
|
|
425
|
+
recommendations,
|
|
426
|
+
},
|
|
427
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
428
|
+
metrics: {
|
|
429
|
+
elapsed_ms: Date.now() - startTime,
|
|
430
|
+
tool: TOOL_NAME,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
return {
|
|
436
|
+
ok: false,
|
|
437
|
+
errors: [normalizeError(error)],
|
|
438
|
+
metrics: {
|
|
439
|
+
elapsed_ms: Date.now() - startTime,
|
|
440
|
+
tool: TOOL_NAME,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=system-health.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task.cancel MCP tool
|
|
3
|
+
* Cancel queued/running analysis tasks.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { ToolDefinition, ToolHandler } from '../types.js';
|
|
7
|
+
import type { JobQueue } from '../job-queue.js';
|
|
8
|
+
export declare const taskCancelInputSchema: z.ZodObject<{
|
|
9
|
+
job_id: z.ZodString;
|
|
10
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
job_id: string;
|
|
13
|
+
reason?: string | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
job_id: string;
|
|
16
|
+
reason?: string | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export type TaskCancelInput = z.infer<typeof taskCancelInputSchema>;
|
|
19
|
+
export declare const taskCancelToolDefinition: ToolDefinition;
|
|
20
|
+
export declare function createTaskCancelHandler(jobQueue: JobQueue): ToolHandler;
|
|
21
|
+
//# sourceMappingURL=task-cancel.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task.cancel MCP tool
|
|
3
|
+
* Cancel queued/running analysis tasks.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const TOOL_NAME = 'task.cancel';
|
|
7
|
+
export const taskCancelInputSchema = z.object({
|
|
8
|
+
job_id: z.string().describe('Job id to cancel'),
|
|
9
|
+
reason: z.string().optional().describe('Optional cancellation reason'),
|
|
10
|
+
});
|
|
11
|
+
export const taskCancelToolDefinition = {
|
|
12
|
+
name: TOOL_NAME,
|
|
13
|
+
description: 'Cancel a queued or running analysis task by job id.',
|
|
14
|
+
inputSchema: taskCancelInputSchema,
|
|
15
|
+
};
|
|
16
|
+
export function createTaskCancelHandler(jobQueue) {
|
|
17
|
+
return async (args) => {
|
|
18
|
+
try {
|
|
19
|
+
const input = taskCancelInputSchema.parse(args);
|
|
20
|
+
const cancelled = jobQueue.cancel(input.job_id, input.reason);
|
|
21
|
+
if (!cancelled) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: 'text',
|
|
26
|
+
text: JSON.stringify({
|
|
27
|
+
ok: false,
|
|
28
|
+
errors: [
|
|
29
|
+
`Unable to cancel job ${input.job_id}. It may not exist or is already finished.`,
|
|
30
|
+
],
|
|
31
|
+
}, null, 2),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: 'text',
|
|
41
|
+
text: JSON.stringify({
|
|
42
|
+
ok: true,
|
|
43
|
+
data: {
|
|
44
|
+
job_id: input.job_id,
|
|
45
|
+
cancelled: true,
|
|
46
|
+
reason: input.reason || null,
|
|
47
|
+
},
|
|
48
|
+
}, null, 2),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{
|
|
58
|
+
type: 'text',
|
|
59
|
+
text: JSON.stringify({
|
|
60
|
+
ok: false,
|
|
61
|
+
errors: [message],
|
|
62
|
+
}, null, 2),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=task-cancel.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task.status MCP tool
|
|
3
|
+
* Query analysis task queue status and optional per-job details.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { ToolDefinition, ToolHandler } from '../types.js';
|
|
7
|
+
import type { JobQueue } from '../job-queue.js';
|
|
8
|
+
export declare const taskStatusInputSchema: z.ZodObject<{
|
|
9
|
+
job_id: z.ZodOptional<z.ZodString>;
|
|
10
|
+
status: z.ZodOptional<z.ZodEnum<["queued", "running", "completed", "failed", "cancelled"]>>;
|
|
11
|
+
include_result: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
12
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
limit: number;
|
|
15
|
+
include_result: boolean;
|
|
16
|
+
status?: "failed" | "queued" | "running" | "completed" | "cancelled" | undefined;
|
|
17
|
+
job_id?: string | undefined;
|
|
18
|
+
}, {
|
|
19
|
+
status?: "failed" | "queued" | "running" | "completed" | "cancelled" | undefined;
|
|
20
|
+
limit?: number | undefined;
|
|
21
|
+
job_id?: string | undefined;
|
|
22
|
+
include_result?: boolean | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
export type TaskStatusInput = z.infer<typeof taskStatusInputSchema>;
|
|
25
|
+
export declare const taskStatusToolDefinition: ToolDefinition;
|
|
26
|
+
export declare function createTaskStatusHandler(jobQueue: JobQueue): ToolHandler;
|
|
27
|
+
//# sourceMappingURL=task-status.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task.status MCP tool
|
|
3
|
+
* Query analysis task queue status and optional per-job details.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const TOOL_NAME = 'task.status';
|
|
7
|
+
export const taskStatusInputSchema = z.object({
|
|
8
|
+
job_id: z.string().optional().describe('Optional job id for single-job lookup'),
|
|
9
|
+
status: z
|
|
10
|
+
.enum(['queued', 'running', 'completed', 'failed', 'cancelled'])
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Optional status filter'),
|
|
13
|
+
include_result: z
|
|
14
|
+
.boolean()
|
|
15
|
+
.optional()
|
|
16
|
+
.default(false)
|
|
17
|
+
.describe('Include completed/failed job result payload for single-job lookup'),
|
|
18
|
+
limit: z
|
|
19
|
+
.number()
|
|
20
|
+
.int()
|
|
21
|
+
.min(1)
|
|
22
|
+
.max(500)
|
|
23
|
+
.optional()
|
|
24
|
+
.default(100)
|
|
25
|
+
.describe('Maximum number of jobs to return'),
|
|
26
|
+
});
|
|
27
|
+
export const taskStatusToolDefinition = {
|
|
28
|
+
name: TOOL_NAME,
|
|
29
|
+
description: 'Get queue/running/completed status for analysis tasks, or inspect a specific job.',
|
|
30
|
+
inputSchema: taskStatusInputSchema,
|
|
31
|
+
};
|
|
32
|
+
export function createTaskStatusHandler(jobQueue) {
|
|
33
|
+
return async (args) => {
|
|
34
|
+
try {
|
|
35
|
+
const input = taskStatusInputSchema.parse(args);
|
|
36
|
+
if (input.job_id) {
|
|
37
|
+
const status = jobQueue.getStatus(input.job_id);
|
|
38
|
+
if (!status) {
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: 'text',
|
|
43
|
+
text: JSON.stringify({
|
|
44
|
+
ok: false,
|
|
45
|
+
errors: [`Job not found: ${input.job_id}`],
|
|
46
|
+
}, null, 2),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: 'text',
|
|
56
|
+
text: JSON.stringify({
|
|
57
|
+
ok: true,
|
|
58
|
+
data: {
|
|
59
|
+
job: status,
|
|
60
|
+
result: input.include_result ? jobQueue.getResult(input.job_id) : undefined,
|
|
61
|
+
},
|
|
62
|
+
}, null, 2),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const listStatus = jobQueue.listStatuses;
|
|
68
|
+
const rows = listStatus
|
|
69
|
+
? listStatus.call(jobQueue, input.status)
|
|
70
|
+
: jobQueue.getJobsByStatus(input.status || 'queued');
|
|
71
|
+
const limitedRows = rows.slice(0, input.limit);
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: JSON.stringify({
|
|
77
|
+
ok: true,
|
|
78
|
+
data: {
|
|
79
|
+
queue_length: jobQueue.getQueueLength(),
|
|
80
|
+
total_jobs: jobQueue.getTotalJobs(),
|
|
81
|
+
count: limitedRows.length,
|
|
82
|
+
jobs: limitedRows,
|
|
83
|
+
},
|
|
84
|
+
}, null, 2),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: JSON.stringify({
|
|
96
|
+
ok: false,
|
|
97
|
+
errors: [message],
|
|
98
|
+
}, null, 2),
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
isError: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=task-status.js.map
|