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,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const ArtifactSelectionProvenanceSchema = z.object({
|
|
3
|
+
scope: z.enum(['all', 'latest', 'session']),
|
|
4
|
+
session_selector: z.string().nullable(),
|
|
5
|
+
artifact_count: z.number().int().nonnegative(),
|
|
6
|
+
artifact_ids: z.array(z.string()),
|
|
7
|
+
session_tags: z.array(z.string()),
|
|
8
|
+
earliest_artifact_at: z.string().nullable(),
|
|
9
|
+
latest_artifact_at: z.string().nullable(),
|
|
10
|
+
scope_note: z.string(),
|
|
11
|
+
});
|
|
12
|
+
export const AnalysisProvenanceSchema = z.object({
|
|
13
|
+
runtime: ArtifactSelectionProvenanceSchema,
|
|
14
|
+
semantic_names: ArtifactSelectionProvenanceSchema.optional(),
|
|
15
|
+
semantic_explanations: ArtifactSelectionProvenanceSchema.optional(),
|
|
16
|
+
});
|
|
17
|
+
function emptyScopeNote(label, scope, sessionTag) {
|
|
18
|
+
if (scope === 'session' && sessionTag?.trim()) {
|
|
19
|
+
return `No ${label} matched session selector "${sessionTag.trim()}".`;
|
|
20
|
+
}
|
|
21
|
+
if (scope === 'latest') {
|
|
22
|
+
return `No ${label} matched the latest selection window.`;
|
|
23
|
+
}
|
|
24
|
+
return `No ${label} were selected.`;
|
|
25
|
+
}
|
|
26
|
+
export function buildRuntimeArtifactProvenance(dynamicEvidence, scope, sessionTag) {
|
|
27
|
+
if (!dynamicEvidence) {
|
|
28
|
+
return {
|
|
29
|
+
scope,
|
|
30
|
+
session_selector: sessionTag?.trim() || null,
|
|
31
|
+
artifact_count: 0,
|
|
32
|
+
artifact_ids: [],
|
|
33
|
+
session_tags: [],
|
|
34
|
+
earliest_artifact_at: null,
|
|
35
|
+
latest_artifact_at: null,
|
|
36
|
+
scope_note: emptyScopeNote('runtime evidence artifacts', scope, sessionTag),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
scope,
|
|
41
|
+
session_selector: dynamicEvidence.session_selector || sessionTag?.trim() || null,
|
|
42
|
+
artifact_count: dynamicEvidence.artifact_count,
|
|
43
|
+
artifact_ids: dynamicEvidence.artifact_ids || [],
|
|
44
|
+
session_tags: dynamicEvidence.session_tags || [],
|
|
45
|
+
earliest_artifact_at: dynamicEvidence.earliest_imported_at || null,
|
|
46
|
+
latest_artifact_at: dynamicEvidence.latest_imported_at || null,
|
|
47
|
+
scope_note: dynamicEvidence.scope_note || emptyScopeNote('runtime evidence artifacts', scope, sessionTag),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function buildSemanticArtifactProvenance(label, index, scope, sessionTag) {
|
|
51
|
+
if (!index) {
|
|
52
|
+
return {
|
|
53
|
+
scope,
|
|
54
|
+
session_selector: sessionTag?.trim() || null,
|
|
55
|
+
artifact_count: 0,
|
|
56
|
+
artifact_ids: [],
|
|
57
|
+
session_tags: [],
|
|
58
|
+
earliest_artifact_at: null,
|
|
59
|
+
latest_artifact_at: null,
|
|
60
|
+
scope_note: emptyScopeNote(label, scope, sessionTag),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
scope,
|
|
65
|
+
session_selector: sessionTag?.trim() || null,
|
|
66
|
+
artifact_count: index.artifact_ids.length,
|
|
67
|
+
artifact_ids: index.artifact_ids,
|
|
68
|
+
session_tags: index.session_tags,
|
|
69
|
+
earliest_artifact_at: index.earliest_created_at,
|
|
70
|
+
latest_artifact_at: index.latest_created_at,
|
|
71
|
+
scope_note: index.scope_note || emptyScopeNote(label, scope, sessionTag),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=analysis-provenance.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background runner for queued analysis jobs.
|
|
3
|
+
* Current scope: execute ghidra.analyze jobs with cancellation/timeouts and stale-job reaping.
|
|
4
|
+
*/
|
|
5
|
+
import type { DatabaseManager } from './database.js';
|
|
6
|
+
import type { WorkspaceManager } from './workspace-manager.js';
|
|
7
|
+
import type { JobQueue } from './job-queue.js';
|
|
8
|
+
import type { CacheManager } from './cache-manager.js';
|
|
9
|
+
export interface AnalysisTaskRunnerOptions {
|
|
10
|
+
pollIntervalMs?: number;
|
|
11
|
+
staleRunningMs?: number | null;
|
|
12
|
+
}
|
|
13
|
+
export declare class AnalysisTaskRunner {
|
|
14
|
+
private readonly jobQueue;
|
|
15
|
+
private readonly decompilerWorker;
|
|
16
|
+
private readonly pollIntervalMs;
|
|
17
|
+
private readonly staleRunningMs?;
|
|
18
|
+
private readonly database;
|
|
19
|
+
private readonly workspaceManager;
|
|
20
|
+
private readonly cacheManager?;
|
|
21
|
+
private timer?;
|
|
22
|
+
private processing;
|
|
23
|
+
private activeControllers;
|
|
24
|
+
constructor(jobQueue: JobQueue, database: DatabaseManager, workspaceManager: WorkspaceManager, cacheManager?: CacheManager, options?: AnalysisTaskRunnerOptions);
|
|
25
|
+
start(): void;
|
|
26
|
+
stop(): void;
|
|
27
|
+
private reapStaleRunning;
|
|
28
|
+
private processNext;
|
|
29
|
+
private executeJob;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=analysis-task-runner.d.ts.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background runner for queued analysis jobs.
|
|
3
|
+
* Current scope: execute ghidra.analyze jobs with cancellation/timeouts and stale-job reaping.
|
|
4
|
+
*/
|
|
5
|
+
import { DecompilerWorker, GhidraProcessError } from './decompiler-worker.js';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
import { deepStaticWorkflow } from './workflows/deep-static.js';
|
|
8
|
+
export class AnalysisTaskRunner {
|
|
9
|
+
jobQueue;
|
|
10
|
+
decompilerWorker;
|
|
11
|
+
pollIntervalMs;
|
|
12
|
+
staleRunningMs;
|
|
13
|
+
database;
|
|
14
|
+
workspaceManager;
|
|
15
|
+
cacheManager;
|
|
16
|
+
timer;
|
|
17
|
+
processing = false;
|
|
18
|
+
activeControllers = new Map();
|
|
19
|
+
constructor(jobQueue, database, workspaceManager, cacheManager, options = {}) {
|
|
20
|
+
this.jobQueue = jobQueue;
|
|
21
|
+
this.database = database;
|
|
22
|
+
this.workspaceManager = workspaceManager;
|
|
23
|
+
this.cacheManager = cacheManager;
|
|
24
|
+
this.decompilerWorker = new DecompilerWorker(database, workspaceManager);
|
|
25
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 500;
|
|
26
|
+
this.staleRunningMs = options.staleRunningMs;
|
|
27
|
+
this.jobQueue.on('job:cancelled', (jobId) => {
|
|
28
|
+
const controller = this.activeControllers.get(jobId);
|
|
29
|
+
if (controller) {
|
|
30
|
+
controller.abort();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
start() {
|
|
35
|
+
if (this.timer) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.timer = setInterval(() => {
|
|
39
|
+
this.reapStaleRunning();
|
|
40
|
+
void this.processNext();
|
|
41
|
+
}, this.pollIntervalMs);
|
|
42
|
+
}
|
|
43
|
+
stop() {
|
|
44
|
+
if (this.timer) {
|
|
45
|
+
clearInterval(this.timer);
|
|
46
|
+
this.timer = undefined;
|
|
47
|
+
}
|
|
48
|
+
for (const controller of this.activeControllers.values()) {
|
|
49
|
+
controller.abort();
|
|
50
|
+
}
|
|
51
|
+
this.activeControllers.clear();
|
|
52
|
+
}
|
|
53
|
+
reapStaleRunning() {
|
|
54
|
+
if (typeof this.staleRunningMs !== 'number' ||
|
|
55
|
+
!Number.isFinite(this.staleRunningMs) ||
|
|
56
|
+
this.staleRunningMs <= 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const queue = this.jobQueue;
|
|
60
|
+
if (typeof queue.reapStaleRunningJobs !== 'function') {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const reaped = queue.reapStaleRunningJobs(this.staleRunningMs);
|
|
64
|
+
for (const jobId of reaped) {
|
|
65
|
+
const controller = this.activeControllers.get(jobId);
|
|
66
|
+
if (controller) {
|
|
67
|
+
controller.abort();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (reaped.length > 0) {
|
|
71
|
+
logger.warn({
|
|
72
|
+
reaped_count: reaped.length,
|
|
73
|
+
stale_running_ms: this.staleRunningMs,
|
|
74
|
+
jobs: reaped,
|
|
75
|
+
}, 'Reaped stale running analysis jobs');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async processNext() {
|
|
79
|
+
if (this.processing) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const job = this.jobQueue.dequeue();
|
|
83
|
+
if (!job) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.processing = true;
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
this.activeControllers.set(job.id, controller);
|
|
90
|
+
try {
|
|
91
|
+
const result = await this.executeJob(job, controller.signal);
|
|
92
|
+
if (typeof result.metrics.elapsedMs !== 'number' || result.metrics.elapsedMs <= 0) {
|
|
93
|
+
result.metrics.elapsedMs = Date.now() - startTime;
|
|
94
|
+
}
|
|
95
|
+
this.jobQueue.complete(job.id, result);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const elapsedMs = Date.now() - startTime;
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
logger.error({
|
|
101
|
+
job_id: job.id,
|
|
102
|
+
tool: job.tool,
|
|
103
|
+
sample_id: job.sampleId,
|
|
104
|
+
error: message,
|
|
105
|
+
}, 'Analysis task failed');
|
|
106
|
+
const normalizedError = error instanceof Error ? error : new Error(message);
|
|
107
|
+
this.jobQueue.complete(job.id, this.decompilerWorker.createErrorJobResult(job.id, normalizedError, elapsedMs));
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
this.activeControllers.delete(job.id);
|
|
111
|
+
this.processing = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async executeJob(job, abortSignal) {
|
|
115
|
+
if (job.tool === 'ghidra.analyze') {
|
|
116
|
+
const options = (job.args?.options || {});
|
|
117
|
+
try {
|
|
118
|
+
const analysisResult = await this.decompilerWorker.analyze(job.sampleId, {
|
|
119
|
+
analysisId: job.id,
|
|
120
|
+
timeout: job.timeout,
|
|
121
|
+
maxCpu: options.max_cpu || '4',
|
|
122
|
+
projectKey: options.project_key,
|
|
123
|
+
abortSignal,
|
|
124
|
+
});
|
|
125
|
+
return this.decompilerWorker.createJobResult(analysisResult, 0);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (error instanceof GhidraProcessError && error.errorCode === 'E_CANCELLED') {
|
|
129
|
+
throw new Error('E_CANCELLED: analysis task cancelled');
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (job.tool === 'workflow.deep_static') {
|
|
135
|
+
if (!this.cacheManager) {
|
|
136
|
+
throw new Error('workflow.deep_static requires cache manager for queued execution');
|
|
137
|
+
}
|
|
138
|
+
const options = (job.args?.options || {});
|
|
139
|
+
const result = await deepStaticWorkflow(job.sampleId, this.workspaceManager, this.database, this.cacheManager, options, {
|
|
140
|
+
onProgress: (progress) => {
|
|
141
|
+
this.jobQueue.updateProgress(job.id, progress);
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
jobId: job.id,
|
|
146
|
+
ok: result.ok,
|
|
147
|
+
data: result.data,
|
|
148
|
+
errors: result.errors || [],
|
|
149
|
+
warnings: result.warnings || [],
|
|
150
|
+
artifacts: [],
|
|
151
|
+
metrics: {
|
|
152
|
+
elapsedMs: 0,
|
|
153
|
+
peakRssMb: 0,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Unsupported queued tool: ${job.tool}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=analysis-task-runner.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WorkspaceManager } from './workspace-manager.js';
|
|
2
|
+
import type { DatabaseManager, Artifact } from './database.js';
|
|
3
|
+
export interface ArtifactInventoryItem extends Artifact {
|
|
4
|
+
exists: boolean;
|
|
5
|
+
size_bytes: number | null;
|
|
6
|
+
modified_at: string | null;
|
|
7
|
+
tracked: boolean;
|
|
8
|
+
session_tag: string | null;
|
|
9
|
+
retention_bucket: 'active' | 'recent' | 'archive';
|
|
10
|
+
age_days: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ArtifactInventoryOptions {
|
|
13
|
+
artifactTypes?: Iterable<string>;
|
|
14
|
+
includeMissing?: boolean;
|
|
15
|
+
includeUntrackedFiles?: boolean;
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
scanRoots?: string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function normalizeRelativeArtifactPath(p: string): string;
|
|
20
|
+
export declare function inferUntrackedArtifactType(relativePath: string): string;
|
|
21
|
+
export declare function deriveArtifactSessionTag(relativePath: string): string | null;
|
|
22
|
+
export declare function listArtifactInventory(workspaceManager: WorkspaceManager, database: DatabaseManager, sampleId: string, options?: ArtifactInventoryOptions): Promise<ArtifactInventoryItem[]>;
|
|
23
|
+
//# sourceMappingURL=artifact-inventory.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
export function normalizeRelativeArtifactPath(p) {
|
|
5
|
+
return p.replace(/\\/g, '/').replace(/^\.?\//, '');
|
|
6
|
+
}
|
|
7
|
+
export function inferUntrackedArtifactType(relativePath) {
|
|
8
|
+
const normalized = relativePath.toLowerCase();
|
|
9
|
+
if (normalized.endsWith('.pseudo.c') || normalized.includes('pseudo')) {
|
|
10
|
+
return 'ghidra_pseudocode';
|
|
11
|
+
}
|
|
12
|
+
if (normalized.includes('manifest')) {
|
|
13
|
+
return 'manifest';
|
|
14
|
+
}
|
|
15
|
+
if (normalized.includes('gaps')) {
|
|
16
|
+
return 'gaps';
|
|
17
|
+
}
|
|
18
|
+
if (normalized.includes('report')) {
|
|
19
|
+
return 'report';
|
|
20
|
+
}
|
|
21
|
+
if (normalized.includes('ioc_export')) {
|
|
22
|
+
return 'ioc_export';
|
|
23
|
+
}
|
|
24
|
+
return 'filesystem_untracked';
|
|
25
|
+
}
|
|
26
|
+
export function deriveArtifactSessionTag(relativePath) {
|
|
27
|
+
const normalized = normalizeRelativeArtifactPath(relativePath);
|
|
28
|
+
const segments = normalized.split('/').filter((item) => item.length > 0);
|
|
29
|
+
if (segments.length < 2) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (segments[0] === 'reports' && segments.length >= 4) {
|
|
33
|
+
return `${segments[0]}/${segments[1]}/${segments[2]}`;
|
|
34
|
+
}
|
|
35
|
+
if (segments[0] === 'reports' && segments.length >= 2) {
|
|
36
|
+
return `${segments[0]}/${segments[1]}`;
|
|
37
|
+
}
|
|
38
|
+
return `${segments[0]}/${segments[1]}`;
|
|
39
|
+
}
|
|
40
|
+
function deriveArtifactAgeDays(referenceIso) {
|
|
41
|
+
const timestamp = referenceIso ? new Date(referenceIso).getTime() : Number.NaN;
|
|
42
|
+
if (!Number.isFinite(timestamp)) {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
return Math.max(0, Math.floor((Date.now() - timestamp) / (24 * 60 * 60 * 1000)));
|
|
46
|
+
}
|
|
47
|
+
function deriveRetentionBucket(ageDays) {
|
|
48
|
+
if (ageDays <= 3) {
|
|
49
|
+
return 'active';
|
|
50
|
+
}
|
|
51
|
+
if (ageDays <= 14) {
|
|
52
|
+
return 'recent';
|
|
53
|
+
}
|
|
54
|
+
return 'archive';
|
|
55
|
+
}
|
|
56
|
+
async function collectFiles(rootPath, recursive) {
|
|
57
|
+
const items = [];
|
|
58
|
+
let entries = [];
|
|
59
|
+
try {
|
|
60
|
+
entries = (await fs.readdir(rootPath, {
|
|
61
|
+
withFileTypes: true,
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return items;
|
|
67
|
+
}
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const absolute = path.join(rootPath, String(entry.name));
|
|
70
|
+
if (entry.isFile()) {
|
|
71
|
+
try {
|
|
72
|
+
const stat = await fs.stat(absolute);
|
|
73
|
+
items.push({ absolute, stat: { size: stat.size, mtime: stat.mtime } });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Ignore transient file errors.
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (recursive && entry.isDirectory()) {
|
|
81
|
+
const nested = await collectFiles(absolute, recursive);
|
|
82
|
+
items.push(...nested);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return items;
|
|
86
|
+
}
|
|
87
|
+
async function enrichTrackedArtifact(workspaceManager, workspaceRoot, artifact) {
|
|
88
|
+
const absolutePath = workspaceManager.normalizePath(workspaceRoot, artifact.path);
|
|
89
|
+
const sessionTag = deriveArtifactSessionTag(artifact.path);
|
|
90
|
+
try {
|
|
91
|
+
const stat = await fs.stat(absolutePath);
|
|
92
|
+
const ageDays = deriveArtifactAgeDays(artifact.created_at || stat.mtime.toISOString());
|
|
93
|
+
return {
|
|
94
|
+
...artifact,
|
|
95
|
+
exists: true,
|
|
96
|
+
size_bytes: stat.size,
|
|
97
|
+
modified_at: stat.mtime.toISOString(),
|
|
98
|
+
tracked: true,
|
|
99
|
+
session_tag: sessionTag,
|
|
100
|
+
retention_bucket: deriveRetentionBucket(ageDays),
|
|
101
|
+
age_days: ageDays,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
const ageDays = deriveArtifactAgeDays(artifact.created_at);
|
|
106
|
+
return {
|
|
107
|
+
...artifact,
|
|
108
|
+
exists: false,
|
|
109
|
+
size_bytes: null,
|
|
110
|
+
modified_at: null,
|
|
111
|
+
tracked: true,
|
|
112
|
+
session_tag: sessionTag,
|
|
113
|
+
retention_bucket: deriveRetentionBucket(ageDays),
|
|
114
|
+
age_days: ageDays,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export async function listArtifactInventory(workspaceManager, database, sampleId, options = {}) {
|
|
119
|
+
const includeMissing = options.includeMissing !== false;
|
|
120
|
+
const includeUntrackedFiles = options.includeUntrackedFiles !== false;
|
|
121
|
+
const recursive = options.recursive !== false;
|
|
122
|
+
const scanRoots = options.scanRoots || ['reports', 'ghidra', 'dotnet'];
|
|
123
|
+
const typeFilter = new Set();
|
|
124
|
+
for (const item of options.artifactTypes || []) {
|
|
125
|
+
if (typeof item === 'string' && item.length > 0) {
|
|
126
|
+
typeFilter.add(item);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const trackedArtifacts = database.findArtifacts(sampleId);
|
|
130
|
+
const filteredTracked = typeFilter.size > 0
|
|
131
|
+
? trackedArtifacts.filter((item) => typeFilter.has(item.type))
|
|
132
|
+
: trackedArtifacts;
|
|
133
|
+
const workspace = await workspaceManager.getWorkspace(sampleId);
|
|
134
|
+
const tracked = await Promise.all(filteredTracked.map((artifact) => enrichTrackedArtifact(workspaceManager, workspace.root, artifact)));
|
|
135
|
+
const trackedPathSet = new Set(tracked.map((item) => normalizeRelativeArtifactPath(item.path).toLowerCase()));
|
|
136
|
+
const untracked = [];
|
|
137
|
+
if (includeUntrackedFiles) {
|
|
138
|
+
for (const scanRoot of scanRoots) {
|
|
139
|
+
const rootAbsolute = path.join(workspace.root, scanRoot);
|
|
140
|
+
const files = await collectFiles(rootAbsolute, recursive);
|
|
141
|
+
for (const file of files) {
|
|
142
|
+
const relative = normalizeRelativeArtifactPath(path.relative(workspace.root, file.absolute));
|
|
143
|
+
const key = relative.toLowerCase();
|
|
144
|
+
if (trackedPathSet.has(key)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
trackedPathSet.add(key);
|
|
148
|
+
const inferredType = inferUntrackedArtifactType(relative);
|
|
149
|
+
const ageDays = deriveArtifactAgeDays(file.stat.mtime.toISOString());
|
|
150
|
+
if (typeFilter.size > 0 && !typeFilter.has(inferredType)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
untracked.push({
|
|
154
|
+
id: `fs:${createHash('sha1').update(relative).digest('hex')}`,
|
|
155
|
+
sample_id: sampleId,
|
|
156
|
+
type: inferredType,
|
|
157
|
+
path: relative,
|
|
158
|
+
sha256: '',
|
|
159
|
+
mime: null,
|
|
160
|
+
created_at: file.stat.mtime.toISOString(),
|
|
161
|
+
exists: true,
|
|
162
|
+
size_bytes: file.stat.size,
|
|
163
|
+
modified_at: file.stat.mtime.toISOString(),
|
|
164
|
+
tracked: false,
|
|
165
|
+
session_tag: deriveArtifactSessionTag(relative),
|
|
166
|
+
retention_bucket: deriveRetentionBucket(ageDays),
|
|
167
|
+
age_days: ageDays,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const merged = [...tracked, ...untracked];
|
|
173
|
+
return includeMissing ? merged : merged.filter((item) => item.exists);
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=artifact-inventory.js.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Manager
|
|
3
|
+
* Implements cache key generation and three-tier caching architecture
|
|
4
|
+
* Requirements: 20.1, 20.2, 20.3, 20.4, 20.5
|
|
5
|
+
*/
|
|
6
|
+
import type { CacheKeyParams } from './types.js';
|
|
7
|
+
import type { DatabaseManager } from './database.js';
|
|
8
|
+
export type CacheTier = 'memory' | 'filesystem' | 'database' | 'unknown';
|
|
9
|
+
export interface CacheHitMetadata {
|
|
10
|
+
key: string;
|
|
11
|
+
tier: CacheTier;
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
expiresAt?: string;
|
|
14
|
+
fetchedAt: string;
|
|
15
|
+
sampleSha256?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CacheHitLookup {
|
|
18
|
+
data: unknown;
|
|
19
|
+
metadata: CacheHitMetadata;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Cache Manager with three-tier architecture
|
|
23
|
+
*
|
|
24
|
+
* Requirements: 20.3, 20.4, 20.5, 26.1 (cache prewarming)
|
|
25
|
+
*
|
|
26
|
+
* Architecture:
|
|
27
|
+
* - L1: Memory cache (LRU, 5 minutes TTL)
|
|
28
|
+
* - L2: File system cache (30 days TTL)
|
|
29
|
+
* - L3: Database cache
|
|
30
|
+
*/
|
|
31
|
+
export declare class CacheManager {
|
|
32
|
+
private memoryCache;
|
|
33
|
+
private fsCache;
|
|
34
|
+
private db;
|
|
35
|
+
private prewarmInProgress;
|
|
36
|
+
constructor(cacheDir: string, db?: DatabaseManager);
|
|
37
|
+
/**
|
|
38
|
+
* Get cached result from three-tier cache
|
|
39
|
+
*
|
|
40
|
+
* Requirements: 20.3
|
|
41
|
+
*
|
|
42
|
+
* Algorithm:
|
|
43
|
+
* 1. Check L1 (memory cache)
|
|
44
|
+
* 2. If miss, check L2 (file system cache) and populate L1
|
|
45
|
+
* 3. If miss, check L3 (database cache) and populate L1 and L2
|
|
46
|
+
* 4. Return null if not found in any layer
|
|
47
|
+
*
|
|
48
|
+
* @param key - Cache key
|
|
49
|
+
* @returns Cached data or null if not found
|
|
50
|
+
*/
|
|
51
|
+
getCachedResult(key: string): Promise<unknown | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Get cached result with hit metadata for observability.
|
|
54
|
+
*/
|
|
55
|
+
getCachedResultWithMetadata(key: string): Promise<CacheHitLookup | null>;
|
|
56
|
+
/**
|
|
57
|
+
* Set cached result in all three tiers
|
|
58
|
+
*
|
|
59
|
+
* Requirements: 20.4
|
|
60
|
+
*
|
|
61
|
+
* Algorithm:
|
|
62
|
+
* 1. Store in L1 (memory cache)
|
|
63
|
+
* 2. Store in L2 (file system cache)
|
|
64
|
+
* 3. Store in L3 (database cache) if available
|
|
65
|
+
*
|
|
66
|
+
* @param key - Cache key
|
|
67
|
+
* @param data - Data to cache
|
|
68
|
+
* @param ttl - Time to live in milliseconds (optional)
|
|
69
|
+
*/
|
|
70
|
+
setCachedResult(key: string, data: unknown, ttl?: number, sampleSha256?: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Clear all caches
|
|
73
|
+
*/
|
|
74
|
+
clearAll(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Prewarm cache by loading frequently accessed data into memory
|
|
77
|
+
*
|
|
78
|
+
* Requirements: 26.1 (cache prewarming)
|
|
79
|
+
*
|
|
80
|
+
* Strategy:
|
|
81
|
+
* 1. Load recent cache entries from database
|
|
82
|
+
* 2. Populate L1 (memory) and L2 (filesystem) caches
|
|
83
|
+
* 3. Prioritize entries with high access frequency
|
|
84
|
+
*
|
|
85
|
+
* @param maxEntries - Maximum number of entries to prewarm (default: 100)
|
|
86
|
+
*/
|
|
87
|
+
prewarmCache(maxEntries?: number): Promise<number>;
|
|
88
|
+
/**
|
|
89
|
+
* Prewarm cache for a specific sample
|
|
90
|
+
* Loads all cached results for a sample into memory
|
|
91
|
+
*
|
|
92
|
+
* Requirements: 26.1 (cache prewarming)
|
|
93
|
+
*
|
|
94
|
+
* @param sampleSha256 - SHA256 hash of the sample
|
|
95
|
+
* @returns Number of entries prewarmed
|
|
96
|
+
*/
|
|
97
|
+
prewarmSampleCache(sampleSha256: string): Promise<number>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generate deterministic cache key from parameters
|
|
101
|
+
*
|
|
102
|
+
* Requirements: 20.1, 20.2
|
|
103
|
+
*
|
|
104
|
+
* Algorithm:
|
|
105
|
+
* 1. Normalize arguments (sort keys, remove defaults)
|
|
106
|
+
* 2. Create canonical representation
|
|
107
|
+
* 3. Generate SHA256 hash
|
|
108
|
+
*
|
|
109
|
+
* @param params - Cache key parameters
|
|
110
|
+
* @returns Cache key string in format "cache:<sha256>"
|
|
111
|
+
*/
|
|
112
|
+
export declare function generateCacheKey(params: CacheKeyParams): string;
|
|
113
|
+
/**
|
|
114
|
+
* Normalize arguments for cache key generation
|
|
115
|
+
*
|
|
116
|
+
* Requirements: 20.2
|
|
117
|
+
*
|
|
118
|
+
* Normalization rules:
|
|
119
|
+
* 1. Sort object keys recursively
|
|
120
|
+
* 2. Remove null and undefined values
|
|
121
|
+
* 3. Recursively normalize nested objects
|
|
122
|
+
* 4. Preserve arrays as-is (order matters)
|
|
123
|
+
*
|
|
124
|
+
* @param args - Arguments object to normalize
|
|
125
|
+
* @returns Normalized arguments object
|
|
126
|
+
*/
|
|
127
|
+
export declare function normalizeArgs(args: Record<string, unknown>): Record<string, unknown>;
|
|
128
|
+
//# sourceMappingURL=cache-manager.d.ts.map
|