defense-mcp-server 0.9.2 → 0.9.4

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 (170) hide show
  1. package/build/core/auto-installer.js +31 -31
  2. package/build/core/command-allowlist.js +1 -1
  3. package/build/core/dependency-validator.js +9 -9
  4. package/build/core/distro-adapter.js +0 -7
  5. package/build/core/distro.js +0 -48
  6. package/build/core/encrypted-state.js +0 -7
  7. package/build/core/logger.js +1 -1
  8. package/build/core/pam-utils.js +1 -1
  9. package/build/core/parsers.js +1 -1
  10. package/build/core/preflight.js +13 -13
  11. package/build/core/progress.js +20 -20
  12. package/build/core/run-command.js +46 -0
  13. package/build/core/sudo-guard.js +4 -4
  14. package/build/core/third-party-installer.js +4 -4
  15. package/build/core/tool-wrapper.js +3 -3
  16. package/build/tools/access-control.js +6 -6
  17. package/build/tools/api-security.js +5 -51
  18. package/build/tools/app-hardening.js +23 -25
  19. package/build/tools/cloud-security.js +5 -51
  20. package/build/tools/compliance.js +9 -13
  21. package/build/tools/container-security.js +51 -52
  22. package/build/tools/deception.js +8 -54
  23. package/build/tools/dns-security.js +2 -48
  24. package/build/tools/encryption.js +86 -87
  25. package/build/tools/firewall.js +324 -30
  26. package/build/tools/hardening.js +12 -13
  27. package/build/tools/incident-response.js +3 -3
  28. package/build/tools/logging.js +17 -59
  29. package/build/tools/malware.js +2 -2
  30. package/build/tools/meta.js +86 -165
  31. package/build/tools/network-defense.js +3 -3
  32. package/build/tools/patch-management.js +8 -8
  33. package/build/tools/process-security.js +38 -92
  34. package/build/tools/sudo-management.js +36 -36
  35. package/build/tools/threat-intel.js +2 -48
  36. package/build/tools/vulnerability-management.js +3 -49
  37. package/build/tools/waf.js +47 -93
  38. package/build/tools/wireless-security.js +9 -55
  39. package/package.json +5 -3
  40. package/build/core/auto-installer.d.ts +0 -102
  41. package/build/core/auto-installer.d.ts.map +0 -1
  42. package/build/core/backup-manager.d.ts +0 -63
  43. package/build/core/backup-manager.d.ts.map +0 -1
  44. package/build/core/changelog.d.ts +0 -119
  45. package/build/core/changelog.d.ts.map +0 -1
  46. package/build/core/command-allowlist.d.ts +0 -129
  47. package/build/core/command-allowlist.d.ts.map +0 -1
  48. package/build/core/config.d.ts +0 -107
  49. package/build/core/config.d.ts.map +0 -1
  50. package/build/core/dependency-validator.d.ts +0 -106
  51. package/build/core/dependency-validator.d.ts.map +0 -1
  52. package/build/core/distro-adapter.d.ts +0 -177
  53. package/build/core/distro-adapter.d.ts.map +0 -1
  54. package/build/core/distro.d.ts +0 -68
  55. package/build/core/distro.d.ts.map +0 -1
  56. package/build/core/encrypted-state.d.ts +0 -76
  57. package/build/core/encrypted-state.d.ts.map +0 -1
  58. package/build/core/executor.d.ts +0 -65
  59. package/build/core/executor.d.ts.map +0 -1
  60. package/build/core/installer.d.ts +0 -129
  61. package/build/core/installer.d.ts.map +0 -1
  62. package/build/core/logger.d.ts +0 -118
  63. package/build/core/logger.d.ts.map +0 -1
  64. package/build/core/metrics.d.ts +0 -74
  65. package/build/core/metrics.d.ts.map +0 -1
  66. package/build/core/metrics.js +0 -97
  67. package/build/core/output-redactor.d.ts +0 -26
  68. package/build/core/output-redactor.d.ts.map +0 -1
  69. package/build/core/pam-utils.d.ts +0 -356
  70. package/build/core/pam-utils.d.ts.map +0 -1
  71. package/build/core/parsers.d.ts +0 -191
  72. package/build/core/parsers.d.ts.map +0 -1
  73. package/build/core/policy-engine.d.ts +0 -170
  74. package/build/core/policy-engine.d.ts.map +0 -1
  75. package/build/core/preflight.d.ts +0 -157
  76. package/build/core/preflight.d.ts.map +0 -1
  77. package/build/core/privilege-manager.d.ts +0 -108
  78. package/build/core/privilege-manager.d.ts.map +0 -1
  79. package/build/core/progress.d.ts +0 -99
  80. package/build/core/progress.d.ts.map +0 -1
  81. package/build/core/rate-limiter.d.ts +0 -101
  82. package/build/core/rate-limiter.d.ts.map +0 -1
  83. package/build/core/rollback.d.ts +0 -73
  84. package/build/core/rollback.d.ts.map +0 -1
  85. package/build/core/safeguards.d.ts +0 -58
  86. package/build/core/safeguards.d.ts.map +0 -1
  87. package/build/core/sanitizer.d.ts +0 -118
  88. package/build/core/sanitizer.d.ts.map +0 -1
  89. package/build/core/secure-fs.d.ts +0 -67
  90. package/build/core/secure-fs.d.ts.map +0 -1
  91. package/build/core/spawn-safe.d.ts +0 -55
  92. package/build/core/spawn-safe.d.ts.map +0 -1
  93. package/build/core/sudo-guard.d.ts +0 -167
  94. package/build/core/sudo-guard.d.ts.map +0 -1
  95. package/build/core/sudo-session.d.ts +0 -143
  96. package/build/core/sudo-session.d.ts.map +0 -1
  97. package/build/core/third-party-installer.d.ts +0 -58
  98. package/build/core/third-party-installer.d.ts.map +0 -1
  99. package/build/core/third-party-manifest.d.ts +0 -48
  100. package/build/core/third-party-manifest.d.ts.map +0 -1
  101. package/build/core/tool-annotations.d.ts +0 -13
  102. package/build/core/tool-annotations.d.ts.map +0 -1
  103. package/build/core/tool-dependencies.d.ts +0 -60
  104. package/build/core/tool-dependencies.d.ts.map +0 -1
  105. package/build/core/tool-durations.d.ts +0 -71
  106. package/build/core/tool-durations.d.ts.map +0 -1
  107. package/build/core/tool-registry.d.ts +0 -112
  108. package/build/core/tool-registry.d.ts.map +0 -1
  109. package/build/core/tool-wrapper.d.ts +0 -73
  110. package/build/core/tool-wrapper.d.ts.map +0 -1
  111. package/build/index.d.ts +0 -3
  112. package/build/index.d.ts.map +0 -1
  113. package/build/tools/access-control.d.ts +0 -11
  114. package/build/tools/access-control.d.ts.map +0 -1
  115. package/build/tools/api-security.d.ts +0 -12
  116. package/build/tools/api-security.d.ts.map +0 -1
  117. package/build/tools/app-hardening.d.ts +0 -11
  118. package/build/tools/app-hardening.d.ts.map +0 -1
  119. package/build/tools/backup.d.ts +0 -8
  120. package/build/tools/backup.d.ts.map +0 -1
  121. package/build/tools/cloud-security.d.ts +0 -17
  122. package/build/tools/cloud-security.d.ts.map +0 -1
  123. package/build/tools/compliance.d.ts +0 -11
  124. package/build/tools/compliance.d.ts.map +0 -1
  125. package/build/tools/container-security.d.ts +0 -14
  126. package/build/tools/container-security.d.ts.map +0 -1
  127. package/build/tools/deception.d.ts +0 -13
  128. package/build/tools/deception.d.ts.map +0 -1
  129. package/build/tools/dns-security.d.ts +0 -93
  130. package/build/tools/dns-security.d.ts.map +0 -1
  131. package/build/tools/ebpf-security.d.ts +0 -15
  132. package/build/tools/ebpf-security.d.ts.map +0 -1
  133. package/build/tools/encryption.d.ts +0 -12
  134. package/build/tools/encryption.d.ts.map +0 -1
  135. package/build/tools/firewall.d.ts +0 -9
  136. package/build/tools/firewall.d.ts.map +0 -1
  137. package/build/tools/hardening.d.ts +0 -8
  138. package/build/tools/hardening.d.ts.map +0 -1
  139. package/build/tools/incident-response.d.ts +0 -11
  140. package/build/tools/incident-response.d.ts.map +0 -1
  141. package/build/tools/integrity.d.ts +0 -15
  142. package/build/tools/integrity.d.ts.map +0 -1
  143. package/build/tools/logging.d.ts +0 -21
  144. package/build/tools/logging.d.ts.map +0 -1
  145. package/build/tools/malware.d.ts +0 -10
  146. package/build/tools/malware.d.ts.map +0 -1
  147. package/build/tools/meta.d.ts +0 -13
  148. package/build/tools/meta.d.ts.map +0 -1
  149. package/build/tools/network-defense.d.ts +0 -11
  150. package/build/tools/network-defense.d.ts.map +0 -1
  151. package/build/tools/patch-management.d.ts +0 -3
  152. package/build/tools/patch-management.d.ts.map +0 -1
  153. package/build/tools/process-security.d.ts +0 -12
  154. package/build/tools/process-security.d.ts.map +0 -1
  155. package/build/tools/secrets.d.ts +0 -8
  156. package/build/tools/secrets.d.ts.map +0 -1
  157. package/build/tools/sudo-management.d.ts +0 -17
  158. package/build/tools/sudo-management.d.ts.map +0 -1
  159. package/build/tools/supply-chain-security.d.ts +0 -8
  160. package/build/tools/supply-chain-security.d.ts.map +0 -1
  161. package/build/tools/threat-intel.d.ts +0 -22
  162. package/build/tools/threat-intel.d.ts.map +0 -1
  163. package/build/tools/vulnerability-management.d.ts +0 -11
  164. package/build/tools/vulnerability-management.d.ts.map +0 -1
  165. package/build/tools/waf.d.ts +0 -12
  166. package/build/tools/waf.d.ts.map +0 -1
  167. package/build/tools/wireless-security.d.ts +0 -19
  168. package/build/tools/wireless-security.d.ts.map +0 -1
  169. package/build/tools/zero-trust-network.d.ts +0 -8
  170. package/build/tools/zero-trust-network.d.ts.map +0 -1
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared async command runner using spawnSafe.
3
+ *
4
+ * Replaces the duplicate runCommand() implementations found in 11 tool files.
5
+ * Wraps spawnSafe in a Promise that collects stdout/stderr and handles
6
+ * timeouts, spawn errors, and process errors gracefully.
7
+ */
8
+ import { spawnSafe } from "./spawn-safe.js";
9
+ export async function runCommand(command, args, timeoutMs = 30_000) {
10
+ return new Promise((resolve) => {
11
+ let child;
12
+ try {
13
+ child = spawnSafe(command, args);
14
+ }
15
+ catch (err) {
16
+ resolve({ stdout: "", stderr: err instanceof Error ? err.message : String(err), exitCode: -1 });
17
+ return;
18
+ }
19
+ let stdout = "";
20
+ let stderr = "";
21
+ let resolved = false;
22
+ const timer = setTimeout(() => {
23
+ if (!resolved) {
24
+ resolved = true;
25
+ child.kill("SIGTERM");
26
+ resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
27
+ }
28
+ }, timeoutMs);
29
+ child.stdout?.on("data", (data) => { stdout += data.toString(); });
30
+ child.stderr?.on("data", (data) => { stderr += data.toString(); });
31
+ child.on("close", (code) => {
32
+ if (!resolved) {
33
+ resolved = true;
34
+ clearTimeout(timer);
35
+ resolve({ stdout, stderr, exitCode: code ?? -1 });
36
+ }
37
+ });
38
+ child.on("error", (err) => {
39
+ if (!resolved) {
40
+ resolved = true;
41
+ clearTimeout(timer);
42
+ resolve({ stdout, stderr: err.message, exitCode: -1 });
43
+ }
44
+ });
45
+ });
46
+ }
@@ -188,14 +188,14 @@ export class SudoGuard {
188
188
  const reasonText = reason ?? "This tool requires elevated (root) privileges to function.";
189
189
  // Build the prompt message for AI clients.
190
190
  const lines = [];
191
- lines.push("🔒 Sudo session required");
192
- lines.push("".repeat(50));
191
+ lines.push("Sudo session required");
192
+ lines.push("");
193
193
  lines.push("");
194
194
  lines.push(`Tool: ${toolName}`);
195
195
  lines.push(`Reason: ${reasonText}`);
196
196
  lines.push("");
197
197
  if (status.elevated && status.remainingSeconds !== null && status.remainingSeconds <= 0) {
198
- lines.push("⚠️ Your sudo session has expired.");
198
+ lines.push("WARNING: Your sudo session has expired.");
199
199
  lines.push("");
200
200
  lines.push("ACTION: Call sudo_session with action=elevate_gui to re-authenticate,");
201
201
  lines.push("or action=extend to extend an active session.");
@@ -210,7 +210,7 @@ export class SudoGuard {
210
210
  lines.push("stored in a zeroable memory buffer, and never visible to the AI.");
211
211
  if (originalError) {
212
212
  lines.push("");
213
- lines.push("".repeat(50));
213
+ lines.push("");
214
214
  lines.push("Original error:");
215
215
  lines.push(originalError.substring(0, 500));
216
216
  }
@@ -471,7 +471,7 @@ async function installGithubRelease(entry) {
471
471
  dryRun: false,
472
472
  success: true,
473
473
  }));
474
- console.error(`[third-party-installer] ${entry.name} v${entry.version} installed successfully`);
474
+ console.error(`[third-party-installer] OK ${entry.name} v${entry.version} installed successfully`);
475
475
  return {
476
476
  binary: entry.binary,
477
477
  success: true,
@@ -585,7 +585,7 @@ async function installAptRepo(entry) {
585
585
  console.error(`[third-party-installer] Running apt-get update...`);
586
586
  const updateResult = execWithSudo(["apt-get", "update"], { timeoutMs: 120_000 });
587
587
  if (!updateResult.success) {
588
- console.error(`[third-party-installer] apt-get update had issues: ${updateResult.stderr.slice(0, 200)}`);
588
+ console.error(`[third-party-installer] WARNING: apt-get update had issues: ${updateResult.stderr.slice(0, 200)}`);
589
589
  }
590
590
  const packages = entry.aptPinnedPackages ?? [entry.binary];
591
591
  for (const pkg of packages) {
@@ -609,7 +609,7 @@ async function installAptRepo(entry) {
609
609
  dryRun: false,
610
610
  success: true,
611
611
  }));
612
- console.error(`[third-party-installer] ${entry.name} v${entry.version} installed successfully via APT`);
612
+ console.error(`[third-party-installer] OK ${entry.name} v${entry.version} installed successfully via APT`);
613
613
  return {
614
614
  binary: entry.binary,
615
615
  success: true,
@@ -658,7 +658,7 @@ async function installNpmLocal(entry) {
658
658
  dryRun: false,
659
659
  success: true,
660
660
  }));
661
- console.error(`[third-party-installer] ${entry.name} v${entry.version} installed via npm`);
661
+ console.error(`[third-party-installer] OK ${entry.name} v${entry.version} installed via npm`);
662
662
  return {
663
663
  binary: entry.binary,
664
664
  success: true,
@@ -195,7 +195,7 @@ function createWrappedHandler(toolName, originalHandler, ctx) {
195
195
  content: [
196
196
  {
197
197
  type: "text",
198
- text: `⚠ Rate limit exceeded\n\n${rateLimitResult.reason}`,
198
+ text: `WARNING: Rate limit exceeded\n\n${rateLimitResult.reason}`,
199
199
  },
200
200
  ],
201
201
  isError: true,
@@ -269,12 +269,12 @@ function createWrappedHandler(toolName, originalHandler, ctx) {
269
269
  catch (err) {
270
270
  // Pre-flight itself threw — return error instead of running without dependency checking
271
271
  const errMsg = err instanceof Error ? err.message : String(err);
272
- console.error(`[preflight] Pre-flight failed unexpectedly for '${toolName}': ${errMsg}`);
272
+ console.error(`[preflight] WARNING: Pre-flight failed unexpectedly for '${toolName}': ${errMsg}`);
273
273
  return {
274
274
  content: [
275
275
  {
276
276
  type: "text",
277
- text: `⚠ Pre-flight internal error for '${toolName}'\n\nThe pre-flight dependency checking system encountered an unexpected error and the tool was not executed.\n\nError: ${errMsg}\n\nPlease retry the operation or check the pre-flight configuration. If this persists, the tool's dependency manifest may need attention.`,
277
+ text: `WARNING: Pre-flight internal error for '${toolName}'\n\nThe pre-flight dependency checking system encountered an unexpected error and the tool was not executed.\n\nError: ${errMsg}\n\nPlease retry the operation or check the pre-flight configuration. If this persists, the tool's dependency manifest may need attention.`,
278
278
  },
279
279
  ],
280
280
  isError: true,
@@ -879,8 +879,8 @@ export function registerAccessControlTools(server) {
879
879
  ? "CRITICAL: Weak SSH algorithms detected. Apply Mozilla Modern SSH configuration immediately."
880
880
  : warnCount > 0
881
881
  ? "WARNING: SSH algorithms not explicitly configured. Set explicit algorithms in sshd_config."
882
- : "PASS: SSH cryptographic configuration meets modern standards.",
883
- }, null, 2))],
882
+ : "SSH cryptographic configuration meets modern standards.",
883
+ }))],
884
884
  };
885
885
  }
886
886
  catch (error) {
@@ -1154,7 +1154,7 @@ export function registerAccessControlTools(server) {
1154
1154
  content: [
1155
1155
  createTextContent(`[DRY-RUN] Would write the following to ${targetFile}:\n\n` +
1156
1156
  configLines.map((l) => ` ${l}`).join("\n") +
1157
- (pwqSanityWarnings ? `\n\n⚠️ Sanity warnings:\n${pwqSanityWarnings}` : "")),
1157
+ (pwqSanityWarnings ? `\n\nWARNING: Sanity warnings:\n${pwqSanityWarnings}` : "")),
1158
1158
  ],
1159
1159
  };
1160
1160
  }
@@ -1200,7 +1200,7 @@ export function registerAccessControlTools(server) {
1200
1200
  content: [
1201
1201
  createTextContent(`pam_pwquality configured in ${targetFile}:\n\n` +
1202
1202
  configLines.map((l) => ` ${l}`).join("\n") +
1203
- (pwqSanityWarnings ? `\n\n⚠️ Sanity warnings:\n${pwqSanityWarnings}` : "")),
1203
+ (pwqSanityWarnings ? `\n\nWARNING: Sanity warnings:\n${pwqSanityWarnings}` : "")),
1204
1204
  ],
1205
1205
  };
1206
1206
  }
@@ -1276,7 +1276,7 @@ export function registerAccessControlTools(server) {
1276
1276
  createTextContent(`[DRY-RUN] Would add/update pam_faillock.so in ${targetFile}:\n\n` +
1277
1277
  ` ${preLine}\n ${authLine}\n\n` +
1278
1278
  `Settings: ${JSON.stringify(merged)}` +
1279
- (flSanityWarnings ? `\n\n⚠️ Sanity warnings:\n${flSanityWarnings}` : "")),
1279
+ (flSanityWarnings ? `\n\nWARNING: Sanity warnings:\n${flSanityWarnings}` : "")),
1280
1280
  ],
1281
1281
  };
1282
1282
  }
@@ -1332,7 +1332,7 @@ export function registerAccessControlTools(server) {
1332
1332
  ` ${preLine}\n ${authLine}\n\n` +
1333
1333
  `Settings: ${JSON.stringify(merged)}\n` +
1334
1334
  `Backup: ${backupEntry.backupPath}` +
1335
- (flSanityWarnings ? `\n\n⚠️ Sanity warnings:\n${flSanityWarnings}` : "")),
1335
+ (flSanityWarnings ? `\n\nWARNING: Sanity warnings:\n${flSanityWarnings}` : "")),
1336
1336
  ],
1337
1337
  };
1338
1338
  }
@@ -8,7 +8,7 @@
8
8
  * verification, TLS configuration checking, and CORS policy analysis.
9
9
  */
10
10
  import { z } from "zod";
11
- import { spawnSafe } from "../core/spawn-safe.js";
11
+ import { runCommand } from "../core/run-command.js";
12
12
  import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
13
13
  // ── Constants ──────────────────────────────────────────────────────────────────
14
14
  const DEFAULT_PORT_RANGE = "80,443,3000,4000,5000,8000,8080,8443,9000";
@@ -21,53 +21,7 @@ const COMMON_API_PATHS = [
21
21
  "/openapi.json",
22
22
  "/.well-known/openid-configuration",
23
23
  ];
24
- /**
25
- * Run a command via spawnSafe and collect output as a promise.
26
- * Handles errors gracefully — returns error info instead of throwing.
27
- */
28
- async function runCommand(command, args, timeoutMs = 30_000) {
29
- return new Promise((resolve) => {
30
- let child;
31
- try {
32
- child = spawnSafe(command, args);
33
- }
34
- catch (err) {
35
- const msg = err instanceof Error ? err.message : String(err);
36
- resolve({ stdout: "", stderr: msg, exitCode: -1 });
37
- return;
38
- }
39
- let stdout = "";
40
- let stderr = "";
41
- let resolved = false;
42
- const timer = setTimeout(() => {
43
- if (!resolved) {
44
- resolved = true;
45
- child.kill("SIGTERM");
46
- resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
47
- }
48
- }, timeoutMs);
49
- child.stdout?.on("data", (data) => {
50
- stdout += data.toString();
51
- });
52
- child.stderr?.on("data", (data) => {
53
- stderr += data.toString();
54
- });
55
- child.on("close", (code) => {
56
- if (!resolved) {
57
- resolved = true;
58
- clearTimeout(timer);
59
- resolve({ stdout, stderr, exitCode: code ?? -1 });
60
- }
61
- });
62
- child.on("error", (err) => {
63
- if (!resolved) {
64
- resolved = true;
65
- clearTimeout(timer);
66
- resolve({ stdout, stderr: err.message, exitCode: -1 });
67
- }
68
- });
69
- });
70
- }
24
+ // ── Helpers ────────────────────────────────────────────────────────────────────
71
25
  /**
72
26
  * Validate and normalize a target URL.
73
27
  * Returns the normalized URL or null if invalid.
@@ -692,7 +646,7 @@ export function registerApiSecurityTools(server) {
692
646
  text += `Auth Type: ${authResult.authType}\n`;
693
647
  text += `Status without auth: ${authResult.statusWithoutAuth}\n`;
694
648
  text += `Status with auth: ${authResult.statusWithAuth}\n`;
695
- text += `Verbose Errors: ${authResult.verboseErrors ? "YES " : "no"}\n`;
649
+ text += `Verbose Errors: ${authResult.verboseErrors ? "YES WARNING" : "no"}\n`;
696
650
  if (authResult.errorDetails.length > 0) {
697
651
  text += `\nError Details:\n`;
698
652
  for (const detail of authResult.errorDetails) {
@@ -864,8 +818,8 @@ export function registerApiSecurityTools(server) {
864
818
  text += `Allow-Origin: ${corsResult.allowOrigin}\n`;
865
819
  text += `Allow-Credentials: ${corsResult.allowCredentials}\n`;
866
820
  text += `Allow-Methods: ${corsResult.allowMethods || "not specified"}\n`;
867
- text += `Wildcard Origin: ${corsResult.wildcardOrigin ? "YES " : "no"}\n`;
868
- text += `Origin Reflection: ${corsResult.originReflection ? "YES " : "no"}\n`;
821
+ text += `Wildcard Origin: ${corsResult.wildcardOrigin ? "YES WARNING" : "no"}\n`;
822
+ text += `Origin Reflection: ${corsResult.originReflection ? "YES WARNING" : "no"}\n`;
869
823
  }
870
824
  if (corsResult.criticalIssues.length > 0) {
871
825
  text += `\nCritical Issues:\n`;
@@ -454,8 +454,8 @@ export function registerAppHardeningTools(server) {
454
454
  case "audit": {
455
455
  try {
456
456
  const sections = [];
457
- sections.push("🔍 Application Security Audit");
458
- sections.push("=".repeat(55));
457
+ sections.push("Application Security Audit");
458
+ sections.push("");
459
459
  const apps = await detectRunningApps();
460
460
  if (apps.length === 0) {
461
461
  sections.push("\nNo known applications detected.");
@@ -467,9 +467,7 @@ export function registerAppHardeningTools(server) {
467
467
  apps.sort((a, b) => riskOrder[a.profile.riskLevel] - riskOrder[b.profile.riskLevel]);
468
468
  let totalRisks = 0;
469
469
  for (const app of apps) {
470
- const riskIcon = app.profile.riskLevel === "critical" ? "⛔" :
471
- app.profile.riskLevel === "high" ? "🔴" : app.profile.riskLevel === "medium" ? "🟡" : "🟢";
472
- sections.push(`── ${riskIcon} ${app.profile.name} ──`);
470
+ sections.push(`── ${app.profile.name} ──`);
473
471
  sections.push(` Category: ${app.profile.category}`);
474
472
  sections.push(` Risk Level: ${app.profile.riskLevel.toUpperCase()}`);
475
473
  sections.push(` Running as: ${app.user}`);
@@ -480,17 +478,17 @@ export function registerAppHardeningTools(server) {
480
478
  sections.push(` Listening Ports:`);
481
479
  for (const lp of app.listenPorts) {
482
480
  const external = !lp.address.includes("127.0.0.1") && !lp.address.includes("::1");
483
- sections.push(` ${lp.protocol}/${lp.port} on ${lp.address} [${external ? "⚠️ EXTERNAL" : "localhost"}]`);
481
+ sections.push(` ${lp.protocol}/${lp.port} on ${lp.address} [${external ? "WARNING: EXTERNAL" : "localhost"}]`);
484
482
  }
485
483
  }
486
484
  sections.push(` Security Concerns:`);
487
485
  for (const concern of app.profile.securityConcerns) {
488
- sections.push(` ⚠️ ${concern}`);
486
+ sections.push(` WARNING: ${concern}`);
489
487
  totalRisks++;
490
488
  }
491
489
  sections.push(` Top Recommendations:`);
492
490
  for (const rec of app.profile.recommendations.slice(0, 3)) {
493
- sections.push(` 💡 ${rec}`);
491
+ sections.push(` ${rec}`);
494
492
  }
495
493
  if (app.profile.recommendations.length > 3) {
496
494
  sections.push(` ... +${app.profile.recommendations.length - 3} more (use action=recommend)`);
@@ -530,24 +528,24 @@ export function registerAppHardeningTools(server) {
530
528
  };
531
529
  }
532
530
  const sections = [];
533
- sections.push(`🛡️ Hardening Guide: ${profile.name}`);
534
- sections.push("=".repeat(55));
531
+ sections.push(`Hardening Guide: ${profile.name}`);
532
+ sections.push("");
535
533
  sections.push(`Category: ${profile.category} | Risk: ${profile.riskLevel.toUpperCase()}`);
536
534
  sections.push("\n── Security Concerns ──");
537
535
  for (const concern of profile.securityConcerns) {
538
- sections.push(` ⚠️ ${concern}`);
536
+ sections.push(` WARNING: ${concern}`);
539
537
  }
540
538
  sections.push("\n── Network Hardening ──");
541
539
  if (profile.requiredPorts.length > 0) {
542
540
  sections.push(" Ports that MUST remain open (core functionality):");
543
541
  for (const p of profile.requiredPorts) {
544
- sections.push(` ${p.protocol}/${p.port} — ${p.purpose}`);
542
+ sections.push(` ${p.protocol}/${p.port} — ${p.purpose}`);
545
543
  }
546
544
  }
547
545
  if (profile.localhostOnlyPorts.length > 0) {
548
546
  sections.push(" Ports to restrict to localhost/LAN:");
549
547
  for (const p of profile.localhostOnlyPorts) {
550
- sections.push(` 🔒 ${p.protocol}/${p.port} — ${p.purpose}`);
548
+ sections.push(` ${p.protocol}/${p.port} — ${p.purpose}`);
551
549
  }
552
550
  }
553
551
  sections.push(" Firewall strategy:");
@@ -571,11 +569,11 @@ export function registerAppHardeningTools(server) {
571
569
  sections.push("\n── Filesystem Permissions ──");
572
570
  sections.push(" Writable paths (required for operation):");
573
571
  for (const p of profile.writablePaths) {
574
- sections.push(` 📝 ${p}`);
572
+ sections.push(` ${p}`);
575
573
  }
576
574
  sections.push(" Read-only paths:");
577
575
  for (const p of profile.readablePaths) {
578
- sections.push(` 📖 ${p}`);
576
+ sections.push(` ${p}`);
579
577
  }
580
578
  return { content: [createTextContent(sections.join("\n"))] };
581
579
  }
@@ -600,8 +598,8 @@ export function registerAppHardeningTools(server) {
600
598
  }
601
599
  const effectiveDryRun = dry_run ?? getConfig().dryRun;
602
600
  const sections = [];
603
- sections.push(`🔥 Firewall Rules for ${profile.name}`);
604
- sections.push("=".repeat(55));
601
+ sections.push(`Firewall Rules for ${profile.name}`);
602
+ sections.push("");
605
603
  sections.push(`LAN CIDR: ${lan_cidr}`);
606
604
  sections.push(effectiveDryRun ? "\n[DRY RUN] Rules that would be applied:\n" : "\nApplying rules:\n");
607
605
  const rules = [];
@@ -632,7 +630,7 @@ export function registerAppHardeningTools(server) {
632
630
  else
633
631
  failed++;
634
632
  }
635
- sections.push(`\n✅ Applied ${applied} rules, ${failed} failed`);
633
+ sections.push(`\nApplied ${applied} rules, ${failed} failed`);
636
634
  }
637
635
  sections.push("\n── Additional Recommendations ──");
638
636
  sections.push(" • Consider using nftables for more granular control");
@@ -690,10 +688,10 @@ export function registerAppHardeningTools(server) {
690
688
  }
691
689
  }
692
690
  const sections = [];
693
- sections.push(`🔒 Systemd Hardening: ${profile.name}`);
694
- sections.push("=".repeat(55));
691
+ sections.push(`Systemd Hardening: ${profile.name}`);
692
+ sections.push("");
695
693
  if (!svcName) {
696
- sections.push(`\n⚠️ No running systemd service found for ${profile.name}.`);
694
+ sections.push(`\nWARNING: No running systemd service found for ${profile.name}.`);
697
695
  sections.push("Provide service_name manually if the service uses a different name.");
698
696
  sections.push("\nRecommended override content for when the service is configured:\n");
699
697
  }
@@ -726,12 +724,12 @@ export function registerAppHardeningTools(server) {
726
724
  });
727
725
  if (writeResult.exitCode === 0) {
728
726
  await executeCommand({ toolName: "app_hardening", command: "sudo", args: ["systemctl", "daemon-reload"], timeout: 10000 });
729
- sections.push(`\n✅ Override written to ${overridePath}`);
730
- sections.push("systemd daemon reloaded");
731
- sections.push(`\n⚠️ Restart the service to apply: sudo systemctl restart ${svcName}`);
727
+ sections.push(`\nOverride written to ${overridePath}`);
728
+ sections.push("systemd daemon reloaded");
729
+ sections.push(`\nRestart the service to apply: sudo systemctl restart ${svcName}`);
732
730
  }
733
731
  else {
734
- sections.push(`\n❌ Failed to write override: ${writeResult.stderr}`);
732
+ sections.push(`\nFailed to write override: ${writeResult.stderr}`);
735
733
  }
736
734
  }
737
735
  sections.push("\n── What These Directives Do ──");
@@ -8,7 +8,7 @@
8
8
  * exposure checking, storage audit, and IMDS security assessment for AWS/GCP/Azure.
9
9
  */
10
10
  import { z } from "zod";
11
- import { spawnSafe } from "../core/spawn-safe.js";
11
+ import { runCommand } from "../core/run-command.js";
12
12
  import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
13
13
  import { existsSync } from "node:fs";
14
14
  /** Sensitive environment variable names for cloud credentials */
@@ -30,53 +30,7 @@ const CREDENTIAL_FILE_PATHS = [
30
30
  { provider: "gcp", path: "~/.config/gcloud/application_default_credentials.json" },
31
31
  { provider: "azure", path: "~/.azure/accessTokens.json" },
32
32
  ];
33
- /**
34
- * Run a command via spawnSafe and collect output as a promise.
35
- * Handles errors gracefully — returns error info instead of throwing.
36
- */
37
- async function runCommand(command, args, timeoutMs = 30_000) {
38
- return new Promise((resolve) => {
39
- let child;
40
- try {
41
- child = spawnSafe(command, args);
42
- }
43
- catch (err) {
44
- const msg = err instanceof Error ? err.message : String(err);
45
- resolve({ stdout: "", stderr: msg, exitCode: -1 });
46
- return;
47
- }
48
- let stdout = "";
49
- let stderr = "";
50
- let resolved = false;
51
- const timer = setTimeout(() => {
52
- if (!resolved) {
53
- resolved = true;
54
- child.kill("SIGTERM");
55
- resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
56
- }
57
- }, timeoutMs);
58
- child.stdout?.on("data", (data) => {
59
- stdout += data.toString();
60
- });
61
- child.stderr?.on("data", (data) => {
62
- stderr += data.toString();
63
- });
64
- child.on("close", (code) => {
65
- if (!resolved) {
66
- resolved = true;
67
- clearTimeout(timer);
68
- resolve({ stdout, stderr, exitCode: code ?? -1 });
69
- }
70
- });
71
- child.on("error", (err) => {
72
- if (!resolved) {
73
- resolved = true;
74
- clearTimeout(timer);
75
- resolve({ stdout, stderr: err.message, exitCode: -1 });
76
- }
77
- });
78
- });
79
- }
33
+ // ── Helpers ────────────────────────────────────────────────────────────────────
80
34
  // ── Credential masking ─────────────────────────────────────────────────────────
81
35
  /**
82
36
  * Mask a credential value, showing first 4 chars + asterisks.
@@ -611,7 +565,7 @@ export function registerCloudSecurityTools(server) {
611
565
  text += "Credential Files:\n";
612
566
  for (const file of creds.credentialFiles) {
613
567
  const status = file.exists
614
- ? `EXISTS (permissions: ${file.permissions || "unknown"})${file.permWarning ? ` ${file.permWarning}` : ""}`
568
+ ? `EXISTS (permissions: ${file.permissions || "unknown"})${file.permWarning ? ` WARNING: ${file.permWarning}` : ""}`
615
569
  : "not found";
616
570
  text += ` • ${file.path}: ${status}\n`;
617
571
  }
@@ -706,10 +660,10 @@ export function registerCloudSecurityTools(server) {
706
660
  return { content: [formatToolOutput(output)] };
707
661
  }
708
662
  let text = "Cloud Security — IMDS Security Check\n\n";
709
- text += `IMDSv1 (unauthenticated): ${imds.v1Accessible ? "ACCESSIBLE " : "not accessible "}\n`;
663
+ text += `IMDSv1 (unauthenticated): ${imds.v1Accessible ? "ACCESSIBLE WARNING" : "not accessible OK"}\n`;
710
664
  text += `IMDSv2 (token-based): ${imds.v2Accessible ? "accessible" : "not accessible"}\n`;
711
665
  text += `IMDSv2 Token Endpoint: ${imds.v2TokenWorks ? "working" : "not working"}\n`;
712
- text += `Iptables IMDS Rules: ${imds.iptablesBlocked ? "BLOCKED " : imds.iptablesRules.length > 0 ? "rules found" : "no rules"}\n`;
666
+ text += `Iptables IMDS Rules: ${imds.iptablesBlocked ? "BLOCKED OK" : imds.iptablesRules.length > 0 ? "rules found" : "no rules"}\n`;
713
667
  text += `Hop Limit: ${imds.hopLimit}\n`;
714
668
  text += `\nSeverity: ${imds.severity}\n`;
715
669
  text += `Security Score: ${imds.securityScore}/100\n`;
@@ -14,7 +14,7 @@ import { generateDurationBanner, generateTimingSummary, startTiming, } from "../
14
14
  import { logChange, createChangeEntry } from "../core/changelog.js";
15
15
  import { getDistroAdapter } from "../core/distro-adapter.js";
16
16
  import { sanitizeArgs } from "../core/sanitizer.js";
17
- import { readFileSync } from "node:fs";
17
+ import { readFileSync, statSync } from "node:fs";
18
18
  import { loadPolicy, evaluatePolicy, getBuiltinPolicies, } from "../core/policy-engine.js";
19
19
  async function runCisCheck(command, args, id, title, level, expectPattern) {
20
20
  try {
@@ -668,7 +668,7 @@ export function registerComplianceTools(server) {
668
668
  }
669
669
  function filePermCheckLocal(filePath, maxPerm) {
670
670
  try {
671
- const { statSync } = require("node:fs");
671
+ // statSync imported at module level
672
672
  const stat = statSync(filePath);
673
673
  const mode = (stat.mode & 0o777).toString(8);
674
674
  return { passed: parseInt(mode, 8) <= parseInt(maxPerm, 8), detail: `${filePath}: ${mode} (max: ${maxPerm})` };
@@ -943,21 +943,17 @@ export function registerComplianceTools(server) {
943
943
  for (const section of report.sections) {
944
944
  md += `## ${section.name}\n\n`;
945
945
  md += `**Score:** ${section.score}/100\n\n`;
946
- md += `\`\`\`json\n${JSON.stringify(section.details, null, 2)}\n\`\`\`\n\n`;
946
+ md += `\`\`\`json\n${JSON.stringify(section.details)}\n\`\`\`\n\n`;
947
947
  }
948
948
  return { content: [createTextContent(md)] };
949
949
  }
950
950
  // Text format
951
- let text = `${"=".repeat(60)}\n`;
952
- text += ` COMPLIANCE REPORT\n`;
953
- text += ` Generated: ${report.timestamp}\n`;
954
- text += ` Overall Score: ${report.overallScore}/100\n`;
955
- text += `${"=".repeat(60)}\n\n`;
951
+ let text = `COMPLIANCE REPORT\n`;
952
+ text += `Generated: ${report.timestamp}\n`;
953
+ text += `Overall Score: ${report.overallScore}/100\n\n`;
956
954
  for (const section of report.sections) {
957
- text += `${"─".repeat(50)}\n`;
958
- text += ` ${section.name} — Score: ${section.score}/100\n`;
959
- text += `${"─".repeat(50)}\n`;
960
- text += `${JSON.stringify(section.details, null, 2)}\n\n`;
955
+ text += `${section.name} — Score: ${section.score}/100\n`;
956
+ text += `${JSON.stringify(section.details)}\n\n`;
961
957
  }
962
958
  return { content: [createTextContent(text)] };
963
959
  }
@@ -981,7 +977,7 @@ export function registerComplianceTools(server) {
981
977
  const currentUser = process.env.USER || process.env.LOGNAME;
982
978
  if (currentUser && currentUser !== "root" && !allowed_users.includes(currentUser)) {
983
979
  allowed_users = [...allowed_users, currentUser];
984
- changes.push(`⚠️ Auto-included current user '${currentUser}' in allowed_users to prevent self-lockout`);
980
+ changes.push(`WARNING: Auto-included current user '${currentUser}' in allowed_users to prevent self-lockout`);
985
981
  }
986
982
  // Validate usernames
987
983
  for (const user of allowed_users) {