@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,46 @@
1
+ /**
2
+ * Archive extraction module for REMnux MCP server
3
+ *
4
+ * Supports .zip, .7z, and .rar archives with automatic password detection.
5
+ * Passwords are tried from a configurable list for malware sample archives.
6
+ */
7
+ import type { Connector } from "./connectors/index.js";
8
+ export type ArchiveType = "zip" | "7z" | "rar" | null;
9
+ export interface ExtractionResult {
10
+ success: boolean;
11
+ files: string[];
12
+ password?: string;
13
+ error?: string;
14
+ outputDir: string;
15
+ }
16
+ /**
17
+ * Detect archive type from filename extension
18
+ */
19
+ export declare function detectArchiveType(filename: string): ArchiveType;
20
+ /**
21
+ * Load password list from config file
22
+ * Returns default list if file not found
23
+ */
24
+ export declare function loadPasswordList(): string[];
25
+ /**
26
+ * Build extraction command for given archive type and tool
27
+ *
28
+ * @param archiveType - Type of archive (zip, 7z, rar)
29
+ * @param archivePath - Full path to archive file
30
+ * @param outputDir - Directory to extract files to
31
+ * @param password - Optional password for encrypted archives
32
+ * @returns Command array for execution
33
+ */
34
+ export declare function getExtractionCommand(archiveType: ArchiveType, archivePath: string, outputDir: string, password?: string): string[];
35
+ /**
36
+ * Extract an archive file with automatic password detection
37
+ *
38
+ * @param connector - Connector to execute commands on REMnux
39
+ * @param archivePath - Full path to archive file inside REMnux
40
+ * @param samplesDir - Base samples directory for output
41
+ * @param customPassword - Optional password to try first
42
+ * @param outputSubdir - Optional subdirectory name (defaults to archive name without extension)
43
+ * @returns Extraction result with file list and password used
44
+ */
45
+ export declare function extractArchive(connector: Connector, archivePath: string, samplesDir: string, customPassword?: string, outputSubdir?: string): Promise<ExtractionResult>;
46
+ //# sourceMappingURL=archive-extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-extractor.d.ts","sourceRoot":"","sources":["../src/archive-extractor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,uBAAuB,CAAC;AAKnE,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAY/D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAgB3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,EAAE,CAwCV;AAgGD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,CAsG3B"}
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Archive extraction module for REMnux MCP server
3
+ *
4
+ * Supports .zip, .7z, and .rar archives with automatic password detection.
5
+ * Passwords are tried from a configurable list for malware sample archives.
6
+ */
7
+ import { readFileSync } from "fs";
8
+ import { dirname, join, basename, extname, resolve, normalize } from "path";
9
+ import { fileURLToPath } from "url";
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+ /**
13
+ * Detect archive type from filename extension
14
+ */
15
+ export function detectArchiveType(filename) {
16
+ const ext = extname(filename).toLowerCase();
17
+ switch (ext) {
18
+ case ".zip":
19
+ return "zip";
20
+ case ".7z":
21
+ return "7z";
22
+ case ".rar":
23
+ return "rar";
24
+ default:
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Load password list from config file
30
+ * Returns default list if file not found
31
+ */
32
+ export function loadPasswordList() {
33
+ const defaultPasswords = ["infected", "malware", "virus"];
34
+ try {
35
+ const passwordFile = join(__dirname, "config", "archive-passwords.txt");
36
+ const content = readFileSync(passwordFile, "utf-8");
37
+ const passwords = content
38
+ .split("\n")
39
+ .map((line) => line.trim())
40
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
41
+ return passwords.length > 0 ? passwords : defaultPasswords;
42
+ }
43
+ catch {
44
+ // Fallback to defaults if file not found
45
+ return defaultPasswords;
46
+ }
47
+ }
48
+ /**
49
+ * Build extraction command for given archive type and tool
50
+ *
51
+ * @param archiveType - Type of archive (zip, 7z, rar)
52
+ * @param archivePath - Full path to archive file
53
+ * @param outputDir - Directory to extract files to
54
+ * @param password - Optional password for encrypted archives
55
+ * @returns Command array for execution
56
+ */
57
+ export function getExtractionCommand(archiveType, archivePath, outputDir, password) {
58
+ // Validate inputs don't contain shell metacharacters
59
+ // This is defense-in-depth; the connector should also escape properly
60
+ if (password && /[;&|`$\n\r'"\\]/.test(password)) {
61
+ throw new Error("Password contains invalid characters");
62
+ }
63
+ switch (archiveType) {
64
+ case "7z":
65
+ // 7z x archive.7z -ooutputdir -ppassword -y
66
+ // -y: assume yes on all queries (non-interactive)
67
+ const cmd7z = ["7z", "x", archivePath, `-o${outputDir}`, "-y"];
68
+ if (password) {
69
+ cmd7z.push(`-p${password}`);
70
+ }
71
+ return cmd7z;
72
+ case "zip":
73
+ // unzip -P password -d outputdir archive.zip
74
+ // -o: overwrite without prompting
75
+ const cmdZip = ["unzip", "-o"];
76
+ if (password) {
77
+ cmdZip.push("-P", password);
78
+ }
79
+ cmdZip.push("-d", outputDir, archivePath);
80
+ return cmdZip;
81
+ case "rar":
82
+ // unrar x -ppassword archive.rar outputdir/
83
+ // -o+: overwrite existing files
84
+ const cmdRar = ["unrar", "x", "-o+"];
85
+ if (password) {
86
+ cmdRar.push(`-p${password}`);
87
+ }
88
+ cmdRar.push(archivePath, outputDir + "/");
89
+ return cmdRar;
90
+ default:
91
+ throw new Error(`Unsupported archive type: ${archiveType}`);
92
+ }
93
+ }
94
+ /**
95
+ * Check if extraction result indicates wrong password
96
+ */
97
+ function isWrongPasswordError(result, _archiveType) {
98
+ const output = (result.stderr + result.stdout).toLowerCase();
99
+ // Common password error patterns across tools
100
+ const passwordErrors = [
101
+ "wrong password",
102
+ "incorrect password",
103
+ "bad password",
104
+ "password required",
105
+ "encrypted",
106
+ "need password",
107
+ "checksum error", // 7z uses this for wrong password
108
+ "crc failed", // unrar uses this for wrong password
109
+ ];
110
+ // Check if exit code indicates failure AND output mentions password issues
111
+ if (result.exitCode !== 0) {
112
+ return passwordErrors.some((pattern) => output.includes(pattern));
113
+ }
114
+ return false;
115
+ }
116
+ /**
117
+ * List files in a directory
118
+ */
119
+ async function listExtractedFiles(connector, outputDir) {
120
+ try {
121
+ const result = await connector.execute(["find", outputDir, "-type", "f", "-printf", "%P\\n"], { timeout: 30000 });
122
+ if (result.exitCode === 0 && result.stdout) {
123
+ return result.stdout
124
+ .split("\n")
125
+ .map((f) => f.trim())
126
+ .filter((f) => f.length > 0);
127
+ }
128
+ // Fallback to ls if find doesn't support -printf (e.g., BSD)
129
+ const lsResult = await connector.execute(["ls", "-1R", outputDir], { timeout: 30000 });
130
+ if (lsResult.exitCode === 0 && lsResult.stdout) {
131
+ return lsResult.stdout
132
+ .split("\n")
133
+ .map((f) => f.trim())
134
+ .filter((f) => f.length > 0 && !f.endsWith(":"));
135
+ }
136
+ return [];
137
+ }
138
+ catch {
139
+ return [];
140
+ }
141
+ }
142
+ /**
143
+ * Validate that extracted files don't escape the sandbox (zip-slip protection)
144
+ * Malicious archives can contain entries like "../../../etc/cron.d/evil"
145
+ *
146
+ * @param files - Relative file paths from extraction
147
+ * @param outputDir - Expected output directory
148
+ * @returns Array of files that attempted path escape (empty if all safe)
149
+ */
150
+ function validateExtractedPaths(files, outputDir) {
151
+ const escapeAttempts = [];
152
+ const normalizedBase = resolve(outputDir);
153
+ for (const file of files) {
154
+ // Check for obvious traversal patterns
155
+ if (file.includes("..") || file.startsWith("/")) {
156
+ escapeAttempts.push(file);
157
+ continue;
158
+ }
159
+ // Resolve the full path and ensure it stays within outputDir
160
+ const resolvedPath = resolve(outputDir, normalize(file));
161
+ if (!resolvedPath.startsWith(normalizedBase + "/") && resolvedPath !== normalizedBase) {
162
+ escapeAttempts.push(file);
163
+ }
164
+ }
165
+ return escapeAttempts;
166
+ }
167
+ /**
168
+ * Extract an archive file with automatic password detection
169
+ *
170
+ * @param connector - Connector to execute commands on REMnux
171
+ * @param archivePath - Full path to archive file inside REMnux
172
+ * @param samplesDir - Base samples directory for output
173
+ * @param customPassword - Optional password to try first
174
+ * @param outputSubdir - Optional subdirectory name (defaults to archive name without extension)
175
+ * @returns Extraction result with file list and password used
176
+ */
177
+ export async function extractArchive(connector, archivePath, samplesDir, customPassword, outputSubdir) {
178
+ // Detect archive type
179
+ const archiveType = detectArchiveType(archivePath);
180
+ if (!archiveType) {
181
+ return {
182
+ success: false,
183
+ files: [],
184
+ error: `Unsupported archive format: ${extname(archivePath)}`,
185
+ outputDir: "",
186
+ };
187
+ }
188
+ // Determine output directory
189
+ const archiveName = basename(archivePath, extname(archivePath));
190
+ const subdir = outputSubdir || archiveName;
191
+ const outputDir = join(samplesDir, subdir);
192
+ // Create output directory
193
+ const mkdirResult = await connector.execute(["mkdir", "-p", outputDir], {
194
+ timeout: 10000,
195
+ });
196
+ if (mkdirResult.exitCode !== 0) {
197
+ return {
198
+ success: false,
199
+ files: [],
200
+ error: `Failed to create output directory: ${mkdirResult.stderr}`,
201
+ outputDir,
202
+ };
203
+ }
204
+ // Build password list to try
205
+ const passwordsToTry = [];
206
+ // 1. Custom password first if provided
207
+ if (customPassword) {
208
+ passwordsToTry.push(customPassword);
209
+ }
210
+ // 2. Try without password (archive might not be encrypted)
211
+ passwordsToTry.push(undefined);
212
+ // 3. Add passwords from config file
213
+ const configPasswords = loadPasswordList();
214
+ for (const pwd of configPasswords) {
215
+ if (pwd !== customPassword) {
216
+ // Avoid duplicates
217
+ passwordsToTry.push(pwd);
218
+ }
219
+ }
220
+ // Try extraction with each password
221
+ let lastError = "";
222
+ for (const password of passwordsToTry) {
223
+ try {
224
+ const cmd = getExtractionCommand(archiveType, archivePath, outputDir, password);
225
+ const result = await connector.execute(cmd, { timeout: 120000 });
226
+ if (result.exitCode === 0) {
227
+ // Success! List extracted files
228
+ const files = await listExtractedFiles(connector, outputDir);
229
+ // Validate for zip-slip attacks (path traversal in archive entries)
230
+ const escapeAttempts = validateExtractedPaths(files, outputDir);
231
+ if (escapeAttempts.length > 0) {
232
+ // Clean up potentially malicious files
233
+ await connector.execute(["rm", "-rf", outputDir], { timeout: 30000 });
234
+ return {
235
+ success: false,
236
+ files: [],
237
+ error: `Archive contains path escape attempts (zip-slip): ${escapeAttempts.slice(0, 3).join(", ")}${escapeAttempts.length > 3 ? ` and ${escapeAttempts.length - 3} more` : ""}`,
238
+ outputDir,
239
+ };
240
+ }
241
+ return {
242
+ success: true,
243
+ files,
244
+ password: password, // undefined if no password was needed
245
+ outputDir,
246
+ };
247
+ }
248
+ // Check if this is a password error
249
+ if (isWrongPasswordError(result, archiveType)) {
250
+ lastError = "Incorrect password";
251
+ continue; // Try next password
252
+ }
253
+ // Some other error - might still try other passwords for encrypted archives
254
+ lastError = result.stderr || result.stdout || "Unknown extraction error";
255
+ }
256
+ catch (err) {
257
+ lastError = err instanceof Error ? err.message : "Extraction failed";
258
+ }
259
+ }
260
+ // All passwords failed
261
+ return {
262
+ success: false,
263
+ files: [],
264
+ error: `Extraction failed: ${lastError}. Tried ${passwordsToTry.length} password(s).`,
265
+ outputDir,
266
+ };
267
+ }
268
+ //# sourceMappingURL=archive-extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive-extractor.js","sourceRoot":"","sources":["../src/archive-extractor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAGpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAYtC;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,KAAK;YACR,OAAO,IAAI,CAAC;QACd,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,gBAAgB,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAE1D,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,uBAAuB,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,OAAO;aACtB,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAE9D,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;QACzC,OAAO,gBAAgB,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAwB,EACxB,WAAmB,EACnB,SAAiB,EACjB,QAAiB;IAEjB,qDAAqD;IACrD,sEAAsE;IACtE,IAAI,QAAQ,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,QAAQ,WAAW,EAAE,CAAC;QACpB,KAAK,IAAI;YACP,4CAA4C;YAC5C,kDAAkD;YAClD,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,EAAE,IAAI,CAAC,CAAC;YAC/D,IAAI,QAAQ,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;YAC9B,CAAC;YACD,OAAO,KAAK,CAAC;QAEf,KAAK,KAAK;YACR,6CAA6C;YAC7C,kCAAkC;YAClC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC/B,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC9B,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAEhB,KAAK,KAAK;YACR,4CAA4C;YAC5C,gCAAgC;YAChC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YACrC,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;YAC/B,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,GAAG,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAEhB;YACE,MAAM,IAAI,KAAK,CAAC,6BAA6B,WAAW,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,MAAkB,EAAE,YAAyB;IACzE,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAE7D,8CAA8C;IAC9C,MAAM,cAAc,GAAG;QACrB,gBAAgB;QAChB,oBAAoB;QACpB,cAAc;QACd,mBAAmB;QACnB,WAAW;QACX,eAAe;QACf,gBAAgB,EAAE,kCAAkC;QACpD,YAAY,EAAE,qCAAqC;KACpD,CAAC;IAEF,2EAA2E;IAC3E,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAC/B,SAAoB,EACpB,SAAiB;IAEjB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CACpC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EACrD,EAAE,OAAO,EAAE,KAAK,EAAE,CACnB,CAAC;QAEF,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAC3C,OAAO,MAAM,CAAC,MAAM;iBACjB,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjC,CAAC;QAED,6DAA6D;QAC7D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,OAAO,CACtC,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,EACxB,EAAE,OAAO,EAAE,KAAK,EAAE,CACnB,CAAC;QAEF,IAAI,QAAQ,CAAC,QAAQ,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YAC/C,OAAO,QAAQ,CAAC,MAAM;iBACnB,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,sBAAsB,CAAC,KAAe,EAAE,SAAiB;IAChE,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,MAAM,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,uCAAuC;QACvC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAChD,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,SAAS;QACX,CAAC;QAED,6DAA6D;QAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,cAAc,GAAG,GAAG,CAAC,IAAI,YAAY,KAAK,cAAc,EAAE,CAAC;YACtF,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAoB,EACpB,WAAmB,EACnB,UAAkB,EAClB,cAAuB,EACvB,YAAqB;IAErB,sBAAsB;IACtB,MAAM,WAAW,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,EAAE;YACT,KAAK,EAAE,+BAA+B,OAAO,CAAC,WAAW,CAAC,EAAE;YAC5D,SAAS,EAAE,EAAE;SACd,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,YAAY,IAAI,WAAW,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAE3C,0BAA0B;IAC1B,MAAM,WAAW,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE;QACtE,OAAO,EAAE,KAAK;KACf,CAAC,CAAC;IACH,IAAI,WAAW,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,EAAE;YACT,KAAK,EAAE,sCAAsC,WAAW,CAAC,MAAM,EAAE;YACjE,SAAS;SACV,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,MAAM,cAAc,GAA2B,EAAE,CAAC;IAElD,uCAAuC;IACvC,IAAI,cAAc,EAAE,CAAC;QACnB,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACtC,CAAC;IAED,2DAA2D;IAC3D,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAE/B,oCAAoC;IACpC,MAAM,eAAe,GAAG,gBAAgB,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAClC,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAC3B,mBAAmB;YACnB,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,oCAAoC;IACpC,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,KAAK,MAAM,QAAQ,IAAI,cAAc,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,oBAAoB,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;YAChF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAEjE,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;gBAC1B,gCAAgC;gBAChC,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;gBAE7D,oEAAoE;gBACpE,MAAM,cAAc,GAAG,sBAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;gBAChE,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,uCAAuC;oBACvC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;oBACtE,OAAO;wBACL,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,EAAE;wBACT,KAAK,EAAE,qDAAqD,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,cAAc,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;wBAC/K,SAAS;qBACV,CAAC;gBACJ,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,KAAK;oBACL,QAAQ,EAAE,QAAQ,EAAE,sCAAsC;oBAC1D,SAAS;iBACV,CAAC;YACJ,CAAC;YAED,oCAAoC;YACpC,IAAI,oBAAoB,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;gBAC9C,SAAS,GAAG,oBAAoB,CAAC;gBACjC,SAAS,CAAC,oBAAoB;YAChC,CAAC;YAED,4EAA4E;YAC5E,SAAS,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,0BAA0B,CAAC;QAC3E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC;QACvE,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,OAAO;QACL,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,EAAE;QACT,KAAK,EAAE,sBAAsB,SAAS,WAAW,cAAc,CAAC,MAAM,eAAe;QACrF,SAAS;KACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Tool Catalog — complete REMnux tool inventory from salt-states.
3
+ *
4
+ * Provides discovery of all ~200 tools on REMnux, complementing the
5
+ * smaller auto-run registry (~35 tools) used by analyze_file.
6
+ */
7
+ export interface CatalogTool {
8
+ command: string;
9
+ name: string;
10
+ category: string;
11
+ description: string;
12
+ website: string;
13
+ }
14
+ export interface ToolsIndex {
15
+ version: string;
16
+ updated: string;
17
+ tools: CatalogTool[];
18
+ }
19
+ declare class ToolCatalog {
20
+ private tools;
21
+ private byMcpCategory;
22
+ private bySaltCategory;
23
+ readonly version: string;
24
+ readonly updated: string;
25
+ constructor(index: ToolsIndex);
26
+ /** All tools in the catalog. */
27
+ all(): readonly CatalogTool[];
28
+ /** Tools matching an MCP file-type category (PE, PDF, OLE2, etc.). */
29
+ forMcpCategory(mcpCategory: string): CatalogTool[];
30
+ /** Tools matching a salt-states category string. */
31
+ forSaltCategory(saltCategory: string): CatalogTool[];
32
+ /** All unique salt-states categories. */
33
+ categories(): string[];
34
+ /** Total tool count. */
35
+ get size(): number;
36
+ }
37
+ /** Singleton catalog instance. */
38
+ export declare const toolCatalog: ToolCatalog;
39
+ export {};
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/catalog/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AA0DD,cAAM,WAAW;IACf,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,cAAc,CAA6B;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,KAAK,EAAE,UAAU;IA0B7B,gCAAgC;IAChC,GAAG,IAAI,SAAS,WAAW,EAAE;IAI7B,sEAAsE;IACtE,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,EAAE;IAIlD,oDAAoD;IACpD,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,EAAE;IAIpD,yCAAyC;IACzC,UAAU,IAAI,MAAM,EAAE;IAItB,wBAAwB;IACxB,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAWD,kCAAkC;AAClC,eAAO,MAAM,WAAW,aAA+B,CAAC"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Tool Catalog — complete REMnux tool inventory from salt-states.
3
+ *
4
+ * Provides discovery of all ~200 tools on REMnux, complementing the
5
+ * smaller auto-run registry (~35 tools) used by analyze_file.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+ import { resolve, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ /**
11
+ * Maps salt-states categories to MCP file-type category names.
12
+ * Multiple salt-states categories can map to one MCP category.
13
+ */
14
+ const SALT_TO_MCP_CATEGORY = {
15
+ // PE
16
+ "Examine Static Properties: PE Files": "PE",
17
+ "Statically Analyze Code: PE Files": "PE",
18
+ "Statically Analyze Code: Unpacking": "PE",
19
+ // .NET
20
+ "Examine Static Properties: .NET": "DOTNET",
21
+ "Statically Analyze Code: .NET": "DOTNET",
22
+ // PDF
23
+ "Analyze Documents: PDF": "PDF",
24
+ // Office (OLE2, OOXML, RTF share tools)
25
+ "Analyze Documents: Microsoft Office": "OLE2",
26
+ // ELF
27
+ "Examine Static Properties: ELF Files": "ELF",
28
+ "Dynamically Reverse-Engineer Code: ELF Files": "ELF",
29
+ // Scripts
30
+ "Statically Analyze Code: Scripts": "Script",
31
+ "Dynamically Reverse-Engineer Code: Scripts": "Script",
32
+ // Java/JAR
33
+ "Statically Analyze Code: Java": "JAR",
34
+ // Email
35
+ "Analyze Documents: Email Messages": "Email",
36
+ // Android/APK
37
+ "Statically Analyze Code: Android": "APK",
38
+ // General document analysis maps to OLE2 (broad category)
39
+ "Analyze Documents: General": "OLE2",
40
+ };
41
+ /**
42
+ * Reverse map: MCP category → set of salt-states categories.
43
+ */
44
+ const MCP_TO_SALT_CATEGORIES = new Map();
45
+ for (const [salt, mcp] of Object.entries(SALT_TO_MCP_CATEGORY)) {
46
+ const existing = MCP_TO_SALT_CATEGORIES.get(mcp) ?? [];
47
+ existing.push(salt);
48
+ MCP_TO_SALT_CATEGORIES.set(mcp, existing);
49
+ }
50
+ // OOXML and RTF share Office analysis tools with OLE2
51
+ const officeCats = MCP_TO_SALT_CATEGORIES.get("OLE2") ?? [];
52
+ MCP_TO_SALT_CATEGORIES.set("OOXML", [...officeCats]);
53
+ MCP_TO_SALT_CATEGORIES.set("RTF", [...officeCats]);
54
+ class ToolCatalog {
55
+ tools;
56
+ byMcpCategory;
57
+ bySaltCategory;
58
+ version;
59
+ updated;
60
+ constructor(index) {
61
+ this.tools = index.tools;
62
+ this.version = index.version;
63
+ this.updated = index.updated;
64
+ // Index by salt-states category
65
+ this.bySaltCategory = new Map();
66
+ for (const tool of this.tools) {
67
+ const existing = this.bySaltCategory.get(tool.category) ?? [];
68
+ existing.push(tool);
69
+ this.bySaltCategory.set(tool.category, existing);
70
+ }
71
+ // Index by MCP category
72
+ this.byMcpCategory = new Map();
73
+ for (const [mcpCat, saltCats] of MCP_TO_SALT_CATEGORIES) {
74
+ const tools = [];
75
+ for (const saltCat of saltCats) {
76
+ tools.push(...(this.bySaltCategory.get(saltCat) ?? []));
77
+ }
78
+ if (tools.length > 0) {
79
+ this.byMcpCategory.set(mcpCat, tools);
80
+ }
81
+ }
82
+ }
83
+ /** All tools in the catalog. */
84
+ all() {
85
+ return this.tools;
86
+ }
87
+ /** Tools matching an MCP file-type category (PE, PDF, OLE2, etc.). */
88
+ forMcpCategory(mcpCategory) {
89
+ return this.byMcpCategory.get(mcpCategory) ?? [];
90
+ }
91
+ /** Tools matching a salt-states category string. */
92
+ forSaltCategory(saltCategory) {
93
+ return this.bySaltCategory.get(saltCategory) ?? [];
94
+ }
95
+ /** All unique salt-states categories. */
96
+ categories() {
97
+ return [...this.bySaltCategory.keys()].sort();
98
+ }
99
+ /** Total tool count. */
100
+ get size() {
101
+ return this.tools.length;
102
+ }
103
+ }
104
+ /** Load the bundled tools-index.json. */
105
+ function loadIndex() {
106
+ const __dirname = dirname(fileURLToPath(import.meta.url));
107
+ // data/ is at package root, two levels up from dist/catalog/ or src/catalog/
108
+ const indexPath = resolve(__dirname, "../../data/tools-index.json");
109
+ const raw = readFileSync(indexPath, "utf-8");
110
+ return JSON.parse(raw);
111
+ }
112
+ /** Singleton catalog instance. */
113
+ export const toolCatalog = new ToolCatalog(loadIndex());
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/catalog/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAgBzC;;;GAGG;AACH,MAAM,oBAAoB,GAA2B;IACnD,KAAK;IACL,qCAAqC,EAAE,IAAI;IAC3C,mCAAmC,EAAE,IAAI;IACzC,oCAAoC,EAAE,IAAI;IAE1C,OAAO;IACP,iCAAiC,EAAE,QAAQ;IAC3C,+BAA+B,EAAE,QAAQ;IAEzC,MAAM;IACN,wBAAwB,EAAE,KAAK;IAE/B,wCAAwC;IACxC,qCAAqC,EAAE,MAAM;IAE7C,MAAM;IACN,sCAAsC,EAAE,KAAK;IAC7C,8CAA8C,EAAE,KAAK;IAErD,UAAU;IACV,kCAAkC,EAAE,QAAQ;IAC5C,4CAA4C,EAAE,QAAQ;IAEtD,WAAW;IACX,+BAA+B,EAAE,KAAK;IAEtC,QAAQ;IACR,mCAAmC,EAAE,OAAO;IAE5C,cAAc;IACd,kCAAkC,EAAE,KAAK;IAEzC,0DAA0D;IAC1D,4BAA4B,EAAE,MAAM;CACrC,CAAC;AAEF;;GAEG;AACH,MAAM,sBAAsB,GAA0B,IAAI,GAAG,EAAE,CAAC;AAChE,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC;IAC/D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IACvD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,sBAAsB,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,GAAG,sBAAsB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AAC5D,sBAAsB,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AACrD,sBAAsB,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AAEnD,MAAM,WAAW;IACP,KAAK,CAAgB;IACrB,aAAa,CAA6B;IAC1C,cAAc,CAA6B;IAC1C,OAAO,CAAS;IAChB,OAAO,CAAS;IAEzB,YAAY,KAAiB;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAE7B,gCAAgC;QAChC,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC9D,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACnD,CAAC;QAED,wBAAwB;QACxB,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,sBAAsB,EAAE,CAAC;YACxD,MAAM,KAAK,GAAkB,EAAE,CAAC;YAChC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;YACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,GAAG;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,sEAAsE;IACtE,cAAc,CAAC,WAAmB;QAChC,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;IAED,oDAAoD;IACpD,eAAe,CAAC,YAAoB;QAClC,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IACrD,CAAC;IAED,yCAAyC;IACzC,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,CAAC;IAED,wBAAwB;IACxB,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;CACF;AAED,yCAAyC;AACzC,SAAS,SAAS;IAChB,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,6EAA6E;IAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,6BAA6B,CAAC,CAAC;IACpE,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;AACvC,CAAC;AAED,kCAAkC;AAClC,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from "./index.js";
3
+ function parseArgs() {
4
+ const args = process.argv.slice(2);
5
+ const config = {
6
+ mode: "docker",
7
+ container: "remnux",
8
+ samplesDir: "/home/remnux/files/samples",
9
+ outputDir: "/home/remnux/files/output",
10
+ timeout: 300,
11
+ noSandbox: true,
12
+ };
13
+ for (let i = 0; i < args.length; i++) {
14
+ let arg = args[i];
15
+ let value = args[i + 1];
16
+ let usedEqualsSyntax = false;
17
+ // Support --flag=value syntax: split on first '='
18
+ if (arg.startsWith("--") && arg.includes("=")) {
19
+ const eqIndex = arg.indexOf("=");
20
+ value = arg.slice(eqIndex + 1);
21
+ arg = arg.slice(0, eqIndex);
22
+ usedEqualsSyntax = true;
23
+ }
24
+ // Helper: only skip next arg if value came from separate arg (not '=' syntax)
25
+ const consumeValue = () => { if (!usedEqualsSyntax)
26
+ i++; };
27
+ switch (arg) {
28
+ case "--mode":
29
+ if (value === "docker" || value === "ssh" || value === "local") {
30
+ config.mode = value;
31
+ }
32
+ consumeValue();
33
+ break;
34
+ case "--container":
35
+ config.container = value;
36
+ consumeValue();
37
+ break;
38
+ case "--host":
39
+ config.host = value;
40
+ consumeValue();
41
+ break;
42
+ case "--user":
43
+ config.user = value;
44
+ consumeValue();
45
+ break;
46
+ case "--port":
47
+ config.port = parseInt(value, 10);
48
+ consumeValue();
49
+ break;
50
+ case "--password":
51
+ config.password = value;
52
+ consumeValue();
53
+ break;
54
+ case "--samples-dir":
55
+ config.samplesDir = value;
56
+ consumeValue();
57
+ break;
58
+ case "--output-dir":
59
+ config.outputDir = value;
60
+ consumeValue();
61
+ break;
62
+ case "--timeout":
63
+ config.timeout = parseInt(value, 10);
64
+ consumeValue();
65
+ break;
66
+ case "--sandbox":
67
+ config.noSandbox = false;
68
+ break;
69
+ case "--no-sandbox":
70
+ // Backwards compatibility — already the default
71
+ break;
72
+ case "--transport":
73
+ if (value === "stdio" || value === "http") {
74
+ config.transport = value;
75
+ }
76
+ consumeValue();
77
+ break;
78
+ case "--http-port":
79
+ config.httpPort = parseInt(value, 10);
80
+ consumeValue();
81
+ break;
82
+ case "--http-host":
83
+ config.httpHost = value;
84
+ consumeValue();
85
+ break;
86
+ case "--http-token":
87
+ config.httpToken = value;
88
+ consumeValue();
89
+ break;
90
+ case "--help":
91
+ case "-h":
92
+ printHelp();
93
+ process.exit(0);
94
+ case "--version":
95
+ case "-v":
96
+ console.log("remnux-mcp-server v0.1.0");
97
+ process.exit(0);
98
+ }
99
+ }
100
+ // Read token from env var if not set via CLI
101
+ if (!config.httpToken && process.env.MCP_TOKEN) {
102
+ config.httpToken = process.env.MCP_TOKEN;
103
+ }
104
+ return config;
105
+ }
106
+ function printHelp() {
107
+ console.log(`
108
+ remnux-mcp-server - MCP server for REMnux malware analysis tools
109
+
110
+ USAGE:
111
+ remnux-mcp-server [OPTIONS]
112
+
113
+ OPTIONS:
114
+ --mode <mode> Connection mode: docker, ssh, or local (default: docker)
115
+ --container <name> Docker container name/ID (default: remnux)
116
+ --host <host> SSH host (for ssh mode)
117
+ --user <user> SSH user (default: remnux)
118
+ --port <port> SSH port (default: 22)
119
+ --password <pass> SSH password (uses SSH agent if omitted)
120
+ --samples-dir <path> Path to samples directory (default: /home/remnux/files/samples)
121
+ --output-dir <path> Path to output directory (default: /home/remnux/files/output)
122
+ --timeout <seconds> Default command timeout (default: 300)
123
+ --sandbox Enable path sandboxing (restrict files to samples/output dirs)
124
+ --no-sandbox No-op (sandbox is already off by default)
125
+ --transport <mode> Transport: stdio (default) or http
126
+ --http-port <port> HTTP port (default: 3000)
127
+ --http-host <host> HTTP bind address (default: 127.0.0.1)
128
+ --http-token <token> Bearer token for HTTP auth (also reads MCP_TOKEN env var)
129
+ -h, --help Show this help message
130
+ -v, --version Show version
131
+
132
+ EXAMPLES:
133
+ # Docker mode (default, stdio transport)
134
+ remnux-mcp-server --mode=docker --container=remnux
135
+
136
+ # SSH mode
137
+ remnux-mcp-server --mode=ssh --host=192.168.1.100 --user=remnux
138
+
139
+ # HTTP transport (Model B: server inside REMnux)
140
+ remnux-mcp-server --mode=local --transport=http --http-token=SECRET
141
+
142
+ # Add to Claude Code (stdio)
143
+ claude mcp add remnux --transport stdio -- npx remnux-mcp-server
144
+
145
+ For tool discovery and documentation, use the REMnux docs MCP:
146
+ https://docs.remnux.org/~gitbook/mcp
147
+ `);
148
+ }
149
+ // Start server
150
+ startServer(parseArgs()).catch((error) => {
151
+ console.error("Failed to start server:", error);
152
+ process.exit(1);
153
+ });
154
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}