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,1374 @@
1
+ /**
2
+ * Triage workflow implementation
3
+ * Quick threat assessment workflow that completes within 5 minutes
4
+ * Requirements: 15.1, 15.2, 15.4, 15.5
5
+ */
6
+ import { z } from 'zod';
7
+ import { createPEFingerprintHandler } from '../tools/pe-fingerprint.js';
8
+ import { createRuntimeDetectHandler } from '../tools/runtime-detect.js';
9
+ import { createPEImportsExtractHandler } from '../tools/pe-imports-extract.js';
10
+ import { createStringsExtractHandler } from '../tools/strings-extract.js';
11
+ import { createYaraScanHandler } from '../tools/yara-scan.js';
12
+ // ============================================================================
13
+ // Constants
14
+ // ============================================================================
15
+ const TOOL_NAME = 'workflow.triage';
16
+ // Suspicious API patterns for IOC detection
17
+ const HIGH_RISK_APIS = [
18
+ 'CreateRemoteThread',
19
+ 'VirtualAllocEx',
20
+ 'WriteProcessMemory',
21
+ 'SetWindowsHookEx',
22
+ ];
23
+ const CONTEXT_DEPENDENT_APIS = [
24
+ 'GetAsyncKeyState',
25
+ 'InternetOpen',
26
+ 'InternetConnect',
27
+ 'HttpSendRequest',
28
+ 'URLDownloadToFile',
29
+ 'WinExec',
30
+ 'ShellExecute',
31
+ 'CreateProcess',
32
+ 'RegSetValue',
33
+ 'RegCreateKey',
34
+ 'CryptEncrypt',
35
+ 'CryptDecrypt',
36
+ ];
37
+ const SUSPICIOUS_APIS = [...new Set([...HIGH_RISK_APIS, ...CONTEXT_DEPENDENT_APIS])];
38
+ const LibraryProfileSchema = z.object({
39
+ ecosystems: z.array(z.string()),
40
+ top_crates: z.array(z.string()),
41
+ notable_libraries: z.array(z.string()),
42
+ evidence: z.array(z.string()),
43
+ });
44
+ const NOTABLE_LIBRARY_HINTS = [
45
+ { name: 'tokio', patterns: [/\btokio\b/i] },
46
+ { name: 'goblin', patterns: [/\bgoblin\b/i] },
47
+ { name: 'iced-x86', patterns: [/\biced[-_]?x86\b/i] },
48
+ { name: 'clap', patterns: [/\bclap\b/i] },
49
+ { name: 'sysinfo', patterns: [/\bsysinfo\b/i] },
50
+ { name: 'reqwest', patterns: [/\breqwest\b/i] },
51
+ { name: 'serde', patterns: [/\bserde\b/i] },
52
+ { name: 'mio', patterns: [/\bmio\b/i] },
53
+ { name: 'pelite', patterns: [/\bpelite\b/i] },
54
+ { name: 'object', patterns: [/\bobject\b/i] },
55
+ { name: 'winapi', patterns: [/\bwinapi\b/i] },
56
+ { name: 'ntapi', patterns: [/\bntapi\b/i] },
57
+ { name: 'windows-sys', patterns: [/\bwindows[-_]?sys\b/i] },
58
+ ];
59
+ // ============================================================================
60
+ // Input/Output Schemas
61
+ // ============================================================================
62
+ /**
63
+ * Input schema for triage workflow
64
+ * Requirements: 15.1
65
+ */
66
+ export const TriageWorkflowInputSchema = z.object({
67
+ sample_id: z.string().describe('Sample ID (format: sha256:<hex>)'),
68
+ force_refresh: z
69
+ .boolean()
70
+ .optional()
71
+ .default(false)
72
+ .describe('Bypass cache in dependent static tools'),
73
+ });
74
+ /**
75
+ * IOC (Indicators of Compromise) structure
76
+ * Requirements: 15.2, 15.5
77
+ */
78
+ const IOCSchema = z.object({
79
+ suspicious_imports: z.array(z.string()).describe('Suspicious imported functions'),
80
+ suspicious_strings: z.array(z.string()).describe('Suspicious strings found'),
81
+ yara_matches: z.array(z.string()).describe('YARA rule matches'),
82
+ yara_low_confidence: z.array(z.string()).optional().describe('YARA matches downgraded due to weak evidence'),
83
+ urls: z.array(z.string()).optional().describe('URLs found in strings'),
84
+ ip_addresses: z.array(z.string()).optional().describe('IP addresses found'),
85
+ file_paths: z.array(z.string()).optional().describe('File paths found'),
86
+ registry_keys: z.array(z.string()).optional().describe('Registry keys found'),
87
+ high_value_iocs: z
88
+ .object({
89
+ suspicious_apis: z.array(z.string()).optional(),
90
+ commands: z.array(z.string()).optional(),
91
+ pipes: z.array(z.string()).optional(),
92
+ urls: z.array(z.string()).optional(),
93
+ network: z.array(z.string()).optional(),
94
+ })
95
+ .optional()
96
+ .describe('Layered high-value IOC view'),
97
+ compiler_artifacts: z
98
+ .object({
99
+ cargo_paths: z.array(z.string()).optional(),
100
+ rust_markers: z.array(z.string()).optional(),
101
+ library_profile: LibraryProfileSchema.optional(),
102
+ })
103
+ .optional()
104
+ .describe('Build/toolchain breadcrumbs separated from high-risk IOC signals'),
105
+ });
106
+ const IntentAssessmentSchema = z.object({
107
+ label: z.enum(['dual_use_tool', 'operator_utility', 'malware_like_payload', 'unknown']),
108
+ confidence: z.number().min(0).max(1),
109
+ evidence: z.array(z.string()),
110
+ counter_evidence: z.array(z.string()),
111
+ });
112
+ const ToolingAssessmentSchema = z.object({
113
+ help_text_detected: z.boolean(),
114
+ cli_surface_detected: z.boolean(),
115
+ framework_hints: z.array(z.string()),
116
+ toolchain_markers: z.array(z.string()),
117
+ library_profile: LibraryProfileSchema.optional(),
118
+ });
119
+ /**
120
+ * Output schema for triage workflow
121
+ * Requirements: 15.2, 15.4, 15.5
122
+ */
123
+ export const TriageWorkflowOutputSchema = z.object({
124
+ ok: z.boolean(),
125
+ data: z.object({
126
+ summary: z.string().describe('Natural language summary of the analysis'),
127
+ confidence: z.number().min(0).max(1).describe('Confidence score (0-1)'),
128
+ threat_level: z.enum(['clean', 'suspicious', 'malicious', 'unknown']).describe('Assessed threat level'),
129
+ iocs: IOCSchema.describe('Indicators of Compromise'),
130
+ evidence: z.array(z.string()).describe('Evidence supporting the assessment'),
131
+ evidence_weights: z
132
+ .object({
133
+ import: z.number().min(0).max(1),
134
+ string: z.number().min(0).max(1),
135
+ runtime: z.number().min(0).max(1),
136
+ })
137
+ .describe('Relative evidence contribution weights for this conclusion'),
138
+ inference: z
139
+ .object({
140
+ classification: z.enum(['benign', 'suspicious', 'malicious', 'unknown']),
141
+ hypotheses: z.array(z.string()),
142
+ false_positive_risks: z.array(z.string()),
143
+ intent_assessment: IntentAssessmentSchema.optional(),
144
+ tooling_assessment: ToolingAssessmentSchema.optional(),
145
+ })
146
+ .optional()
147
+ .describe('Inference layer derived from evidence, separated for auditability'),
148
+ recommendation: z.string().describe('Recommended next steps'),
149
+ raw_results: z.object({
150
+ fingerprint: z.any().optional(),
151
+ runtime: z.any().optional(),
152
+ imports: z.any().optional(),
153
+ strings: z.any().optional(),
154
+ yara: z.any().optional(),
155
+ }).describe('Raw results from individual tools'),
156
+ }).optional(),
157
+ warnings: z.array(z.string()).optional(),
158
+ errors: z.array(z.string()).optional(),
159
+ metrics: z.object({
160
+ elapsed_ms: z.number(),
161
+ tool: z.string(),
162
+ }).optional(),
163
+ });
164
+ // ============================================================================
165
+ // Tool Definition
166
+ // ============================================================================
167
+ /**
168
+ * Tool definition for triage workflow
169
+ */
170
+ export const triageWorkflowToolDefinition = {
171
+ name: TOOL_NAME,
172
+ description: '快速画像工作流:在 5 分钟内完成基础威胁评估,包括 PE 指纹、运行时检测、导入表分析、字符串提取和 YARA 扫描',
173
+ inputSchema: TriageWorkflowInputSchema,
174
+ outputSchema: TriageWorkflowOutputSchema,
175
+ };
176
+ // ============================================================================
177
+ // Helper Functions
178
+ // ============================================================================
179
+ /**
180
+ * Analyze imports for suspicious APIs
181
+ * Requirements: 15.5
182
+ */
183
+ function analyzeSuspiciousImports(imports) {
184
+ const suspicious = [];
185
+ try {
186
+ for (const [dll, functions] of Object.entries(imports)) {
187
+ // Ensure functions is an array
188
+ if (!Array.isArray(functions)) {
189
+ continue;
190
+ }
191
+ for (const func of functions) {
192
+ if (typeof func === 'string' && SUSPICIOUS_APIS.some(api => func.toLowerCase().includes(api.toLowerCase()))) {
193
+ suspicious.push(`${dll}!${func}`);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ catch (error) {
199
+ // Silently handle errors to prevent workflow failure
200
+ console.error('Error analyzing suspicious imports:', error);
201
+ }
202
+ return suspicious;
203
+ }
204
+ /**
205
+ * Analyze strings for suspicious patterns
206
+ * Requirements: 15.5
207
+ */
208
+ function normalizeStringEntry(entry) {
209
+ if (typeof entry === 'string') {
210
+ return entry;
211
+ }
212
+ if (entry &&
213
+ typeof entry === 'object' &&
214
+ 'string' in entry &&
215
+ typeof entry.string === 'string') {
216
+ return entry.string;
217
+ }
218
+ return null;
219
+ }
220
+ export function extractCrateNameFromCargoPath(input) {
221
+ const normalized = input.replace(/\//g, '\\');
222
+ const match = normalized.match(/cargo\\(?:registry\\src|git\\checkouts)\\[^\\]+\\([^\\]+)(?:\\|$)/i);
223
+ if (!match?.[1]) {
224
+ return null;
225
+ }
226
+ const rawCrate = match[1].trim();
227
+ if (!rawCrate) {
228
+ return null;
229
+ }
230
+ const versionMatch = rawCrate.match(/^(.*)-\d[\w.+-]*$/);
231
+ const crateName = versionMatch?.[1] || rawCrate;
232
+ return crateName.trim() || null;
233
+ }
234
+ function detectLibraryHints(str) {
235
+ return NOTABLE_LIBRARY_HINTS
236
+ .filter((hint) => hint.patterns.some((pattern) => pattern.test(str)))
237
+ .map((hint) => hint.name);
238
+ }
239
+ function analyzeSuspiciousStrings(strings) {
240
+ const suspicious = [];
241
+ const urls = [];
242
+ const ips = [];
243
+ const paths = [];
244
+ const registry = [];
245
+ const commands = [];
246
+ const pipes = [];
247
+ const cargoPaths = [];
248
+ const rustMarkers = [];
249
+ const crateNames = [];
250
+ const libraryHints = [];
251
+ for (const rawEntry of strings) {
252
+ const str = normalizeStringEntry(rawEntry);
253
+ if (!str) {
254
+ continue;
255
+ }
256
+ // Check for URLs
257
+ const urlMatch = str.match(/https?:\/\/[^\s]+/i);
258
+ if (urlMatch) {
259
+ urls.push(urlMatch[0]);
260
+ suspicious.push(str);
261
+ }
262
+ // Check for IP addresses
263
+ const ipMatch = str.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
264
+ if (ipMatch) {
265
+ ips.push(ipMatch[0]);
266
+ suspicious.push(str);
267
+ }
268
+ // Check for file paths
269
+ const pathMatch = str.match(/[A-Za-z]:\\[^\s]+/);
270
+ if (pathMatch) {
271
+ paths.push(pathMatch[0]);
272
+ if (pathMatch[0].toLowerCase().includes('temp') ||
273
+ pathMatch[0].toLowerCase().includes('appdata')) {
274
+ suspicious.push(str);
275
+ }
276
+ }
277
+ // Check for registry keys
278
+ const regMatch = str.match(/HKEY_[A-Z_]+\\[^\s]+/i);
279
+ if (regMatch) {
280
+ registry.push(regMatch[0]);
281
+ suspicious.push(str);
282
+ }
283
+ // Check for shell executables
284
+ if (/cmd\.exe|powershell\.exe|wscript\.exe/i.test(str)) {
285
+ suspicious.push(str);
286
+ commands.push(str);
287
+ }
288
+ // Check for named pipes / IPC
289
+ const pipeMatch = str.match(/\\\\\.\\pipe\\[^\s]+|\\\\pipe\\[^\s]+/i);
290
+ if (pipeMatch) {
291
+ pipes.push(pipeMatch[0]);
292
+ suspicious.push(str);
293
+ }
294
+ // Split compiler/toolchain artifacts from high-value IOC
295
+ const cargoMatch = str.match(/cargo\\registry\\src\\[^\s]+/i);
296
+ if (cargoMatch) {
297
+ cargoPaths.push(cargoMatch[0]);
298
+ const crateName = extractCrateNameFromCargoPath(cargoMatch[0]);
299
+ if (crateName) {
300
+ crateNames.push(crateName);
301
+ }
302
+ }
303
+ if (/rust_panic|core::panicking|\\src\\main\.rs|\\src\\lib\.rs/i.test(str)) {
304
+ rustMarkers.push(str);
305
+ }
306
+ libraryHints.push(...detectLibraryHints(str));
307
+ }
308
+ return {
309
+ suspicious: [...new Set(suspicious)], // Remove duplicates
310
+ urls: [...new Set(urls)],
311
+ ips: [...new Set(ips)],
312
+ paths: [...new Set(paths)],
313
+ registry: [...new Set(registry)],
314
+ commands: [...new Set(commands)],
315
+ pipes: [...new Set(pipes)],
316
+ cargoPaths: [...new Set(cargoPaths)],
317
+ rustMarkers: [...new Set(rustMarkers)],
318
+ crateNames: [...new Set(crateNames)],
319
+ libraryHints: [...new Set(libraryHints)],
320
+ };
321
+ }
322
+ function normalizeEcosystemLabel(value) {
323
+ const lowered = value.toLowerCase();
324
+ if (lowered.includes('rust')) {
325
+ return 'rust';
326
+ }
327
+ if (lowered.includes('dotnet') || lowered.includes('.net') || lowered.includes('clr')) {
328
+ return '.net';
329
+ }
330
+ if (lowered.includes('go')) {
331
+ return 'go';
332
+ }
333
+ if (lowered.includes('native') || lowered.includes('c++') || lowered.includes('pe')) {
334
+ return 'native';
335
+ }
336
+ return null;
337
+ }
338
+ export function buildLibraryProfile(stringAnalysis, runtime) {
339
+ const ecosystems = new Set();
340
+ const crateCounts = new Map();
341
+ for (const runtimeHint of Array.isArray(runtime?.suspected) ? runtime.suspected : []) {
342
+ const runtimeName = typeof runtimeHint?.runtime === 'string' ? runtimeHint.runtime : '';
343
+ const ecosystem = normalizeEcosystemLabel(runtimeName);
344
+ if (ecosystem) {
345
+ ecosystems.add(ecosystem);
346
+ }
347
+ }
348
+ if (stringAnalysis.cargoPaths.length > 0 ||
349
+ stringAnalysis.rustMarkers.length > 0 ||
350
+ stringAnalysis.crateNames.length > 0) {
351
+ ecosystems.add('rust');
352
+ }
353
+ for (const crateName of [...stringAnalysis.crateNames, ...stringAnalysis.libraryHints]) {
354
+ const normalized = crateName.trim().toLowerCase();
355
+ if (!normalized) {
356
+ continue;
357
+ }
358
+ crateCounts.set(normalized, (crateCounts.get(normalized) || 0) + 1);
359
+ }
360
+ const rankedCrates = Array.from(crateCounts.entries())
361
+ .sort((left, right) => {
362
+ if (right[1] !== left[1]) {
363
+ return right[1] - left[1];
364
+ }
365
+ return left[0].localeCompare(right[0]);
366
+ })
367
+ .map(([crate]) => crate);
368
+ const topCrates = rankedCrates.slice(0, 8);
369
+ const notableLibraries = Array.from(new Set(rankedCrates.filter((crate) => NOTABLE_LIBRARY_HINTS.some((hint) => hint.name.toLowerCase() === crate)))).slice(0, 8);
370
+ const evidence = [];
371
+ if (topCrates.length > 0) {
372
+ evidence.push(`Cargo/library references observed: ${topCrates.slice(0, 5).join(', ')}`);
373
+ }
374
+ if (stringAnalysis.cargoPaths.length > 0) {
375
+ evidence.push(`Cargo registry paths observed: ${stringAnalysis.cargoPaths.slice(0, 2).join(' | ')}`);
376
+ }
377
+ if (stringAnalysis.rustMarkers.length > 0) {
378
+ evidence.push(`Rust toolchain markers observed: ${stringAnalysis.rustMarkers.slice(0, 2).join(' | ')}`);
379
+ }
380
+ if (ecosystems.size > 0) {
381
+ evidence.push(`Ecosystem hints: ${Array.from(ecosystems).join(', ')}`);
382
+ }
383
+ if (ecosystems.size === 0 && topCrates.length === 0 && notableLibraries.length === 0) {
384
+ return undefined;
385
+ }
386
+ return {
387
+ ecosystems: Array.from(ecosystems),
388
+ top_crates: topCrates,
389
+ notable_libraries: notableLibraries,
390
+ evidence: Array.from(new Set(evidence)),
391
+ };
392
+ }
393
+ function summarizeLibraryProfile(profile) {
394
+ if (!profile) {
395
+ return '';
396
+ }
397
+ const libraries = profile.notable_libraries.length > 0
398
+ ? profile.notable_libraries
399
+ : profile.top_crates;
400
+ if (libraries.length === 0) {
401
+ return profile.ecosystems.join(', ');
402
+ }
403
+ return libraries.slice(0, 3).join(' + ');
404
+ }
405
+ function downgradeYaraLevel(level) {
406
+ if (level === 'high') {
407
+ return 'medium';
408
+ }
409
+ if (level === 'medium') {
410
+ return 'low';
411
+ }
412
+ return 'low';
413
+ }
414
+ function normalizeYaraSignals(matches) {
415
+ if (!Array.isArray(matches)) {
416
+ return [];
417
+ }
418
+ return matches
419
+ .map((match) => {
420
+ const rule = typeof match?.rule === 'string'
421
+ ? String(match.rule)
422
+ : '';
423
+ if (!rule) {
424
+ return null;
425
+ }
426
+ const levelRaw = typeof match?.confidence?.level === 'string'
427
+ ? String(match.confidence?.level).toLowerCase()
428
+ : 'unknown';
429
+ const level = levelRaw === 'high' || levelRaw === 'medium' || levelRaw === 'low'
430
+ ? levelRaw
431
+ : 'unknown';
432
+ const numericScore = Number(match?.confidence?.score || 0);
433
+ const stringOnly = Boolean(match?.evidence?.string_only);
434
+ const loweredRule = rule.toLowerCase();
435
+ return {
436
+ rule,
437
+ level,
438
+ score: Number.isFinite(numericScore) ? numericScore : 0,
439
+ stringOnly,
440
+ generic: loweredRule.includes('generic') ||
441
+ (loweredRule.includes('trojan') && !loweredRule.includes('downloader') && !loweredRule.includes('backdoor')),
442
+ };
443
+ })
444
+ .filter((item) => Boolean(item));
445
+ }
446
+ function assessIntentAndTooling(stringsSummary, suspiciousImports, stringAnalysis, yaraSignals, runtime) {
447
+ const contextWindows = Array.isArray(stringsSummary?.context_windows)
448
+ ? stringsSummary.context_windows
449
+ : [];
450
+ const windowTexts = contextWindows.map((window) => Array.isArray(window?.strings)
451
+ ? window.strings
452
+ .map((entry) => String(entry?.string || ''))
453
+ .filter((item) => item.length > 0)
454
+ .join('\n')
455
+ : '');
456
+ const joinedWindows = windowTexts.join('\n').toLowerCase();
457
+ const helpTextDetected = /usage:|options?:|examples?:|--help\b|-h\b|commands?:|syntax:/.test(joinedWindows);
458
+ const cliSurfaceDetected = /--[a-z0-9_-]+|-[a-z0-9]\b/.test(joinedWindows) ||
459
+ /\b(pid|process|thread|target|list|inject|suspend|resume|kill|dump)\b/.test(joinedWindows);
460
+ const importApis = suspiciousImports.map((item) => (item.split('!').pop() || item).toLowerCase());
461
+ const processOpsCount = importApis.filter((api) => [
462
+ 'openprocess',
463
+ 'writeprocessmemory',
464
+ 'createremotethread',
465
+ 'virtualallocex',
466
+ 'suspendthread',
467
+ 'resumethread',
468
+ 'terminatethread',
469
+ 'terminateprocess',
470
+ ].some((needle) => api.includes(needle))).length;
471
+ const malwareSpecificYara = yaraSignals.some((signal) => /ransomware|backdoor|downloader|keylogger/.test(signal.rule.toLowerCase()) &&
472
+ signal.level !== 'low');
473
+ const networkBehavior = stringAnalysis.urls.length > 0 || stringAnalysis.ips.length > 0;
474
+ const persistenceBehavior = stringAnalysis.registry.length > 0;
475
+ const suspectedRuntimes = Array.isArray(runtime?.suspected)
476
+ ? runtime.suspected
477
+ .map((item) => String(item?.runtime || '').trim())
478
+ .filter((item) => item.length > 0)
479
+ : [];
480
+ const toolchainMarkers = [
481
+ ...(Array.isArray(runtime?.suspected)
482
+ ? runtime.suspected
483
+ .map((item) => `${String(item?.runtime || '').trim()}${typeof item?.confidence === 'number' ? `(${item.confidence.toFixed(2)})` : ''}`)
484
+ .filter((item) => item.length > 0)
485
+ : []),
486
+ ...stringAnalysis.cargoPaths.slice(0, 5),
487
+ ...stringAnalysis.rustMarkers.slice(0, 5),
488
+ ];
489
+ const libraryProfile = buildLibraryProfile(stringAnalysis, runtime);
490
+ const frameworkHints = Array.from(new Set([
491
+ ...suspectedRuntimes,
492
+ ...(libraryProfile?.ecosystems || []),
493
+ ...(libraryProfile?.notable_libraries.slice(0, 3) || []),
494
+ ]));
495
+ if (helpTextDetected &&
496
+ cliSurfaceDetected &&
497
+ processOpsCount > 0 &&
498
+ !malwareSpecificYara &&
499
+ !networkBehavior &&
500
+ !persistenceBehavior) {
501
+ return {
502
+ intent: {
503
+ label: 'dual_use_tool',
504
+ confidence: 0.78,
505
+ evidence: [
506
+ 'Long-form help/usage text grouped in nearby string windows.',
507
+ 'CLI-style options and operator verbs are present.',
508
+ 'Process-operation APIs are present without stronger malware-specific corroboration.',
509
+ ],
510
+ counter_evidence: [
511
+ 'Static evidence alone cannot rule out malicious repurposing of the tool.',
512
+ ],
513
+ },
514
+ tooling: {
515
+ help_text_detected: helpTextDetected,
516
+ cli_surface_detected: cliSurfaceDetected,
517
+ framework_hints: frameworkHints,
518
+ toolchain_markers: toolchainMarkers.slice(0, 10),
519
+ library_profile: libraryProfile,
520
+ },
521
+ };
522
+ }
523
+ if (helpTextDetected || cliSurfaceDetected) {
524
+ return {
525
+ intent: {
526
+ label: 'operator_utility',
527
+ confidence: 0.62,
528
+ evidence: [
529
+ 'Operator-facing help or CLI surface detected in grouped string windows.',
530
+ ],
531
+ counter_evidence: malwareSpecificYara
532
+ ? ['Malware-specific YARA evidence is also present; treat as suspicious until validated.']
533
+ : [],
534
+ },
535
+ tooling: {
536
+ help_text_detected: helpTextDetected,
537
+ cli_surface_detected: cliSurfaceDetected,
538
+ framework_hints: frameworkHints,
539
+ toolchain_markers: toolchainMarkers.slice(0, 10),
540
+ library_profile: libraryProfile,
541
+ },
542
+ };
543
+ }
544
+ if (malwareSpecificYara) {
545
+ return {
546
+ intent: {
547
+ label: 'malware_like_payload',
548
+ confidence: 0.74,
549
+ evidence: ['Malware-family-like YARA evidence is present.'],
550
+ counter_evidence: [],
551
+ },
552
+ tooling: {
553
+ help_text_detected: helpTextDetected,
554
+ cli_surface_detected: cliSurfaceDetected,
555
+ framework_hints: frameworkHints,
556
+ toolchain_markers: toolchainMarkers.slice(0, 10),
557
+ library_profile: libraryProfile,
558
+ },
559
+ };
560
+ }
561
+ return {
562
+ intent: {
563
+ label: 'unknown',
564
+ confidence: 0.35,
565
+ evidence: [],
566
+ counter_evidence: [],
567
+ },
568
+ tooling: {
569
+ help_text_detected: helpTextDetected,
570
+ cli_surface_detected: cliSurfaceDetected,
571
+ framework_hints: frameworkHints,
572
+ toolchain_markers: toolchainMarkers.slice(0, 10),
573
+ library_profile: libraryProfile,
574
+ },
575
+ };
576
+ }
577
+ export function applyIntentAwareYaraAdjustments(yaraSignals, intentAssessment) {
578
+ if (!Array.isArray(yaraSignals) ||
579
+ yaraSignals.length === 0 ||
580
+ !['dual_use_tool', 'operator_utility'].includes(intentAssessment.label) ||
581
+ intentAssessment.confidence < 0.55) {
582
+ return yaraSignals;
583
+ }
584
+ return yaraSignals.map((signal) => {
585
+ if (!signal.generic || hasMalwareSpecificSignal(signal)) {
586
+ return signal;
587
+ }
588
+ const dualUse = intentAssessment.label === 'dual_use_tool';
589
+ const adjustedLevel = dualUse ? 'low' : downgradeYaraLevel(signal.level);
590
+ const adjustedScore = Number((Math.max(0, signal.score) * (dualUse ? 0.45 : 0.7)).toFixed(2));
591
+ return {
592
+ ...signal,
593
+ level: adjustedLevel,
594
+ score: adjustedScore,
595
+ };
596
+ });
597
+ }
598
+ /**
599
+ * Calculate threat level based on IOCs
600
+ * Requirements: 15.2, 15.4
601
+ */
602
+ function calculateThreatLevel(yaraMatches, suspiciousImports, suspiciousStrings, lowConfidenceYaraCount = 0) {
603
+ let score = 0;
604
+ let maxScore = 0;
605
+ // YARA matches (highest weight)
606
+ maxScore += 50;
607
+ if (yaraMatches.length > 0) {
608
+ // Check for malware family matches
609
+ const hasMalwareMatch = yaraMatches.some(rule => rule.toLowerCase().includes('trojan') ||
610
+ rule.toLowerCase().includes('ransomware') ||
611
+ rule.toLowerCase().includes('backdoor') ||
612
+ rule.toLowerCase().includes('malware'));
613
+ if (hasMalwareMatch) {
614
+ score += 50;
615
+ }
616
+ else {
617
+ // Packer or other matches
618
+ score += 20;
619
+ }
620
+ }
621
+ else if (lowConfidenceYaraCount > 0) {
622
+ // Weak YARA-only signal: keep as low weight to reduce false positives.
623
+ score += Math.min(lowConfidenceYaraCount * 2, 6);
624
+ }
625
+ // Suspicious imports (context-aware weighting to reduce false positives)
626
+ maxScore += 30;
627
+ if (suspiciousImports.length > 0) {
628
+ const importApis = suspiciousImports
629
+ .map((item) => item.split('!').pop() || item)
630
+ .map((item) => item.toLowerCase());
631
+ const highRiskCount = importApis.filter((name) => HIGH_RISK_APIS.some((api) => name.includes(api.toLowerCase()))).length;
632
+ const contextDependentCount = importApis.filter((name) => CONTEXT_DEPENDENT_APIS.some((api) => name.includes(api.toLowerCase()))).length;
633
+ let importScore = 0;
634
+ // High-risk primitives (injection/hooking) are strong signals.
635
+ importScore += Math.min(highRiskCount * 8, 22);
636
+ // Context-dependent APIs are weaker alone (debuggers/installers use them too).
637
+ importScore += Math.min(contextDependentCount * 2, 8);
638
+ const hasWriteProcessMemory = importApis.some((name) => name.includes('writeprocessmemory'));
639
+ const hasCreateRemoteThread = importApis.some((name) => name.includes('createremotethread'));
640
+ const hasVirtualAllocEx = importApis.some((name) => name.includes('virtualallocex'));
641
+ if (hasWriteProcessMemory && (hasCreateRemoteThread || hasVirtualAllocEx)) {
642
+ importScore += 6;
643
+ }
644
+ score += Math.min(importScore, 30);
645
+ }
646
+ // Suspicious strings (lower weight)
647
+ maxScore += 20;
648
+ if (suspiciousStrings.length > 0) {
649
+ score += Math.min(suspiciousStrings.length * 2, 20);
650
+ }
651
+ // Calculate confidence
652
+ const confidence = maxScore > 0 ? score / maxScore : 0;
653
+ // Determine threat level
654
+ let level;
655
+ if (score >= 40) {
656
+ level = 'malicious';
657
+ }
658
+ else if (score >= 15) {
659
+ level = 'suspicious';
660
+ }
661
+ else if (score > 0) {
662
+ level = 'suspicious';
663
+ }
664
+ else {
665
+ level = 'clean';
666
+ }
667
+ return { level, confidence };
668
+ }
669
+ /**
670
+ * Generate evidence list
671
+ * Requirements: 15.2
672
+ */
673
+ function generateEvidence(yaraMatches, suspiciousImports, suspiciousStrings, runtime) {
674
+ const evidence = [];
675
+ if (yaraMatches.length > 0) {
676
+ evidence.push(`YARA 规则匹配: ${yaraMatches.join(', ')}`);
677
+ }
678
+ if (suspiciousImports.length > 0) {
679
+ evidence.push(`检测到 ${suspiciousImports.length} 个可疑导入函数`);
680
+ if (suspiciousImports.length <= 5) {
681
+ evidence.push(`可疑导入: ${suspiciousImports.join(', ')}`);
682
+ }
683
+ }
684
+ if (suspiciousStrings.length > 0) {
685
+ evidence.push(`检测到 ${suspiciousStrings.length} 个可疑字符串`);
686
+ }
687
+ if (runtime?.is_dotnet) {
688
+ evidence.push(`.NET 程序 (${runtime.dotnet_version || 'unknown version'})`);
689
+ }
690
+ if (runtime?.suspected && runtime.suspected.length > 0) {
691
+ const runtimes = runtime.suspected.map((s) => s.runtime).join(', ');
692
+ evidence.push(`检测到运行时: ${runtimes}`);
693
+ }
694
+ return evidence;
695
+ }
696
+ /**
697
+ * Generate summary and recommendation
698
+ * Requirements: 15.2
699
+ */
700
+ function generateSummaryAndRecommendation(threatLevel, yaraMatches, runtime) {
701
+ let summary = '';
702
+ let recommendation = '';
703
+ // Generate summary based on threat level
704
+ if (threatLevel === 'malicious') {
705
+ const malwareTypes = yaraMatches
706
+ .filter(rule => rule.toLowerCase().includes('trojan') ||
707
+ rule.toLowerCase().includes('ransomware') ||
708
+ rule.toLowerCase().includes('backdoor'))
709
+ .map(rule => rule.split('_')[0]);
710
+ if (malwareTypes.length > 0) {
711
+ summary = `检测到恶意软件: ${malwareTypes.join(', ')}`;
712
+ }
713
+ else {
714
+ summary = '检测到高度可疑的恶意行为特征';
715
+ }
716
+ recommendation = '强烈建议在隔离环境中进行深度分析,不要在生产环境执行此文件';
717
+ }
718
+ else if (threatLevel === 'suspicious') {
719
+ const packerMatches = yaraMatches.filter(rule => rule.toLowerCase().includes('upx') ||
720
+ rule.toLowerCase().includes('packer') ||
721
+ rule.toLowerCase().includes('themida') ||
722
+ rule.toLowerCase().includes('vmprotect'));
723
+ if (packerMatches.length > 0) {
724
+ summary = `检测到加壳器: ${packerMatches.join(', ')}`;
725
+ recommendation = '建议进行脱壳分析或深度静态分析以了解真实行为';
726
+ }
727
+ else {
728
+ summary = '检测到可疑行为特征,需要进一步分析';
729
+ recommendation = '建议进行深度静态分析或在隔离环境中进行动态分析';
730
+ }
731
+ }
732
+ else if (threatLevel === 'clean') {
733
+ summary = '未检测到明显的恶意行为特征';
734
+ recommendation = '样本看起来相对安全,但建议根据具体使用场景进行进一步验证';
735
+ }
736
+ else {
737
+ summary = '无法确定威胁等级,需要更多信息';
738
+ recommendation = '建议进行深度静态分析以获取更多信息';
739
+ }
740
+ // Add runtime info to summary
741
+ if (runtime?.is_dotnet) {
742
+ summary += ` (.NET 程序)`;
743
+ }
744
+ return { summary, recommendation };
745
+ }
746
+ function calculateEvidenceWeights(suspiciousImports, suspiciousStrings, runtime, yaraMatches, yaraLowConfidenceMatches) {
747
+ let importWeight = Math.min(0.9, suspiciousImports.length * 0.06);
748
+ let stringWeight = Math.min(0.8, suspiciousStrings.length * 0.03) +
749
+ Math.min(0.35, yaraMatches.length * 0.09 + yaraLowConfidenceMatches.length * 0.03);
750
+ let runtimeWeight = 0.05;
751
+ if (runtime?.is_dotnet) {
752
+ runtimeWeight += 0.25;
753
+ }
754
+ if (Array.isArray(runtime?.suspected) && runtime.suspected.length > 0) {
755
+ const topConfidence = Number(runtime.suspected[0]?.confidence || 0);
756
+ runtimeWeight += Math.min(0.45, Math.max(0, topConfidence) * 0.5);
757
+ }
758
+ const total = importWeight + stringWeight + runtimeWeight;
759
+ if (total <= 0) {
760
+ return { import: 0.34, string: 0.33, runtime: 0.33 };
761
+ }
762
+ return {
763
+ import: Number((importWeight / total).toFixed(2)),
764
+ string: Number((stringWeight / total).toFixed(2)),
765
+ runtime: Number((runtimeWeight / total).toFixed(2)),
766
+ };
767
+ }
768
+ function buildInferenceLayer(threatLevel, yaraMatches, yaraLowConfidenceMatches, suspiciousImports, suspiciousStrings) {
769
+ const hypotheses = [];
770
+ const falsePositiveRisks = [];
771
+ if (yaraMatches.length > 0) {
772
+ hypotheses.push(`YARA medium/high confidence match: ${yaraMatches.slice(0, 5).join(', ')}`);
773
+ }
774
+ if (yaraLowConfidenceMatches.length > 0) {
775
+ hypotheses.push(`YARA low-confidence hints: ${yaraLowConfidenceMatches.slice(0, 5).join(', ')}`);
776
+ falsePositiveRisks.push('Low-confidence YARA hits may be string overlap without strong import/API corroboration.');
777
+ }
778
+ if (suspiciousImports.length > 0) {
779
+ hypotheses.push(`Suspicious API imports observed: ${Math.min(suspiciousImports.length, 10)}`);
780
+ const hasOnlyContextDependentAPIs = suspiciousImports.every((item) => CONTEXT_DEPENDENT_APIS.some((api) => (item.split('!').pop() || item).toLowerCase().includes(api.toLowerCase()))) && suspiciousImports.length > 0;
781
+ if (hasOnlyContextDependentAPIs) {
782
+ falsePositiveRisks.push('Import evidence is mostly context-dependent APIs (debuggers/installers may also use them).');
783
+ }
784
+ }
785
+ if (suspiciousStrings.length > 0) {
786
+ hypotheses.push(`Behavior-related strings observed: ${Math.min(suspiciousStrings.length, 20)}`);
787
+ }
788
+ let classification = 'unknown';
789
+ if (threatLevel === 'clean') {
790
+ classification = 'benign';
791
+ }
792
+ else if (threatLevel === 'malicious') {
793
+ classification = 'malicious';
794
+ }
795
+ else if (threatLevel === 'suspicious') {
796
+ classification = 'suspicious';
797
+ }
798
+ if (hypotheses.length === 0) {
799
+ hypotheses.push('Insufficient evidence to build high-confidence behavioral inference.');
800
+ }
801
+ return {
802
+ classification,
803
+ hypotheses,
804
+ false_positive_risks: falsePositiveRisks,
805
+ };
806
+ }
807
+ void [
808
+ calculateThreatLevel,
809
+ generateEvidence,
810
+ generateSummaryAndRecommendation,
811
+ calculateEvidenceWeights,
812
+ buildInferenceLayer,
813
+ ];
814
+ function hasMalwareSpecificSignal(signal) {
815
+ return /ransomware|backdoor|downloader|keylogger|stealer|rat|loader/.test(signal.rule.toLowerCase());
816
+ }
817
+ export function calculateThreatLevelV2(yaraSignals, suspiciousImports, suspiciousStrings, intentAssessment) {
818
+ let score = 0;
819
+ const maxScore = 90;
820
+ let yaraScore = 0;
821
+ for (const signal of yaraSignals) {
822
+ const levelWeight = signal.level === 'high'
823
+ ? 16
824
+ : signal.level === 'medium'
825
+ ? 10
826
+ : signal.level === 'low'
827
+ ? 4
828
+ : 6;
829
+ let signalScore = levelWeight + Math.min(4, Math.max(0, signal.score) * 3);
830
+ if (signal.stringOnly) {
831
+ signalScore *= 0.35;
832
+ }
833
+ if (signal.generic) {
834
+ signalScore *= 0.55;
835
+ }
836
+ if ((intentAssessment.label === 'dual_use_tool' ||
837
+ intentAssessment.label === 'operator_utility') &&
838
+ signal.generic) {
839
+ signalScore *= 0.55;
840
+ }
841
+ if (intentAssessment.label === 'dual_use_tool' &&
842
+ signal.stringOnly &&
843
+ !hasMalwareSpecificSignal(signal)) {
844
+ signalScore *= 0.7;
845
+ }
846
+ yaraScore += signalScore;
847
+ }
848
+ score += Math.min(36, yaraScore);
849
+ if (suspiciousImports.length > 0) {
850
+ const importApis = suspiciousImports
851
+ .map((item) => item.split('!').pop() || item)
852
+ .map((item) => item.toLowerCase());
853
+ const highRiskCount = importApis.filter((name) => HIGH_RISK_APIS.some((api) => name.includes(api.toLowerCase()))).length;
854
+ const contextDependentCount = importApis.filter((name) => CONTEXT_DEPENDENT_APIS.some((api) => name.includes(api.toLowerCase()))).length;
855
+ const hasWriteProcessMemory = importApis.some((name) => name.includes('writeprocessmemory'));
856
+ const hasCreateRemoteThread = importApis.some((name) => name.includes('createremotethread'));
857
+ const hasVirtualAllocEx = importApis.some((name) => name.includes('virtualallocex'));
858
+ let importScore = Math.min(highRiskCount * 7, 22) + Math.min(contextDependentCount * 2, 8);
859
+ if (hasWriteProcessMemory && (hasCreateRemoteThread || hasVirtualAllocEx)) {
860
+ importScore += 6;
861
+ }
862
+ if (intentAssessment.label === 'dual_use_tool') {
863
+ importScore *= 0.8;
864
+ }
865
+ else if (intentAssessment.label === 'operator_utility') {
866
+ importScore *= 0.9;
867
+ }
868
+ score += Math.min(28, importScore);
869
+ }
870
+ let stringScore = Math.min(suspiciousStrings.length * 1.4, 18);
871
+ if (intentAssessment.label === 'dual_use_tool') {
872
+ stringScore *= 0.7;
873
+ }
874
+ else if (intentAssessment.label === 'operator_utility') {
875
+ stringScore *= 0.85;
876
+ }
877
+ score += Math.min(18, stringScore);
878
+ if (intentAssessment.label === 'malware_like_payload') {
879
+ score += 8;
880
+ }
881
+ else if (intentAssessment.label === 'dual_use_tool') {
882
+ score -= 4;
883
+ }
884
+ const boundedScore = Math.max(0, score);
885
+ const confidence = Number(Math.max(0, Math.min(1, boundedScore / maxScore)).toFixed(2));
886
+ const strongMalwareYara = yaraSignals.some((signal) => !signal.generic &&
887
+ !signal.stringOnly &&
888
+ hasMalwareSpecificSignal(signal) &&
889
+ signal.level !== 'low');
890
+ let level = 'clean';
891
+ if (strongMalwareYara && boundedScore >= 34 && intentAssessment.label !== 'dual_use_tool') {
892
+ level = 'malicious';
893
+ }
894
+ else if (boundedScore >= 44 && intentAssessment.label !== 'dual_use_tool') {
895
+ level = 'malicious';
896
+ }
897
+ else if (boundedScore >= 12) {
898
+ level = 'suspicious';
899
+ }
900
+ const hasMeaningfulStaticCapability = suspiciousImports.length > 0 || suspiciousStrings.length > 0 || yaraSignals.length > 0;
901
+ if (level === 'clean' &&
902
+ hasMeaningfulStaticCapability &&
903
+ (intentAssessment.label === 'dual_use_tool' || intentAssessment.label === 'operator_utility')) {
904
+ level = 'suspicious';
905
+ }
906
+ return { level, confidence };
907
+ }
908
+ function generateEvidenceV2(yaraSignals, suspiciousImports, suspiciousStrings, runtime, intentAssessment, toolingAssessment) {
909
+ const evidence = [];
910
+ const strongSignals = yaraSignals
911
+ .filter((signal) => signal.level !== 'low')
912
+ .map((signal) => `${signal.rule}${signal.stringOnly ? ' [string-only]' : ''}${signal.generic ? ' [generic]' : ''}`);
913
+ const downgradedSignals = yaraSignals
914
+ .filter((signal) => signal.level === 'low')
915
+ .map((signal) => signal.rule);
916
+ if (strongSignals.length > 0) {
917
+ evidence.push(`YARA medium/high confidence matches: ${strongSignals.slice(0, 6).join(', ')}`);
918
+ }
919
+ if (downgradedSignals.length > 0) {
920
+ evidence.push(`YARA low-confidence hints: ${downgradedSignals.slice(0, 6).join(', ')}`);
921
+ }
922
+ if (suspiciousImports.length > 0) {
923
+ evidence.push(`Suspicious imports observed: ${suspiciousImports.length}`);
924
+ if (suspiciousImports.length <= 5) {
925
+ evidence.push(`Import details: ${suspiciousImports.join(', ')}`);
926
+ }
927
+ }
928
+ if (suspiciousStrings.length > 0) {
929
+ evidence.push(`Behavior-related strings observed: ${suspiciousStrings.length}`);
930
+ }
931
+ if (toolingAssessment.help_text_detected || toolingAssessment.cli_surface_detected) {
932
+ evidence.push('Grouped strings indicate operator-facing help text or CLI options.');
933
+ }
934
+ if (toolingAssessment.library_profile) {
935
+ const librarySummary = summarizeLibraryProfile(toolingAssessment.library_profile);
936
+ if (librarySummary) {
937
+ evidence.push(`Library/crate profile: ${librarySummary}`);
938
+ }
939
+ evidence.push(...toolingAssessment.library_profile.evidence.slice(0, 2));
940
+ }
941
+ if (intentAssessment.evidence.length > 0) {
942
+ evidence.push(...intentAssessment.evidence.slice(0, 2));
943
+ }
944
+ if (runtime?.is_dotnet) {
945
+ evidence.push(`.NET program detected (${runtime.dotnet_version || 'unknown version'})`);
946
+ }
947
+ if (Array.isArray(runtime?.suspected) && runtime.suspected.length > 0) {
948
+ const runtimes = runtime.suspected
949
+ .map((item) => String(item?.runtime || '').trim())
950
+ .filter((item) => item.length > 0);
951
+ if (runtimes.length > 0) {
952
+ evidence.push(`Runtime hints: ${Array.from(new Set(runtimes)).join(', ')}`);
953
+ }
954
+ }
955
+ return Array.from(new Set(evidence));
956
+ }
957
+ function generateSummaryAndRecommendationV2(threatLevel, yaraSignals, runtime, intentAssessment, toolingAssessment) {
958
+ const strongRules = yaraSignals
959
+ .filter((signal) => signal.level !== 'low')
960
+ .map((signal) => signal.rule);
961
+ const packerMatches = strongRules.filter((rule) => /upx|packer|themida|vmprotect/i.test(rule));
962
+ const runtimeSuffix = runtime?.is_dotnet ? ' (.NET)' : '';
963
+ const librarySuffix = summarizeLibraryProfile(toolingAssessment.library_profile)
964
+ ? ` Tooling stack hints: ${summarizeLibraryProfile(toolingAssessment.library_profile)}.`
965
+ : '';
966
+ if (intentAssessment.label === 'dual_use_tool') {
967
+ return {
968
+ summary: 'Static evidence is more consistent with a dual-use operator utility than a pure malware payload.' +
969
+ runtimeSuffix +
970
+ librarySuffix,
971
+ recommendation: 'Validate provenance, operator workflow, and deployment context before labeling it malicious. ' +
972
+ 'Treat generic or string-only YARA hits as weak until dynamic or function-level evidence confirms abuse.',
973
+ };
974
+ }
975
+ if (intentAssessment.label === 'operator_utility') {
976
+ return {
977
+ summary: 'The sample exposes an operator-facing CLI/help surface and should be treated as a suspicious utility pending validation.' +
978
+ runtimeSuffix +
979
+ librarySuffix,
980
+ recommendation: 'Correlate with execution context, parent process, and any dropped artifacts before concluding malicious intent.',
981
+ };
982
+ }
983
+ if (threatLevel === 'malicious') {
984
+ const malwareRules = strongRules.filter((rule) => /trojan|ransomware|backdoor|loader|stealer/i.test(rule));
985
+ return {
986
+ summary: malwareRules.length > 0
987
+ ? `Static evidence aligns with malware-like behavior: ${malwareRules.slice(0, 4).join(', ')}${runtimeSuffix}${librarySuffix}`
988
+ : `Static evidence indicates a high-risk malicious payload${runtimeSuffix}${librarySuffix}`,
989
+ recommendation: 'Handle the sample in an isolated environment and collect dynamic evidence before any operational use.',
990
+ };
991
+ }
992
+ if (threatLevel === 'suspicious') {
993
+ if (packerMatches.length > 0) {
994
+ return {
995
+ summary: `Packed or protected traits detected: ${packerMatches.slice(0, 4).join(', ')}${runtimeSuffix}${librarySuffix}`,
996
+ recommendation: 'Unpack or deepen static analysis before making a final malware classification.',
997
+ };
998
+ }
999
+ return {
1000
+ summary: toolingAssessment.help_text_detected || toolingAssessment.cli_surface_detected
1001
+ ? `Suspicious capability set detected, but the sample also exposes an operator-facing surface${runtimeSuffix}${librarySuffix}`
1002
+ : `Suspicious static behavior detected${runtimeSuffix}${librarySuffix}`,
1003
+ recommendation: 'Escalate to deeper static analysis or controlled dynamic execution to resolve intent and capability.',
1004
+ };
1005
+ }
1006
+ if (threatLevel === 'clean') {
1007
+ return {
1008
+ summary: `No strong malicious indicators were confirmed from current static evidence${runtimeSuffix}${librarySuffix}`,
1009
+ recommendation: 'Retain the sample for context-aware review if provenance is unknown, but current evidence alone is weak.',
1010
+ };
1011
+ }
1012
+ return {
1013
+ summary: `Threat level could not be determined with current evidence${runtimeSuffix}${librarySuffix}`,
1014
+ recommendation: 'Collect additional static or dynamic evidence before drawing behavioral conclusions.',
1015
+ };
1016
+ }
1017
+ function calculateEvidenceWeightsV2(suspiciousImports, suspiciousStrings, runtime, yaraSignals, intentAssessment) {
1018
+ let importWeight = Math.min(0.9, suspiciousImports.length * 0.06);
1019
+ const strongYara = yaraSignals.filter((signal) => signal.level !== 'low').length;
1020
+ const weakYara = yaraSignals.length - strongYara;
1021
+ let stringWeight = Math.min(0.8, suspiciousStrings.length * 0.03) +
1022
+ Math.min(0.28, strongYara * 0.07 + weakYara * 0.02);
1023
+ let runtimeWeight = 0.05;
1024
+ if (runtime?.is_dotnet) {
1025
+ runtimeWeight += 0.25;
1026
+ }
1027
+ if (Array.isArray(runtime?.suspected) && runtime.suspected.length > 0) {
1028
+ const topConfidence = Number(runtime.suspected[0]?.confidence || 0);
1029
+ runtimeWeight += Math.min(0.45, Math.max(0, topConfidence) * 0.5);
1030
+ }
1031
+ if (intentAssessment.label === 'dual_use_tool') {
1032
+ stringWeight *= 0.85;
1033
+ }
1034
+ const total = importWeight + stringWeight + runtimeWeight;
1035
+ if (total <= 0) {
1036
+ return { import: 0.34, string: 0.33, runtime: 0.33 };
1037
+ }
1038
+ return {
1039
+ import: Number((importWeight / total).toFixed(2)),
1040
+ string: Number((stringWeight / total).toFixed(2)),
1041
+ runtime: Number((runtimeWeight / total).toFixed(2)),
1042
+ };
1043
+ }
1044
+ function buildInferenceLayerV2(threatLevel, yaraSignals, suspiciousImports, suspiciousStrings, intentAssessment, toolingAssessment) {
1045
+ const hypotheses = [];
1046
+ const falsePositiveRisks = [];
1047
+ const strongYara = yaraSignals.filter((signal) => signal.level !== 'low');
1048
+ const weakYara = yaraSignals.filter((signal) => signal.level === 'low');
1049
+ if (strongYara.length > 0) {
1050
+ hypotheses.push(`YARA medium/high confidence match: ${strongYara
1051
+ .map((signal) => signal.rule)
1052
+ .slice(0, 5)
1053
+ .join(', ')}`);
1054
+ }
1055
+ if (weakYara.length > 0) {
1056
+ hypotheses.push(`YARA low-confidence hints: ${weakYara
1057
+ .map((signal) => signal.rule)
1058
+ .slice(0, 5)
1059
+ .join(', ')}`);
1060
+ falsePositiveRisks.push('Low-confidence YARA hits may be string overlap without strong import/API corroboration.');
1061
+ }
1062
+ if (strongYara.some((signal) => signal.stringOnly)) {
1063
+ falsePositiveRisks.push('Some medium/high YARA hits remain string-heavy and should not be treated as execution proof.');
1064
+ }
1065
+ if (strongYara.some((signal) => signal.generic)) {
1066
+ falsePositiveRisks.push('Generic malware-family YARA matches can overlap with dual-use process tooling.');
1067
+ }
1068
+ if (suspiciousImports.length > 0) {
1069
+ hypotheses.push(`Suspicious API imports observed: ${Math.min(suspiciousImports.length, 10)}`);
1070
+ }
1071
+ if (suspiciousStrings.length > 0) {
1072
+ hypotheses.push(`Behavior-related strings observed: ${Math.min(suspiciousStrings.length, 20)}`);
1073
+ }
1074
+ if (intentAssessment.evidence.length > 0) {
1075
+ hypotheses.push(...intentAssessment.evidence.slice(0, 2));
1076
+ }
1077
+ if (toolingAssessment.library_profile) {
1078
+ const librarySummary = summarizeLibraryProfile(toolingAssessment.library_profile);
1079
+ if (librarySummary) {
1080
+ hypotheses.push(`Observed crate/library profile: ${librarySummary}`);
1081
+ }
1082
+ }
1083
+ falsePositiveRisks.push(...intentAssessment.counter_evidence);
1084
+ let classification = 'unknown';
1085
+ if (threatLevel === 'clean') {
1086
+ classification = 'benign';
1087
+ }
1088
+ else if (threatLevel === 'malicious') {
1089
+ classification = 'malicious';
1090
+ }
1091
+ else if (threatLevel === 'suspicious') {
1092
+ classification = 'suspicious';
1093
+ }
1094
+ if (hypotheses.length === 0) {
1095
+ hypotheses.push('Insufficient evidence to build high-confidence behavioral inference.');
1096
+ }
1097
+ return {
1098
+ classification,
1099
+ hypotheses: Array.from(new Set(hypotheses)),
1100
+ false_positive_risks: Array.from(new Set(falsePositiveRisks)),
1101
+ intent_assessment: intentAssessment,
1102
+ tooling_assessment: toolingAssessment,
1103
+ };
1104
+ }
1105
+ // ============================================================================
1106
+ // Standalone Workflow Function
1107
+ // ============================================================================
1108
+ /**
1109
+ * Execute triage workflow
1110
+ * Requirements: 15.1, 15.2, 15.4, 15.5
1111
+ *
1112
+ * This is a standalone function that can be called by other workflows
1113
+ */
1114
+ export async function triageWorkflow(sampleId, workspaceManager, database, cacheManager) {
1115
+ const handler = createTriageWorkflowHandler(workspaceManager, database, cacheManager);
1116
+ const result = await handler({ sample_id: sampleId });
1117
+ // Convert WorkerResult to TriageWorkflowOutput
1118
+ return {
1119
+ ok: result.ok,
1120
+ data: result.data,
1121
+ errors: result.errors,
1122
+ warnings: result.warnings
1123
+ };
1124
+ }
1125
+ // ============================================================================
1126
+ // Workflow Handler
1127
+ // ============================================================================
1128
+ /**
1129
+ * Create triage workflow handler
1130
+ * Requirements: 15.1, 15.2, 15.4, 15.5
1131
+ */
1132
+ export function createTriageWorkflowHandler(workspaceManager, database, cacheManager) {
1133
+ // Create tool handlers
1134
+ const peFingerprintHandler = createPEFingerprintHandler(workspaceManager, database, cacheManager);
1135
+ const runtimeDetectHandler = createRuntimeDetectHandler(workspaceManager, database, cacheManager);
1136
+ const peImportsExtractHandler = createPEImportsExtractHandler(workspaceManager, database, cacheManager);
1137
+ const stringsExtractHandler = createStringsExtractHandler(workspaceManager, database, cacheManager);
1138
+ const yaraScanHandler = createYaraScanHandler(workspaceManager, database, cacheManager);
1139
+ return async (args) => {
1140
+ const input = args;
1141
+ const startTime = Date.now();
1142
+ const warnings = [];
1143
+ const errors = [];
1144
+ try {
1145
+ // Verify sample exists
1146
+ const sample = database.findSample(input.sample_id);
1147
+ if (!sample) {
1148
+ return {
1149
+ ok: false,
1150
+ errors: [`Sample not found: ${input.sample_id}`],
1151
+ };
1152
+ }
1153
+ // Step 1: PE Fingerprint (fast mode)
1154
+ // Requirement: 15.1
1155
+ const fingerprintResult = await peFingerprintHandler({
1156
+ sample_id: input.sample_id,
1157
+ fast: true,
1158
+ force_refresh: input.force_refresh,
1159
+ });
1160
+ if (!fingerprintResult.ok) {
1161
+ errors.push('PE fingerprint extraction failed');
1162
+ }
1163
+ if (fingerprintResult.warnings) {
1164
+ warnings.push(...fingerprintResult.warnings);
1165
+ }
1166
+ // Step 2: Runtime Detection
1167
+ // Requirement: 15.1
1168
+ const runtimeResult = await runtimeDetectHandler({
1169
+ sample_id: input.sample_id,
1170
+ force_refresh: input.force_refresh,
1171
+ });
1172
+ if (!runtimeResult.ok) {
1173
+ errors.push('Runtime detection failed');
1174
+ }
1175
+ if (runtimeResult.warnings) {
1176
+ warnings.push(...runtimeResult.warnings);
1177
+ }
1178
+ // Step 3: Import Table Extraction
1179
+ // Requirement: 15.1
1180
+ const importsResult = await peImportsExtractHandler({
1181
+ sample_id: input.sample_id,
1182
+ group_by_dll: true,
1183
+ force_refresh: input.force_refresh,
1184
+ });
1185
+ if (!importsResult.ok) {
1186
+ errors.push('Import table extraction failed');
1187
+ }
1188
+ if (importsResult.warnings) {
1189
+ warnings.push(...importsResult.warnings);
1190
+ }
1191
+ // Step 4: String Extraction
1192
+ // Requirement: 15.1
1193
+ const stringsResult = await stringsExtractHandler({
1194
+ sample_id: input.sample_id,
1195
+ min_len: 6,
1196
+ encoding: 'all',
1197
+ force_refresh: input.force_refresh,
1198
+ });
1199
+ if (!stringsResult.ok) {
1200
+ errors.push('String extraction failed');
1201
+ }
1202
+ if (stringsResult.warnings) {
1203
+ warnings.push(...stringsResult.warnings);
1204
+ }
1205
+ // Step 5: YARA Scan
1206
+ // Requirement: 15.1
1207
+ const yaraResult = await yaraScanHandler({
1208
+ sample_id: input.sample_id,
1209
+ rule_set: 'malware_families',
1210
+ rule_tier: 'production',
1211
+ force_refresh: input.force_refresh,
1212
+ });
1213
+ if (!yaraResult.ok) {
1214
+ errors.push('YARA scan failed');
1215
+ }
1216
+ if (yaraResult.warnings) {
1217
+ warnings.push(...yaraResult.warnings);
1218
+ }
1219
+ // If all tools failed, return error
1220
+ if (errors.length >= 5) {
1221
+ return {
1222
+ ok: false,
1223
+ errors: ['All analysis tools failed', ...errors],
1224
+ warnings,
1225
+ metrics: {
1226
+ elapsed_ms: Date.now() - startTime,
1227
+ tool: TOOL_NAME,
1228
+ },
1229
+ };
1230
+ }
1231
+ // Step 6: Aggregate results and generate structured summary
1232
+ // Requirements: 15.2, 15.4, 15.5
1233
+ // Extract YARA matches
1234
+ let yaraSignals = [];
1235
+ if (yaraResult.ok && yaraResult.data) {
1236
+ const yaraData = yaraResult.data;
1237
+ if (yaraData.matches && Array.isArray(yaraData.matches)) {
1238
+ yaraSignals = normalizeYaraSignals(yaraData.matches);
1239
+ }
1240
+ }
1241
+ // Analyze imports for suspicious APIs
1242
+ const suspiciousImports = [];
1243
+ if (importsResult.ok && importsResult.data) {
1244
+ const importsData = importsResult.data;
1245
+ if (importsData.imports && typeof importsData.imports === 'object') {
1246
+ suspiciousImports.push(...analyzeSuspiciousImports(importsData.imports));
1247
+ }
1248
+ }
1249
+ // Analyze strings for suspicious patterns
1250
+ const stringAnalysis = {
1251
+ suspicious: [],
1252
+ urls: [],
1253
+ ips: [],
1254
+ paths: [],
1255
+ registry: [],
1256
+ commands: [],
1257
+ pipes: [],
1258
+ cargoPaths: [],
1259
+ rustMarkers: [],
1260
+ crateNames: [],
1261
+ libraryHints: [],
1262
+ };
1263
+ if (stringsResult.ok && stringsResult.data) {
1264
+ const stringsData = stringsResult.data;
1265
+ if (stringsData.strings && Array.isArray(stringsData.strings)) {
1266
+ const analysis = analyzeSuspiciousStrings(stringsData.strings);
1267
+ stringAnalysis.suspicious = analysis.suspicious;
1268
+ stringAnalysis.urls = analysis.urls;
1269
+ stringAnalysis.ips = analysis.ips;
1270
+ stringAnalysis.paths = analysis.paths;
1271
+ stringAnalysis.registry = analysis.registry;
1272
+ stringAnalysis.commands = analysis.commands;
1273
+ stringAnalysis.pipes = analysis.pipes;
1274
+ stringAnalysis.cargoPaths = analysis.cargoPaths;
1275
+ stringAnalysis.rustMarkers = analysis.rustMarkers;
1276
+ stringAnalysis.crateNames = analysis.crateNames;
1277
+ stringAnalysis.libraryHints = analysis.libraryHints;
1278
+ }
1279
+ }
1280
+ const stringsSummary = stringsResult.data?.summary;
1281
+ const { intent, tooling } = assessIntentAndTooling(stringsSummary, suspiciousImports, stringAnalysis, yaraSignals, runtimeResult.data);
1282
+ yaraSignals = applyIntentAwareYaraAdjustments(yaraSignals, intent);
1283
+ const yaraMatches = Array.from(new Set(yaraSignals
1284
+ .filter((signal) => signal.level !== 'low')
1285
+ .map((signal) => signal.rule)));
1286
+ const yaraLowConfidenceMatches = Array.from(new Set(yaraSignals
1287
+ .filter((signal) => signal.level === 'low')
1288
+ .map((signal) => signal.rule)));
1289
+ // Calculate threat level and confidence
1290
+ const { level: threatLevel, confidence } = calculateThreatLevelV2(yaraSignals, suspiciousImports, stringAnalysis.suspicious, intent);
1291
+ // Generate evidence
1292
+ const evidence = generateEvidenceV2(yaraSignals, suspiciousImports, stringAnalysis.suspicious, runtimeResult.data, intent, tooling);
1293
+ if (yaraLowConfidenceMatches.length > 0) {
1294
+ evidence.push(`YARA low-confidence matches (downgraded): ${yaraLowConfidenceMatches.join(', ')}`);
1295
+ }
1296
+ // Generate summary and recommendation
1297
+ const { summary, recommendation } = generateSummaryAndRecommendationV2(threatLevel, yaraSignals, runtimeResult.data, intent, tooling);
1298
+ const inference = buildInferenceLayerV2(threatLevel, yaraSignals, suspiciousImports, stringAnalysis.suspicious, intent, tooling);
1299
+ const evidenceWeights = calculateEvidenceWeightsV2(suspiciousImports, stringAnalysis.suspicious, runtimeResult.data, yaraSignals, intent);
1300
+ const highValueIocs = {
1301
+ suspicious_apis: suspiciousImports.length > 0 ? suspiciousImports.slice(0, 20) : undefined,
1302
+ commands: stringAnalysis.commands.length > 0 ? stringAnalysis.commands.slice(0, 15) : undefined,
1303
+ pipes: stringAnalysis.pipes.length > 0 ? stringAnalysis.pipes.slice(0, 15) : undefined,
1304
+ urls: stringAnalysis.urls.length > 0 ? stringAnalysis.urls.slice(0, 15) : undefined,
1305
+ network: stringAnalysis.ips.length > 0 ? stringAnalysis.ips.slice(0, 15) : undefined,
1306
+ };
1307
+ const compilerArtifacts = {
1308
+ cargo_paths: stringAnalysis.cargoPaths.length > 0 ? stringAnalysis.cargoPaths.slice(0, 10) : undefined,
1309
+ rust_markers: stringAnalysis.rustMarkers.length > 0 ? stringAnalysis.rustMarkers.slice(0, 10) : undefined,
1310
+ library_profile: tooling.library_profile,
1311
+ };
1312
+ const hasHighValue = Boolean(highValueIocs.suspicious_apis?.length) ||
1313
+ Boolean(highValueIocs.commands?.length) ||
1314
+ Boolean(highValueIocs.pipes?.length) ||
1315
+ Boolean(highValueIocs.urls?.length) ||
1316
+ Boolean(highValueIocs.network?.length);
1317
+ const hasCompilerArtifacts = Boolean(compilerArtifacts.cargo_paths?.length) ||
1318
+ Boolean(compilerArtifacts.rust_markers?.length) ||
1319
+ Boolean(compilerArtifacts.library_profile);
1320
+ // Build IOCs
1321
+ const iocs = {
1322
+ suspicious_imports: suspiciousImports,
1323
+ suspicious_strings: stringAnalysis.suspicious.slice(0, 20), // Limit to top 20
1324
+ yara_matches: yaraMatches,
1325
+ yara_low_confidence: yaraLowConfidenceMatches.length > 0 ? yaraLowConfidenceMatches : undefined,
1326
+ urls: stringAnalysis.urls.length > 0 ? stringAnalysis.urls : undefined,
1327
+ ip_addresses: stringAnalysis.ips.length > 0 ? stringAnalysis.ips : undefined,
1328
+ file_paths: stringAnalysis.paths.length > 0 ? stringAnalysis.paths.slice(0, 10) : undefined,
1329
+ registry_keys: stringAnalysis.registry.length > 0 ? stringAnalysis.registry.slice(0, 10) : undefined,
1330
+ high_value_iocs: hasHighValue ? highValueIocs : undefined,
1331
+ compiler_artifacts: hasCompilerArtifacts ? compilerArtifacts : undefined,
1332
+ };
1333
+ // Return structured result
1334
+ return {
1335
+ ok: true,
1336
+ data: {
1337
+ summary,
1338
+ confidence,
1339
+ threat_level: threatLevel,
1340
+ iocs,
1341
+ evidence,
1342
+ evidence_weights: evidenceWeights,
1343
+ inference,
1344
+ recommendation,
1345
+ raw_results: {
1346
+ fingerprint: fingerprintResult.data || null,
1347
+ runtime: runtimeResult.data || null,
1348
+ imports: importsResult.data || null,
1349
+ strings: stringsResult.data || null,
1350
+ yara: yaraResult.data || null,
1351
+ },
1352
+ },
1353
+ warnings: warnings.length > 0 ? warnings : undefined,
1354
+ errors: errors.length > 0 ? errors : undefined,
1355
+ metrics: {
1356
+ elapsed_ms: Date.now() - startTime,
1357
+ tool: TOOL_NAME,
1358
+ },
1359
+ };
1360
+ }
1361
+ catch (error) {
1362
+ return {
1363
+ ok: false,
1364
+ errors: [error.message, ...errors],
1365
+ warnings: warnings.length > 0 ? warnings : undefined,
1366
+ metrics: {
1367
+ elapsed_ms: Date.now() - startTime,
1368
+ tool: TOOL_NAME,
1369
+ },
1370
+ };
1371
+ }
1372
+ };
1373
+ }
1374
+ //# sourceMappingURL=triage.js.map