@remnux/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 (220) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +720 -0
  3. package/dist/archive-extractor.d.ts +46 -0
  4. package/dist/archive-extractor.d.ts.map +1 -0
  5. package/dist/archive-extractor.js +268 -0
  6. package/dist/archive-extractor.js.map +1 -0
  7. package/dist/catalog/index.d.ts +40 -0
  8. package/dist/catalog/index.d.ts.map +1 -0
  9. package/dist/catalog/index.js +114 -0
  10. package/dist/catalog/index.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +154 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config/archive-passwords.txt +3 -0
  16. package/dist/connectors/docker.d.ts +13 -0
  17. package/dist/connectors/docker.d.ts.map +1 -0
  18. package/dist/connectors/docker.js +201 -0
  19. package/dist/connectors/docker.js.map +1 -0
  20. package/dist/connectors/index.d.ts +27 -0
  21. package/dist/connectors/index.d.ts.map +1 -0
  22. package/dist/connectors/index.js +23 -0
  23. package/dist/connectors/index.js.map +1 -0
  24. package/dist/connectors/local.d.ts +10 -0
  25. package/dist/connectors/local.d.ts.map +1 -0
  26. package/dist/connectors/local.js +105 -0
  27. package/dist/connectors/local.js.map +1 -0
  28. package/dist/connectors/ssh.d.ts +21 -0
  29. package/dist/connectors/ssh.d.ts.map +1 -0
  30. package/dist/connectors/ssh.js +237 -0
  31. package/dist/connectors/ssh.js.map +1 -0
  32. package/dist/errors/error-mapper.d.ts +9 -0
  33. package/dist/errors/error-mapper.d.ts.map +1 -0
  34. package/dist/errors/error-mapper.js +24 -0
  35. package/dist/errors/error-mapper.js.map +1 -0
  36. package/dist/errors/remnux-error.d.ts +14 -0
  37. package/dist/errors/remnux-error.d.ts.map +1 -0
  38. package/dist/errors/remnux-error.js +19 -0
  39. package/dist/errors/remnux-error.js.map +1 -0
  40. package/dist/file-type-mappings.d.ts +30 -0
  41. package/dist/file-type-mappings.d.ts.map +1 -0
  42. package/dist/file-type-mappings.js +136 -0
  43. package/dist/file-type-mappings.js.map +1 -0
  44. package/dist/file-upload.d.ts +44 -0
  45. package/dist/file-upload.d.ts.map +1 -0
  46. package/dist/file-upload.js +170 -0
  47. package/dist/file-upload.js.map +1 -0
  48. package/dist/handlers/analyze-file.d.ts +10 -0
  49. package/dist/handlers/analyze-file.d.ts.map +1 -0
  50. package/dist/handlers/analyze-file.js +149 -0
  51. package/dist/handlers/analyze-file.js.map +1 -0
  52. package/dist/handlers/check-tools.d.ts +9 -0
  53. package/dist/handlers/check-tools.d.ts.map +1 -0
  54. package/dist/handlers/check-tools.js +47 -0
  55. package/dist/handlers/check-tools.js.map +1 -0
  56. package/dist/handlers/download-file.d.ts +10 -0
  57. package/dist/handlers/download-file.d.ts.map +1 -0
  58. package/dist/handlers/download-file.js +113 -0
  59. package/dist/handlers/download-file.js.map +1 -0
  60. package/dist/handlers/download-from-url.d.ts +30 -0
  61. package/dist/handlers/download-from-url.d.ts.map +1 -0
  62. package/dist/handlers/download-from-url.js +295 -0
  63. package/dist/handlers/download-from-url.js.map +1 -0
  64. package/dist/handlers/extract-archive.d.ts +10 -0
  65. package/dist/handlers/extract-archive.d.ts.map +1 -0
  66. package/dist/handlers/extract-archive.js +57 -0
  67. package/dist/handlers/extract-archive.js.map +1 -0
  68. package/dist/handlers/extract-iocs.d.ts +10 -0
  69. package/dist/handlers/extract-iocs.d.ts.map +1 -0
  70. package/dist/handlers/extract-iocs.js +21 -0
  71. package/dist/handlers/extract-iocs.js.map +1 -0
  72. package/dist/handlers/get-file-info.d.ts +10 -0
  73. package/dist/handlers/get-file-info.d.ts.map +1 -0
  74. package/dist/handlers/get-file-info.js +89 -0
  75. package/dist/handlers/get-file-info.js.map +1 -0
  76. package/dist/handlers/list-files.d.ts +10 -0
  77. package/dist/handlers/list-files.d.ts.map +1 -0
  78. package/dist/handlers/list-files.js +60 -0
  79. package/dist/handlers/list-files.js.map +1 -0
  80. package/dist/handlers/run-tool.d.ts +10 -0
  81. package/dist/handlers/run-tool.d.ts.map +1 -0
  82. package/dist/handlers/run-tool.js +99 -0
  83. package/dist/handlers/run-tool.js.map +1 -0
  84. package/dist/handlers/suggest-tools.d.ts +10 -0
  85. package/dist/handlers/suggest-tools.d.ts.map +1 -0
  86. package/dist/handlers/suggest-tools.js +202 -0
  87. package/dist/handlers/suggest-tools.js.map +1 -0
  88. package/dist/handlers/types.d.ts +15 -0
  89. package/dist/handlers/types.d.ts.map +1 -0
  90. package/dist/handlers/types.js +2 -0
  91. package/dist/handlers/types.js.map +1 -0
  92. package/dist/handlers/upload-file.d.ts +10 -0
  93. package/dist/handlers/upload-file.d.ts.map +1 -0
  94. package/dist/handlers/upload-file.js +33 -0
  95. package/dist/handlers/upload-file.js.map +1 -0
  96. package/dist/handlers/upload-from-host.d.ts +10 -0
  97. package/dist/handlers/upload-from-host.d.ts.map +1 -0
  98. package/dist/handlers/upload-from-host.js +33 -0
  99. package/dist/handlers/upload-from-host.js.map +1 -0
  100. package/dist/handlers/upload-sample.d.ts +10 -0
  101. package/dist/handlers/upload-sample.d.ts.map +1 -0
  102. package/dist/handlers/upload-sample.js +26 -0
  103. package/dist/handlers/upload-sample.js.map +1 -0
  104. package/dist/index.d.ts +15 -0
  105. package/dist/index.d.ts.map +1 -0
  106. package/dist/index.js +254 -0
  107. package/dist/index.js.map +1 -0
  108. package/dist/ioc/extractor.d.ts +21 -0
  109. package/dist/ioc/extractor.d.ts.map +1 -0
  110. package/dist/ioc/extractor.js +91 -0
  111. package/dist/ioc/extractor.js.map +1 -0
  112. package/dist/ioc/known-values.d.ts +7 -0
  113. package/dist/ioc/known-values.d.ts.map +1 -0
  114. package/dist/ioc/known-values.js +43 -0
  115. package/dist/ioc/known-values.js.map +1 -0
  116. package/dist/ioc/noise.d.ts +6 -0
  117. package/dist/ioc/noise.d.ts.map +1 -0
  118. package/dist/ioc/noise.js +170 -0
  119. package/dist/ioc/noise.js.map +1 -0
  120. package/dist/ioc/patterns.d.ts +10 -0
  121. package/dist/ioc/patterns.d.ts.map +1 -0
  122. package/dist/ioc/patterns.js +65 -0
  123. package/dist/ioc/patterns.js.map +1 -0
  124. package/dist/ioc/scoring.d.ts +6 -0
  125. package/dist/ioc/scoring.d.ts.map +1 -0
  126. package/dist/ioc/scoring.js +69 -0
  127. package/dist/ioc/scoring.js.map +1 -0
  128. package/dist/parsers/capa.d.ts +9 -0
  129. package/dist/parsers/capa.d.ts.map +1 -0
  130. package/dist/parsers/capa.js +55 -0
  131. package/dist/parsers/capa.js.map +1 -0
  132. package/dist/parsers/diec.d.ts +9 -0
  133. package/dist/parsers/diec.d.ts.map +1 -0
  134. package/dist/parsers/diec.js +53 -0
  135. package/dist/parsers/diec.js.map +1 -0
  136. package/dist/parsers/floss.d.ts +14 -0
  137. package/dist/parsers/floss.d.ts.map +1 -0
  138. package/dist/parsers/floss.js +89 -0
  139. package/dist/parsers/floss.js.map +1 -0
  140. package/dist/parsers/index.d.ts +16 -0
  141. package/dist/parsers/index.d.ts.map +1 -0
  142. package/dist/parsers/index.js +46 -0
  143. package/dist/parsers/index.js.map +1 -0
  144. package/dist/parsers/oleid.d.ts +8 -0
  145. package/dist/parsers/oleid.d.ts.map +1 -0
  146. package/dist/parsers/oleid.js +94 -0
  147. package/dist/parsers/oleid.js.map +1 -0
  148. package/dist/parsers/olevba.d.ts +8 -0
  149. package/dist/parsers/olevba.d.ts.map +1 -0
  150. package/dist/parsers/olevba.js +83 -0
  151. package/dist/parsers/olevba.js.map +1 -0
  152. package/dist/parsers/passthrough.d.ts +6 -0
  153. package/dist/parsers/passthrough.d.ts.map +1 -0
  154. package/dist/parsers/passthrough.js +13 -0
  155. package/dist/parsers/passthrough.js.map +1 -0
  156. package/dist/parsers/pdf-parser.d.ts +9 -0
  157. package/dist/parsers/pdf-parser.d.ts.map +1 -0
  158. package/dist/parsers/pdf-parser.js +76 -0
  159. package/dist/parsers/pdf-parser.js.map +1 -0
  160. package/dist/parsers/pdfid.d.ts +9 -0
  161. package/dist/parsers/pdfid.d.ts.map +1 -0
  162. package/dist/parsers/pdfid.js +56 -0
  163. package/dist/parsers/pdfid.js.map +1 -0
  164. package/dist/parsers/peframe.d.ts +8 -0
  165. package/dist/parsers/peframe.d.ts.map +1 -0
  166. package/dist/parsers/peframe.js +76 -0
  167. package/dist/parsers/peframe.js.map +1 -0
  168. package/dist/parsers/readelf.d.ts +8 -0
  169. package/dist/parsers/readelf.d.ts.map +1 -0
  170. package/dist/parsers/readelf.js +50 -0
  171. package/dist/parsers/readelf.js.map +1 -0
  172. package/dist/parsers/types.d.ts +30 -0
  173. package/dist/parsers/types.d.ts.map +1 -0
  174. package/dist/parsers/types.js +5 -0
  175. package/dist/parsers/types.js.map +1 -0
  176. package/dist/parsers/yara.d.ts +8 -0
  177. package/dist/parsers/yara.d.ts.map +1 -0
  178. package/dist/parsers/yara.js +88 -0
  179. package/dist/parsers/yara.js.map +1 -0
  180. package/dist/response.d.ts +44 -0
  181. package/dist/response.d.ts.map +1 -0
  182. package/dist/response.js +48 -0
  183. package/dist/response.js.map +1 -0
  184. package/dist/schemas/tools.d.ts +135 -0
  185. package/dist/schemas/tools.d.ts.map +1 -0
  186. package/dist/schemas/tools.js +53 -0
  187. package/dist/schemas/tools.js.map +1 -0
  188. package/dist/security/blocklist.d.ts +69 -0
  189. package/dist/security/blocklist.d.ts.map +1 -0
  190. package/dist/security/blocklist.js +148 -0
  191. package/dist/security/blocklist.js.map +1 -0
  192. package/dist/state/session.d.ts +35 -0
  193. package/dist/state/session.d.ts.map +1 -0
  194. package/dist/state/session.js +45 -0
  195. package/dist/state/session.js.map +1 -0
  196. package/dist/tools/definitions.d.ts +9 -0
  197. package/dist/tools/definitions.d.ts.map +1 -0
  198. package/dist/tools/definitions.js +708 -0
  199. package/dist/tools/definitions.js.map +1 -0
  200. package/dist/tools/invoker.d.ts +17 -0
  201. package/dist/tools/invoker.d.ts.map +1 -0
  202. package/dist/tools/invoker.js +44 -0
  203. package/dist/tools/invoker.js.map +1 -0
  204. package/dist/tools/registry.d.ts +62 -0
  205. package/dist/tools/registry.d.ts.map +1 -0
  206. package/dist/tools/registry.js +53 -0
  207. package/dist/tools/registry.js.map +1 -0
  208. package/dist/workflows/engine.d.ts +27 -0
  209. package/dist/workflows/engine.d.ts.map +1 -0
  210. package/dist/workflows/engine.js +224 -0
  211. package/dist/workflows/engine.js.map +1 -0
  212. package/dist/workflows/loader.d.ts +33 -0
  213. package/dist/workflows/loader.d.ts.map +1 -0
  214. package/dist/workflows/loader.js +130 -0
  215. package/dist/workflows/loader.js.map +1 -0
  216. package/dist/workflows/types.d.ts +109 -0
  217. package/dist/workflows/types.d.ts.map +1 -0
  218. package/dist/workflows/types.js +5 -0
  219. package/dist/workflows/types.js.map +1 -0
  220. package/package.json +68 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * File upload module for REMnux MCP server
3
+ *
4
+ * Allows AI assistants to upload files from the host filesystem to the
5
+ * samples directory via MCP. Reads files locally — no base64 in context.
6
+ */
7
+ import { createHash } from "crypto";
8
+ import { createReadStream, lstatSync } from "fs";
9
+ import { pipeline } from "stream/promises";
10
+ import { basename, isAbsolute } from "path";
11
+ // 200MB limit for uploaded files (stat-based check, no in-memory buffering)
12
+ const MAX_FILE_SIZE = 200 * 1024 * 1024;
13
+ /**
14
+ * Validate filename for security
15
+ * - No path separators (/, \)
16
+ * - No path traversal (..)
17
+ * - No null bytes
18
+ * - Reasonable length
19
+ */
20
+ export function validateFilename(filename) {
21
+ if (!filename || filename.length === 0) {
22
+ return { valid: false, error: "Filename cannot be empty" };
23
+ }
24
+ if (filename.length > 255) {
25
+ return { valid: false, error: "Filename too long (max 255 characters)" };
26
+ }
27
+ if (filename.includes("/") || filename.includes("\\")) {
28
+ return { valid: false, error: "Filename cannot contain path separators" };
29
+ }
30
+ if (filename.includes("..")) {
31
+ return { valid: false, error: "Filename cannot contain '..'" };
32
+ }
33
+ if (filename.includes("\0")) {
34
+ return { valid: false, error: "Filename cannot contain null bytes" };
35
+ }
36
+ // Check for shell metacharacters that could cause issues
37
+ if (/[;&|`$\n\r'"<>]/.test(filename)) {
38
+ return { valid: false, error: "Filename contains invalid characters" };
39
+ }
40
+ return { valid: true };
41
+ }
42
+ /**
43
+ * Validate a host filesystem path for security
44
+ */
45
+ export function validateHostPath(hostPath) {
46
+ if (!hostPath || hostPath.length === 0) {
47
+ return { valid: false, error: "host_path cannot be empty" };
48
+ }
49
+ if (!isAbsolute(hostPath)) {
50
+ return { valid: false, error: "host_path must be an absolute path" };
51
+ }
52
+ if (hostPath.includes("\0")) {
53
+ return { valid: false, error: "host_path cannot contain null bytes" };
54
+ }
55
+ // Reject path traversal — always reject ".." components
56
+ if (hostPath.includes("..")) {
57
+ return { valid: false, error: "host_path cannot contain path traversal (..)" };
58
+ }
59
+ // Reject shell metacharacters
60
+ if (/[;&|`$\n\r'"<>]/.test(hostPath)) {
61
+ return { valid: false, error: "host_path contains invalid characters" };
62
+ }
63
+ return { valid: true };
64
+ }
65
+ /**
66
+ * Upload a file from the host filesystem to the samples directory
67
+ *
68
+ * @param connector - Connector to write files on REMnux
69
+ * @param samplesDir - Base samples directory on REMnux
70
+ * @param hostPath - Absolute path on the host filesystem
71
+ * @param filename - Override filename (defaults to basename of hostPath)
72
+ * @param overwrite - Whether to overwrite if file exists (default: false)
73
+ * @returns Upload result with file path, size, and SHA256 hash
74
+ */
75
+ export async function uploadSampleFromHost(connector, samplesDir, hostPath, filename, overwrite = false) {
76
+ // Validate host path
77
+ const pathValidation = validateHostPath(hostPath);
78
+ if (!pathValidation.valid) {
79
+ return { success: false, error: pathValidation.error };
80
+ }
81
+ // Reject symlinks
82
+ let stat;
83
+ try {
84
+ stat = lstatSync(hostPath);
85
+ }
86
+ catch (_err) {
87
+ return { success: false, error: `File not found: ${hostPath}` };
88
+ }
89
+ if (stat.isSymbolicLink()) {
90
+ return { success: false, error: "Symlinks are not allowed" };
91
+ }
92
+ if (!stat.isFile()) {
93
+ return { success: false, error: "host_path must point to a regular file" };
94
+ }
95
+ // Check file size via stat (no buffering)
96
+ if (stat.size > MAX_FILE_SIZE) {
97
+ const sizeMB = (stat.size / 1024 / 1024).toFixed(0);
98
+ const limitMB = MAX_FILE_SIZE / 1024 / 1024;
99
+ return {
100
+ success: false,
101
+ error: `File size (${sizeMB}MB) exceeds the ${limitMB}MB upload limit. ` +
102
+ `For large files such as memory images, mount a host directory into the container instead:\n` +
103
+ ` docker run -v /path/to/evidence:/home/remnux/files/samples/evidence remnux/remnux-distro\n` +
104
+ `Then reference files as: evidence/<filename>`,
105
+ };
106
+ }
107
+ // Determine target filename
108
+ const targetFilename = filename ?? basename(hostPath);
109
+ // Validate target filename
110
+ const filenameValidation = validateFilename(targetFilename);
111
+ if (!filenameValidation.valid) {
112
+ return { success: false, error: filenameValidation.error };
113
+ }
114
+ // Calculate SHA256 hash via streaming
115
+ let sha256;
116
+ try {
117
+ const hash = createHash("sha256");
118
+ await pipeline(createReadStream(hostPath), hash);
119
+ sha256 = hash.digest("hex");
120
+ }
121
+ catch (err) {
122
+ return {
123
+ success: false,
124
+ error: `Failed to read file: ${err instanceof Error ? err.message : "Unknown error"}`,
125
+ };
126
+ }
127
+ // Build full file path
128
+ const filePath = `${samplesDir}/${targetFilename}`;
129
+ // Check if file already exists (unless overwrite is true)
130
+ if (!overwrite) {
131
+ try {
132
+ const checkResult = await connector.execute(["test", "-e", filePath], {
133
+ timeout: 5000,
134
+ });
135
+ if (checkResult.exitCode === 0) {
136
+ return {
137
+ success: false,
138
+ error: "File already exists. Use overwrite=true to replace.",
139
+ };
140
+ }
141
+ }
142
+ catch {
143
+ // test command failed, file probably doesn't exist
144
+ }
145
+ }
146
+ // Ensure target directory exists (fresh containers may lack it)
147
+ try {
148
+ await connector.execute(["mkdir", "-p", samplesDir], { timeout: 5000 });
149
+ }
150
+ catch {
151
+ // Ignore — real errors surface in writeFileFromPath
152
+ }
153
+ // Write file using connector's streaming path-based method
154
+ try {
155
+ await connector.writeFileFromPath(filePath, hostPath);
156
+ }
157
+ catch (err) {
158
+ return {
159
+ success: false,
160
+ error: `Failed to write file: ${err instanceof Error ? err.message : "Unknown error"}`,
161
+ };
162
+ }
163
+ return {
164
+ success: true,
165
+ path: filePath,
166
+ size_bytes: stat.size,
167
+ sha256,
168
+ };
169
+ }
170
+ //# sourceMappingURL=file-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-upload.js","sourceRoot":"","sources":["../src/file-upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { HandlerDeps } from "./types.js";
2
+ import type { AnalyzeFileArgs } from "../schemas/tools.js";
3
+ export declare function handleAnalyzeFile(deps: HandlerDeps, args: AnalyzeFileArgs): Promise<{
4
+ content: Array<{
5
+ type: "text";
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }>;
10
+ //# sourceMappingURL=analyze-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-file.d.ts","sourceRoot":"","sources":["../../src/handlers/analyze-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAwC3D,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,eAAe;;;;;;GAwJtB"}
@@ -0,0 +1,149 @@
1
+ import { validateFilePath } from "../security/blocklist.js";
2
+ import { matchFileType, CATEGORY_TAG_MAP } from "../file-type-mappings.js";
3
+ import { toolRegistry } from "../tools/registry.js";
4
+ import { buildCommandFromDefinition } from "../tools/invoker.js";
5
+ import { parseToolOutput } from "../parsers/index.js";
6
+ import { formatResponse, formatError } from "../response.js";
7
+ import { REMnuxError } from "../errors/remnux-error.js";
8
+ import { toREMnuxError } from "../errors/error-mapper.js";
9
+ import { extractIOCs } from "../ioc/extractor.js";
10
+ const DEFAULT_OUTPUT_BUDGET = 80 * 1024; // 80KB default
11
+ const TOTAL_RESPONSE_BUDGET = 200 * 1024; // 200KB total across all tools
12
+ /** Per-tool output budgets — tools known to produce large output get tighter limits. */
13
+ const TOOL_OUTPUT_BUDGETS = {
14
+ floss: 20 * 1024,
15
+ ilspycmd: 15 * 1024,
16
+ rtfdump: 10 * 1024,
17
+ olevba: 30 * 1024,
18
+ oledump: 20 * 1024,
19
+ exiftool: 10 * 1024,
20
+ zipdump: 15 * 1024,
21
+ };
22
+ export async function handleAnalyzeFile(deps, args) {
23
+ const startTime = Date.now();
24
+ try {
25
+ const { connector, config } = deps;
26
+ const depth = (args.depth ?? "standard");
27
+ // Validate file path (skip unless --sandbox)
28
+ if (!config.noSandbox) {
29
+ const validation = validateFilePath(args.file, config.samplesDir);
30
+ if (!validation.safe) {
31
+ return formatError("analyze_file", new REMnuxError(validation.error || "Invalid file path", "INVALID_PATH", "validation", "Use a relative path within the samples directory"), startTime);
32
+ }
33
+ }
34
+ const filePath = `${config.samplesDir}/${args.file}`;
35
+ const perToolTimeout = (args.timeout_per_tool || 60) * 1000;
36
+ // Step 1: Detect file type
37
+ let fileOutput;
38
+ try {
39
+ const result = await connector.execute(["file", filePath], { timeout: 30000 });
40
+ fileOutput = result.stdout?.trim() || "";
41
+ if (!fileOutput) {
42
+ return formatError("analyze_file", new REMnuxError("Could not determine file type (empty `file` output)", "EMPTY_OUTPUT", "tool_failure", "Check that the file exists and is readable"), startTime);
43
+ }
44
+ }
45
+ catch (error) {
46
+ const msg = `Error running file command: ${error instanceof Error ? error.message : "Unknown error"}`;
47
+ return formatError("analyze_file", new REMnuxError(msg, "EMPTY_OUTPUT", "tool_failure", "Check that the file exists and is readable"), startTime);
48
+ }
49
+ // Step 2: Match to category and get tools from registry by tag + depth
50
+ const category = matchFileType(fileOutput, args.file);
51
+ const tag = CATEGORY_TAG_MAP[category.name] ?? "fallback";
52
+ const tools = toolRegistry.byTagAndTier(tag, depth);
53
+ const toolsRun = [];
54
+ const toolsFailed = [];
55
+ const toolsSkipped = [];
56
+ let totalOutputSize = 0;
57
+ // Step 3: Run each tool
58
+ for (const tool of tools) {
59
+ const cmd = buildCommandFromDefinition(tool, filePath);
60
+ // Use the greater of user-specified timeout and tool's own timeout
61
+ const effectiveTimeout = Math.max(perToolTimeout, (tool.timeout ?? 60) * 1000);
62
+ try {
63
+ const result = await connector.executeShell(cmd, {
64
+ timeout: effectiveTimeout,
65
+ cwd: config.samplesDir,
66
+ });
67
+ let stderr = result.stderr || "";
68
+ // Filter Volatility 3 progress bar noise from stderr
69
+ stderr = stderr.replace(/^Progress:\s+[\d.]+\s+.*$/gm, "").trim();
70
+ // Detect missing tools by exit code or error messages
71
+ if (result.exitCode === 127 || stderr.includes("not found") || stderr.includes("No such file or directory")) {
72
+ toolsSkipped.push({ name: tool.name, command: cmd, reason: "Tool not installed" });
73
+ continue;
74
+ }
75
+ let output = result.stdout || stderr || "(no output)";
76
+ const fullLen = output.length;
77
+ // Per-tool budget, further reduced if approaching total response budget
78
+ const remainingBudget = Math.max(5 * 1024, TOTAL_RESPONSE_BUDGET - totalOutputSize);
79
+ const budget = Math.min(TOOL_OUTPUT_BUDGETS[tool.name] ?? DEFAULT_OUTPUT_BUDGET, remainingBudget);
80
+ const outputTruncated = output.length > budget;
81
+ let savedOutputFile;
82
+ if (outputTruncated) {
83
+ // Save full output to output dir for later retrieval
84
+ const safeFile = args.file.replace(/[^a-zA-Z0-9._-]/g, "_");
85
+ const outFilename = `${tool.name}-${safeFile}.txt`;
86
+ try {
87
+ const outPath = `${config.outputDir}/${outFilename}`;
88
+ await connector.writeFile(outPath, Buffer.from(output, "utf-8"));
89
+ savedOutputFile = outFilename;
90
+ }
91
+ catch {
92
+ // Non-fatal: truncation hint won't include file reference
93
+ }
94
+ output = output.slice(0, budget) +
95
+ `\n\n[Truncated at ${Math.round(budget / 1024)}KB of ${Math.round(fullLen / 1024)}KB total` +
96
+ (savedOutputFile ? `. Full output: output/${savedOutputFile} — use download_file to retrieve]` : "]");
97
+ }
98
+ totalOutputSize += output.length;
99
+ const parsed = parseToolOutput(tool.name, output);
100
+ // capa exit code 14 = packed file detected
101
+ const extraMetadata = {};
102
+ if ((tool.name === "capa" || tool.name === "capa-json") && result.exitCode === 14) {
103
+ extraMetadata.analyst_note = "capa detected a packed file — capabilities analysis may be incomplete. Consider unpacking first.";
104
+ }
105
+ toolsRun.push({
106
+ name: tool.name,
107
+ command: cmd,
108
+ output,
109
+ exit_code: result.exitCode,
110
+ ...(outputTruncated && { truncated: true, full_output_length: fullLen }),
111
+ ...(parsed.parsed && {
112
+ findings: parsed.findings,
113
+ metadata: { ...parsed.metadata, ...extraMetadata },
114
+ }),
115
+ ...(!parsed.parsed && Object.keys(extraMetadata).length > 0 && { metadata: extraMetadata }),
116
+ });
117
+ }
118
+ catch (error) {
119
+ const msg = error instanceof Error ? error.message : "Unknown error";
120
+ if (msg.toLowerCase().includes("timeout")) {
121
+ toolsFailed.push({ name: tool.name, command: cmd, error: "Timed out" });
122
+ }
123
+ else {
124
+ toolsFailed.push({ name: tool.name, command: cmd, error: msg });
125
+ }
126
+ }
127
+ }
128
+ const combinedOutput = toolsRun.map(t => t.output).join("\n\n");
129
+ const iocResult = extractIOCs(combinedOutput);
130
+ return formatResponse("analyze_file", {
131
+ file: args.file,
132
+ detected_type: fileOutput,
133
+ matched_category: category.name,
134
+ depth,
135
+ ...(tools.length === 0 && {
136
+ warning: `No tools registered for category "${category.name}" at depth "${depth}". Try depth "deep" or use run_tool directly.`,
137
+ }),
138
+ iocs: iocResult.iocs,
139
+ ioc_summary: iocResult.summary,
140
+ tools_run: toolsRun,
141
+ tools_failed: toolsFailed,
142
+ tools_skipped: toolsSkipped,
143
+ }, startTime);
144
+ }
145
+ catch (error) {
146
+ return formatError("analyze_file", toREMnuxError(error), startTime);
147
+ }
148
+ }
149
+ //# sourceMappingURL=analyze-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze-file.js","sourceRoot":"","sources":["../../src/handlers/analyze-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { HandlerDeps } from "./types.js";
2
+ export declare function handleCheckTools(deps: HandlerDeps): Promise<{
3
+ content: Array<{
4
+ type: "text";
5
+ text: string;
6
+ }>;
7
+ isError?: boolean;
8
+ }>;
9
+ //# sourceMappingURL=check-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-tools.d.ts","sourceRoot":"","sources":["../../src/handlers/check-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,WAAW;;;;;;GA+CvD"}
@@ -0,0 +1,47 @@
1
+ import { toolRegistry } from "../tools/registry.js";
2
+ import { formatResponse, formatError } from "../response.js";
3
+ import { toREMnuxError } from "../errors/error-mapper.js";
4
+ export async function handleCheckTools(deps) {
5
+ const startTime = Date.now();
6
+ const { connector } = deps;
7
+ // Collect unique command names from the tool registry
8
+ const toolNames = new Set();
9
+ for (const def of toolRegistry.all()) {
10
+ // Extract the command name (first word)
11
+ const cmdName = def.command.split(/\s/)[0];
12
+ toolNames.add(cmdName);
13
+ }
14
+ const tools = [];
15
+ // Verify container connectivity before checking individual tools
16
+ try {
17
+ await connector.execute(["true"], { timeout: 5000 });
18
+ }
19
+ catch (error) {
20
+ return formatError("check_tools", toREMnuxError(error), startTime);
21
+ }
22
+ try {
23
+ const results = await Promise.all([...toolNames].map(async (name) => {
24
+ try {
25
+ const result = await connector.execute(["which", name], { timeout: 5000 });
26
+ if (result.exitCode === 0 && result.stdout?.trim()) {
27
+ return { tool: name, available: true, path: result.stdout.trim() };
28
+ }
29
+ return { tool: name, available: false };
30
+ }
31
+ catch {
32
+ return { tool: name, available: false };
33
+ }
34
+ }));
35
+ tools.push(...results);
36
+ const available = tools.filter(t => t.available).length;
37
+ const missing = tools.filter(t => !t.available).length;
38
+ return formatResponse("check_tools", {
39
+ summary: { total: tools.length, available, missing },
40
+ tools: tools.sort((a, b) => a.tool.localeCompare(b.tool)),
41
+ }, startTime);
42
+ }
43
+ catch (error) {
44
+ return formatError("check_tools", toREMnuxError(error), startTime);
45
+ }
46
+ }
47
+ //# sourceMappingURL=check-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-tools.js","sourceRoot":"","sources":["../../src/handlers/check-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAiB;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAE3B,sDAAsD;IACtD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC;QACrC,wCAAwC;QACxC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,KAAK,GAA+D,EAAE,CAAC;IAE7E,iEAAiE;IACjE,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YAChC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3E,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;oBACnD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACrE,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;YAC1C,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;QAEvB,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAEvD,OAAO,cAAc,CAAC,aAAa,EAAE;YACnC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE;YACpD,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAC1D,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,aAAa,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { HandlerDeps } from "./types.js";
2
+ import type { DownloadFileArgs } from "../schemas/tools.js";
3
+ export declare function handleDownloadFile(deps: HandlerDeps, args: DownloadFileArgs): Promise<{
4
+ content: Array<{
5
+ type: "text";
6
+ text: string;
7
+ }>;
8
+ isError?: boolean;
9
+ }>;
10
+ //# sourceMappingURL=download-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-file.d.ts","sourceRoot":"","sources":["../../src/handlers/download-file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAiC5D,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,gBAAgB;;;;;;GA0IvB"}
@@ -0,0 +1,113 @@
1
+ import { existsSync, statSync } from "fs";
2
+ import { join, basename } from "path";
3
+ import { validateFilePath } from "../security/blocklist.js";
4
+ import { validateHostPath } from "../file-upload.js";
5
+ import { formatResponse, formatError } from "../response.js";
6
+ import { REMnuxError } from "../errors/remnux-error.js";
7
+ import { toREMnuxError } from "../errors/error-mapper.js";
8
+ import { DEFAULT_ARCHIVE_PASSWORD, DEFAULT_ARCHIVE_FORMAT } from "../state/session.js";
9
+ const MAX_DOWNLOAD_SIZE = 200 * 1024 * 1024; // 200MB
10
+ /**
11
+ * Build the command to create a password-protected archive inside REMnux.
12
+ */
13
+ function getArchiveCommand(format, archivePath, sourcePath, password) {
14
+ switch (format) {
15
+ case "zip":
16
+ return ["zip", "-j", "-P", password, archivePath, sourcePath];
17
+ case "7z":
18
+ return ["7z", "a", `-p${password}`, "-mhe=on", archivePath, sourcePath];
19
+ case "rar":
20
+ return ["rar", "a", `-p${password}`, "-hp", archivePath, sourcePath];
21
+ }
22
+ }
23
+ function archiveExtension(format) {
24
+ return `.${format}`;
25
+ }
26
+ export async function handleDownloadFile(deps, args) {
27
+ const startTime = Date.now();
28
+ const { connector, config } = deps;
29
+ const shouldArchive = args.archive !== false;
30
+ // Validate file path (skip unless --sandbox)
31
+ if (!config.noSandbox) {
32
+ const validation = validateFilePath(args.file_path, config.outputDir);
33
+ if (!validation.safe) {
34
+ return formatError("download_file", new REMnuxError(validation.error || "Invalid file path", "INVALID_PATH", "validation", "Use a relative path within the output directory"), startTime);
35
+ }
36
+ }
37
+ // Validate outputPath
38
+ const pathValidation = validateHostPath(args.output_path);
39
+ if (!pathValidation.valid) {
40
+ return formatError("download_file", new REMnuxError(pathValidation.error || "Invalid output path", "INVALID_PATH", "validation", "Provide an absolute path to a directory on the host filesystem"), startTime);
41
+ }
42
+ // Verify output directory exists and is a directory
43
+ if (!existsSync(args.output_path) || !statSync(args.output_path).isDirectory()) {
44
+ return formatError("download_file", new REMnuxError(`Output path does not exist or is not a directory: ${args.output_path}`, "INVALID_PATH", "validation", "Provide an absolute path to an existing directory"), startTime);
45
+ }
46
+ const fullPath = `${config.outputDir}/${args.file_path}`;
47
+ try {
48
+ // Get file size and hash (separate calls to avoid shell interpolation)
49
+ const statResult = await connector.execute(["stat", "-c", "%s", fullPath], { timeout: 30000 });
50
+ const hashResult = await connector.execute(["sha256sum", fullPath], { timeout: 30000 });
51
+ const sizeBytes = parseInt((statResult.stdout || "0").trim(), 10);
52
+ // Guard against oversized downloads
53
+ if (sizeBytes > MAX_DOWNLOAD_SIZE) {
54
+ return formatError("download_file", new REMnuxError(`File exceeds ${MAX_DOWNLOAD_SIZE / 1024 / 1024}MB download limit (got ${(sizeBytes / 1024 / 1024).toFixed(2)}MB)`, "FILE_TOO_LARGE", "validation", "Use run_tool with 'split' to break the file into smaller parts first"), startTime);
55
+ }
56
+ const sha256 = (hashResult.stdout || "").trim().split(/\s+/)[0] || "unknown";
57
+ const filename = basename(args.file_path);
58
+ if (shouldArchive) {
59
+ // Determine archive format and password from session state
60
+ const archiveMeta = deps.sessionState.getArchiveInfo(filename);
61
+ const archiveFormat = archiveMeta?.format ?? DEFAULT_ARCHIVE_FORMAT;
62
+ const archivePassword = archiveMeta?.password ?? DEFAULT_ARCHIVE_PASSWORD;
63
+ // Defense-in-depth: reject passwords with shell metacharacters
64
+ if (/[;&|`$\n\r'"\\]/.test(archivePassword)) {
65
+ return formatError("download_file", new REMnuxError("Archive password contains unsafe characters", "INVALID_PASSWORD", "validation", "Try downloading with archive: false"), startTime);
66
+ }
67
+ // Create temp archive path inside REMnux
68
+ const timestamp = Date.now();
69
+ const archiveName = `${filename}${archiveExtension(archiveFormat)}`;
70
+ const remoteTmpArchive = `/tmp/dl_${timestamp}_${archiveName}`;
71
+ // Create password-protected archive
72
+ const archiveCmd = getArchiveCommand(archiveFormat, remoteTmpArchive, fullPath, archivePassword);
73
+ const archiveResult = await connector.execute(archiveCmd, {
74
+ timeout: Math.max(60000, sizeBytes / 1024),
75
+ });
76
+ if (archiveResult.exitCode !== 0) {
77
+ return formatError("download_file", new REMnuxError(`Failed to create archive: ${archiveResult.stderr || archiveResult.stdout}`, "ARCHIVE_FAILED", "tool_failure", "Try downloading with archive: false"), startTime);
78
+ }
79
+ // Transfer archive to host
80
+ const hostPath = join(args.output_path, archiveName);
81
+ try {
82
+ await connector.readFileToPath(remoteTmpArchive, hostPath);
83
+ }
84
+ finally {
85
+ // Clean up temp archive inside REMnux
86
+ await connector.execute(["rm", "-f", remoteTmpArchive], { timeout: 10000 }).catch(() => { });
87
+ }
88
+ return formatResponse("download_file", {
89
+ file_path: args.file_path,
90
+ size_bytes: sizeBytes,
91
+ sha256,
92
+ host_path: hostPath,
93
+ archived: true,
94
+ archive_format: archiveFormat,
95
+ archive_password: archivePassword,
96
+ }, startTime);
97
+ }
98
+ // No archiving — transfer raw file
99
+ const hostPath = join(args.output_path, filename);
100
+ await connector.readFileToPath(fullPath, hostPath);
101
+ return formatResponse("download_file", {
102
+ file_path: args.file_path,
103
+ size_bytes: sizeBytes,
104
+ sha256,
105
+ host_path: hostPath,
106
+ archived: false,
107
+ }, startTime);
108
+ }
109
+ catch (error) {
110
+ return formatError("download_file", toREMnuxError(error), startTime);
111
+ }
112
+ }
113
+ //# sourceMappingURL=download-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-file.js","sourceRoot":"","sources":["../../src/handlers/download-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { HandlerDeps } from "./types.js";
2
+ import type { DownloadFromUrlArgs } from "../schemas/tools.js";
3
+ /**
4
+ * Validate a URL for download: must be http(s), no control chars, no single quotes.
5
+ */
6
+ export declare function validateUrl(url: string): {
7
+ valid: boolean;
8
+ error?: string;
9
+ };
10
+ /**
11
+ * Validate a single HTTP header string.
12
+ * Must be "Name: value" with no injection characters.
13
+ */
14
+ export declare function validateHeader(header: string): {
15
+ valid: boolean;
16
+ error?: string;
17
+ };
18
+ /**
19
+ * Derive a filename from a URL path.
20
+ * Falls back to "downloaded_sample" if no basename can be extracted.
21
+ */
22
+ export declare function deriveFilename(url: string): string;
23
+ export declare function handleDownloadFromUrl(deps: HandlerDeps, args: DownloadFromUrlArgs): Promise<{
24
+ content: Array<{
25
+ type: "text";
26
+ text: string;
27
+ }>;
28
+ isError?: boolean;
29
+ }>;
30
+ //# sourceMappingURL=download-from-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-from-url.d.ts","sourceRoot":"","sources":["../../src/handlers/download-from-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AA4B/D;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAkB3E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAYjF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBlD;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,mBAAmB;;;;;;GA0H1B"}