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.
Files changed (190) hide show
  1. package/CODEX_INSTALLATION.md +69 -0
  2. package/COPILOT_INSTALLATION.md +77 -0
  3. package/LICENSE +21 -0
  4. package/README.md +314 -0
  5. package/bin/windows-exe-decompiler-mcp-server.js +3 -0
  6. package/dist/analysis-provenance.d.ts +184 -0
  7. package/dist/analysis-provenance.js +74 -0
  8. package/dist/analysis-task-runner.d.ts +31 -0
  9. package/dist/analysis-task-runner.js +160 -0
  10. package/dist/artifact-inventory.d.ts +23 -0
  11. package/dist/artifact-inventory.js +175 -0
  12. package/dist/cache-manager.d.ts +128 -0
  13. package/dist/cache-manager.js +454 -0
  14. package/dist/confidence-semantics.d.ts +66 -0
  15. package/dist/confidence-semantics.js +122 -0
  16. package/dist/config.d.ts +335 -0
  17. package/dist/config.js +193 -0
  18. package/dist/database.d.ts +227 -0
  19. package/dist/database.js +601 -0
  20. package/dist/decompiler-worker.d.ts +441 -0
  21. package/dist/decompiler-worker.js +1962 -0
  22. package/dist/dynamic-trace.d.ts +95 -0
  23. package/dist/dynamic-trace.js +629 -0
  24. package/dist/env-validator.d.ts +15 -0
  25. package/dist/env-validator.js +249 -0
  26. package/dist/error-handler.d.ts +28 -0
  27. package/dist/error-handler.example.d.ts +22 -0
  28. package/dist/error-handler.example.js +141 -0
  29. package/dist/error-handler.js +139 -0
  30. package/dist/ghidra-analysis-status.d.ts +49 -0
  31. package/dist/ghidra-analysis-status.js +178 -0
  32. package/dist/ghidra-config.d.ts +134 -0
  33. package/dist/ghidra-config.js +464 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.js +200 -0
  36. package/dist/job-queue.d.ts +169 -0
  37. package/dist/job-queue.js +407 -0
  38. package/dist/logger.d.ts +106 -0
  39. package/dist/logger.js +176 -0
  40. package/dist/policy-guard.d.ts +115 -0
  41. package/dist/policy-guard.js +243 -0
  42. package/dist/process-output.d.ts +15 -0
  43. package/dist/process-output.js +90 -0
  44. package/dist/prompts/function-explanation-review.d.ts +5 -0
  45. package/dist/prompts/function-explanation-review.js +64 -0
  46. package/dist/prompts/semantic-name-review.d.ts +5 -0
  47. package/dist/prompts/semantic-name-review.js +63 -0
  48. package/dist/runtime-correlation.d.ts +34 -0
  49. package/dist/runtime-correlation.js +279 -0
  50. package/dist/runtime-paths.d.ts +3 -0
  51. package/dist/runtime-paths.js +11 -0
  52. package/dist/selection-diff.d.ts +667 -0
  53. package/dist/selection-diff.js +53 -0
  54. package/dist/semantic-name-suggestion-artifacts.d.ts +116 -0
  55. package/dist/semantic-name-suggestion-artifacts.js +314 -0
  56. package/dist/server.d.ts +129 -0
  57. package/dist/server.js +578 -0
  58. package/dist/tools/artifact-read.d.ts +235 -0
  59. package/dist/tools/artifact-read.js +317 -0
  60. package/dist/tools/artifacts-diff.d.ts +728 -0
  61. package/dist/tools/artifacts-diff.js +304 -0
  62. package/dist/tools/artifacts-list.d.ts +515 -0
  63. package/dist/tools/artifacts-list.js +389 -0
  64. package/dist/tools/attack-map.d.ts +290 -0
  65. package/dist/tools/attack-map.js +519 -0
  66. package/dist/tools/cache-observability.d.ts +4 -0
  67. package/dist/tools/cache-observability.js +36 -0
  68. package/dist/tools/code-function-cfg.d.ts +50 -0
  69. package/dist/tools/code-function-cfg.js +102 -0
  70. package/dist/tools/code-function-decompile.d.ts +55 -0
  71. package/dist/tools/code-function-decompile.js +103 -0
  72. package/dist/tools/code-function-disassemble.d.ts +43 -0
  73. package/dist/tools/code-function-disassemble.js +185 -0
  74. package/dist/tools/code-function-explain-apply.d.ts +255 -0
  75. package/dist/tools/code-function-explain-apply.js +225 -0
  76. package/dist/tools/code-function-explain-prepare.d.ts +535 -0
  77. package/dist/tools/code-function-explain-prepare.js +276 -0
  78. package/dist/tools/code-function-explain-review.d.ts +397 -0
  79. package/dist/tools/code-function-explain-review.js +589 -0
  80. package/dist/tools/code-function-rename-apply.d.ts +248 -0
  81. package/dist/tools/code-function-rename-apply.js +220 -0
  82. package/dist/tools/code-function-rename-prepare.d.ts +506 -0
  83. package/dist/tools/code-function-rename-prepare.js +279 -0
  84. package/dist/tools/code-function-rename-review.d.ts +574 -0
  85. package/dist/tools/code-function-rename-review.js +761 -0
  86. package/dist/tools/code-functions-list.d.ts +37 -0
  87. package/dist/tools/code-functions-list.js +91 -0
  88. package/dist/tools/code-functions-rank.d.ts +34 -0
  89. package/dist/tools/code-functions-rank.js +90 -0
  90. package/dist/tools/code-functions-reconstruct.d.ts +2725 -0
  91. package/dist/tools/code-functions-reconstruct.js +2807 -0
  92. package/dist/tools/code-functions-search.d.ts +39 -0
  93. package/dist/tools/code-functions-search.js +90 -0
  94. package/dist/tools/code-reconstruct-export.d.ts +1212 -0
  95. package/dist/tools/code-reconstruct-export.js +4002 -0
  96. package/dist/tools/code-reconstruct-plan.d.ts +274 -0
  97. package/dist/tools/code-reconstruct-plan.js +342 -0
  98. package/dist/tools/dotnet-metadata-extract.d.ts +541 -0
  99. package/dist/tools/dotnet-metadata-extract.js +355 -0
  100. package/dist/tools/dotnet-reconstruct-export.d.ts +567 -0
  101. package/dist/tools/dotnet-reconstruct-export.js +1151 -0
  102. package/dist/tools/dotnet-types-list.d.ts +325 -0
  103. package/dist/tools/dotnet-types-list.js +201 -0
  104. package/dist/tools/dynamic-dependencies.d.ts +115 -0
  105. package/dist/tools/dynamic-dependencies.js +213 -0
  106. package/dist/tools/dynamic-memory-import.d.ts +10 -0
  107. package/dist/tools/dynamic-memory-import.js +567 -0
  108. package/dist/tools/dynamic-trace-import.d.ts +10 -0
  109. package/dist/tools/dynamic-trace-import.js +235 -0
  110. package/dist/tools/entrypoint-fallback-disasm.d.ts +30 -0
  111. package/dist/tools/entrypoint-fallback-disasm.js +89 -0
  112. package/dist/tools/ghidra-analyze.d.ts +88 -0
  113. package/dist/tools/ghidra-analyze.js +208 -0
  114. package/dist/tools/ghidra-health.d.ts +37 -0
  115. package/dist/tools/ghidra-health.js +212 -0
  116. package/dist/tools/ioc-export.d.ts +209 -0
  117. package/dist/tools/ioc-export.js +542 -0
  118. package/dist/tools/packer-detect.d.ts +165 -0
  119. package/dist/tools/packer-detect.js +284 -0
  120. package/dist/tools/pe-exports-extract.d.ts +175 -0
  121. package/dist/tools/pe-exports-extract.js +253 -0
  122. package/dist/tools/pe-fingerprint.d.ts +234 -0
  123. package/dist/tools/pe-fingerprint.js +269 -0
  124. package/dist/tools/pe-imports-extract.d.ts +105 -0
  125. package/dist/tools/pe-imports-extract.js +245 -0
  126. package/dist/tools/report-generate.d.ts +157 -0
  127. package/dist/tools/report-generate.js +457 -0
  128. package/dist/tools/report-summarize.d.ts +2131 -0
  129. package/dist/tools/report-summarize.js +596 -0
  130. package/dist/tools/runtime-detect.d.ts +135 -0
  131. package/dist/tools/runtime-detect.js +247 -0
  132. package/dist/tools/sample-ingest.d.ts +94 -0
  133. package/dist/tools/sample-ingest.js +327 -0
  134. package/dist/tools/sample-profile-get.d.ts +183 -0
  135. package/dist/tools/sample-profile-get.js +121 -0
  136. package/dist/tools/sandbox-execute.d.ts +441 -0
  137. package/dist/tools/sandbox-execute.js +392 -0
  138. package/dist/tools/strings-extract.d.ts +375 -0
  139. package/dist/tools/strings-extract.js +314 -0
  140. package/dist/tools/strings-floss-decode.d.ts +143 -0
  141. package/dist/tools/strings-floss-decode.js +259 -0
  142. package/dist/tools/system-health.d.ts +434 -0
  143. package/dist/tools/system-health.js +446 -0
  144. package/dist/tools/task-cancel.d.ts +21 -0
  145. package/dist/tools/task-cancel.js +70 -0
  146. package/dist/tools/task-status.d.ts +27 -0
  147. package/dist/tools/task-status.js +106 -0
  148. package/dist/tools/task-sweep.d.ts +22 -0
  149. package/dist/tools/task-sweep.js +77 -0
  150. package/dist/tools/tool-help.d.ts +340 -0
  151. package/dist/tools/tool-help.js +261 -0
  152. package/dist/tools/yara-scan.d.ts +554 -0
  153. package/dist/tools/yara-scan.js +313 -0
  154. package/dist/types.d.ts +266 -0
  155. package/dist/types.js +41 -0
  156. package/dist/worker-pool.d.ts +204 -0
  157. package/dist/worker-pool.js +650 -0
  158. package/dist/workflows/deep-static.d.ts +104 -0
  159. package/dist/workflows/deep-static.js +276 -0
  160. package/dist/workflows/function-explanation-review.d.ts +655 -0
  161. package/dist/workflows/function-explanation-review.js +440 -0
  162. package/dist/workflows/reconstruct.d.ts +2053 -0
  163. package/dist/workflows/reconstruct.js +666 -0
  164. package/dist/workflows/semantic-name-review.d.ts +2418 -0
  165. package/dist/workflows/semantic-name-review.js +521 -0
  166. package/dist/workflows/triage.d.ts +659 -0
  167. package/dist/workflows/triage.js +1374 -0
  168. package/dist/workspace-manager.d.ts +150 -0
  169. package/dist/workspace-manager.js +411 -0
  170. package/ghidra_scripts/DecompileFunction.java +487 -0
  171. package/ghidra_scripts/DecompileFunction.py +150 -0
  172. package/ghidra_scripts/ExtractCFG.java +256 -0
  173. package/ghidra_scripts/ExtractCFG.py +233 -0
  174. package/ghidra_scripts/ExtractFunctions.java +442 -0
  175. package/ghidra_scripts/ExtractFunctions.py +101 -0
  176. package/ghidra_scripts/README.md +125 -0
  177. package/ghidra_scripts/SearchFunctionReferences.java +380 -0
  178. package/helpers/DotNetMetadataProbe/DotNetMetadataProbe.csproj +9 -0
  179. package/helpers/DotNetMetadataProbe/Program.cs +566 -0
  180. package/install-to-codex.ps1 +178 -0
  181. package/install-to-copilot.ps1 +303 -0
  182. package/package.json +101 -0
  183. package/requirements.txt +9 -0
  184. package/workers/requirements-dynamic.txt +11 -0
  185. package/workers/requirements.txt +8 -0
  186. package/workers/speakeasy_compat.py +175 -0
  187. package/workers/static_worker.py +5183 -0
  188. package/workers/yara_rules/default.yar +33 -0
  189. package/workers/yara_rules/malware_families.yar +93 -0
  190. package/workers/yara_rules/packers.yar +80 -0
@@ -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