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,656 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { dirname, basename } from "node:path";
3
+ import { z } from "zod";
4
+ import { executeCommand } from "./executor.js";
5
+ import { getConfig } from "./config.js";
6
+ import { isAllowlisted } from "./command-allowlist.js";
7
+ import { secureWriteFileSync, secureMkdirSync } from "./secure-fs.js";
8
+ // ── Zod Schemas for Policy Validation ────────────────────────────────────────
9
+ const PolicySeveritySchema = z.enum([
10
+ "critical",
11
+ "high",
12
+ "medium",
13
+ "low",
14
+ "info",
15
+ ]);
16
+ /**
17
+ * Zod schema for a single policy rule.
18
+ * Fields that may be absent in legacy policy files have defaults.
19
+ */
20
+ const PolicyRuleSchema = z.object({
21
+ id: z.string().min(1).max(128),
22
+ title: z.string().max(256).default(""),
23
+ description: z.string().max(1024).default(""),
24
+ severity: PolicySeveritySchema.default("medium"),
25
+ category: z.string().max(128).default("general"),
26
+ check: z.array(z.string().max(1024)).min(1).max(50),
27
+ // SECURITY (CORE-009): Limit expectedOutput regex to 200 chars to match safeRegexTest limit
28
+ expectedOutput: z.string().max(200).optional(),
29
+ remediation: z.array(z.string().max(1024)).min(1).max(50).optional(),
30
+ references: z.array(z.string().max(256)).max(20).optional(),
31
+ });
32
+ /**
33
+ * Zod schema for a complete policy set.
34
+ */
35
+ const PolicySetSchema = z.object({
36
+ name: z.string().min(1).max(256),
37
+ version: z.string().max(64).default("1.0.0"),
38
+ description: z.string().max(2048).default(""),
39
+ rules: z.array(PolicyRuleSchema).min(1).max(200),
40
+ });
41
+ // ── Security Validation ──────────────────────────────────────────────────────
42
+ /**
43
+ * Shell interpreters that are explicitly blocked in policy rules.
44
+ * Even if somehow added to the command allowlist, these must never be
45
+ * invoked by a policy rule — they would enable arbitrary command execution.
46
+ */
47
+ const BLOCKED_INTERPRETERS = new Set([
48
+ "sh",
49
+ "bash",
50
+ "zsh",
51
+ "fish",
52
+ "csh",
53
+ "dash",
54
+ "ksh",
55
+ "tcsh",
56
+ "/bin/sh",
57
+ "/bin/bash",
58
+ "/bin/zsh",
59
+ "/bin/dash",
60
+ "/bin/csh",
61
+ "/bin/ksh",
62
+ "/bin/tcsh",
63
+ "/bin/fish",
64
+ "/usr/bin/sh",
65
+ "/usr/bin/bash",
66
+ "/usr/bin/zsh",
67
+ "/usr/bin/dash",
68
+ "/usr/bin/csh",
69
+ "/usr/bin/ksh",
70
+ "/usr/bin/tcsh",
71
+ "/usr/bin/fish",
72
+ ]);
73
+ /** Control characters regex — matches dangerous non-printable characters */
74
+ const CONTROL_CHAR_RE = /[\x00-\x08\x0e-\x1f\x7f]/;
75
+ /**
76
+ * Validates a policy rule's check (or remediation) command array.
77
+ *
78
+ * Security controls:
79
+ * 1. Command (check[0]) must be in the security allowlist
80
+ * 2. Shell interpreters are explicitly blocked (even if allowlisted)
81
+ * 3. Arguments are checked for null bytes and control characters
82
+ *
83
+ * Note: Shell metacharacters (|, &, $, etc.) in arguments are NOT blocked
84
+ * because policy rules use execFile (no shell), making these characters
85
+ * harmless literal values. Policy rules legitimately need regex
86
+ * metacharacters as arguments to grep/awk/sed.
87
+ *
88
+ * @param check The command array [command, ...args]
89
+ * @param label Human-readable label for error messages (e.g., "check", "remediation")
90
+ * @throws {Error} If validation fails
91
+ */
92
+ export function validateRuleCheck(check, label = "check") {
93
+ if (!Array.isArray(check) || check.length === 0) {
94
+ throw new Error(`Policy rule ${label} must be a non-empty array`);
95
+ }
96
+ const command = check[0];
97
+ // Block shell interpreters explicitly — this is defense-in-depth
98
+ // even if they were somehow added to the command allowlist
99
+ if (BLOCKED_INTERPRETERS.has(command)) {
100
+ throw new Error(`Shell interpreter '${command}' is not allowed in policy rules`);
101
+ }
102
+ // Validate command against the security allowlist
103
+ if (!isAllowlisted(command)) {
104
+ throw new Error(`Command '${command}' is not in the security allowlist`);
105
+ }
106
+ // Validate arguments for null bytes and control characters
107
+ const args = check.slice(1);
108
+ for (let i = 0; i < args.length; i++) {
109
+ const arg = args[i];
110
+ if (typeof arg !== "string") {
111
+ throw new Error(`${label} argument at index ${i + 1} is not a string`);
112
+ }
113
+ if (arg.includes("\0")) {
114
+ throw new Error(`${label} argument at index ${i + 1} contains null bytes`);
115
+ }
116
+ if (CONTROL_CHAR_RE.test(arg)) {
117
+ throw new Error(`${label} argument at index ${i + 1} contains control characters`);
118
+ }
119
+ }
120
+ }
121
+ /**
122
+ * SECURITY (CORE-009): ReDoS (Regular Expression Denial of Service) protection.
123
+ *
124
+ * Safely tests a regex pattern against input with multiple layers of defense
125
+ * against catastrophic backtracking:
126
+ *
127
+ * 1. **Length limit**: Patterns longer than 200 characters are rejected to reduce
128
+ * the attack surface for complex regex injection.
129
+ * 2. **Nested quantifier detection**: Patterns like `(a+)+`, `(a*)*`, `(a+)*`
130
+ * are rejected because they cause exponential backtracking on non-matching
131
+ * input. The check uses two heuristics:
132
+ * - Repeated quantifiers: `a++`, `a**`, `{n,m}{` (possessive-like syntax
133
+ * that JavaScript doesn't support, indicating malformed patterns)
134
+ * - Group-level nesting: `([...]+)+` or `([...]*)*` where a quantified
135
+ * group is itself quantified
136
+ * 3. **try-catch**: Invalid regex syntax is caught and reported clearly.
137
+ *
138
+ * These checks are applied to user-supplied `expectedOutput` regex patterns
139
+ * in policy rules before they are compiled or executed.
140
+ *
141
+ * @param pattern The regex pattern string
142
+ * @param input The string to test against
143
+ * @returns Whether the pattern matches the input
144
+ * @throws {Error} If the pattern is dangerous, invalid, or too long
145
+ */
146
+ export function safeRegexTest(pattern, input) {
147
+ // 1. Reject excessively long patterns (reduced from 1024 to 200 for CORE-009)
148
+ if (pattern.length > 200) {
149
+ throw new Error("Regex pattern too long (max 200 characters)");
150
+ }
151
+ // 2a. Reject obviously dangerous patterns that cause catastrophic backtracking:
152
+ // - Repeated quantifiers: a++, a**, {n,m}{
153
+ if (/(\+\+|\*\*|\{\d+,\d*\}\{)/.test(pattern)) {
154
+ throw new Error("Regex pattern too complex (potential ReDoS)");
155
+ }
156
+ // 2b. Detect nested quantifiers like (a+)+, (a+)*, ([a-z]+)+
157
+ if (/\([^)]*[+*][^)]*\)[+*{]/.test(pattern)) {
158
+ throw new Error("Regex pattern contains nested quantifiers (potential ReDoS)");
159
+ }
160
+ // 3. Compile and execute with error handling
161
+ try {
162
+ const re = new RegExp(pattern, "m");
163
+ return re.test(input);
164
+ }
165
+ catch {
166
+ throw new Error(`Invalid regex pattern: ${pattern}`);
167
+ }
168
+ }
169
+ // ── Core Functions ───────────────────────────────────────────────────────────
170
+ /**
171
+ * Evaluates a single policy rule by executing its check command
172
+ * and comparing the output against the expected pattern.
173
+ *
174
+ * Before execution, the check command is validated against the
175
+ * security allowlist and shell interpreters are blocked.
176
+ *
177
+ * @param rule The policy rule to evaluate
178
+ * @returns The evaluation result
179
+ */
180
+ export async function evaluateRule(rule) {
181
+ try {
182
+ if (!rule.check || rule.check.length === 0) {
183
+ return {
184
+ rule,
185
+ passed: false,
186
+ actual: "",
187
+ message: "Rule has no check command defined",
188
+ };
189
+ }
190
+ // SECURITY: Validate command against allowlist and block shell interpreters
191
+ validateRuleCheck(rule.check, "check");
192
+ const [command, ...args] = rule.check;
193
+ const result = await executeCommand({
194
+ command,
195
+ args,
196
+ timeout: 30_000,
197
+ });
198
+ const actual = result.stdout.trim();
199
+ // If no expected output defined, passing means exit code 0
200
+ if (!rule.expectedOutput) {
201
+ const passed = result.exitCode === 0;
202
+ return {
203
+ rule,
204
+ passed,
205
+ actual,
206
+ message: passed
207
+ ? `Check passed (exit code 0)`
208
+ : `Check failed (exit code ${result.exitCode}): ${result.stderr.trim()}`,
209
+ };
210
+ }
211
+ // Use safe regex test with ReDoS protection
212
+ let passed = false;
213
+ try {
214
+ passed = safeRegexTest(rule.expectedOutput, actual);
215
+ }
216
+ catch (regexErr) {
217
+ // If regex is invalid or dangerous, try exact match as fallback
218
+ const regexMsg = regexErr instanceof Error ? regexErr.message : String(regexErr);
219
+ if (regexMsg.includes("ReDoS") || regexMsg.includes("too complex")) {
220
+ // Don't fallback for ReDoS — that's a security issue
221
+ return {
222
+ rule,
223
+ passed: false,
224
+ actual,
225
+ message: `Error: ${regexMsg}`,
226
+ };
227
+ }
228
+ // For simple invalid regex, fall back to substring match
229
+ passed = actual.includes(rule.expectedOutput);
230
+ }
231
+ return {
232
+ rule,
233
+ passed,
234
+ actual,
235
+ message: passed
236
+ ? `Check passed: output matches expected pattern`
237
+ : `Check failed: expected pattern "${rule.expectedOutput}" not found in output`,
238
+ };
239
+ }
240
+ catch (err) {
241
+ const message = err instanceof Error ? err.message : String(err);
242
+ return {
243
+ rule,
244
+ passed: false,
245
+ actual: "",
246
+ message: `Error evaluating rule: ${message}`,
247
+ };
248
+ }
249
+ }
250
+ /**
251
+ * Evaluates all rules in a policy set and returns a summary.
252
+ *
253
+ * @param policySet The policy set to evaluate
254
+ * @returns Evaluation summary with individual results
255
+ */
256
+ export async function evaluatePolicy(policySet) {
257
+ const results = [];
258
+ let passed = 0;
259
+ let failed = 0;
260
+ let errors = 0;
261
+ console.error(`[policy-engine] Evaluating policy: ${policySet.name} (${policySet.rules.length} rules)`);
262
+ for (const rule of policySet.rules) {
263
+ const result = await evaluateRule(rule);
264
+ results.push(result);
265
+ if (result.passed) {
266
+ passed++;
267
+ }
268
+ else if (result.message.startsWith("Error")) {
269
+ errors++;
270
+ }
271
+ else {
272
+ failed++;
273
+ }
274
+ }
275
+ const totalRules = policySet.rules.length;
276
+ const compliancePercent = totalRules > 0 ? Math.round((passed / totalRules) * 100) : 0;
277
+ console.error(`[policy-engine] Results: ${passed}/${totalRules} passed (${compliancePercent}% compliance)`);
278
+ return {
279
+ policyName: policySet.name,
280
+ totalRules,
281
+ passed,
282
+ failed,
283
+ errors,
284
+ compliancePercent,
285
+ results,
286
+ };
287
+ }
288
+ /**
289
+ * Loads a policy set from a JSON file with strict schema validation.
290
+ *
291
+ * Validates:
292
+ * 1. JSON structure via Zod schema (field types, lengths, required fields)
293
+ * 2. All check commands against the security allowlist
294
+ * 3. All remediation commands against the security allowlist
295
+ *
296
+ * @param path Absolute or relative path to the policy JSON file
297
+ * @returns The loaded and validated policy set
298
+ * @throws If the file cannot be read, parsed, or fails validation
299
+ */
300
+ export function loadPolicy(path) {
301
+ const content = readFileSync(path, "utf-8");
302
+ const parsed = JSON.parse(content);
303
+ // Validate structure with Zod schema
304
+ const result = PolicySetSchema.safeParse(parsed);
305
+ if (!result.success) {
306
+ const issues = result.error.issues
307
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
308
+ .join("; ");
309
+ throw new Error(`Invalid policy file ${path}: ${issues}`);
310
+ }
311
+ const validated = result.data;
312
+ // Validate all rule commands against the security allowlist
313
+ for (const rule of validated.rules) {
314
+ validateRuleCheck(rule.check, `rule ${rule.id} check`);
315
+ if (rule.remediation) {
316
+ validateRuleCheck(rule.remediation, `rule ${rule.id} remediation`);
317
+ }
318
+ }
319
+ return validated;
320
+ }
321
+ /**
322
+ * Saves a policy set to a JSON file with secure permissions.
323
+ * Creates parent directories with owner-only permissions (0o700).
324
+ * Files are written with owner-only permissions (0o600).
325
+ *
326
+ * @param path Path to save the policy file
327
+ * @param policy The policy set to save
328
+ */
329
+ export function savePolicy(path, policy) {
330
+ // SECURITY (CORE-013): Explicitly create parent directory via secure-fs
331
+ // (0o700 permissions) instead of relying on bare mkdirSync.
332
+ const parentDir = dirname(path);
333
+ secureMkdirSync(parentDir);
334
+ // Use secureWriteFileSync which writes files with 0o600
335
+ secureWriteFileSync(path, JSON.stringify(policy, null, 2), "utf-8");
336
+ console.error(`[policy-engine] Saved policy to ${path}`);
337
+ }
338
+ /**
339
+ * Returns a list of built-in policy file names from the policy directory.
340
+ * Returns empty array if the directory doesn't exist or is empty.
341
+ */
342
+ export function getBuiltinPolicies() {
343
+ try {
344
+ const config = getConfig();
345
+ const policyDir = config.policyDir;
346
+ const files = readdirSync(policyDir);
347
+ return files
348
+ .filter((f) => f.endsWith(".json"))
349
+ .map((f) => basename(f, ".json"));
350
+ }
351
+ catch {
352
+ return [];
353
+ }
354
+ }
355
+ /**
356
+ * Built-in policy rule templates for common hardening checks.
357
+ * These can be used as a starting point for custom policies.
358
+ *
359
+ * SECURITY: All check commands use direct binary invocation (no shell).
360
+ * Shell interpreters (sh, bash, etc.) are never used in check or remediation arrays.
361
+ */
362
+ export const BUILTIN_RULE_TEMPLATES = [
363
+ {
364
+ id: "KERN-001",
365
+ title: "IP forwarding disabled",
366
+ description: "Ensure IP forwarding is disabled unless the system is a router",
367
+ severity: "high",
368
+ category: "network",
369
+ check: ["sysctl", "-n", "net.ipv4.ip_forward"],
370
+ expectedOutput: "^0$",
371
+ remediation: ["sysctl", "-w", "net.ipv4.ip_forward=0"],
372
+ references: ["CIS-3.1.1", "NIST-SC-7"],
373
+ },
374
+ {
375
+ id: "KERN-002",
376
+ title: "ICMP redirects disabled",
377
+ description: "Ensure ICMP redirects are not accepted",
378
+ severity: "high",
379
+ category: "network",
380
+ check: ["sysctl", "-n", "net.ipv4.conf.all.accept_redirects"],
381
+ expectedOutput: "^0$",
382
+ remediation: ["sysctl", "-w", "net.ipv4.conf.all.accept_redirects=0"],
383
+ references: ["CIS-3.2.2", "NIST-SC-7"],
384
+ },
385
+ {
386
+ id: "KERN-003",
387
+ title: "Source routing disabled",
388
+ description: "Ensure source routed packets are not accepted",
389
+ severity: "high",
390
+ category: "network",
391
+ check: ["sysctl", "-n", "net.ipv4.conf.all.accept_source_route"],
392
+ expectedOutput: "^0$",
393
+ remediation: ["sysctl", "-w", "net.ipv4.conf.all.accept_source_route=0"],
394
+ references: ["CIS-3.2.1", "NIST-SC-7"],
395
+ },
396
+ {
397
+ id: "KERN-004",
398
+ title: "SYN cookies enabled",
399
+ description: "Ensure TCP SYN cookies are enabled to prevent SYN flood attacks",
400
+ severity: "high",
401
+ category: "network",
402
+ check: ["sysctl", "-n", "net.ipv4.tcp_syncookies"],
403
+ expectedOutput: "^1$",
404
+ remediation: ["sysctl", "-w", "net.ipv4.tcp_syncookies=1"],
405
+ references: ["CIS-3.2.8", "NIST-SC-5"],
406
+ },
407
+ {
408
+ id: "AUTH-001",
409
+ title: "Root login via SSH disabled",
410
+ description: "Ensure root login is disabled in SSH configuration",
411
+ severity: "critical",
412
+ category: "authentication",
413
+ check: ["grep", "-i", "^PermitRootLogin", "/etc/ssh/sshd_config"],
414
+ expectedOutput: "PermitRootLogin\\s+no",
415
+ remediation: [
416
+ "sed",
417
+ "-i",
418
+ "s/^#\\?PermitRootLogin.*/PermitRootLogin no/",
419
+ "/etc/ssh/sshd_config",
420
+ ],
421
+ references: ["CIS-5.2.10", "NIST-IA-2"],
422
+ },
423
+ {
424
+ id: "AUTH-002",
425
+ title: "Password authentication disabled for SSH",
426
+ description: "Ensure password authentication is disabled in favor of key-based auth",
427
+ severity: "high",
428
+ category: "authentication",
429
+ check: [
430
+ "grep",
431
+ "-i",
432
+ "^PasswordAuthentication",
433
+ "/etc/ssh/sshd_config",
434
+ ],
435
+ expectedOutput: "PasswordAuthentication\\s+no",
436
+ references: ["CIS-5.2.12", "NIST-IA-2"],
437
+ },
438
+ {
439
+ id: "FS-001",
440
+ title: "/tmp has noexec mount option",
441
+ description: "Ensure /tmp is mounted with noexec option",
442
+ severity: "medium",
443
+ category: "filesystem",
444
+ check: ["findmnt", "-n", "-o", "OPTIONS", "/tmp"],
445
+ expectedOutput: "noexec",
446
+ references: ["CIS-1.1.4", "NIST-CM-6"],
447
+ },
448
+ {
449
+ id: "FS-002",
450
+ title: "Sticky bit on world-writable directories",
451
+ description: "Ensure sticky bit is set on all world-writable directories",
452
+ severity: "medium",
453
+ category: "filesystem",
454
+ check: [
455
+ "find",
456
+ "/",
457
+ "-xdev",
458
+ "-type",
459
+ "d",
460
+ "-perm",
461
+ "-0002",
462
+ "!",
463
+ "-perm",
464
+ "-1000",
465
+ "-print",
466
+ ],
467
+ expectedOutput: "^$",
468
+ references: ["CIS-1.1.21", "NIST-CM-6"],
469
+ },
470
+ {
471
+ id: "SVC-001",
472
+ title: "Firewall service active",
473
+ description: "Ensure a firewall service (iptables/nftables/ufw) is running",
474
+ severity: "critical",
475
+ category: "services",
476
+ check: ["systemctl", "is-active", "ufw"],
477
+ expectedOutput: "^active$",
478
+ references: ["CIS-3.5.1", "NIST-SC-7"],
479
+ },
480
+ {
481
+ id: "SVC-002",
482
+ title: "Auditd service active",
483
+ description: "Ensure the audit daemon is running",
484
+ severity: "high",
485
+ category: "services",
486
+ check: ["systemctl", "is-active", "auditd"],
487
+ expectedOutput: "^active$",
488
+ remediation: ["systemctl", "enable", "--now", "auditd"],
489
+ references: ["CIS-4.1.1.1", "NIST-AU-2"],
490
+ },
491
+ {
492
+ id: "SVC-003",
493
+ title: "Fail2ban service active",
494
+ description: "Ensure fail2ban is running to protect against brute force attacks",
495
+ severity: "high",
496
+ category: "services",
497
+ check: ["systemctl", "is-active", "fail2ban"],
498
+ expectedOutput: "^active$",
499
+ remediation: ["systemctl", "enable", "--now", "fail2ban"],
500
+ references: ["NIST-SI-4"],
501
+ },
502
+ {
503
+ id: "PERM-001",
504
+ title: "No world-writable files in system directories",
505
+ description: "Check for world-writable files in critical system directories",
506
+ severity: "high",
507
+ category: "permissions",
508
+ check: [
509
+ "find",
510
+ "/etc",
511
+ "-xdev",
512
+ "-type",
513
+ "f",
514
+ "-perm",
515
+ "-0002",
516
+ "-print",
517
+ ],
518
+ expectedOutput: "^$",
519
+ references: ["CIS-6.1.10", "NIST-CM-6"],
520
+ },
521
+ {
522
+ id: "PERM-002",
523
+ title: "/etc/shadow permissions",
524
+ description: "Ensure /etc/shadow has restrictive permissions",
525
+ severity: "critical",
526
+ category: "permissions",
527
+ check: ["stat", "-c", "%a", "/etc/shadow"],
528
+ expectedOutput: "^(0|600|640)$",
529
+ references: ["CIS-6.1.3", "NIST-AC-3"],
530
+ },
531
+ {
532
+ id: "PERM-003",
533
+ title: "/etc/passwd permissions",
534
+ description: "Ensure /etc/passwd has proper permissions",
535
+ severity: "high",
536
+ category: "permissions",
537
+ check: ["stat", "-c", "%a", "/etc/passwd"],
538
+ expectedOutput: "^644$",
539
+ references: ["CIS-6.1.2", "NIST-AC-3"],
540
+ },
541
+ {
542
+ id: "AUTH-003",
543
+ title: "PAM Password Quality",
544
+ description: "Ensure pam_pwquality is configured with minimum length 14. " +
545
+ "Remediate with: access_pam_configure with module=pwquality",
546
+ severity: "high",
547
+ category: "authentication",
548
+ check: ["grep", "-i", "minlen", "/etc/security/pwquality.conf"],
549
+ expectedOutput: "minlen",
550
+ references: ["CIS-5.3.1", "NIST-IA-5"],
551
+ },
552
+ {
553
+ id: "AUTH-004",
554
+ title: "PAM Account Lockout",
555
+ description: "Ensure pam_faillock is configured for account lockout. " +
556
+ "Remediate with: access_pam_configure with module=faillock",
557
+ severity: "high",
558
+ category: "authentication",
559
+ check: ["grep", "-E", "pam_faillock", "/etc/pam.d/common-auth"],
560
+ expectedOutput: "pam_faillock",
561
+ references: ["CIS-5.3.2", "NIST-AC-7"],
562
+ },
563
+ {
564
+ id: "AUTH-005",
565
+ title: "Password Maximum Age",
566
+ description: "Ensure password expiry is 365 days or less. " +
567
+ "Remediate with: access_password_policy with action=set, max_days=365",
568
+ severity: "medium",
569
+ category: "authentication",
570
+ check: ["grep", "-E", "^PASS_MAX_DAYS", "/etc/login.defs"],
571
+ expectedOutput: "PASS_MAX_DAYS\\s+([1-9]|[1-9][0-9]|[12][0-9]{2}|3[0-5][0-9]|36[0-5])$",
572
+ references: ["CIS-5.4.1.1", "NIST-IA-5"],
573
+ },
574
+ {
575
+ id: "FS-003",
576
+ title: "Default Umask 027",
577
+ description: "Ensure default umask is 027 or more restrictive. " +
578
+ "Remediate with: harden_umask_set with umask_value=027",
579
+ severity: "medium",
580
+ category: "filesystem",
581
+ check: ["grep", "UMASK", "/etc/login.defs"],
582
+ expectedOutput: "UMASK\\s+(027|077)",
583
+ references: ["CIS-5.4.4", "NIST-CM-6"],
584
+ },
585
+ {
586
+ id: "FS-004",
587
+ title: "AIDE File Integrity",
588
+ description: "Ensure AIDE file integrity monitoring is installed. " +
589
+ "Remediate with: defense_install with tool=aide",
590
+ severity: "high",
591
+ category: "filesystem",
592
+ check: ["which", "aide"],
593
+ references: ["CIS-1.3.1", "NIST-SI-7"],
594
+ },
595
+ {
596
+ id: "SVC-004",
597
+ title: "Fail2Ban Active",
598
+ description: "Ensure fail2ban service is running. " +
599
+ "Remediate with: defense_install with tool=fail2ban, then enable the service",
600
+ severity: "high",
601
+ category: "services",
602
+ check: ["systemctl", "is-active", "fail2ban"],
603
+ expectedOutput: "^active$",
604
+ remediation: ["systemctl", "enable", "--now", "fail2ban"],
605
+ references: ["NIST-SI-4"],
606
+ },
607
+ {
608
+ id: "SVC-005",
609
+ title: "Docker Security Options Enabled",
610
+ description: "Ensure Docker daemon has security options (seccomp, apparmor) enabled. " +
611
+ "For per-container privileged mode checks, use: container_docker_bench",
612
+ severity: "critical",
613
+ category: "services",
614
+ check: ["docker", "info", "--format", "{{.SecurityOptions}}"],
615
+ expectedOutput: "seccomp",
616
+ references: ["CIS-Docker-5.4"],
617
+ },
618
+ {
619
+ id: "SVC-006",
620
+ title: "AppArmor Enforcing",
621
+ description: "Ensure AppArmor is enabled and has enforcing profiles. " +
622
+ "Remediate with: container_apparmor_install with action=install_profiles",
623
+ severity: "medium",
624
+ category: "services",
625
+ check: ["aa-enabled"],
626
+ expectedOutput: "^Yes$",
627
+ references: ["CIS-1.6.1.1", "NIST-AC-3"],
628
+ },
629
+ {
630
+ id: "FS-005",
631
+ title: "Core Dumps Disabled",
632
+ description: "Ensure core dumps are restricted. " +
633
+ "Remediate with: harden_coredump_disable",
634
+ severity: "medium",
635
+ category: "filesystem",
636
+ check: [
637
+ "grep",
638
+ "-E",
639
+ "\\*\\s+hard\\s+core\\s+0",
640
+ "/etc/security/limits.conf",
641
+ ],
642
+ expectedOutput: "\\*\\s+hard\\s+core\\s+0",
643
+ references: ["CIS-1.5.1", "NIST-CM-6"],
644
+ },
645
+ {
646
+ id: "FS-006",
647
+ title: "/var/tmp noexec Mount",
648
+ description: "Ensure /var/tmp is mounted with noexec option. " +
649
+ "Remediate with: compliance_tmp_hardening with action=apply",
650
+ severity: "medium",
651
+ category: "filesystem",
652
+ check: ["findmnt", "-n", "-o", "OPTIONS", "/var/tmp"],
653
+ expectedOutput: "noexec",
654
+ references: ["CIS-1.1.4", "NIST-CM-6"],
655
+ },
656
+ ];