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
package/dist/database.js
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database module for Windows EXE Decompiler MCP Server
|
|
3
|
+
* Manages SQLite database schema and operations
|
|
4
|
+
*/
|
|
5
|
+
import Database from 'better-sqlite3';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { logger, logDebug } from './logger.js';
|
|
9
|
+
/**
|
|
10
|
+
* Database schema SQL statements
|
|
11
|
+
*/
|
|
12
|
+
const SCHEMA_SQL = `
|
|
13
|
+
-- samples 表:存储样本基础信息
|
|
14
|
+
CREATE TABLE IF NOT EXISTS samples (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
sha256 TEXT UNIQUE NOT NULL,
|
|
17
|
+
md5 TEXT,
|
|
18
|
+
size INTEGER NOT NULL,
|
|
19
|
+
file_type TEXT,
|
|
20
|
+
created_at TEXT NOT NULL,
|
|
21
|
+
source TEXT
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_samples_sha256 ON samples(sha256);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_samples_created_at ON samples(created_at);
|
|
26
|
+
|
|
27
|
+
-- analyses 表:存储分析任务记录
|
|
28
|
+
CREATE TABLE IF NOT EXISTS analyses (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
sample_id TEXT NOT NULL,
|
|
31
|
+
stage TEXT NOT NULL,
|
|
32
|
+
backend TEXT NOT NULL,
|
|
33
|
+
status TEXT NOT NULL,
|
|
34
|
+
started_at TEXT,
|
|
35
|
+
finished_at TEXT,
|
|
36
|
+
output_json TEXT,
|
|
37
|
+
metrics_json TEXT,
|
|
38
|
+
FOREIGN KEY (sample_id) REFERENCES samples(id)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_sample_stage ON analyses(sample_id, stage);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_analyses_status ON analyses(status);
|
|
43
|
+
|
|
44
|
+
-- functions 表:存储函数信息
|
|
45
|
+
CREATE TABLE IF NOT EXISTS functions (
|
|
46
|
+
sample_id TEXT NOT NULL,
|
|
47
|
+
address TEXT NOT NULL,
|
|
48
|
+
name TEXT,
|
|
49
|
+
size INTEGER,
|
|
50
|
+
score REAL,
|
|
51
|
+
tags TEXT,
|
|
52
|
+
summary TEXT,
|
|
53
|
+
caller_count INTEGER DEFAULT 0,
|
|
54
|
+
callee_count INTEGER DEFAULT 0,
|
|
55
|
+
is_entry_point INTEGER DEFAULT 0,
|
|
56
|
+
is_exported INTEGER DEFAULT 0,
|
|
57
|
+
callees TEXT,
|
|
58
|
+
PRIMARY KEY (sample_id, address),
|
|
59
|
+
FOREIGN KEY (sample_id) REFERENCES samples(id)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_functions_name ON functions(sample_id, name);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_functions_score ON functions(sample_id, score DESC);
|
|
64
|
+
|
|
65
|
+
-- artifacts 表:存储分析产物
|
|
66
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
sample_id TEXT NOT NULL,
|
|
69
|
+
type TEXT NOT NULL,
|
|
70
|
+
path TEXT NOT NULL,
|
|
71
|
+
sha256 TEXT NOT NULL,
|
|
72
|
+
mime TEXT,
|
|
73
|
+
created_at TEXT NOT NULL,
|
|
74
|
+
FOREIGN KEY (sample_id) REFERENCES samples(id)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_sample_type ON artifacts(sample_id, type);
|
|
78
|
+
|
|
79
|
+
-- cache 表:存储缓存结果
|
|
80
|
+
CREATE TABLE IF NOT EXISTS cache (
|
|
81
|
+
key TEXT PRIMARY KEY,
|
|
82
|
+
data TEXT NOT NULL,
|
|
83
|
+
sample_sha256 TEXT,
|
|
84
|
+
created_at TEXT NOT NULL,
|
|
85
|
+
expires_at TEXT
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_cache_expires_at ON cache(expires_at);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_cache_sample_sha256 ON cache(sample_sha256);
|
|
90
|
+
`;
|
|
91
|
+
/**
|
|
92
|
+
* Database manager class
|
|
93
|
+
*/
|
|
94
|
+
export class DatabaseManager {
|
|
95
|
+
db;
|
|
96
|
+
constructor(dbPath) {
|
|
97
|
+
// Ensure directory exists
|
|
98
|
+
const dbDir = path.dirname(dbPath);
|
|
99
|
+
if (!fs.existsSync(dbDir)) {
|
|
100
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
101
|
+
logDebug('Created database directory', { path: dbDir });
|
|
102
|
+
}
|
|
103
|
+
// Initialize database
|
|
104
|
+
logger.info({ dbPath }, 'Initializing database');
|
|
105
|
+
this.db = new Database(dbPath);
|
|
106
|
+
// Enable foreign keys
|
|
107
|
+
this.db.pragma('foreign_keys = ON');
|
|
108
|
+
// Initialize schema
|
|
109
|
+
this.initializeSchema();
|
|
110
|
+
logger.info('Database initialized successfully');
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Initialize database schema
|
|
114
|
+
*/
|
|
115
|
+
initializeSchema() {
|
|
116
|
+
this.db.exec(SCHEMA_SQL);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get the underlying database instance
|
|
120
|
+
*/
|
|
121
|
+
getDatabase() {
|
|
122
|
+
return this.db;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Close the database connection
|
|
126
|
+
*/
|
|
127
|
+
close() {
|
|
128
|
+
this.db.close();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Execute a transaction
|
|
132
|
+
*/
|
|
133
|
+
transaction(fn) {
|
|
134
|
+
const txn = this.db.transaction(fn);
|
|
135
|
+
return txn();
|
|
136
|
+
}
|
|
137
|
+
// ==================== Sample Operations ====================
|
|
138
|
+
/**
|
|
139
|
+
* Insert a new sample
|
|
140
|
+
*/
|
|
141
|
+
insertSample(sample) {
|
|
142
|
+
const stmt = this.db.prepare(`
|
|
143
|
+
INSERT INTO samples (id, sha256, md5, size, file_type, created_at, source)
|
|
144
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
145
|
+
`);
|
|
146
|
+
stmt.run(sample.id, sample.sha256, sample.md5, sample.size, sample.file_type, sample.created_at, sample.source);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Find a sample by ID
|
|
150
|
+
*/
|
|
151
|
+
findSample(sampleId) {
|
|
152
|
+
const stmt = this.db.prepare('SELECT * FROM samples WHERE id = ?');
|
|
153
|
+
return stmt.get(sampleId);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Find a sample by SHA256
|
|
157
|
+
*/
|
|
158
|
+
findSampleBySha256(sha256) {
|
|
159
|
+
const stmt = this.db.prepare('SELECT * FROM samples WHERE sha256 = ?');
|
|
160
|
+
return stmt.get(sha256);
|
|
161
|
+
}
|
|
162
|
+
// ==================== Analysis Operations ====================
|
|
163
|
+
/**
|
|
164
|
+
* Insert a new analysis
|
|
165
|
+
*/
|
|
166
|
+
insertAnalysis(analysis) {
|
|
167
|
+
const stmt = this.db.prepare(`
|
|
168
|
+
INSERT INTO analyses (id, sample_id, stage, backend, status, started_at, finished_at, output_json, metrics_json)
|
|
169
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
170
|
+
`);
|
|
171
|
+
stmt.run(analysis.id, analysis.sample_id, analysis.stage, analysis.backend, analysis.status, analysis.started_at, analysis.finished_at, analysis.output_json, analysis.metrics_json);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update an analysis
|
|
175
|
+
*/
|
|
176
|
+
updateAnalysis(analysisId, updates) {
|
|
177
|
+
const fields = [];
|
|
178
|
+
const values = [];
|
|
179
|
+
if (updates.stage !== undefined) {
|
|
180
|
+
fields.push('stage = ?');
|
|
181
|
+
values.push(updates.stage);
|
|
182
|
+
}
|
|
183
|
+
if (updates.backend !== undefined) {
|
|
184
|
+
fields.push('backend = ?');
|
|
185
|
+
values.push(updates.backend);
|
|
186
|
+
}
|
|
187
|
+
if (updates.status !== undefined) {
|
|
188
|
+
fields.push('status = ?');
|
|
189
|
+
values.push(updates.status);
|
|
190
|
+
}
|
|
191
|
+
if (updates.started_at !== undefined) {
|
|
192
|
+
fields.push('started_at = ?');
|
|
193
|
+
values.push(updates.started_at);
|
|
194
|
+
}
|
|
195
|
+
if (updates.finished_at !== undefined) {
|
|
196
|
+
fields.push('finished_at = ?');
|
|
197
|
+
values.push(updates.finished_at);
|
|
198
|
+
}
|
|
199
|
+
if (updates.output_json !== undefined) {
|
|
200
|
+
fields.push('output_json = ?');
|
|
201
|
+
values.push(updates.output_json);
|
|
202
|
+
}
|
|
203
|
+
if (updates.metrics_json !== undefined) {
|
|
204
|
+
fields.push('metrics_json = ?');
|
|
205
|
+
values.push(updates.metrics_json);
|
|
206
|
+
}
|
|
207
|
+
if (fields.length === 0) {
|
|
208
|
+
return; // No updates to perform
|
|
209
|
+
}
|
|
210
|
+
values.push(analysisId);
|
|
211
|
+
const stmt = this.db.prepare(`
|
|
212
|
+
UPDATE analyses SET ${fields.join(', ')} WHERE id = ?
|
|
213
|
+
`);
|
|
214
|
+
stmt.run(...values);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Find an analysis by ID
|
|
218
|
+
*/
|
|
219
|
+
findAnalysis(analysisId) {
|
|
220
|
+
const stmt = this.db.prepare('SELECT * FROM analyses WHERE id = ?');
|
|
221
|
+
return stmt.get(analysisId);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Find all analyses for a sample
|
|
225
|
+
*/
|
|
226
|
+
findAnalysesBySample(sampleId) {
|
|
227
|
+
const stmt = this.db.prepare('SELECT * FROM analyses WHERE sample_id = ? ORDER BY started_at DESC');
|
|
228
|
+
return stmt.all(sampleId);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Find recent samples ordered by creation time.
|
|
232
|
+
*/
|
|
233
|
+
findRecentSamples(limit = 20) {
|
|
234
|
+
const safeLimit = Math.max(1, Math.min(limit, 500));
|
|
235
|
+
const stmt = this.db.prepare('SELECT * FROM samples ORDER BY datetime(created_at) DESC LIMIT ?');
|
|
236
|
+
return stmt.all(safeLimit);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Mark stale running analyses as failed so persisted status does not remain misleading.
|
|
240
|
+
*/
|
|
241
|
+
reapStaleAnalyses(maxRuntimeMs, sampleId) {
|
|
242
|
+
const cutoffIso = new Date(Date.now() - maxRuntimeMs).toISOString();
|
|
243
|
+
const params = [cutoffIso];
|
|
244
|
+
const sampleClause = sampleId ? ' AND sample_id = ?' : '';
|
|
245
|
+
if (sampleId) {
|
|
246
|
+
params.push(sampleId);
|
|
247
|
+
}
|
|
248
|
+
const selectStmt = this.db.prepare(`SELECT * FROM analyses
|
|
249
|
+
WHERE status = 'running'
|
|
250
|
+
AND started_at IS NOT NULL
|
|
251
|
+
AND started_at < ?${sampleClause}
|
|
252
|
+
ORDER BY started_at ASC`);
|
|
253
|
+
const stale = selectStmt.all(...params);
|
|
254
|
+
if (stale.length === 0) {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
const updateStmt = this.db.prepare(`
|
|
258
|
+
UPDATE analyses
|
|
259
|
+
SET status = ?, finished_at = ?, output_json = ?, metrics_json = ?
|
|
260
|
+
WHERE id = ?
|
|
261
|
+
`);
|
|
262
|
+
const finishedAt = new Date().toISOString();
|
|
263
|
+
const updated = this.db.transaction((rows) => {
|
|
264
|
+
for (const row of rows) {
|
|
265
|
+
const error = `E_TIMEOUT: stale persisted analysis reaped after exceeding ${maxRuntimeMs}ms`;
|
|
266
|
+
let output = {};
|
|
267
|
+
try {
|
|
268
|
+
output =
|
|
269
|
+
row.output_json && row.output_json.trim().length > 0
|
|
270
|
+
? JSON.parse(row.output_json)
|
|
271
|
+
: {};
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
output = {};
|
|
275
|
+
}
|
|
276
|
+
output = {
|
|
277
|
+
...output,
|
|
278
|
+
error,
|
|
279
|
+
stale_reaped: true,
|
|
280
|
+
stale_reaped_at: finishedAt,
|
|
281
|
+
};
|
|
282
|
+
let metrics = {};
|
|
283
|
+
try {
|
|
284
|
+
metrics =
|
|
285
|
+
row.metrics_json && row.metrics_json.trim().length > 0
|
|
286
|
+
? JSON.parse(row.metrics_json)
|
|
287
|
+
: {};
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
metrics = {};
|
|
291
|
+
}
|
|
292
|
+
const startedAtMs = row.started_at ? new Date(row.started_at).getTime() : NaN;
|
|
293
|
+
const elapsedMs = Number.isFinite(startedAtMs)
|
|
294
|
+
? Math.max(0, Date.now() - startedAtMs)
|
|
295
|
+
: maxRuntimeMs;
|
|
296
|
+
metrics = {
|
|
297
|
+
...metrics,
|
|
298
|
+
elapsed_ms: elapsedMs,
|
|
299
|
+
stale_reaped: true,
|
|
300
|
+
};
|
|
301
|
+
updateStmt.run('failed', finishedAt, JSON.stringify(output), JSON.stringify(metrics), row.id);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
updated(stale);
|
|
305
|
+
return stale.map((row) => ({
|
|
306
|
+
...row,
|
|
307
|
+
status: 'failed',
|
|
308
|
+
finished_at: finishedAt,
|
|
309
|
+
output_json: JSON.stringify({
|
|
310
|
+
...(row.output_json ? (() => {
|
|
311
|
+
try {
|
|
312
|
+
return JSON.parse(row.output_json);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return {};
|
|
316
|
+
}
|
|
317
|
+
})() : {}),
|
|
318
|
+
error: `E_TIMEOUT: stale persisted analysis reaped after exceeding ${maxRuntimeMs}ms`,
|
|
319
|
+
stale_reaped: true,
|
|
320
|
+
stale_reaped_at: finishedAt,
|
|
321
|
+
}),
|
|
322
|
+
metrics_json: JSON.stringify({
|
|
323
|
+
...(row.metrics_json ? (() => {
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(row.metrics_json);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
})() : {}),
|
|
331
|
+
stale_reaped: true,
|
|
332
|
+
}),
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
// ==================== Function Operations ====================
|
|
336
|
+
/**
|
|
337
|
+
* Insert a new function
|
|
338
|
+
*/
|
|
339
|
+
insertFunction(func) {
|
|
340
|
+
const stmt = this.db.prepare(`
|
|
341
|
+
INSERT INTO functions (sample_id, address, name, size, score, tags, summary, caller_count, callee_count, is_entry_point, is_exported, callees)
|
|
342
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
343
|
+
`);
|
|
344
|
+
stmt.run(func.sample_id, func.address, func.name, func.size, func.score, func.tags, func.summary, func.caller_count ?? 0, func.callee_count ?? 0, func.is_entry_point ?? 0, func.is_exported ?? 0, func.callees);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Find all functions for a sample
|
|
348
|
+
*/
|
|
349
|
+
findFunctions(sampleId) {
|
|
350
|
+
const stmt = this.db.prepare('SELECT * FROM functions WHERE sample_id = ? ORDER BY address');
|
|
351
|
+
return stmt.all(sampleId);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Find functions by sample with score ordering
|
|
355
|
+
*/
|
|
356
|
+
findFunctionsByScore(sampleId, limit) {
|
|
357
|
+
let sql = 'SELECT * FROM functions WHERE sample_id = ? ORDER BY score DESC';
|
|
358
|
+
if (limit !== undefined) {
|
|
359
|
+
sql += ` LIMIT ${limit}`;
|
|
360
|
+
}
|
|
361
|
+
const stmt = this.db.prepare(sql);
|
|
362
|
+
return stmt.all(sampleId);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Update a function
|
|
366
|
+
*/
|
|
367
|
+
updateFunction(sampleId, address, updates) {
|
|
368
|
+
const fields = [];
|
|
369
|
+
const values = [];
|
|
370
|
+
if (updates.name !== undefined) {
|
|
371
|
+
fields.push('name = ?');
|
|
372
|
+
values.push(updates.name);
|
|
373
|
+
}
|
|
374
|
+
if (updates.size !== undefined) {
|
|
375
|
+
fields.push('size = ?');
|
|
376
|
+
values.push(updates.size);
|
|
377
|
+
}
|
|
378
|
+
if (updates.score !== undefined) {
|
|
379
|
+
fields.push('score = ?');
|
|
380
|
+
values.push(updates.score);
|
|
381
|
+
}
|
|
382
|
+
if (updates.tags !== undefined) {
|
|
383
|
+
fields.push('tags = ?');
|
|
384
|
+
values.push(updates.tags);
|
|
385
|
+
}
|
|
386
|
+
if (updates.summary !== undefined) {
|
|
387
|
+
fields.push('summary = ?');
|
|
388
|
+
values.push(updates.summary);
|
|
389
|
+
}
|
|
390
|
+
if (fields.length === 0) {
|
|
391
|
+
return; // No updates to perform
|
|
392
|
+
}
|
|
393
|
+
values.push(sampleId, address);
|
|
394
|
+
const stmt = this.db.prepare(`
|
|
395
|
+
UPDATE functions SET ${fields.join(', ')} WHERE sample_id = ? AND address = ?
|
|
396
|
+
`);
|
|
397
|
+
stmt.run(...values);
|
|
398
|
+
}
|
|
399
|
+
// ==================== Artifact Operations ====================
|
|
400
|
+
/**
|
|
401
|
+
* Insert a new artifact
|
|
402
|
+
*/
|
|
403
|
+
insertArtifact(artifact) {
|
|
404
|
+
const stmt = this.db.prepare(`
|
|
405
|
+
INSERT INTO artifacts (id, sample_id, type, path, sha256, mime, created_at)
|
|
406
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
407
|
+
`);
|
|
408
|
+
stmt.run(artifact.id, artifact.sample_id, artifact.type, artifact.path, artifact.sha256, artifact.mime, artifact.created_at);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Find all artifacts for a sample
|
|
412
|
+
*/
|
|
413
|
+
findArtifacts(sampleId) {
|
|
414
|
+
const stmt = this.db.prepare('SELECT * FROM artifacts WHERE sample_id = ? ORDER BY created_at DESC');
|
|
415
|
+
return stmt.all(sampleId);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Find artifacts by sample and type
|
|
419
|
+
*/
|
|
420
|
+
findArtifactsByType(sampleId, type) {
|
|
421
|
+
const stmt = this.db.prepare('SELECT * FROM artifacts WHERE sample_id = ? AND type = ? ORDER BY created_at DESC');
|
|
422
|
+
return stmt.all(sampleId, type);
|
|
423
|
+
}
|
|
424
|
+
// ==================== Cache Operations ====================
|
|
425
|
+
/**
|
|
426
|
+
* Get cached result from database
|
|
427
|
+
* Requirements: 20.5
|
|
428
|
+
*/
|
|
429
|
+
async getCachedResult(key) {
|
|
430
|
+
const stmt = this.db.prepare('SELECT data, created_at, expires_at, sample_sha256 FROM cache WHERE key = ?');
|
|
431
|
+
const row = stmt.get(key);
|
|
432
|
+
if (!row) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
const data = JSON.parse(row.data);
|
|
437
|
+
return {
|
|
438
|
+
data,
|
|
439
|
+
createdAt: row.created_at || undefined,
|
|
440
|
+
expiresAt: row.expires_at || undefined,
|
|
441
|
+
sampleSha256: row.sample_sha256 || undefined,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
// Invalid JSON, remove from cache
|
|
446
|
+
this.db.prepare('DELETE FROM cache WHERE key = ?').run(key);
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Set cached result in database
|
|
452
|
+
* Requirements: 20.5
|
|
453
|
+
*/
|
|
454
|
+
async setCachedResult(key, data, expiresAt, sampleSha256) {
|
|
455
|
+
const stmt = this.db.prepare(`
|
|
456
|
+
INSERT OR REPLACE INTO cache (key, data, sample_sha256, created_at, expires_at)
|
|
457
|
+
VALUES (?, ?, ?, ?, ?)
|
|
458
|
+
`);
|
|
459
|
+
stmt.run(key, JSON.stringify(data), sampleSha256 || null, new Date().toISOString(), expiresAt || null);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Delete expired cache entries
|
|
463
|
+
*/
|
|
464
|
+
cleanExpiredCache() {
|
|
465
|
+
const stmt = this.db.prepare('DELETE FROM cache WHERE expires_at IS NOT NULL AND expires_at < ?');
|
|
466
|
+
const result = stmt.run(new Date().toISOString());
|
|
467
|
+
return result.changes;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get recent cache entries for prewarming
|
|
471
|
+
* Requirements: 26.1 (cache prewarming), 26.2 (query optimization)
|
|
472
|
+
*
|
|
473
|
+
* @param limit - Maximum number of entries to return
|
|
474
|
+
* @returns Array of cache entries ordered by creation time (most recent first)
|
|
475
|
+
*/
|
|
476
|
+
async getRecentCacheEntries(limit) {
|
|
477
|
+
const stmt = this.db.prepare(`
|
|
478
|
+
SELECT key, data, expires_at
|
|
479
|
+
FROM cache
|
|
480
|
+
WHERE expires_at IS NULL OR expires_at > ?
|
|
481
|
+
ORDER BY created_at DESC
|
|
482
|
+
LIMIT ?
|
|
483
|
+
`);
|
|
484
|
+
return stmt.all(new Date().toISOString(), limit);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Get cache entries for a specific sample
|
|
488
|
+
* Requirements: 26.1 (cache prewarming), 26.2 (query optimization)
|
|
489
|
+
*
|
|
490
|
+
* @param sampleSha256 - SHA256 hash of the sample
|
|
491
|
+
* @returns Array of cache entries for the sample
|
|
492
|
+
*/
|
|
493
|
+
async getCacheEntriesBySample(sampleSha256) {
|
|
494
|
+
// Query cache entries by sample_sha256 column
|
|
495
|
+
const stmt = this.db.prepare(`
|
|
496
|
+
SELECT key, data, expires_at
|
|
497
|
+
FROM cache
|
|
498
|
+
WHERE sample_sha256 = ?
|
|
499
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
500
|
+
ORDER BY created_at DESC
|
|
501
|
+
`);
|
|
502
|
+
return stmt.all(sampleSha256, new Date().toISOString());
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Batch insert functions for better performance
|
|
506
|
+
* Requirements: 26.2 (database query optimization)
|
|
507
|
+
*
|
|
508
|
+
* @param functions - Array of functions to insert
|
|
509
|
+
*/
|
|
510
|
+
insertFunctionsBatch(functions) {
|
|
511
|
+
if (functions.length === 0) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
// Use transaction for batch insert
|
|
515
|
+
const insertStmt = this.db.prepare(`
|
|
516
|
+
INSERT OR REPLACE INTO functions (sample_id, address, name, size, score, tags, summary, caller_count, callee_count, is_entry_point, is_exported, callees)
|
|
517
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
518
|
+
`);
|
|
519
|
+
const insertMany = this.db.transaction((funcs) => {
|
|
520
|
+
for (const func of funcs) {
|
|
521
|
+
insertStmt.run(func.sample_id, func.address, func.name, func.size, func.score, func.tags, func.summary, func.caller_count ?? 0, func.callee_count ?? 0, func.is_entry_point ?? 0, func.is_exported ?? 0, func.callees);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
insertMany(functions);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Batch insert artifacts for better performance
|
|
528
|
+
* Requirements: 26.2 (database query optimization)
|
|
529
|
+
*
|
|
530
|
+
* @param artifacts - Array of artifacts to insert
|
|
531
|
+
*/
|
|
532
|
+
insertArtifactsBatch(artifacts) {
|
|
533
|
+
if (artifacts.length === 0) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// Use transaction for batch insert
|
|
537
|
+
const insertStmt = this.db.prepare(`
|
|
538
|
+
INSERT INTO artifacts (id, sample_id, type, path, sha256, mime, created_at)
|
|
539
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
540
|
+
`);
|
|
541
|
+
const insertMany = this.db.transaction((arts) => {
|
|
542
|
+
for (const artifact of arts) {
|
|
543
|
+
insertStmt.run(artifact.id, artifact.sample_id, artifact.type, artifact.path, artifact.sha256, artifact.mime, artifact.created_at);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
insertMany(artifacts);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Optimize database by running VACUUM and ANALYZE
|
|
550
|
+
* Requirements: 26.2 (database query optimization)
|
|
551
|
+
*
|
|
552
|
+
* Should be run periodically to maintain performance
|
|
553
|
+
*/
|
|
554
|
+
optimizeDatabase() {
|
|
555
|
+
// ANALYZE updates statistics for query planner
|
|
556
|
+
this.db.exec('ANALYZE');
|
|
557
|
+
// VACUUM reclaims space and defragments
|
|
558
|
+
// Note: VACUUM can be slow on large databases
|
|
559
|
+
this.db.exec('VACUUM');
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Get database statistics for monitoring
|
|
563
|
+
* Requirements: 26.2 (database query optimization)
|
|
564
|
+
*
|
|
565
|
+
* @returns Object with database statistics
|
|
566
|
+
*/
|
|
567
|
+
getDatabaseStats() {
|
|
568
|
+
const sampleCount = this.db.prepare('SELECT COUNT(*) as count FROM samples').get();
|
|
569
|
+
const analysisCount = this.db.prepare('SELECT COUNT(*) as count FROM analyses').get();
|
|
570
|
+
const functionCount = this.db.prepare('SELECT COUNT(*) as count FROM functions').get();
|
|
571
|
+
const artifactCount = this.db.prepare('SELECT COUNT(*) as count FROM artifacts').get();
|
|
572
|
+
const cacheCount = this.db.prepare('SELECT COUNT(*) as count FROM cache').get();
|
|
573
|
+
// Get database file size
|
|
574
|
+
const dbPath = this.db.name; // Access internal property
|
|
575
|
+
let dbSizeBytes = 0;
|
|
576
|
+
try {
|
|
577
|
+
if (typeof dbPath === 'string' && dbPath.length > 0) {
|
|
578
|
+
const stats = fs.statSync(dbPath);
|
|
579
|
+
dbSizeBytes = stats.size;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
// Ignore errors
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
sampleCount: sampleCount.count,
|
|
587
|
+
analysisCount: analysisCount.count,
|
|
588
|
+
functionCount: functionCount.count,
|
|
589
|
+
artifactCount: artifactCount.count,
|
|
590
|
+
cacheCount: cacheCount.count,
|
|
591
|
+
dbSizeBytes
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Create and initialize a database instance
|
|
597
|
+
*/
|
|
598
|
+
export function createDatabase(dbPath) {
|
|
599
|
+
return new DatabaseManager(dbPath);
|
|
600
|
+
}
|
|
601
|
+
//# sourceMappingURL=database.js.map
|