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,249 @@
1
+ /**
2
+ * Environment validation for MCP Server
3
+ * Validates that required dependencies and paths exist
4
+ */
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { execSync } from 'child_process';
8
+ /**
9
+ * Check if a file or directory exists
10
+ */
11
+ function pathExists(path) {
12
+ try {
13
+ fs.accessSync(path, fs.constants.F_OK);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ /**
21
+ * Check if a command is available in PATH
22
+ */
23
+ function commandExists(command) {
24
+ try {
25
+ execSync(`which ${command}`, { stdio: 'ignore' });
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Validate Node.js version
34
+ */
35
+ function validateNodeVersion() {
36
+ const version = process.version;
37
+ const major = parseInt(version.slice(1).split('.')[0], 10);
38
+ if (major < 18) {
39
+ return {
40
+ valid: false,
41
+ error: `Node.js version ${version} is not supported. Minimum required version is 18.0.0`,
42
+ };
43
+ }
44
+ return { valid: true };
45
+ }
46
+ /**
47
+ * Validate workspace directory
48
+ */
49
+ function validateWorkspace(workspaceRoot) {
50
+ if (!pathExists(workspaceRoot)) {
51
+ try {
52
+ fs.mkdirSync(workspaceRoot, { recursive: true });
53
+ return { valid: true, warning: `Created workspace directory: ${workspaceRoot}` };
54
+ }
55
+ catch (error) {
56
+ return {
57
+ valid: false,
58
+ error: `Failed to create workspace directory ${workspaceRoot}: ${error.message}`,
59
+ };
60
+ }
61
+ }
62
+ // Check if directory is writable
63
+ try {
64
+ fs.accessSync(workspaceRoot, fs.constants.W_OK);
65
+ return { valid: true };
66
+ }
67
+ catch {
68
+ return {
69
+ valid: false,
70
+ error: `Workspace directory ${workspaceRoot} is not writable`,
71
+ };
72
+ }
73
+ }
74
+ /**
75
+ * Validate database configuration
76
+ */
77
+ function validateDatabase(dbConfig) {
78
+ if (dbConfig.type === 'sqlite') {
79
+ if (dbConfig.path) {
80
+ const dir = path.dirname(dbConfig.path);
81
+ if (dir && dir !== '.' && !pathExists(dir)) {
82
+ try {
83
+ fs.mkdirSync(dir, { recursive: true });
84
+ return { valid: true, warning: `Created database directory: ${dir}` };
85
+ }
86
+ catch (error) {
87
+ return {
88
+ valid: false,
89
+ error: `Failed to create database directory ${dir}: ${error.message}`,
90
+ };
91
+ }
92
+ }
93
+ }
94
+ return { valid: true };
95
+ }
96
+ if (dbConfig.type === 'postgresql') {
97
+ if (!dbConfig.host || !dbConfig.database) {
98
+ return {
99
+ valid: false,
100
+ error: 'PostgreSQL configuration requires host and database name',
101
+ };
102
+ }
103
+ return { valid: true };
104
+ }
105
+ return { valid: true };
106
+ }
107
+ /**
108
+ * Validate Ghidra worker configuration
109
+ */
110
+ function validateGhidraWorker(ghidraConfig) {
111
+ if (!ghidraConfig.enabled) {
112
+ return { valid: true };
113
+ }
114
+ if (!ghidraConfig.path) {
115
+ return {
116
+ valid: false,
117
+ error: 'Ghidra worker is enabled but GHIDRA_PATH is not configured',
118
+ };
119
+ }
120
+ if (!pathExists(ghidraConfig.path)) {
121
+ return {
122
+ valid: false,
123
+ error: `Ghidra path does not exist: ${ghidraConfig.path}`,
124
+ };
125
+ }
126
+ // Check for analyzeHeadless script
127
+ const analyzeHeadless = `${ghidraConfig.path}/support/analyzeHeadless`;
128
+ if (!pathExists(analyzeHeadless) && !pathExists(`${analyzeHeadless}.bat`)) {
129
+ return {
130
+ valid: false,
131
+ error: `Ghidra analyzeHeadless script not found at ${analyzeHeadless}`,
132
+ };
133
+ }
134
+ return { valid: true };
135
+ }
136
+ /**
137
+ * Validate Python worker configuration
138
+ */
139
+ function validatePythonWorker(staticConfig) {
140
+ if (!staticConfig.enabled) {
141
+ return { valid: true };
142
+ }
143
+ const pythonCmd = staticConfig.pythonPath || 'python3';
144
+ if (!commandExists(pythonCmd)) {
145
+ return {
146
+ valid: false,
147
+ error: `Python command not found: ${pythonCmd}`,
148
+ };
149
+ }
150
+ // Check Python version
151
+ try {
152
+ const version = execSync(`${pythonCmd} --version`, { encoding: 'utf-8' });
153
+ const match = version.match(/Python (\d+)\.(\d+)/);
154
+ if (match) {
155
+ const major = parseInt(match[1], 10);
156
+ const minor = parseInt(match[2], 10);
157
+ if (major < 3 || (major === 3 && minor < 9)) {
158
+ return {
159
+ valid: false,
160
+ error: `Python version ${version.trim()} is not supported. Minimum required version is 3.9`,
161
+ };
162
+ }
163
+ }
164
+ }
165
+ catch (error) {
166
+ return {
167
+ valid: false,
168
+ error: `Failed to check Python version: ${error.message}`,
169
+ };
170
+ }
171
+ return { valid: true };
172
+ }
173
+ /**
174
+ * Validate .NET worker configuration
175
+ */
176
+ function validateDotNetWorker(dotnetConfig) {
177
+ if (!dotnetConfig.enabled) {
178
+ return { valid: true };
179
+ }
180
+ if (!dotnetConfig.ilspyPath) {
181
+ return {
182
+ valid: false,
183
+ error: '.NET worker is enabled but ilspyPath is not configured',
184
+ };
185
+ }
186
+ if (!pathExists(dotnetConfig.ilspyPath)) {
187
+ return {
188
+ valid: false,
189
+ error: `ILSpy path does not exist: ${dotnetConfig.ilspyPath}`,
190
+ };
191
+ }
192
+ return { valid: true };
193
+ }
194
+ /**
195
+ * Validate the entire environment based on configuration
196
+ */
197
+ export function validateEnvironment(config) {
198
+ const errors = [];
199
+ const warnings = [];
200
+ // Validate Node.js version
201
+ const nodeValidation = validateNodeVersion();
202
+ if (!nodeValidation.valid && nodeValidation.error) {
203
+ errors.push(nodeValidation.error);
204
+ }
205
+ // Validate workspace
206
+ const workspaceValidation = validateWorkspace(config.workspace.root);
207
+ if (!workspaceValidation.valid && workspaceValidation.error) {
208
+ errors.push(workspaceValidation.error);
209
+ }
210
+ if (workspaceValidation.warning) {
211
+ warnings.push(workspaceValidation.warning);
212
+ }
213
+ // Validate database
214
+ const dbValidation = validateDatabase(config.database);
215
+ if (!dbValidation.valid && dbValidation.error) {
216
+ errors.push(dbValidation.error);
217
+ }
218
+ if (dbValidation.warning) {
219
+ warnings.push(dbValidation.warning);
220
+ }
221
+ // Validate workers
222
+ const ghidraValidation = validateGhidraWorker(config.workers.ghidra);
223
+ if (!ghidraValidation.valid && ghidraValidation.error) {
224
+ errors.push(ghidraValidation.error);
225
+ }
226
+ if (ghidraValidation.warning) {
227
+ warnings.push(ghidraValidation.warning);
228
+ }
229
+ const pythonValidation = validatePythonWorker(config.workers.static);
230
+ if (!pythonValidation.valid && pythonValidation.error) {
231
+ errors.push(pythonValidation.error);
232
+ }
233
+ if (pythonValidation.warning) {
234
+ warnings.push(pythonValidation.warning);
235
+ }
236
+ const dotnetValidation = validateDotNetWorker(config.workers.dotnet);
237
+ if (!dotnetValidation.valid && dotnetValidation.error) {
238
+ errors.push(dotnetValidation.error);
239
+ }
240
+ if (dotnetValidation.warning) {
241
+ warnings.push(dotnetValidation.warning);
242
+ }
243
+ return {
244
+ valid: errors.length === 0,
245
+ errors,
246
+ warnings,
247
+ };
248
+ }
249
+ //# sourceMappingURL=env-validator.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Error handling and classification module
3
+ * Requirements: 22.1, 22.2, 22.3, 22.4, 22.5, 22.6
4
+ */
5
+ import { ErrorCategory, ErrorContext, ErrorResult } from './types.js';
6
+ /**
7
+ * Classify an error into a category
8
+ * Requirements: 22.1, 22.2, 22.3
9
+ */
10
+ export declare function classifyError(error: Error): ErrorCategory;
11
+ /**
12
+ * Calculate exponential backoff delay
13
+ * Requirements: 22.4
14
+ *
15
+ * Formula: min(baseMs * 2^attempt, maxMs) + jitter
16
+ */
17
+ export declare function exponentialBackoff(attempt: number, baseMs?: number, maxMs?: number): number;
18
+ /**
19
+ * Handle an error and determine retry strategy
20
+ * Requirements: 22.1, 22.2, 22.3, 22.4, 22.5, 22.6
21
+ */
22
+ export declare function handleError(error: Error, context: ErrorContext): ErrorResult;
23
+ /**
24
+ * Create a standardized error message
25
+ */
26
+ export declare function formatErrorMessage(category: ErrorCategory, originalError: Error, context: ErrorContext): string;
27
+ export { ErrorCategory, ErrorContext, ErrorResult } from './types.js';
28
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Example usage of the error handler module
3
+ * This file demonstrates how to integrate error handling into tools and workers
4
+ */
5
+ /**
6
+ * Example: Retry logic for a tool with exponential backoff
7
+ */
8
+ declare function executeToolWithRetry<T>(toolName: string, sampleId: string, operation: () => Promise<T>, maxRetries?: number): Promise<T>;
9
+ /**
10
+ * Example: PE fingerprint tool with fallback parser
11
+ */
12
+ declare function peFingerprint(sampleId: string, useLief?: boolean): Promise<any>;
13
+ /**
14
+ * Example: Ghidra analysis with timeout handling
15
+ */
16
+ declare function ghidraAnalyze(sampleId: string): Promise<any>;
17
+ /**
18
+ * Example: Policy-denied error (non-retryable)
19
+ */
20
+ declare function sandboxExecute(sampleId: string, requireApproval: boolean): Promise<any>;
21
+ export { executeToolWithRetry, peFingerprint, ghidraAnalyze, sandboxExecute };
22
+ //# sourceMappingURL=error-handler.example.d.ts.map
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Example usage of the error handler module
3
+ * This file demonstrates how to integrate error handling into tools and workers
4
+ */
5
+ import { handleError, classifyError, formatErrorMessage, ErrorCategory } from './error-handler.js';
6
+ /**
7
+ * Example: Retry logic for a tool with exponential backoff
8
+ */
9
+ async function executeToolWithRetry(toolName, sampleId, operation, maxRetries = 3) {
10
+ let lastError = null;
11
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
12
+ try {
13
+ return await operation();
14
+ }
15
+ catch (error) {
16
+ lastError = error;
17
+ const context = {
18
+ tool: toolName,
19
+ sampleId,
20
+ attempt,
21
+ maxRetries
22
+ };
23
+ const result = handleError(lastError, context);
24
+ // Log the error with formatted message
25
+ const category = classifyError(lastError);
26
+ const message = formatErrorMessage(category, lastError, context);
27
+ console.error(message);
28
+ if (!result.shouldRetry) {
29
+ // Handle fallback actions
30
+ if (result.fallbackAction === 'use_lief_instead_of_pefile') {
31
+ console.log('Attempting fallback parser...');
32
+ // Implement fallback logic here
33
+ }
34
+ else if (result.fallbackAction === 'log_warning') {
35
+ console.warn('Partial success, continuing...');
36
+ }
37
+ else if (result.fallbackAction === 'notify_admin') {
38
+ console.error('Max retries exceeded, admin notification required');
39
+ }
40
+ throw lastError;
41
+ }
42
+ // Wait before retrying
43
+ if (result.backoffMs) {
44
+ console.log(`Retrying in ${result.backoffMs}ms...`);
45
+ await sleep(result.backoffMs);
46
+ }
47
+ }
48
+ }
49
+ throw lastError;
50
+ }
51
+ /**
52
+ * Example: PE fingerprint tool with fallback parser
53
+ */
54
+ async function peFingerprint(sampleId, useLief = false) {
55
+ const context = {
56
+ tool: 'pe.fingerprint',
57
+ sampleId,
58
+ attempt: 0,
59
+ maxRetries: 1
60
+ };
61
+ try {
62
+ if (useLief) {
63
+ // Use LIEF parser
64
+ return await parsePEWithLief(sampleId);
65
+ }
66
+ else {
67
+ // Use pefile parser
68
+ return await parsePEWithPefile(sampleId);
69
+ }
70
+ }
71
+ catch (error) {
72
+ const result = handleError(error, context);
73
+ if (result.fallbackAction === 'use_lief_instead_of_pefile') {
74
+ console.log('Primary parser failed, trying LIEF...');
75
+ return await peFingerprint(sampleId, true);
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+ /**
81
+ * Example: Ghidra analysis with timeout handling
82
+ */
83
+ async function ghidraAnalyze(sampleId) {
84
+ return executeToolWithRetry('ghidra.analyze', sampleId, async () => {
85
+ // Simulate Ghidra analysis
86
+ const result = await runGhidraHeadless(sampleId);
87
+ return result;
88
+ }, 3 // Max 3 retries for timeout errors
89
+ );
90
+ }
91
+ /**
92
+ * Example: Policy-denied error (non-retryable)
93
+ */
94
+ async function sandboxExecute(sampleId, requireApproval) {
95
+ const context = {
96
+ tool: 'sandbox.execute',
97
+ sampleId,
98
+ attempt: 0,
99
+ maxRetries: 0 // No retries for policy errors
100
+ };
101
+ try {
102
+ if (!requireApproval) {
103
+ throw new Error('Policy denied: approval required for dynamic execution');
104
+ }
105
+ return await executeSandbox(sampleId);
106
+ }
107
+ catch (error) {
108
+ const result = handleError(error, context);
109
+ if (!result.shouldRetry) {
110
+ const category = classifyError(error);
111
+ if (category === ErrorCategory.POLICY_DENIED) {
112
+ console.error('Operation denied by policy guard');
113
+ // Log to audit log
114
+ }
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+ // Helper functions (stubs for demonstration)
120
+ async function sleep(ms) {
121
+ return new Promise(resolve => setTimeout(resolve, ms));
122
+ }
123
+ async function parsePEWithPefile(_sampleId) {
124
+ // Stub implementation
125
+ throw new Error('Parse error: malformed PE header');
126
+ }
127
+ async function parsePEWithLief(_sampleId) {
128
+ // Stub implementation
129
+ return { success: true, parser: 'lief' };
130
+ }
131
+ async function runGhidraHeadless(_sampleId) {
132
+ // Stub implementation
133
+ return { functions: [] };
134
+ }
135
+ async function executeSandbox(_sampleId) {
136
+ // Stub implementation
137
+ return { traces: [] };
138
+ }
139
+ // Export examples for documentation
140
+ export { executeToolWithRetry, peFingerprint, ghidraAnalyze, sandboxExecute };
141
+ //# sourceMappingURL=error-handler.example.js.map
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Error handling and classification module
3
+ * Requirements: 22.1, 22.2, 22.3, 22.4, 22.5, 22.6
4
+ */
5
+ import { ErrorCategory } from './types.js';
6
+ /**
7
+ * Classify an error into a category
8
+ * Requirements: 22.1, 22.2, 22.3
9
+ */
10
+ export function classifyError(error) {
11
+ const message = error.message.toLowerCase();
12
+ // Timeout errors
13
+ if (message.includes('timeout') || message.includes('timed out')) {
14
+ return ErrorCategory.TIMEOUT;
15
+ }
16
+ // Resource exhaustion
17
+ if (message.includes('out of memory') ||
18
+ message.includes('resource exhausted') ||
19
+ message.includes('enomem') ||
20
+ message.includes('enospc')) {
21
+ return ErrorCategory.RESOURCE_EXHAUSTED;
22
+ }
23
+ // Worker unavailable
24
+ if (message.includes('worker unavailable') ||
25
+ message.includes('connection refused') ||
26
+ message.includes('econnrefused')) {
27
+ return ErrorCategory.WORKER_UNAVAILABLE;
28
+ }
29
+ // Invalid input
30
+ if (message.includes('invalid input') ||
31
+ message.includes('validation failed') ||
32
+ message.includes('invalid argument')) {
33
+ return ErrorCategory.INVALID_INPUT;
34
+ }
35
+ // Parse errors
36
+ if (message.includes('parse error') ||
37
+ message.includes('malformed') ||
38
+ message.includes('invalid pe')) {
39
+ return ErrorCategory.PARSE_ERROR;
40
+ }
41
+ // Policy denied
42
+ if (message.includes('policy denied') ||
43
+ message.includes('permission denied') ||
44
+ message.includes('unauthorized')) {
45
+ return ErrorCategory.POLICY_DENIED;
46
+ }
47
+ // Not found
48
+ if (message.includes('not found') || message.includes('enoent')) {
49
+ return ErrorCategory.NOT_FOUND;
50
+ }
51
+ // Partial success
52
+ if (message.includes('partial')) {
53
+ return ErrorCategory.PARTIAL_SUCCESS;
54
+ }
55
+ return ErrorCategory.UNKNOWN;
56
+ }
57
+ /**
58
+ * Calculate exponential backoff delay
59
+ * Requirements: 22.4
60
+ *
61
+ * Formula: min(baseMs * 2^attempt, maxMs) + jitter
62
+ */
63
+ export function exponentialBackoff(attempt, baseMs = 1000, maxMs = 30000) {
64
+ const exponentialDelay = Math.min(baseMs * Math.pow(2, attempt), maxMs);
65
+ // Add jitter (0-20% of delay) to prevent thundering herd
66
+ const jitter = Math.random() * 0.2 * exponentialDelay;
67
+ return Math.floor(exponentialDelay + jitter);
68
+ }
69
+ /**
70
+ * Handle an error and determine retry strategy
71
+ * Requirements: 22.1, 22.2, 22.3, 22.4, 22.5, 22.6
72
+ */
73
+ export function handleError(error, context) {
74
+ const category = classifyError(error);
75
+ switch (category) {
76
+ case ErrorCategory.TIMEOUT:
77
+ case ErrorCategory.RESOURCE_EXHAUSTED:
78
+ case ErrorCategory.WORKER_UNAVAILABLE:
79
+ // Retryable errors with exponential backoff
80
+ if (context.attempt < context.maxRetries) {
81
+ return {
82
+ shouldRetry: true,
83
+ backoffMs: exponentialBackoff(context.attempt)
84
+ };
85
+ }
86
+ else {
87
+ return {
88
+ shouldRetry: false,
89
+ fallbackAction: 'notify_admin'
90
+ };
91
+ }
92
+ case ErrorCategory.INVALID_INPUT:
93
+ case ErrorCategory.POLICY_DENIED:
94
+ // Non-retryable errors - log and fail immediately
95
+ return {
96
+ shouldRetry: false
97
+ };
98
+ case ErrorCategory.PARSE_ERROR:
99
+ // Try fallback parser for PE parsing errors
100
+ if (context.tool === 'pe.fingerprint') {
101
+ return {
102
+ shouldRetry: true,
103
+ fallbackAction: 'use_lief_instead_of_pefile'
104
+ };
105
+ }
106
+ else {
107
+ return {
108
+ shouldRetry: false
109
+ };
110
+ }
111
+ case ErrorCategory.PARTIAL_SUCCESS:
112
+ // Log warning but don't retry
113
+ return {
114
+ shouldRetry: false,
115
+ fallbackAction: 'log_warning'
116
+ };
117
+ case ErrorCategory.NOT_FOUND:
118
+ // Don't retry for not found errors
119
+ return {
120
+ shouldRetry: false
121
+ };
122
+ case ErrorCategory.UNKNOWN:
123
+ default:
124
+ // Unknown errors - don't retry by default
125
+ return {
126
+ shouldRetry: false,
127
+ fallbackAction: 'log_error'
128
+ };
129
+ }
130
+ }
131
+ /**
132
+ * Create a standardized error message
133
+ */
134
+ export function formatErrorMessage(category, originalError, context) {
135
+ return `[${category}] ${context.tool} failed for sample ${context.sampleId} (attempt ${context.attempt}/${context.maxRetries}): ${originalError.message}`;
136
+ }
137
+ // Re-export types for convenience
138
+ export { ErrorCategory } from './types.js';
139
+ //# sourceMappingURL=error-handler.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared helpers for interpreting persisted Ghidra analysis states.
3
+ */
4
+ import type { Analysis } from './database.js';
5
+ export declare const GHIDRA_READY_STATUSES: Set<string>;
6
+ export declare const GHIDRA_STRICT_READY_STATUSES: Set<string>;
7
+ export type GhidraCapability = 'function_index' | 'decompile' | 'cfg';
8
+ export type GhidraCapabilityState = 'ready' | 'degraded' | 'missing';
9
+ export interface GhidraCapabilityStatus {
10
+ available: boolean;
11
+ status: GhidraCapabilityState;
12
+ reason?: string;
13
+ warnings?: string[];
14
+ checked_at?: string;
15
+ target?: string;
16
+ details?: Record<string, unknown>;
17
+ }
18
+ export interface GhidraAnalysisMetadata {
19
+ function_count?: number;
20
+ project_path?: string;
21
+ project_key?: string;
22
+ function_extraction?: {
23
+ status?: string;
24
+ script_used?: string;
25
+ warnings?: string[];
26
+ attempts?: unknown[];
27
+ };
28
+ readiness?: Partial<Record<GhidraCapability, GhidraCapabilityStatus>>;
29
+ end_to_end_probe?: {
30
+ target?: string;
31
+ decompile?: GhidraCapabilityStatus;
32
+ cfg?: GhidraCapabilityStatus;
33
+ checked_at?: string;
34
+ };
35
+ [key: string]: unknown;
36
+ }
37
+ export interface GhidraReadinessMatrix {
38
+ function_index: GhidraCapabilityStatus;
39
+ decompile: GhidraCapabilityStatus;
40
+ cfg: GhidraCapabilityStatus;
41
+ }
42
+ export declare function parseGhidraAnalysisMetadata(outputJson: string | null | undefined): GhidraAnalysisMetadata;
43
+ export declare function isGhidraReadyStatus(status: string | null | undefined): boolean;
44
+ export declare function isStrictGhidraReadyStatus(status: string | null | undefined): boolean;
45
+ export declare function getGhidraReadiness(analysis: Pick<Analysis, 'status' | 'output_json'>): GhidraReadinessMatrix;
46
+ export declare function getGhidraCapabilityStatus(analysis: Pick<Analysis, 'status' | 'output_json'>, capability: GhidraCapability): GhidraCapabilityStatus;
47
+ export declare function isGhidraCapabilityReady(analysis: Pick<Analysis, 'status' | 'output_json'>, capability: GhidraCapability): boolean;
48
+ export declare function findBestGhidraAnalysis(analyses: Analysis[], capability?: GhidraCapability): Analysis | undefined;
49
+ //# sourceMappingURL=ghidra-analysis-status.d.ts.map