defense-mcp-server 0.6.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 (186) hide show
  1. package/CHANGELOG.md +471 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/build/core/auto-installer.d.ts +102 -0
  5. package/build/core/auto-installer.d.ts.map +1 -0
  6. package/build/core/auto-installer.js +833 -0
  7. package/build/core/backup-manager.d.ts +63 -0
  8. package/build/core/backup-manager.d.ts.map +1 -0
  9. package/build/core/backup-manager.js +189 -0
  10. package/build/core/changelog.d.ts +75 -0
  11. package/build/core/changelog.d.ts.map +1 -0
  12. package/build/core/changelog.js +123 -0
  13. package/build/core/command-allowlist.d.ts +129 -0
  14. package/build/core/command-allowlist.d.ts.map +1 -0
  15. package/build/core/command-allowlist.js +849 -0
  16. package/build/core/config.d.ts +79 -0
  17. package/build/core/config.d.ts.map +1 -0
  18. package/build/core/config.js +193 -0
  19. package/build/core/dependency-validator.d.ts +106 -0
  20. package/build/core/dependency-validator.d.ts.map +1 -0
  21. package/build/core/dependency-validator.js +405 -0
  22. package/build/core/distro-adapter.d.ts +177 -0
  23. package/build/core/distro-adapter.d.ts.map +1 -0
  24. package/build/core/distro-adapter.js +481 -0
  25. package/build/core/distro.d.ts +68 -0
  26. package/build/core/distro.d.ts.map +1 -0
  27. package/build/core/distro.js +457 -0
  28. package/build/core/encrypted-state.d.ts +76 -0
  29. package/build/core/encrypted-state.d.ts.map +1 -0
  30. package/build/core/encrypted-state.js +209 -0
  31. package/build/core/executor.d.ts +56 -0
  32. package/build/core/executor.d.ts.map +1 -0
  33. package/build/core/executor.js +350 -0
  34. package/build/core/installer.d.ts +92 -0
  35. package/build/core/installer.d.ts.map +1 -0
  36. package/build/core/installer.js +1072 -0
  37. package/build/core/logger.d.ts +102 -0
  38. package/build/core/logger.d.ts.map +1 -0
  39. package/build/core/logger.js +132 -0
  40. package/build/core/parsers.d.ts +151 -0
  41. package/build/core/parsers.d.ts.map +1 -0
  42. package/build/core/parsers.js +479 -0
  43. package/build/core/policy-engine.d.ts +170 -0
  44. package/build/core/policy-engine.d.ts.map +1 -0
  45. package/build/core/policy-engine.js +656 -0
  46. package/build/core/preflight.d.ts +157 -0
  47. package/build/core/preflight.d.ts.map +1 -0
  48. package/build/core/preflight.js +638 -0
  49. package/build/core/privilege-manager.d.ts +108 -0
  50. package/build/core/privilege-manager.d.ts.map +1 -0
  51. package/build/core/privilege-manager.js +363 -0
  52. package/build/core/rate-limiter.d.ts +67 -0
  53. package/build/core/rate-limiter.d.ts.map +1 -0
  54. package/build/core/rate-limiter.js +129 -0
  55. package/build/core/rollback.d.ts +73 -0
  56. package/build/core/rollback.d.ts.map +1 -0
  57. package/build/core/rollback.js +278 -0
  58. package/build/core/safeguards.d.ts +58 -0
  59. package/build/core/safeguards.d.ts.map +1 -0
  60. package/build/core/safeguards.js +448 -0
  61. package/build/core/sanitizer.d.ts +118 -0
  62. package/build/core/sanitizer.d.ts.map +1 -0
  63. package/build/core/sanitizer.js +459 -0
  64. package/build/core/secure-fs.d.ts +67 -0
  65. package/build/core/secure-fs.d.ts.map +1 -0
  66. package/build/core/secure-fs.js +143 -0
  67. package/build/core/spawn-safe.d.ts +55 -0
  68. package/build/core/spawn-safe.d.ts.map +1 -0
  69. package/build/core/spawn-safe.js +146 -0
  70. package/build/core/sudo-guard.d.ts +145 -0
  71. package/build/core/sudo-guard.d.ts.map +1 -0
  72. package/build/core/sudo-guard.js +349 -0
  73. package/build/core/sudo-session.d.ts +100 -0
  74. package/build/core/sudo-session.d.ts.map +1 -0
  75. package/build/core/sudo-session.js +319 -0
  76. package/build/core/tool-dependencies.d.ts +61 -0
  77. package/build/core/tool-dependencies.d.ts.map +1 -0
  78. package/build/core/tool-dependencies.js +571 -0
  79. package/build/core/tool-registry.d.ts +111 -0
  80. package/build/core/tool-registry.d.ts.map +1 -0
  81. package/build/core/tool-registry.js +656 -0
  82. package/build/core/tool-wrapper.d.ts +73 -0
  83. package/build/core/tool-wrapper.d.ts.map +1 -0
  84. package/build/core/tool-wrapper.js +296 -0
  85. package/build/index.d.ts +3 -0
  86. package/build/index.d.ts.map +1 -0
  87. package/build/index.js +247 -0
  88. package/build/tools/access-control.d.ts +9 -0
  89. package/build/tools/access-control.d.ts.map +1 -0
  90. package/build/tools/access-control.js +1818 -0
  91. package/build/tools/api-security.d.ts +12 -0
  92. package/build/tools/api-security.d.ts.map +1 -0
  93. package/build/tools/api-security.js +901 -0
  94. package/build/tools/app-hardening.d.ts +11 -0
  95. package/build/tools/app-hardening.d.ts.map +1 -0
  96. package/build/tools/app-hardening.js +768 -0
  97. package/build/tools/backup.d.ts +8 -0
  98. package/build/tools/backup.d.ts.map +1 -0
  99. package/build/tools/backup.js +381 -0
  100. package/build/tools/cloud-security.d.ts +17 -0
  101. package/build/tools/cloud-security.d.ts.map +1 -0
  102. package/build/tools/cloud-security.js +739 -0
  103. package/build/tools/compliance.d.ts +10 -0
  104. package/build/tools/compliance.d.ts.map +1 -0
  105. package/build/tools/compliance.js +1225 -0
  106. package/build/tools/container-security.d.ts +14 -0
  107. package/build/tools/container-security.d.ts.map +1 -0
  108. package/build/tools/container-security.js +788 -0
  109. package/build/tools/deception.d.ts +13 -0
  110. package/build/tools/deception.d.ts.map +1 -0
  111. package/build/tools/deception.js +763 -0
  112. package/build/tools/dns-security.d.ts +93 -0
  113. package/build/tools/dns-security.d.ts.map +1 -0
  114. package/build/tools/dns-security.js +745 -0
  115. package/build/tools/drift-detection.d.ts +8 -0
  116. package/build/tools/drift-detection.d.ts.map +1 -0
  117. package/build/tools/drift-detection.js +326 -0
  118. package/build/tools/ebpf-security.d.ts +15 -0
  119. package/build/tools/ebpf-security.d.ts.map +1 -0
  120. package/build/tools/ebpf-security.js +294 -0
  121. package/build/tools/encryption.d.ts +9 -0
  122. package/build/tools/encryption.d.ts.map +1 -0
  123. package/build/tools/encryption.js +1667 -0
  124. package/build/tools/firewall.d.ts +9 -0
  125. package/build/tools/firewall.d.ts.map +1 -0
  126. package/build/tools/firewall.js +1398 -0
  127. package/build/tools/hardening.d.ts +10 -0
  128. package/build/tools/hardening.d.ts.map +1 -0
  129. package/build/tools/hardening.js +2654 -0
  130. package/build/tools/ids.d.ts +9 -0
  131. package/build/tools/ids.d.ts.map +1 -0
  132. package/build/tools/ids.js +624 -0
  133. package/build/tools/incident-response.d.ts +10 -0
  134. package/build/tools/incident-response.d.ts.map +1 -0
  135. package/build/tools/incident-response.js +1180 -0
  136. package/build/tools/logging.d.ts +12 -0
  137. package/build/tools/logging.d.ts.map +1 -0
  138. package/build/tools/logging.js +454 -0
  139. package/build/tools/malware.d.ts +10 -0
  140. package/build/tools/malware.d.ts.map +1 -0
  141. package/build/tools/malware.js +532 -0
  142. package/build/tools/meta.d.ts +11 -0
  143. package/build/tools/meta.d.ts.map +1 -0
  144. package/build/tools/meta.js +2278 -0
  145. package/build/tools/network-defense.d.ts +12 -0
  146. package/build/tools/network-defense.d.ts.map +1 -0
  147. package/build/tools/network-defense.js +760 -0
  148. package/build/tools/patch-management.d.ts +3 -0
  149. package/build/tools/patch-management.d.ts.map +1 -0
  150. package/build/tools/patch-management.js +708 -0
  151. package/build/tools/process-security.d.ts +12 -0
  152. package/build/tools/process-security.d.ts.map +1 -0
  153. package/build/tools/process-security.js +784 -0
  154. package/build/tools/reporting.d.ts +11 -0
  155. package/build/tools/reporting.d.ts.map +1 -0
  156. package/build/tools/reporting.js +559 -0
  157. package/build/tools/secrets.d.ts +9 -0
  158. package/build/tools/secrets.d.ts.map +1 -0
  159. package/build/tools/secrets.js +596 -0
  160. package/build/tools/siem-integration.d.ts +18 -0
  161. package/build/tools/siem-integration.d.ts.map +1 -0
  162. package/build/tools/siem-integration.js +754 -0
  163. package/build/tools/sudo-management.d.ts +18 -0
  164. package/build/tools/sudo-management.d.ts.map +1 -0
  165. package/build/tools/sudo-management.js +737 -0
  166. package/build/tools/supply-chain-security.d.ts +8 -0
  167. package/build/tools/supply-chain-security.d.ts.map +1 -0
  168. package/build/tools/supply-chain-security.js +256 -0
  169. package/build/tools/threat-intel.d.ts +22 -0
  170. package/build/tools/threat-intel.d.ts.map +1 -0
  171. package/build/tools/threat-intel.js +749 -0
  172. package/build/tools/vulnerability-management.d.ts +11 -0
  173. package/build/tools/vulnerability-management.d.ts.map +1 -0
  174. package/build/tools/vulnerability-management.js +667 -0
  175. package/build/tools/waf.d.ts +12 -0
  176. package/build/tools/waf.d.ts.map +1 -0
  177. package/build/tools/waf.js +843 -0
  178. package/build/tools/wireless-security.d.ts +19 -0
  179. package/build/tools/wireless-security.d.ts.map +1 -0
  180. package/build/tools/wireless-security.js +826 -0
  181. package/build/tools/zero-trust-network.d.ts +8 -0
  182. package/build/tools/zero-trust-network.d.ts.map +1 -0
  183. package/build/tools/zero-trust-network.js +367 -0
  184. package/docs/SAFEGUARDS.md +518 -0
  185. package/docs/TOOLS-REFERENCE.md +665 -0
  186. package/package.json +87 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Output parsing utilities for defensive security tool output.
3
+ * Converts raw command output into structured data for MCP responses.
4
+ */
5
+ // ─── Generic Parsers ─────────────────────────────────────────────
6
+ /**
7
+ * Parses key:value pair output into a Record.
8
+ * Lines without the separator are skipped.
9
+ */
10
+ export function parseKeyValue(output, separator = ":") {
11
+ const result = {};
12
+ for (const line of output.split("\n")) {
13
+ const idx = line.indexOf(separator);
14
+ if (idx === -1)
15
+ continue;
16
+ const key = line.substring(0, idx).trim();
17
+ const value = line.substring(idx + separator.length).trim();
18
+ if (key.length > 0) {
19
+ result[key] = value;
20
+ }
21
+ }
22
+ return result;
23
+ }
24
+ /**
25
+ * Parses whitespace-delimited table output into an array of Records.
26
+ * First non-empty line is treated as the header row.
27
+ */
28
+ export function parseTable(output) {
29
+ const lines = output
30
+ .split("\n")
31
+ .map((l) => l.trim())
32
+ .filter((l) => l.length > 0);
33
+ if (lines.length < 2)
34
+ return [];
35
+ const headers = lines[0].split(/\s+/).map((h) => h.toLowerCase());
36
+ const rows = [];
37
+ for (let i = 1; i < lines.length; i++) {
38
+ const values = lines[i].split(/\s+/);
39
+ const row = {};
40
+ for (let j = 0; j < headers.length; j++) {
41
+ // Last column gets remainder of line to handle values with spaces
42
+ if (j === headers.length - 1) {
43
+ row[headers[j]] = values.slice(j).join(" ");
44
+ }
45
+ else {
46
+ row[headers[j]] = values[j] ?? "";
47
+ }
48
+ }
49
+ rows.push(row);
50
+ }
51
+ return rows;
52
+ }
53
+ /**
54
+ * Safely parses JSON text. Returns null on parse failure.
55
+ */
56
+ export function parseJsonSafe(text) {
57
+ try {
58
+ return JSON.parse(text);
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * Formats any data into MCP text content.
66
+ * Objects are JSON-stringified with indentation.
67
+ */
68
+ export function formatToolOutput(data) {
69
+ if (typeof data === "string") {
70
+ return { type: "text", text: data };
71
+ }
72
+ return { type: "text", text: JSON.stringify(data, null, 2) };
73
+ }
74
+ /**
75
+ * Creates a simple MCP text content object.
76
+ */
77
+ export function createTextContent(text) {
78
+ return { type: "text", text };
79
+ }
80
+ /**
81
+ * Creates an MCP text content object with an error prefix.
82
+ */
83
+ export function createErrorContent(msg) {
84
+ return { type: "text", text: `Error: ${msg}` };
85
+ }
86
+ /**
87
+ * Parses `iptables -L -n -v` output into structured rules.
88
+ */
89
+ export function parseIptablesOutput(output) {
90
+ const rules = [];
91
+ let currentChain = "";
92
+ let currentPolicy = "";
93
+ for (const line of output.split("\n")) {
94
+ const trimmed = line.trim();
95
+ if (!trimmed)
96
+ continue;
97
+ // Chain header: "Chain INPUT (policy ACCEPT 0 packets, 0 bytes)"
98
+ const chainMatch = trimmed.match(/^Chain\s+(\S+)\s+\(policy\s+(\S+).*\)/);
99
+ if (chainMatch) {
100
+ currentChain = chainMatch[1];
101
+ currentPolicy = chainMatch[2];
102
+ continue;
103
+ }
104
+ // Chain header without policy: "Chain DOCKER (1 references)"
105
+ const chainRefMatch = trimmed.match(/^Chain\s+(\S+)\s+\(/);
106
+ if (chainRefMatch) {
107
+ currentChain = chainRefMatch[1];
108
+ currentPolicy = "";
109
+ continue;
110
+ }
111
+ // Skip table header
112
+ if (trimmed.startsWith("pkts") || trimmed.startsWith("num"))
113
+ continue;
114
+ // Rule line: "pkts bytes target prot opt in out source destination extra..."
115
+ const parts = trimmed.split(/\s+/);
116
+ if (parts.length >= 9) {
117
+ rules.push({
118
+ chain: currentChain,
119
+ policy: currentPolicy || undefined,
120
+ packets: parts[0],
121
+ bytes: parts[1],
122
+ target: parts[2],
123
+ protocol: parts[3],
124
+ opt: parts[4],
125
+ in: parts[5],
126
+ out: parts[6],
127
+ source: parts[7],
128
+ destination: parts[8],
129
+ extra: parts.slice(9).join(" "),
130
+ });
131
+ }
132
+ }
133
+ return rules;
134
+ }
135
+ /**
136
+ * Parses `nft list ruleset` output into structured sections.
137
+ */
138
+ export function parseNftOutput(output) {
139
+ const result = {};
140
+ let currentTable = "";
141
+ for (const line of output.split("\n")) {
142
+ const trimmed = line.trim();
143
+ if (!trimmed)
144
+ continue;
145
+ const tableMatch = trimmed.match(/^table\s+(\S+\s+\S+)\s*\{/);
146
+ if (tableMatch) {
147
+ currentTable = tableMatch[1];
148
+ result[currentTable] = [];
149
+ continue;
150
+ }
151
+ if (currentTable && trimmed !== "}") {
152
+ result[currentTable]?.push(trimmed);
153
+ }
154
+ if (trimmed === "}" && currentTable) {
155
+ currentTable = "";
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ /**
161
+ * Parses `sysctl -a` output into structured entries.
162
+ */
163
+ export function parseSysctlOutput(output) {
164
+ const entries = [];
165
+ for (const line of output.split("\n")) {
166
+ const idx = line.indexOf("=");
167
+ if (idx === -1)
168
+ continue;
169
+ const key = line.substring(0, idx).trim();
170
+ const value = line.substring(idx + 1).trim();
171
+ if (key.length > 0) {
172
+ entries.push({ key, value });
173
+ }
174
+ }
175
+ return entries;
176
+ }
177
+ /**
178
+ * Parses `ausearch` output into structured audit entries.
179
+ */
180
+ export function parseAuditdOutput(output) {
181
+ const entries = [];
182
+ let current = null;
183
+ for (const line of output.split("\n")) {
184
+ const trimmed = line.trim();
185
+ if (!trimmed || trimmed.startsWith("----") || trimmed.startsWith("time->"))
186
+ continue;
187
+ // type=SYSCALL msg=audit(1234567890.123:456): ...
188
+ const typeMatch = trimmed.match(/^type=(\S+)\s+msg=audit\(([^)]+)\):\s*(.*)/);
189
+ if (typeMatch) {
190
+ current = {
191
+ type: typeMatch[1],
192
+ timestamp: typeMatch[2],
193
+ fields: {},
194
+ };
195
+ // Parse key=value pairs from rest of line
196
+ const rest = typeMatch[3];
197
+ const kvRe = /(\w+)=("[^"]*"|\S+)/g;
198
+ let match;
199
+ while ((match = kvRe.exec(rest)) !== null) {
200
+ current.fields[match[1]] = match[2].replace(/^"|"$/g, "");
201
+ }
202
+ entries.push(current);
203
+ }
204
+ }
205
+ return entries;
206
+ }
207
+ /**
208
+ * Parses Lynis audit output for findings/warnings/suggestions.
209
+ */
210
+ export function parseLynisOutput(output) {
211
+ const findings = [];
212
+ for (const line of output.split("\n")) {
213
+ const trimmed = line.trim();
214
+ // Warning: [TEST-ID] Description
215
+ const warningMatch = trimmed.match(/^\s*Warning:\s*\[(\S+)\]\s*(.*)/);
216
+ if (warningMatch) {
217
+ findings.push({
218
+ severity: "warning",
219
+ testId: warningMatch[1],
220
+ description: warningMatch[2],
221
+ });
222
+ continue;
223
+ }
224
+ // Suggestion: [TEST-ID] Description
225
+ const suggestionMatch = trimmed.match(/^\s*Suggestion:\s*\[(\S+)\]\s*(.*)/);
226
+ if (suggestionMatch) {
227
+ findings.push({
228
+ severity: "suggestion",
229
+ testId: suggestionMatch[1],
230
+ description: suggestionMatch[2],
231
+ });
232
+ continue;
233
+ }
234
+ // * Finding [TEST-ID]
235
+ const findingMatch = trimmed.match(/^\s*\*\s*Finding\s*\[(\S+)\]\s*(.*)/);
236
+ if (findingMatch) {
237
+ findings.push({
238
+ severity: "finding",
239
+ testId: findingMatch[1],
240
+ description: findingMatch[2],
241
+ });
242
+ }
243
+ }
244
+ return findings;
245
+ }
246
+ /**
247
+ * Parses OpenSCAP text/XML results output.
248
+ * Handles the common text report format.
249
+ */
250
+ export function parseOscapOutput(output) {
251
+ const results = [];
252
+ // Match rule results from oscap text output
253
+ // Title
254
+ // Rule ID: xccdf_...
255
+ // Result: pass/fail/notapplicable
256
+ // Severity: low/medium/high
257
+ let currentTitle = "";
258
+ let currentRule = {};
259
+ for (const line of output.split("\n")) {
260
+ const trimmed = line.trim();
261
+ const titleMatch = trimmed.match(/^Title\s*:\s*(.*)/);
262
+ if (titleMatch) {
263
+ currentTitle = titleMatch[1];
264
+ currentRule = { title: currentTitle };
265
+ continue;
266
+ }
267
+ const ruleMatch = trimmed.match(/^Rule\s*:\s*(.*)/);
268
+ if (ruleMatch) {
269
+ currentRule.ruleId = ruleMatch[1];
270
+ continue;
271
+ }
272
+ const resultMatch = trimmed.match(/^Result\s*:\s*(.*)/);
273
+ if (resultMatch) {
274
+ currentRule.result = resultMatch[1];
275
+ continue;
276
+ }
277
+ const sevMatch = trimmed.match(/^Severity\s*:\s*(.*)/);
278
+ if (sevMatch) {
279
+ currentRule.severity = sevMatch[1];
280
+ // We have all fields, push the result
281
+ if (currentRule.ruleId && currentRule.result) {
282
+ results.push({
283
+ ruleId: currentRule.ruleId,
284
+ result: currentRule.result,
285
+ severity: currentRule.severity ?? "unknown",
286
+ title: currentRule.title ?? "",
287
+ });
288
+ }
289
+ currentRule = {};
290
+ }
291
+ }
292
+ return results;
293
+ }
294
+ /**
295
+ * Parses `clamscan` output into structured results.
296
+ */
297
+ export function parseClamavOutput(output) {
298
+ const results = [];
299
+ for (const line of output.split("\n")) {
300
+ const trimmed = line.trim();
301
+ if (!trimmed)
302
+ continue;
303
+ // /path/to/file: OK
304
+ if (trimmed.endsWith(": OK")) {
305
+ results.push({
306
+ file: trimmed.slice(0, -4),
307
+ status: "OK",
308
+ });
309
+ continue;
310
+ }
311
+ // /path/to/file: VirusName FOUND
312
+ const foundMatch = trimmed.match(/^(.+?):\s+(.+?)\s+FOUND$/);
313
+ if (foundMatch) {
314
+ results.push({
315
+ file: foundMatch[1],
316
+ status: "FOUND",
317
+ virus: foundMatch[2],
318
+ });
319
+ continue;
320
+ }
321
+ // /path/to/file: Error message ERROR
322
+ const errorMatch = trimmed.match(/^(.+?):\s+.*ERROR$/);
323
+ if (errorMatch) {
324
+ results.push({
325
+ file: errorMatch[1],
326
+ status: "ERROR",
327
+ });
328
+ }
329
+ }
330
+ return results;
331
+ }
332
+ /**
333
+ * Parses `ss -tulnp` output into structured entries.
334
+ */
335
+ export function parseSsOutput(output) {
336
+ const entries = [];
337
+ const lines = output.split("\n");
338
+ // Skip header
339
+ for (let i = 1; i < lines.length; i++) {
340
+ const line = lines[i].trim();
341
+ if (!line)
342
+ continue;
343
+ // ss -tulnp columns:
344
+ // 0: Netid 1: State 2: Recv-Q 3: Send-Q 4: Local Address:Port 5: Peer Address:Port 6+: Process
345
+ const parts = line.split(/\s+/);
346
+ if (parts.length >= 6) {
347
+ entries.push({
348
+ state: parts[1],
349
+ recv: parts[2],
350
+ send: parts[3],
351
+ local: parts[4],
352
+ peer: parts[5],
353
+ process: parts.slice(6).join(" "),
354
+ });
355
+ }
356
+ }
357
+ return entries;
358
+ }
359
+ /**
360
+ * Parses `fail2ban-client status` output.
361
+ */
362
+ export function parseFail2banOutput(output) {
363
+ const jails = [];
364
+ let current = null;
365
+ for (const line of output.split("\n")) {
366
+ const trimmed = line.trim();
367
+ // Jail name from status output
368
+ const jailMatch = trimmed.match(/^Status for the jail:\s*(\S+)/);
369
+ if (jailMatch) {
370
+ current = { name: jailMatch[1], bannedIPs: [] };
371
+ continue;
372
+ }
373
+ if (!current)
374
+ continue;
375
+ const kvMatch = trimmed.match(/^\|-\s*(\S.*?):\s*(.*)/);
376
+ if (kvMatch) {
377
+ const key = kvMatch[1].trim().toLowerCase();
378
+ const value = kvMatch[2].trim();
379
+ if (key.includes("currently failed")) {
380
+ current.currentlyFailed = parseInt(value, 10) || 0;
381
+ }
382
+ else if (key.includes("total failed")) {
383
+ current.totalFailed = parseInt(value, 10) || 0;
384
+ }
385
+ else if (key.includes("currently banned")) {
386
+ current.currentlyBanned = parseInt(value, 10) || 0;
387
+ }
388
+ else if (key.includes("total banned")) {
389
+ current.totalBanned = parseInt(value, 10) || 0;
390
+ }
391
+ else if (key.includes("banned ip")) {
392
+ current.bannedIPs = value
393
+ .split(/\s+/)
394
+ .filter((ip) => ip.length > 0);
395
+ }
396
+ continue;
397
+ }
398
+ const leafMatch = trimmed.match(/^`-\s*(\S.*?):\s*(.*)/);
399
+ if (leafMatch) {
400
+ const key = leafMatch[1].trim().toLowerCase();
401
+ const value = leafMatch[2].trim();
402
+ if (key.includes("currently failed")) {
403
+ current.currentlyFailed = parseInt(value, 10) || 0;
404
+ }
405
+ else if (key.includes("total failed")) {
406
+ current.totalFailed = parseInt(value, 10) || 0;
407
+ }
408
+ else if (key.includes("currently banned")) {
409
+ current.currentlyBanned = parseInt(value, 10) || 0;
410
+ }
411
+ else if (key.includes("total banned")) {
412
+ current.totalBanned = parseInt(value, 10) || 0;
413
+ }
414
+ else if (key.includes("banned ip")) {
415
+ current.bannedIPs = value
416
+ .split(/\s+/)
417
+ .filter((ip) => ip.length > 0);
418
+ // Last field, push the jail
419
+ jails.push({
420
+ name: current.name ?? "unknown",
421
+ status: current.status ?? "active",
422
+ currentlyFailed: current.currentlyFailed ?? 0,
423
+ totalFailed: current.totalFailed ?? 0,
424
+ currentlyBanned: current.currentlyBanned ?? 0,
425
+ totalBanned: current.totalBanned ?? 0,
426
+ bannedIPs: current.bannedIPs ?? [],
427
+ });
428
+ current = null;
429
+ }
430
+ }
431
+ }
432
+ // Push any remaining jail
433
+ if (current?.name) {
434
+ jails.push({
435
+ name: current.name,
436
+ status: current.status ?? "active",
437
+ currentlyFailed: current.currentlyFailed ?? 0,
438
+ totalFailed: current.totalFailed ?? 0,
439
+ currentlyBanned: current.currentlyBanned ?? 0,
440
+ totalBanned: current.totalBanned ?? 0,
441
+ bannedIPs: current.bannedIPs ?? [],
442
+ });
443
+ }
444
+ return jails;
445
+ }
446
+ /**
447
+ * Parses `systemctl list-units` output into structured entries.
448
+ */
449
+ export function parseSystemctlOutput(output) {
450
+ const units = [];
451
+ const lines = output.split("\n");
452
+ for (const line of lines) {
453
+ const trimmed = line.trim();
454
+ if (!trimmed)
455
+ continue;
456
+ // Skip header lines and legend
457
+ if (trimmed.startsWith("UNIT") ||
458
+ trimmed.startsWith("LOAD") ||
459
+ trimmed.startsWith("To show") ||
460
+ trimmed.includes("loaded units listed") ||
461
+ trimmed.startsWith("LEGEND")) {
462
+ continue;
463
+ }
464
+ // "● unit.service loaded active running Description text"
465
+ // or "unit.service loaded active running Description text"
466
+ const cleanLine = trimmed.replace(/^[●○]\s*/, "");
467
+ const parts = cleanLine.split(/\s+/);
468
+ if (parts.length >= 4) {
469
+ units.push({
470
+ unit: parts[0],
471
+ load: parts[1],
472
+ active: parts[2],
473
+ sub: parts[3],
474
+ description: parts.slice(4).join(" "),
475
+ });
476
+ }
477
+ }
478
+ return units;
479
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Severity levels for policy rules, aligned with common security frameworks.
3
+ */
4
+ export type PolicySeverity = "critical" | "high" | "medium" | "low" | "info";
5
+ /**
6
+ * A single compliance policy rule defining a check and optional remediation.
7
+ */
8
+ export interface PolicyRule {
9
+ /** Unique rule identifier (e.g., "CIS-1.1.1") */
10
+ id: string;
11
+ /** Human-readable title */
12
+ title: string;
13
+ /** Detailed description of what this rule checks */
14
+ description: string;
15
+ /** Severity of non-compliance */
16
+ severity: PolicySeverity;
17
+ /** Category (e.g., "filesystem", "network", "authentication") */
18
+ category: string;
19
+ /** Command to run to check compliance (array: [command, ...args]) */
20
+ check: string[];
21
+ /** Expected output pattern (regex string or exact match) */
22
+ expectedOutput?: string;
23
+ /** Command to remediate non-compliance (array: [command, ...args]) */
24
+ remediation?: string[];
25
+ /** Reference IDs (e.g., CIS benchmark, NIST control IDs) */
26
+ references?: string[];
27
+ }
28
+ /**
29
+ * Result of evaluating a single policy rule.
30
+ */
31
+ export interface PolicyResult {
32
+ /** The rule that was evaluated */
33
+ rule: PolicyRule;
34
+ /** Whether the system passed this check */
35
+ passed: boolean;
36
+ /** Actual output from the check command */
37
+ actual: string;
38
+ /** Human-readable result message */
39
+ message: string;
40
+ }
41
+ /**
42
+ * A collection of policy rules forming a compliance policy set.
43
+ */
44
+ export interface PolicySet {
45
+ /** Policy set name (e.g., "CIS Level 1 - Server") */
46
+ name: string;
47
+ /** Version of this policy set */
48
+ version: string;
49
+ /** Description of the policy set */
50
+ description: string;
51
+ /** Array of policy rules */
52
+ rules: PolicyRule[];
53
+ }
54
+ /**
55
+ * Summary of a policy evaluation.
56
+ */
57
+ export interface PolicyEvaluationSummary {
58
+ /** Policy set that was evaluated */
59
+ policyName: string;
60
+ /** Total number of rules */
61
+ totalRules: number;
62
+ /** Number of rules that passed */
63
+ passed: number;
64
+ /** Number of rules that failed */
65
+ failed: number;
66
+ /** Number of rules with errors */
67
+ errors: number;
68
+ /** Compliance percentage (0-100) */
69
+ compliancePercent: number;
70
+ /** Individual rule results */
71
+ results: PolicyResult[];
72
+ }
73
+ /**
74
+ * Validates a policy rule's check (or remediation) command array.
75
+ *
76
+ * Security controls:
77
+ * 1. Command (check[0]) must be in the security allowlist
78
+ * 2. Shell interpreters are explicitly blocked (even if allowlisted)
79
+ * 3. Arguments are checked for null bytes and control characters
80
+ *
81
+ * Note: Shell metacharacters (|, &, $, etc.) in arguments are NOT blocked
82
+ * because policy rules use execFile (no shell), making these characters
83
+ * harmless literal values. Policy rules legitimately need regex
84
+ * metacharacters as arguments to grep/awk/sed.
85
+ *
86
+ * @param check The command array [command, ...args]
87
+ * @param label Human-readable label for error messages (e.g., "check", "remediation")
88
+ * @throws {Error} If validation fails
89
+ */
90
+ export declare function validateRuleCheck(check: string[], label?: string): void;
91
+ /**
92
+ * SECURITY (CORE-009): ReDoS (Regular Expression Denial of Service) protection.
93
+ *
94
+ * Safely tests a regex pattern against input with multiple layers of defense
95
+ * against catastrophic backtracking:
96
+ *
97
+ * 1. **Length limit**: Patterns longer than 200 characters are rejected to reduce
98
+ * the attack surface for complex regex injection.
99
+ * 2. **Nested quantifier detection**: Patterns like `(a+)+`, `(a*)*`, `(a+)*`
100
+ * are rejected because they cause exponential backtracking on non-matching
101
+ * input. The check uses two heuristics:
102
+ * - Repeated quantifiers: `a++`, `a**`, `{n,m}{` (possessive-like syntax
103
+ * that JavaScript doesn't support, indicating malformed patterns)
104
+ * - Group-level nesting: `([...]+)+` or `([...]*)*` where a quantified
105
+ * group is itself quantified
106
+ * 3. **try-catch**: Invalid regex syntax is caught and reported clearly.
107
+ *
108
+ * These checks are applied to user-supplied `expectedOutput` regex patterns
109
+ * in policy rules before they are compiled or executed.
110
+ *
111
+ * @param pattern The regex pattern string
112
+ * @param input The string to test against
113
+ * @returns Whether the pattern matches the input
114
+ * @throws {Error} If the pattern is dangerous, invalid, or too long
115
+ */
116
+ export declare function safeRegexTest(pattern: string, input: string): boolean;
117
+ /**
118
+ * Evaluates a single policy rule by executing its check command
119
+ * and comparing the output against the expected pattern.
120
+ *
121
+ * Before execution, the check command is validated against the
122
+ * security allowlist and shell interpreters are blocked.
123
+ *
124
+ * @param rule The policy rule to evaluate
125
+ * @returns The evaluation result
126
+ */
127
+ export declare function evaluateRule(rule: PolicyRule): Promise<PolicyResult>;
128
+ /**
129
+ * Evaluates all rules in a policy set and returns a summary.
130
+ *
131
+ * @param policySet The policy set to evaluate
132
+ * @returns Evaluation summary with individual results
133
+ */
134
+ export declare function evaluatePolicy(policySet: PolicySet): Promise<PolicyEvaluationSummary>;
135
+ /**
136
+ * Loads a policy set from a JSON file with strict schema validation.
137
+ *
138
+ * Validates:
139
+ * 1. JSON structure via Zod schema (field types, lengths, required fields)
140
+ * 2. All check commands against the security allowlist
141
+ * 3. All remediation commands against the security allowlist
142
+ *
143
+ * @param path Absolute or relative path to the policy JSON file
144
+ * @returns The loaded and validated policy set
145
+ * @throws If the file cannot be read, parsed, or fails validation
146
+ */
147
+ export declare function loadPolicy(path: string): PolicySet;
148
+ /**
149
+ * Saves a policy set to a JSON file with secure permissions.
150
+ * Creates parent directories with owner-only permissions (0o700).
151
+ * Files are written with owner-only permissions (0o600).
152
+ *
153
+ * @param path Path to save the policy file
154
+ * @param policy The policy set to save
155
+ */
156
+ export declare function savePolicy(path: string, policy: PolicySet): void;
157
+ /**
158
+ * Returns a list of built-in policy file names from the policy directory.
159
+ * Returns empty array if the directory doesn't exist or is empty.
160
+ */
161
+ export declare function getBuiltinPolicies(): string[];
162
+ /**
163
+ * Built-in policy rule templates for common hardening checks.
164
+ * These can be used as a starting point for custom policies.
165
+ *
166
+ * SECURITY: All check commands use direct binary invocation (no shell).
167
+ * Shell interpreters (sh, bash, etc.) are never used in check or remediation arrays.
168
+ */
169
+ export declare const BUILTIN_RULE_TEMPLATES: PolicyRule[];
170
+ //# sourceMappingURL=policy-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-engine.d.ts","sourceRoot":"","sources":["../../src/core/policy-engine.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,QAAQ,EAAE,cAAc,CAAC;IACzB,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,8BAA8B;IAC9B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AA4ED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,SAAU,GAAG,IAAI,CA0CxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CA0BrE;AAID;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CA0E1E;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,uBAAuB,CAAC,CAwClC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAwBlD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAQhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAY7C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,UAAU,EAwT9C,CAAC"}