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.
- package/build/core/auto-installer.js +31 -31
- package/build/core/command-allowlist.js +1 -1
- package/build/core/dependency-validator.js +9 -9
- package/build/core/distro-adapter.d.ts +0 -5
- package/build/core/distro-adapter.d.ts.map +1 -1
- package/build/core/distro-adapter.js +0 -7
- package/build/core/distro.d.ts +0 -11
- package/build/core/distro.d.ts.map +1 -1
- package/build/core/distro.js +0 -48
- package/build/core/encrypted-state.d.ts +0 -7
- package/build/core/encrypted-state.d.ts.map +1 -1
- package/build/core/encrypted-state.js +0 -7
- package/build/core/logger.js +1 -1
- package/build/core/pam-utils.js +1 -1
- package/build/core/parsers.js +1 -1
- package/build/core/preflight.d.ts +4 -4
- package/build/core/preflight.js +13 -13
- package/build/core/progress.js +20 -20
- package/build/core/run-command.d.ts +14 -0
- package/build/core/run-command.d.ts.map +1 -0
- package/build/core/run-command.js +46 -0
- package/build/core/spawn-safe.d.ts +6 -6
- package/build/core/spawn-safe.d.ts.map +1 -1
- package/build/core/sudo-guard.js +4 -4
- package/build/core/third-party-installer.js +4 -4
- package/build/core/tool-wrapper.js +3 -3
- package/build/tools/access-control.js +6 -6
- package/build/tools/api-security.d.ts.map +1 -1
- package/build/tools/api-security.js +5 -51
- package/build/tools/app-hardening.d.ts.map +1 -1
- package/build/tools/app-hardening.js +23 -25
- package/build/tools/cloud-security.d.ts.map +1 -1
- package/build/tools/cloud-security.js +5 -51
- package/build/tools/compliance.d.ts.map +1 -1
- package/build/tools/compliance.js +9 -13
- package/build/tools/container-security.d.ts.map +1 -1
- package/build/tools/container-security.js +51 -52
- package/build/tools/deception.d.ts.map +1 -1
- package/build/tools/deception.js +8 -54
- package/build/tools/dns-security.d.ts.map +1 -1
- package/build/tools/dns-security.js +2 -48
- package/build/tools/encryption.d.ts.map +1 -1
- package/build/tools/encryption.js +86 -87
- package/build/tools/firewall.d.ts.map +1 -1
- package/build/tools/firewall.js +324 -30
- package/build/tools/hardening.d.ts.map +1 -1
- package/build/tools/hardening.js +12 -13
- package/build/tools/incident-response.d.ts.map +1 -1
- package/build/tools/incident-response.js +3 -3
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/logging.js +17 -59
- package/build/tools/malware.js +2 -2
- package/build/tools/meta.d.ts.map +1 -1
- package/build/tools/meta.js +86 -165
- package/build/tools/network-defense.d.ts.map +1 -1
- package/build/tools/network-defense.js +3 -3
- package/build/tools/patch-management.js +8 -8
- package/build/tools/process-security.d.ts.map +1 -1
- package/build/tools/process-security.js +38 -92
- package/build/tools/sudo-management.js +36 -36
- package/build/tools/threat-intel.d.ts.map +1 -1
- package/build/tools/threat-intel.js +2 -48
- package/build/tools/vulnerability-management.d.ts.map +1 -1
- package/build/tools/vulnerability-management.js +3 -49
- package/build/tools/waf.d.ts.map +1 -1
- package/build/tools/waf.js +47 -93
- package/build/tools/wireless-security.d.ts.map +1 -1
- package/build/tools/wireless-security.js +9 -55
- package/package.json +4 -2
package/build/tools/waf.js
CHANGED
|
@@ -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 {
|
|
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("
|
|
105
|
-
sections.push("
|
|
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(`
|
|
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("
|
|
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("
|
|
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("
|
|
122
|
+
sections.push(" SecRuleEngine: On (active protection)");
|
|
169
123
|
}
|
|
170
124
|
else if (engineMode === "DetectionOnly") {
|
|
171
|
-
sections.push("
|
|
125
|
+
sections.push(" SecRuleEngine: DetectionOnly (logging only, not blocking)");
|
|
172
126
|
}
|
|
173
127
|
else if (engineMode === "Off") {
|
|
174
|
-
sections.push("
|
|
128
|
+
sections.push(" SecRuleEngine: Off (WAF disabled!)");
|
|
175
129
|
}
|
|
176
130
|
else {
|
|
177
|
-
sections.push(`
|
|
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("
|
|
167
|
+
sections.push(" No common misconfigurations detected");
|
|
214
168
|
}
|
|
215
169
|
else {
|
|
216
170
|
for (const issue of issues) {
|
|
217
|
-
sections.push(`
|
|
171
|
+
sections.push(` WARNING: ${issue}`);
|
|
218
172
|
}
|
|
219
173
|
}
|
|
220
174
|
}
|
|
221
175
|
else {
|
|
222
|
-
sections.push("
|
|
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("
|
|
248
|
-
sections.push("
|
|
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(`
|
|
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
|
|
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(`
|
|
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(`
|
|
285
|
+
sections.push(` Rule ${ruleId} disabled (added ${removeDirective})`);
|
|
332
286
|
sections.push(` Config: ${activeConfPath}`);
|
|
333
|
-
sections.push(`
|
|
287
|
+
sections.push(` Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
|
|
334
288
|
jsonData.status = "disabled";
|
|
335
289
|
}
|
|
336
290
|
else {
|
|
337
|
-
sections.push(`
|
|
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(`
|
|
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(`
|
|
310
|
+
sections.push(` Rule ${ruleId} enabled (removed ${removeDirective})`);
|
|
357
311
|
sections.push(` Config: ${activeConfPath}`);
|
|
358
|
-
sections.push(`
|
|
312
|
+
sections.push(` Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
|
|
359
313
|
jsonData.status = "enabled";
|
|
360
314
|
}
|
|
361
315
|
else {
|
|
362
|
-
sections.push(`
|
|
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("
|
|
394
|
-
sections.push("
|
|
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(`
|
|
366
|
+
sections.push(` ${zone.trim()}`);
|
|
413
367
|
}
|
|
414
368
|
}
|
|
415
369
|
else {
|
|
416
|
-
sections.push("
|
|
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(`
|
|
375
|
+
sections.push(` ${req.trim()}`);
|
|
422
376
|
}
|
|
423
377
|
}
|
|
424
378
|
else {
|
|
425
|
-
sections.push("
|
|
379
|
+
sections.push(" No limit_req directives found");
|
|
426
380
|
}
|
|
427
381
|
}
|
|
428
382
|
else {
|
|
429
|
-
sections.push("
|
|
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 ? "
|
|
458
|
-
sections.push(` mod_evasive: ${hasEvasive ? "
|
|
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("
|
|
506
|
-
sections.push("
|
|
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
|
|
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
|
|
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(`
|
|
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("
|
|
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("
|
|
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(`
|
|
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("
|
|
627
|
-
sections.push("
|
|
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
|
|
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(`
|
|
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;
|
|
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 {
|
|
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
|
|
636
|
-
text += `Discoverable: ${audit.discoverable ? "YES
|
|
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
|
|
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 += "\
|
|
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 ? "
|
|
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.
|
|
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
|
}
|