defense-mcp-server 0.9.2 → 0.9.3

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 (69) 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.d.ts +0 -5
  5. package/build/core/distro-adapter.d.ts.map +1 -1
  6. package/build/core/distro-adapter.js +0 -7
  7. package/build/core/distro.d.ts +0 -11
  8. package/build/core/distro.d.ts.map +1 -1
  9. package/build/core/distro.js +0 -48
  10. package/build/core/encrypted-state.d.ts +0 -7
  11. package/build/core/encrypted-state.d.ts.map +1 -1
  12. package/build/core/encrypted-state.js +0 -7
  13. package/build/core/logger.js +1 -1
  14. package/build/core/pam-utils.js +1 -1
  15. package/build/core/parsers.js +1 -1
  16. package/build/core/preflight.d.ts +4 -4
  17. package/build/core/preflight.js +13 -13
  18. package/build/core/progress.js +20 -20
  19. package/build/core/run-command.d.ts +14 -0
  20. package/build/core/run-command.d.ts.map +1 -0
  21. package/build/core/run-command.js +46 -0
  22. package/build/core/spawn-safe.d.ts +6 -6
  23. package/build/core/spawn-safe.d.ts.map +1 -1
  24. package/build/core/sudo-guard.js +4 -4
  25. package/build/core/third-party-installer.js +4 -4
  26. package/build/core/tool-wrapper.js +3 -3
  27. package/build/tools/access-control.js +6 -6
  28. package/build/tools/api-security.d.ts.map +1 -1
  29. package/build/tools/api-security.js +5 -51
  30. package/build/tools/app-hardening.d.ts.map +1 -1
  31. package/build/tools/app-hardening.js +23 -25
  32. package/build/tools/cloud-security.d.ts.map +1 -1
  33. package/build/tools/cloud-security.js +5 -51
  34. package/build/tools/compliance.d.ts.map +1 -1
  35. package/build/tools/compliance.js +9 -13
  36. package/build/tools/container-security.d.ts.map +1 -1
  37. package/build/tools/container-security.js +51 -52
  38. package/build/tools/deception.d.ts.map +1 -1
  39. package/build/tools/deception.js +8 -54
  40. package/build/tools/dns-security.d.ts.map +1 -1
  41. package/build/tools/dns-security.js +2 -48
  42. package/build/tools/encryption.d.ts.map +1 -1
  43. package/build/tools/encryption.js +86 -87
  44. package/build/tools/firewall.d.ts.map +1 -1
  45. package/build/tools/firewall.js +324 -30
  46. package/build/tools/hardening.d.ts.map +1 -1
  47. package/build/tools/hardening.js +12 -13
  48. package/build/tools/incident-response.d.ts.map +1 -1
  49. package/build/tools/incident-response.js +3 -3
  50. package/build/tools/logging.d.ts.map +1 -1
  51. package/build/tools/logging.js +17 -59
  52. package/build/tools/malware.js +2 -2
  53. package/build/tools/meta.d.ts.map +1 -1
  54. package/build/tools/meta.js +86 -165
  55. package/build/tools/network-defense.d.ts.map +1 -1
  56. package/build/tools/network-defense.js +3 -3
  57. package/build/tools/patch-management.js +8 -8
  58. package/build/tools/process-security.d.ts.map +1 -1
  59. package/build/tools/process-security.js +38 -92
  60. package/build/tools/sudo-management.js +36 -36
  61. package/build/tools/threat-intel.d.ts.map +1 -1
  62. package/build/tools/threat-intel.js +2 -48
  63. package/build/tools/vulnerability-management.d.ts.map +1 -1
  64. package/build/tools/vulnerability-management.js +3 -49
  65. package/build/tools/waf.d.ts.map +1 -1
  66. package/build/tools/waf.js +47 -93
  67. package/build/tools/wireless-security.d.ts.map +1 -1
  68. package/build/tools/wireless-security.js +9 -55
  69. package/package.json +4 -2
@@ -8,7 +8,7 @@
8
8
  * configuration, OWASP CRS deployment checks, and blocked request 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 MODSEC_CONF_PATHS = {
@@ -40,53 +40,7 @@ const OWASP_CRS_PATHS = [
40
40
  "/opt/owasp-crs",
41
41
  "/etc/modsecurity/crs",
42
42
  ];
43
- /**
44
- * Run a command via spawnSafe and collect output as a promise.
45
- * Handles errors gracefully — returns error info instead of throwing.
46
- */
47
- async function runCommand(command, args, timeoutMs = 30_000) {
48
- return new Promise((resolve) => {
49
- let child;
50
- try {
51
- child = spawnSafe(command, args);
52
- }
53
- catch (err) {
54
- const msg = err instanceof Error ? err.message : String(err);
55
- resolve({ stdout: "", stderr: msg, exitCode: -1 });
56
- return;
57
- }
58
- let stdout = "";
59
- let stderr = "";
60
- let resolved = false;
61
- const timer = setTimeout(() => {
62
- if (!resolved) {
63
- resolved = true;
64
- child.kill("SIGTERM");
65
- resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
66
- }
67
- }, timeoutMs);
68
- child.stdout?.on("data", (data) => {
69
- stdout += data.toString();
70
- });
71
- child.stderr?.on("data", (data) => {
72
- stderr += data.toString();
73
- });
74
- child.on("close", (code) => {
75
- if (!resolved) {
76
- resolved = true;
77
- clearTimeout(timer);
78
- resolve({ stdout, stderr, exitCode: code ?? -1 });
79
- }
80
- });
81
- child.on("error", (err) => {
82
- if (!resolved) {
83
- resolved = true;
84
- clearTimeout(timer);
85
- resolve({ stdout, stderr: err.message, exitCode: -1 });
86
- }
87
- });
88
- });
89
- }
43
+ // ── Helpers ────────────────────────────────────────────────────────────────────
90
44
  /**
91
45
  * Run a command via sudo through spawnSafe.
92
46
  */
@@ -101,8 +55,8 @@ async function handleModsecAudit(webServer, outputFormat) {
101
55
  try {
102
56
  const sections = [];
103
57
  const jsonData = {};
104
- sections.push("🛡️ ModSecurity WAF Audit");
105
- sections.push("=".repeat(55));
58
+ sections.push("ModSecurity WAF Audit");
59
+ sections.push("");
106
60
  sections.push(`Web Server: ${webServer}`);
107
61
  // Check if ModSecurity is installed
108
62
  const dpkgCheck = await runCommand("dpkg", ["-l", "libapache2-mod-security2"]);
@@ -121,12 +75,12 @@ async function handleModsecAudit(webServer, outputFormat) {
121
75
  const testResult = await runCommand("test", ["-f", confPath]);
122
76
  if (testResult.exitCode === 0) {
123
77
  configFound = true;
124
- sections.push(` ⚠️ ModSecurity package not found via dpkg, but config exists: ${confPath}`);
78
+ sections.push(` ModSecurity package not found via dpkg, but config exists: ${confPath}`);
125
79
  break;
126
80
  }
127
81
  }
128
82
  if (!configFound) {
129
- sections.push(" ModSecurity is NOT installed");
83
+ sections.push(" ModSecurity is NOT installed");
130
84
  sections.push(" Install with:");
131
85
  if (webServer === "nginx") {
132
86
  sections.push(" sudo apt install libnginx-mod-security");
@@ -142,7 +96,7 @@ async function handleModsecAudit(webServer, outputFormat) {
142
96
  }
143
97
  }
144
98
  else {
145
- sections.push(" ModSecurity is installed");
99
+ sections.push(" ModSecurity is installed");
146
100
  }
147
101
  // Read ModSecurity configuration
148
102
  const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
@@ -165,16 +119,16 @@ async function handleModsecAudit(webServer, outputFormat) {
165
119
  const engineMode = engineMatch ? engineMatch[1] : "unknown";
166
120
  jsonData.engine_mode = engineMode;
167
121
  if (engineMode === "On") {
168
- sections.push(" SecRuleEngine: On (active protection)");
122
+ sections.push(" SecRuleEngine: On (active protection)");
169
123
  }
170
124
  else if (engineMode === "DetectionOnly") {
171
- sections.push(" ⚠️ SecRuleEngine: DetectionOnly (logging only, not blocking)");
125
+ sections.push(" SecRuleEngine: DetectionOnly (logging only, not blocking)");
172
126
  }
173
127
  else if (engineMode === "Off") {
174
- sections.push(" SecRuleEngine: Off (WAF disabled!)");
128
+ sections.push(" SecRuleEngine: Off (WAF disabled!)");
175
129
  }
176
130
  else {
177
- sections.push(` SecRuleEngine: ${engineMode}`);
131
+ sections.push(` SecRuleEngine: ${engineMode}`);
178
132
  }
179
133
  // Check audit logging
180
134
  const auditLogMatch = configContent.match(/^\s*SecAuditLog\s+(\S+)/m);
@@ -210,16 +164,16 @@ async function handleModsecAudit(webServer, outputFormat) {
210
164
  }
211
165
  jsonData.issues = issues;
212
166
  if (issues.length === 0) {
213
- sections.push(" No common misconfigurations detected");
167
+ sections.push(" No common misconfigurations detected");
214
168
  }
215
169
  else {
216
170
  for (const issue of issues) {
217
- sections.push(` ⚠️ ${issue}`);
171
+ sections.push(` WARNING: ${issue}`);
218
172
  }
219
173
  }
220
174
  }
221
175
  else {
222
- sections.push(" No ModSecurity configuration file found");
176
+ sections.push(" No ModSecurity configuration file found");
223
177
  sections.push(" Searched paths:");
224
178
  for (const p of confPaths) {
225
179
  sections.push(` - ${p}`);
@@ -244,8 +198,8 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
244
198
  try {
245
199
  const sections = [];
246
200
  const jsonData = {};
247
- sections.push("📋 ModSecurity Rules Management");
248
- sections.push("=".repeat(55));
201
+ sections.push("ModSecurity Rules Management");
202
+ sections.push("");
249
203
  switch (ruleAction) {
250
204
  case "list": {
251
205
  sections.push("\n── Loaded Rule Files ──");
@@ -263,7 +217,7 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
263
217
  })
264
218
  .filter((f) => f && f.endsWith(".conf"));
265
219
  for (const file of files) {
266
- sections.push(` 📄 ${file}`);
220
+ sections.push(` ${file}`);
267
221
  allFiles.push(`${rulesDir}/${file}`);
268
222
  }
269
223
  if (files.length === 0) {
@@ -274,7 +228,7 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
274
228
  jsonData.rule_files = allFiles;
275
229
  jsonData.total_files = allFiles.length;
276
230
  if (allFiles.length === 0) {
277
- sections.push("\n No rule files found in standard directories");
231
+ sections.push("\n No rule files found in standard directories");
278
232
  sections.push(" Searched:");
279
233
  for (const d of MODSEC_RULES_DIRS) {
280
234
  sections.push(` - ${d}`);
@@ -318,7 +272,7 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
318
272
  const hasRemoveDirective = configContent.includes(removeDirective);
319
273
  if (ruleAction === "disable") {
320
274
  if (hasRemoveDirective) {
321
- sections.push(` ℹ️ Rule ${ruleId} is already disabled`);
275
+ sections.push(` INFO: Rule ${ruleId} is already disabled`);
322
276
  jsonData.status = "already_disabled";
323
277
  }
324
278
  else {
@@ -328,13 +282,13 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
328
282
  `echo '${removeDirective}' >> ${activeConfPath}`,
329
283
  ]);
330
284
  if (appendResult.exitCode === 0) {
331
- sections.push(` Rule ${ruleId} disabled (added ${removeDirective})`);
285
+ sections.push(` Rule ${ruleId} disabled (added ${removeDirective})`);
332
286
  sections.push(` Config: ${activeConfPath}`);
333
- sections.push(` ⚠️ Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
287
+ sections.push(` Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
334
288
  jsonData.status = "disabled";
335
289
  }
336
290
  else {
337
- sections.push(` Failed to disable rule: ${appendResult.stderr}`);
291
+ sections.push(` Failed to disable rule: ${appendResult.stderr}`);
338
292
  jsonData.status = "error";
339
293
  jsonData.error = appendResult.stderr;
340
294
  }
@@ -343,7 +297,7 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
343
297
  else {
344
298
  // enable — remove the SecRuleRemoveById directive
345
299
  if (!hasRemoveDirective) {
346
- sections.push(` ℹ️ Rule ${ruleId} is already enabled (no removal directive found)`);
300
+ sections.push(` INFO: Rule ${ruleId} is already enabled (no removal directive found)`);
347
301
  jsonData.status = "already_enabled";
348
302
  }
349
303
  else {
@@ -353,13 +307,13 @@ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
353
307
  activeConfPath,
354
308
  ]);
355
309
  if (sedResult.exitCode === 0) {
356
- sections.push(` Rule ${ruleId} enabled (removed ${removeDirective})`);
310
+ sections.push(` Rule ${ruleId} enabled (removed ${removeDirective})`);
357
311
  sections.push(` Config: ${activeConfPath}`);
358
- sections.push(` ⚠️ Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
312
+ sections.push(` Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
359
313
  jsonData.status = "enabled";
360
314
  }
361
315
  else {
362
- sections.push(` Failed to enable rule: ${sedResult.stderr}`);
316
+ sections.push(` Failed to enable rule: ${sedResult.stderr}`);
363
317
  jsonData.status = "error";
364
318
  jsonData.error = sedResult.stderr;
365
319
  }
@@ -390,8 +344,8 @@ async function handleRateLimitConfig(webServer, rateLimit, rateLimitZone, output
390
344
  try {
391
345
  const sections = [];
392
346
  const jsonData = {};
393
- sections.push("Rate Limiting Configuration");
394
- sections.push("=".repeat(55));
347
+ sections.push("Rate Limiting Configuration");
348
+ sections.push("");
395
349
  sections.push(`Web Server: ${webServer}`);
396
350
  jsonData.web_server = webServer;
397
351
  if (webServer === "nginx") {
@@ -409,24 +363,24 @@ async function handleRateLimitConfig(webServer, rateLimit, rateLimitZone, output
409
363
  if (zoneMatches.length > 0) {
410
364
  sections.push(" Rate limit zones defined:");
411
365
  for (const zone of zoneMatches) {
412
- sections.push(` 📊 ${zone.trim()}`);
366
+ sections.push(` ${zone.trim()}`);
413
367
  }
414
368
  }
415
369
  else {
416
- sections.push(" No limit_req_zone directives found");
370
+ sections.push(" No limit_req_zone directives found");
417
371
  }
418
372
  if (reqMatches.length > 0) {
419
373
  sections.push(" Rate limit enforcement:");
420
374
  for (const req of reqMatches) {
421
- sections.push(` 🔒 ${req.trim()}`);
375
+ sections.push(` ${req.trim()}`);
422
376
  }
423
377
  }
424
378
  else {
425
- sections.push(" No limit_req directives found");
379
+ sections.push(" No limit_req directives found");
426
380
  }
427
381
  }
428
382
  else {
429
- sections.push(" Could not read /etc/nginx/nginx.conf");
383
+ sections.push(" Could not read /etc/nginx/nginx.conf");
430
384
  jsonData.error = "Could not read nginx config";
431
385
  }
432
386
  // Suggest configuration
@@ -454,8 +408,8 @@ async function handleRateLimitConfig(webServer, rateLimit, rateLimitZone, output
454
408
  }
455
409
  jsonData.mod_ratelimit = hasRatelimit;
456
410
  jsonData.mod_evasive = hasEvasive;
457
- sections.push(` mod_ratelimit: ${hasRatelimit ? "loaded" : "not loaded"}`);
458
- sections.push(` mod_evasive: ${hasEvasive ? "loaded" : "not loaded"}`);
411
+ sections.push(` mod_ratelimit: ${hasRatelimit ? "loaded" : "not loaded"}`);
412
+ sections.push(` mod_evasive: ${hasEvasive ? "loaded" : "not loaded"}`);
459
413
  if (hasEvasive) {
460
414
  const evasiveConf = await runSudoCommand("cat", ["/etc/apache2/mods-enabled/evasive.conf"]);
461
415
  if (evasiveConf.exitCode === 0) {
@@ -502,8 +456,8 @@ async function handleOwaspCrsDeploy(webServer, outputFormat) {
502
456
  try {
503
457
  const sections = [];
504
458
  const jsonData = {};
505
- sections.push("🌐 OWASP Core Rule Set (CRS) Status");
506
- sections.push("=".repeat(55));
459
+ sections.push("OWASP Core Rule Set (CRS) Status");
460
+ sections.push("");
507
461
  // Check if CRS is installed
508
462
  let crsPath = "";
509
463
  for (const path of OWASP_CRS_PATHS) {
@@ -516,7 +470,7 @@ async function handleOwaspCrsDeploy(webServer, outputFormat) {
516
470
  jsonData.installed = !!crsPath;
517
471
  jsonData.crs_path = crsPath || null;
518
472
  if (!crsPath) {
519
- sections.push("\n OWASP CRS is NOT installed");
473
+ sections.push("\n OWASP CRS is NOT installed");
520
474
  sections.push("\n── Installation Instructions ──");
521
475
  sections.push(" Option 1 — Package manager:");
522
476
  sections.push(" sudo apt install modsecurity-crs");
@@ -531,7 +485,7 @@ async function handleOwaspCrsDeploy(webServer, outputFormat) {
531
485
  }
532
486
  return { content: [createTextContent(sections.join("\n"))] };
533
487
  }
534
- sections.push(`\n CRS found at: ${crsPath}`);
488
+ sections.push(`\n CRS found at: ${crsPath}`);
535
489
  // Check version
536
490
  const changelogResult = await runCommand("head", ["-5", `${crsPath}/CHANGES`]);
537
491
  const versionFileResult = await runCommand("cat", [`${crsPath}/VERSION`]);
@@ -566,7 +520,7 @@ async function handleOwaspCrsDeploy(webServer, outputFormat) {
566
520
  if (confContent.exitCode === 0) {
567
521
  if (confContent.stdout.includes("modsecurity-crs") || confContent.stdout.includes("crs-setup")) {
568
522
  integrated = true;
569
- sections.push(` CRS Include directives found in ${confPath}`);
523
+ sections.push(` CRS Include directives found in ${confPath}`);
570
524
  break;
571
525
  }
572
526
  }
@@ -578,12 +532,12 @@ async function handleOwaspCrsDeploy(webServer, outputFormat) {
578
532
  : await runSudoCommand("cat", ["/etc/apache2/apache2.conf"]);
579
533
  if (mainConf.exitCode === 0 && (mainConf.stdout.includes("modsecurity-crs") || mainConf.stdout.includes("crs-setup"))) {
580
534
  integrated = true;
581
- sections.push(" CRS Include directives found in main config");
535
+ sections.push(" CRS Include directives found in main config");
582
536
  }
583
537
  }
584
538
  jsonData.integrated = integrated;
585
539
  if (!integrated) {
586
- sections.push(" CRS Include directives NOT found in ModSecurity configuration");
540
+ sections.push(" CRS Include directives NOT found in ModSecurity configuration");
587
541
  sections.push(" Add these lines to your ModSecurity configuration:");
588
542
  sections.push(` Include ${crsPath}/crs-setup.conf`);
589
543
  sections.push(` Include ${crsPath}/rules/*.conf`);
@@ -600,7 +554,7 @@ async function handleOwaspCrsDeploy(webServer, outputFormat) {
600
554
  .sort();
601
555
  for (const file of ruleFiles) {
602
556
  categories.push(file);
603
- sections.push(` 📄 ${file}`);
557
+ sections.push(` ${file}`);
604
558
  }
605
559
  }
606
560
  jsonData.rule_categories = categories;
@@ -623,8 +577,8 @@ async function handleBlockedRequests(logPath, outputFormat) {
623
577
  try {
624
578
  const sections = [];
625
579
  const jsonData = {};
626
- sections.push("🚫 WAF Blocked Requests Analysis");
627
- sections.push("=".repeat(55));
580
+ sections.push("WAF Blocked Requests Analysis");
581
+ sections.push("");
628
582
  // Find the log file
629
583
  let activeLogPath = logPath;
630
584
  if (!activeLogPath) {
@@ -637,7 +591,7 @@ async function handleBlockedRequests(logPath, outputFormat) {
637
591
  }
638
592
  }
639
593
  if (!activeLogPath) {
640
- sections.push("\n No ModSecurity audit log found");
594
+ sections.push("\n No ModSecurity audit log found");
641
595
  sections.push(" Searched paths:");
642
596
  for (const p of MODSEC_AUDIT_LOG_PATHS) {
643
597
  sections.push(` - ${p}`);
@@ -710,7 +664,7 @@ async function handleBlockedRequests(logPath, outputFormat) {
710
664
  if (fpCandidates.length > 0) {
711
665
  sections.push(" Rules triggered excessively (may be false positives):");
712
666
  for (const [ruleId, count] of fpCandidates) {
713
- sections.push(` ⚠️ Rule ${ruleId}: ${count} hits — review for tuning`);
667
+ sections.push(` Rule ${ruleId}: ${count} hits — review for tuning`);
714
668
  }
715
669
  }
716
670
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"wireless-security.d.ts","sourceRoot":"","sources":["../../src/tools/wireless-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6GpE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAkC5E;AA+jBD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyQrE"}
1
+ {"version":3,"file":"wireless-security.d.ts","sourceRoot":"","sources":["../../src/tools/wireless-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA+CpE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAkC5E;AA+jBD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyQrE"}
@@ -9,7 +9,7 @@
9
9
  * wireless interface disabling with kernel module blacklist recommendations.
10
10
  */
11
11
  import { z } from "zod";
12
- import { spawnSafe } from "../core/spawn-safe.js";
12
+ import { runCommand } from "../core/run-command.js";
13
13
  import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
14
14
  import { existsSync, readFileSync } from "node:fs";
15
15
  // ── Constants ──────────────────────────────────────────────────────────────────
@@ -17,53 +17,7 @@ import { existsSync, readFileSync } from "node:fs";
17
17
  const KNOWN_APS_PATH = "/var/lib/defense-mcp/wireless/known-aps.json";
18
18
  /** Wireless kernel modules that can be blacklisted */
19
19
  const WIRELESS_MODULES = ["bluetooth", "btusb", "iwlwifi", "ath9k", "ath10k_pci", "rt2800usb"];
20
- /**
21
- * Run a command via spawnSafe and collect output as a promise.
22
- * Handles errors gracefully — returns error info instead of throwing.
23
- */
24
- async function runCommand(command, args, timeoutMs = 30_000) {
25
- return new Promise((resolve) => {
26
- let child;
27
- try {
28
- child = spawnSafe(command, args);
29
- }
30
- catch (err) {
31
- const msg = err instanceof Error ? err.message : String(err);
32
- resolve({ stdout: "", stderr: msg, exitCode: -1 });
33
- return;
34
- }
35
- let stdout = "";
36
- let stderr = "";
37
- let resolved = false;
38
- const timer = setTimeout(() => {
39
- if (!resolved) {
40
- resolved = true;
41
- child.kill("SIGTERM");
42
- resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
43
- }
44
- }, timeoutMs);
45
- child.stdout?.on("data", (data) => {
46
- stdout += data.toString();
47
- });
48
- child.stderr?.on("data", (data) => {
49
- stderr += data.toString();
50
- });
51
- child.on("close", (code) => {
52
- if (!resolved) {
53
- resolved = true;
54
- clearTimeout(timer);
55
- resolve({ stdout, stderr, exitCode: code ?? -1 });
56
- }
57
- });
58
- child.on("error", (err) => {
59
- if (!resolved) {
60
- resolved = true;
61
- clearTimeout(timer);
62
- resolve({ stdout, stderr: err.message, exitCode: -1 });
63
- }
64
- });
65
- });
66
- }
20
+ // ── Helpers ────────────────────────────────────────────────────────────────────
67
21
  /**
68
22
  * Load known APs from the configuration file.
69
23
  * Returns empty array if file doesn't exist or is invalid.
@@ -632,8 +586,8 @@ export function registerWirelessSecurityTools(server) {
632
586
  let text = "Wireless Security — Bluetooth Audit\n\n";
633
587
  text += `Adapter Found: ${audit.adapterFound ? "yes" : "no"}\n`;
634
588
  text += `Adapter Status: ${audit.adapterStatus}\n`;
635
- text += `Powered: ${audit.powered ? "YES " : "no"}\n`;
636
- text += `Discoverable: ${audit.discoverable ? "YES ⚠⚠" : "no "}\n`;
589
+ text += `Powered: ${audit.powered ? "YES WARNING" : "no"}\n`;
590
+ text += `Discoverable: ${audit.discoverable ? "YES WARNING" : "no OK"}\n`;
637
591
  text += `Paired Devices: ${audit.pairedDevicesCount}\n`;
638
592
  if (audit.pairedDevices.length > 0) {
639
593
  text += "\nPaired Devices:\n";
@@ -641,7 +595,7 @@ export function registerWirelessSecurityTools(server) {
641
595
  text += ` • ${dev}\n`;
642
596
  }
643
597
  }
644
- text += `\nBluetooth Service: ${audit.serviceRunning ? "running " : "not running "}\n`;
598
+ text += `\nBluetooth Service: ${audit.serviceRunning ? "running WARNING" : "not running OK"}\n`;
645
599
  text += `Risk Level: ${audit.riskLevel}\n`;
646
600
  if (audit.recommendations.length > 0) {
647
601
  text += "\nRecommendations:\n";
@@ -743,7 +697,7 @@ export function registerWirelessSecurityTools(server) {
743
697
  }
744
698
  }
745
699
  if (scan.potentialEvilTwins.length > 0) {
746
- text += "\n⚠ Potential Evil Twins:\n";
700
+ text += "\nWARNING: Potential Evil Twins:\n";
747
701
  for (const twin of scan.potentialEvilTwins) {
748
702
  text += ` • SSID: ${twin.ap.ssid} | BSSID: ${twin.ap.bssid} — mimics known AP: ${twin.matchedKnown}\n`;
749
703
  }
@@ -789,8 +743,8 @@ export function registerWirelessSecurityTools(server) {
789
743
  const status = wi.inUse
790
744
  ? "IN USE (not disabled)"
791
745
  : wi.disabled
792
- ? "DISABLED "
793
- : "could not disable ";
746
+ ? "DISABLED OK"
747
+ : "could not disable WARNING";
794
748
  text += ` • ${wi.name}: ${status}\n`;
795
749
  }
796
750
  }
@@ -800,7 +754,7 @@ export function registerWirelessSecurityTools(server) {
800
754
  }
801
755
  else {
802
756
  for (const mod of disable.loadedModules) {
803
- text += ` • ${mod.name}: ${mod.loaded ? "LOADED" : "not loaded"}${mod.canBlacklist ? " can be blacklisted" : ""}\n`;
757
+ text += ` • ${mod.name}: ${mod.loaded ? "LOADED" : "not loaded"}${mod.canBlacklist ? " WARNING: can be blacklisted" : ""}\n`;
804
758
  }
805
759
  }
806
760
  text += `\nInterfaces Disabled: ${disable.interfacesDisabled}\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "defense-mcp-server",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Defense MCP Server — 31 defensive security tools with 250+ actions for system hardening, compliance, and threat detection on Linux",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -25,6 +25,7 @@
25
25
  "test": "vitest run",
26
26
  "test:watch": "vitest",
27
27
  "test:coverage": "vitest run --coverage",
28
+ "knip": "knip",
28
29
  "lint:security": "eslint src/ --max-warnings=0",
29
30
  "audit:security": "npm audit --audit-level=high",
30
31
  "changelog:check": "git diff HEAD~1 --name-only | grep -q CHANGELOG.md || echo 'Warning: CHANGELOG.md not updated'",
@@ -83,15 +84,16 @@
83
84
  "zod": "3.25.76"
84
85
  },
85
86
  "devDependencies": {
86
- "@eslint/js": "~9.39.4",
87
87
  "@types/node": "~22.19.11",
88
88
  "@vitest/coverage-v8": "~4.0.18",
89
89
  "eslint": "~9.39.4",
90
90
  "eslint-plugin-security": "~3.0.1",
91
91
  "husky": "~9.1.7",
92
+ "knip": "^6.2.0",
92
93
  "license-checker": "~25.0.1",
93
94
  "tsx": "~4.21.0",
94
95
  "typescript": "~5.9.3",
96
+ "typescript-eslint": "^8.58.0",
95
97
  "vitest": "~4.0.18"
96
98
  }
97
99
  }