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/meta.js
CHANGED
|
@@ -22,7 +22,7 @@ import { THIRD_PARTY_MANIFEST } from "../core/third-party-manifest.js";
|
|
|
22
22
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, renameSync } from "node:fs";
|
|
23
23
|
import { join } from "node:path";
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
|
-
import {
|
|
25
|
+
import { runCommand } from "../core/run-command.js";
|
|
26
26
|
import { secureWriteFileSync, verifySecurePermissions } from "../core/secure-fs.js";
|
|
27
27
|
import { verifyAllBinaries } from "../core/command-allowlist.js";
|
|
28
28
|
import { RateLimiter } from "../core/rate-limiter.js";
|
|
@@ -452,50 +452,11 @@ function riskSortValue(risk) {
|
|
|
452
452
|
default: return 3;
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
|
-
async function runRemediateCmd(command, args, timeoutMs = 30_000) {
|
|
456
|
-
return new Promise((resolve) => {
|
|
457
|
-
let child;
|
|
458
|
-
try {
|
|
459
|
-
child = spawnSafe(command, args);
|
|
460
|
-
}
|
|
461
|
-
catch (err) {
|
|
462
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
463
|
-
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
let stdout = "";
|
|
467
|
-
let stderr = "";
|
|
468
|
-
let resolved = false;
|
|
469
|
-
const timer = setTimeout(() => {
|
|
470
|
-
if (!resolved) {
|
|
471
|
-
resolved = true;
|
|
472
|
-
child.kill("SIGTERM");
|
|
473
|
-
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
474
|
-
}
|
|
475
|
-
}, timeoutMs);
|
|
476
|
-
child.stdout?.on("data", (data) => { stdout += data.toString(); });
|
|
477
|
-
child.stderr?.on("data", (data) => { stderr += data.toString(); });
|
|
478
|
-
child.on("close", (code) => {
|
|
479
|
-
if (!resolved) {
|
|
480
|
-
resolved = true;
|
|
481
|
-
clearTimeout(timer);
|
|
482
|
-
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
child.on("error", (err) => {
|
|
486
|
-
if (!resolved) {
|
|
487
|
-
resolved = true;
|
|
488
|
-
clearTimeout(timer);
|
|
489
|
-
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
455
|
async function gatherRemediationFindings(source, severityFilter) {
|
|
495
456
|
const findings = [];
|
|
496
457
|
const shouldInclude = (cat) => source === "all" || source === cat;
|
|
497
458
|
if (shouldInclude("hardening")) {
|
|
498
|
-
const sysctlResult = await
|
|
459
|
+
const sysctlResult = await runCommand("sysctl", ["-a"]);
|
|
499
460
|
if (sysctlResult.exitCode === 0) {
|
|
500
461
|
const sysctlValues = new Map();
|
|
501
462
|
for (const line of sysctlResult.stdout.split("\n")) {
|
|
@@ -517,7 +478,7 @@ async function gatherRemediationFindings(source, severityFilter) {
|
|
|
517
478
|
}
|
|
518
479
|
}
|
|
519
480
|
if (shouldInclude("access_control")) {
|
|
520
|
-
const sshResult = await
|
|
481
|
+
const sshResult = await runCommand("grep", ["-E", "^PermitRootLogin|^PermitEmptyPasswords", "/etc/ssh/sshd_config"]);
|
|
521
482
|
if (sshResult.exitCode === 0 || sshResult.stdout.length > 0) {
|
|
522
483
|
if (sshResult.stdout.includes("PermitRootLogin yes")) {
|
|
523
484
|
const f = KNOWN_REMEDIATIONS.find(r => r.finding_id === "ACCESS-001");
|
|
@@ -532,7 +493,7 @@ async function gatherRemediationFindings(source, severityFilter) {
|
|
|
532
493
|
}
|
|
533
494
|
}
|
|
534
495
|
if (shouldInclude("firewall")) {
|
|
535
|
-
const fwResult = await
|
|
496
|
+
const fwResult = await runCommand("iptables", ["-L", "-n"]);
|
|
536
497
|
if (fwResult.exitCode === 0) {
|
|
537
498
|
if (fwResult.stdout.includes("Chain INPUT (policy ACCEPT)")) {
|
|
538
499
|
const f = KNOWN_REMEDIATIONS.find(r => r.finding_id === "FW-001");
|
|
@@ -547,7 +508,7 @@ async function gatherRemediationFindings(source, severityFilter) {
|
|
|
547
508
|
}
|
|
548
509
|
}
|
|
549
510
|
if (shouldInclude("compliance")) {
|
|
550
|
-
const lynisResult = await
|
|
511
|
+
const lynisResult = await runCommand("lynis", ["audit", "system", "--quick", "--no-colors"], 120_000);
|
|
551
512
|
if (lynisResult.exitCode === 0 || lynisResult.stdout.length > 0) {
|
|
552
513
|
for (const f of KNOWN_REMEDIATIONS) {
|
|
553
514
|
if (findings.some(e => e.finding_id === f.finding_id))
|
|
@@ -594,52 +555,13 @@ const ALL_SECTIONS = [
|
|
|
594
555
|
"compliance_summary",
|
|
595
556
|
"recommendations",
|
|
596
557
|
];
|
|
597
|
-
async function runReportCommand(command, args, timeoutMs = 30_000) {
|
|
598
|
-
return new Promise((resolve) => {
|
|
599
|
-
let child;
|
|
600
|
-
try {
|
|
601
|
-
child = spawnSafe(command, args);
|
|
602
|
-
}
|
|
603
|
-
catch (err) {
|
|
604
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
605
|
-
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
let stdout = "";
|
|
609
|
-
let stderr = "";
|
|
610
|
-
let resolved = false;
|
|
611
|
-
const timer = setTimeout(() => {
|
|
612
|
-
if (!resolved) {
|
|
613
|
-
resolved = true;
|
|
614
|
-
child.kill("SIGTERM");
|
|
615
|
-
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
616
|
-
}
|
|
617
|
-
}, timeoutMs);
|
|
618
|
-
child.stdout?.on("data", (data) => { stdout += data.toString(); });
|
|
619
|
-
child.stderr?.on("data", (data) => { stderr += data.toString(); });
|
|
620
|
-
child.on("close", (code) => {
|
|
621
|
-
if (!resolved) {
|
|
622
|
-
resolved = true;
|
|
623
|
-
clearTimeout(timer);
|
|
624
|
-
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
child.on("error", (err) => {
|
|
628
|
-
if (!resolved) {
|
|
629
|
-
resolved = true;
|
|
630
|
-
clearTimeout(timer);
|
|
631
|
-
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
558
|
async function runSudoReportCommand(command, args, timeoutMs = 30_000) {
|
|
637
|
-
return
|
|
559
|
+
return runCommand("sudo", [command, ...args], timeoutMs);
|
|
638
560
|
}
|
|
639
561
|
async function gatherSystemOverview() {
|
|
640
|
-
const uname = await
|
|
641
|
-
const hostname = await
|
|
642
|
-
const uptime = await
|
|
562
|
+
const uname = await runCommand("uname", ["-a"]);
|
|
563
|
+
const hostname = await runCommand("hostname", []);
|
|
564
|
+
const uptime = await runCommand("uptime", []);
|
|
643
565
|
let data = "";
|
|
644
566
|
if (uname.exitCode === 0)
|
|
645
567
|
data += `Kernel: ${uname.stdout.trim()}\n`;
|
|
@@ -657,12 +579,12 @@ async function gatherFirewallStatus() {
|
|
|
657
579
|
return { name: "Firewall Status", key: "firewall_status", data, error: iptables.exitCode !== 0 ? iptables.stderr.trim() : undefined };
|
|
658
580
|
}
|
|
659
581
|
async function gatherServiceAudit() {
|
|
660
|
-
const services = await
|
|
582
|
+
const services = await runCommand("systemctl", ["list-units", "--type=service", "--state=running", "--no-pager", "--no-legend"]);
|
|
661
583
|
const data = services.exitCode === 0 ? services.stdout.trim() : `[Error listing services: ${services.stderr.trim()}]`;
|
|
662
584
|
return { name: "Service Audit", key: "service_audit", data, error: services.exitCode !== 0 ? services.stderr.trim() : undefined };
|
|
663
585
|
}
|
|
664
586
|
async function gatherActiveConnections() {
|
|
665
|
-
const ss = await
|
|
587
|
+
const ss = await runCommand("ss", ["-tulnp"]);
|
|
666
588
|
const data = ss.exitCode === 0 ? ss.stdout.trim() : `[Error listing connections: ${ss.stderr.trim()}]`;
|
|
667
589
|
return { name: "Active Connections", key: "active_connections", data, error: ss.exitCode !== 0 ? ss.stderr.trim() : undefined };
|
|
668
590
|
}
|
|
@@ -670,7 +592,7 @@ async function gatherRecentLogins(since) {
|
|
|
670
592
|
const args = ["_COMM=sshd", "-n", "50", "--no-pager"];
|
|
671
593
|
if (since)
|
|
672
594
|
args.push("--since", since);
|
|
673
|
-
const logins = await
|
|
595
|
+
const logins = await runCommand("journalctl", args);
|
|
674
596
|
let data = "";
|
|
675
597
|
if (logins.exitCode === 0 && logins.stdout.trim().length > 0) {
|
|
676
598
|
data = logins.stdout.trim();
|
|
@@ -751,7 +673,7 @@ function formatAsMarkdown(sections, reportType, timestamp) {
|
|
|
751
673
|
for (const section of sections) {
|
|
752
674
|
md += `## ${section.name}\n\n`;
|
|
753
675
|
if (section.error)
|
|
754
|
-
md += `>
|
|
676
|
+
md += `> Error: ${section.error}\n\n`;
|
|
755
677
|
md += `\`\`\`\n${section.data}\n\`\`\`\n\n`;
|
|
756
678
|
}
|
|
757
679
|
return md;
|
|
@@ -764,14 +686,14 @@ function formatAsHtml(sections, reportType, timestamp) {
|
|
|
764
686
|
for (const section of sections) {
|
|
765
687
|
html += `<h2>${escapeHtml(section.name)}</h2>\n`;
|
|
766
688
|
if (section.error)
|
|
767
|
-
html += `<p class="warning"
|
|
689
|
+
html += `<p class="warning">WARNING: Error: ${escapeHtml(section.error)}</p>\n`;
|
|
768
690
|
html += `<pre>${escapeHtml(section.data)}</pre>\n`;
|
|
769
691
|
}
|
|
770
692
|
html += `</body></html>`;
|
|
771
693
|
return html;
|
|
772
694
|
}
|
|
773
695
|
function formatAsJson(sections, reportType, timestamp) {
|
|
774
|
-
return JSON.stringify({ reportType, generatedAt: timestamp, sections: sections.map((s) => ({ name: s.name, key: s.key, data: s.data, error: s.error || null })) }
|
|
696
|
+
return JSON.stringify({ reportType, generatedAt: timestamp, sections: sections.map((s) => ({ name: s.name, key: s.key, data: s.data, error: s.error || null })) });
|
|
775
697
|
}
|
|
776
698
|
function formatAsCsv(sections, _reportType, _timestamp) {
|
|
777
699
|
const lines = ["Section,Status,Summary"];
|
|
@@ -851,12 +773,12 @@ export function registerMetaTools(server) {
|
|
|
851
773
|
const { category, install_missing, dry_run, gate } = params;
|
|
852
774
|
try {
|
|
853
775
|
const sections = [];
|
|
854
|
-
sections.push("
|
|
855
|
-
sections.push("
|
|
776
|
+
sections.push("Defensive Tool Availability Check");
|
|
777
|
+
sections.push("");
|
|
856
778
|
const validCategories = ["hardening", "firewall", "monitoring", "assessment", "network", "access", "encryption", "container", "malware", "forensics"];
|
|
857
779
|
const filterCategory = category && validCategories.includes(category) ? category : undefined;
|
|
858
780
|
if (category && !filterCategory) {
|
|
859
|
-
sections.push(`\
|
|
781
|
+
sections.push(`\nWARNING: Unknown category '${category}'. Valid: ${validCategories.join(", ")}`);
|
|
860
782
|
sections.push("Showing all categories.\n");
|
|
861
783
|
}
|
|
862
784
|
const results = await checkAllTools(filterCategory);
|
|
@@ -875,14 +797,14 @@ export function registerMetaTools(server) {
|
|
|
875
797
|
if (t.installed) {
|
|
876
798
|
installed++;
|
|
877
799
|
const version = t.version ? ` (${t.version.substring(0, 60)})` : "";
|
|
878
|
-
sections.push(`
|
|
800
|
+
sections.push(` ${t.tool.name}${version}`);
|
|
879
801
|
if (t.path)
|
|
880
802
|
sections.push(` Path: ${t.path}`);
|
|
881
803
|
}
|
|
882
804
|
else {
|
|
883
805
|
missing++;
|
|
884
806
|
const req = t.tool.required ? " [REQUIRED]" : " [optional]";
|
|
885
|
-
sections.push(`
|
|
807
|
+
sections.push(` ${t.tool.name}${req}`);
|
|
886
808
|
}
|
|
887
809
|
}
|
|
888
810
|
}
|
|
@@ -899,7 +821,7 @@ export function registerMetaTools(server) {
|
|
|
899
821
|
sections.push(" Installing missing tools...\n");
|
|
900
822
|
const installResults = await installMissing(filterCategory, false);
|
|
901
823
|
for (const r of installResults) {
|
|
902
|
-
const icon = r.success ? "
|
|
824
|
+
const icon = r.success ? "PASS" : "FAIL";
|
|
903
825
|
sections.push(` ${icon} ${r.message}`);
|
|
904
826
|
}
|
|
905
827
|
logChange(createChangeEntry({ tool: "defense_mgmt", action: "install_missing", target: filterCategory || "all", after: `Attempted to install ${installResults.length} tools`, dryRun: false, success: installResults.every((r) => r.success) }));
|
|
@@ -919,8 +841,8 @@ export function registerMetaTools(server) {
|
|
|
919
841
|
const optionalMissing = allMissing.filter(r => !r.tool.required);
|
|
920
842
|
if (requiredMissing.length > 0) {
|
|
921
843
|
sections.push("");
|
|
922
|
-
sections.push("
|
|
923
|
-
sections.push("
|
|
844
|
+
sections.push("AUDIT GATE: BLOCKED");
|
|
845
|
+
sections.push("");
|
|
924
846
|
sections.push(`${requiredMissing.length} required tool(s) missing. Audit cannot proceed.\n`);
|
|
925
847
|
sections.push("Required (must fix):");
|
|
926
848
|
for (const r of requiredMissing) {
|
|
@@ -955,7 +877,7 @@ export function registerMetaTools(server) {
|
|
|
955
877
|
}
|
|
956
878
|
if (optionalMissing.length > 0) {
|
|
957
879
|
sections.push("");
|
|
958
|
-
sections.push("
|
|
880
|
+
sections.push("AUDIT GATE: PASSED (with warnings)");
|
|
959
881
|
sections.push(`${optionalMissing.length} optional tool(s) missing — audit can proceed but some checks may be skipped.`);
|
|
960
882
|
for (const r of optionalMissing) {
|
|
961
883
|
const hint = await getInstallHint(r.tool);
|
|
@@ -974,7 +896,7 @@ export function registerMetaTools(server) {
|
|
|
974
896
|
};
|
|
975
897
|
}
|
|
976
898
|
// All tools present
|
|
977
|
-
sections.push("\
|
|
899
|
+
sections.push("\nAUDIT GATE: PASSED — all tools available.");
|
|
978
900
|
return {
|
|
979
901
|
content: [createTextContent(sections.join("\n"))],
|
|
980
902
|
_meta: {
|
|
@@ -1001,9 +923,9 @@ export function registerMetaTools(server) {
|
|
|
1001
923
|
if (!objective)
|
|
1002
924
|
return { content: [createErrorContent("objective is required for workflow_suggest action")], isError: true };
|
|
1003
925
|
const sections = [];
|
|
1004
|
-
sections.push(
|
|
926
|
+
sections.push(`Recommended Workflow: ${objective.replace(/_/g, " ").toUpperCase()}`);
|
|
1005
927
|
sections.push(`System type: ${system_type}`);
|
|
1006
|
-
sections.push("
|
|
928
|
+
sections.push("");
|
|
1007
929
|
const suggestions = WORKFLOW_SUGGESTIONS[objective]?.[system_type] || [];
|
|
1008
930
|
if (suggestions.length === 0) {
|
|
1009
931
|
sections.push("\nNo specific workflow available for this combination.");
|
|
@@ -1035,8 +957,8 @@ export function registerMetaTools(server) {
|
|
|
1035
957
|
if (!workflow)
|
|
1036
958
|
return { content: [createErrorContent("workflow is required for workflow_run action")], isError: true };
|
|
1037
959
|
const sections = [];
|
|
1038
|
-
sections.push(
|
|
1039
|
-
sections.push("
|
|
960
|
+
sections.push(`Workflow: ${workflow.replace(/_/g, " ").toUpperCase()}`);
|
|
961
|
+
sections.push("");
|
|
1040
962
|
const steps = WORKFLOWS[workflow];
|
|
1041
963
|
if (!steps || steps.length === 0)
|
|
1042
964
|
return { content: [createErrorContent(`Unknown workflow: ${workflow}`)], isError: true };
|
|
@@ -1077,9 +999,9 @@ export function registerMetaTools(server) {
|
|
|
1077
999
|
sections.push(`── Step ${i + 1}/${steps.length}: ${step.description} ──`);
|
|
1078
1000
|
const stepSafety = await SafeguardRegistry.getInstance().checkSafety(`defense_mgmt_${workflow}_step_${i + 1}`, { command: step.command, args: step.args, description: step.description });
|
|
1079
1001
|
if (stepSafety.warnings.length > 0)
|
|
1080
|
-
sections.push(`
|
|
1002
|
+
sections.push(` WARNING: Safety warnings: ${stepSafety.warnings.join("; ")}`);
|
|
1081
1003
|
if (!stepSafety.safe) {
|
|
1082
|
-
sections.push(`
|
|
1004
|
+
sections.push(` Step blocked by safeguards: ${stepSafety.blockers.join("; ")}`);
|
|
1083
1005
|
sections.push(` Impacted: ${stepSafety.impactedApps.join(", ")}`);
|
|
1084
1006
|
failCount++;
|
|
1085
1007
|
logChange(createChangeEntry({ tool: "defense_mgmt", action: `${workflow}_step_${i + 1}`, target: step.description, after: `blocked by safeguards: ${stepSafety.blockers.join("; ")}`, dryRun: false, success: false, error: "Blocked by safeguard checks" }));
|
|
@@ -1091,7 +1013,7 @@ export function registerMetaTools(server) {
|
|
|
1091
1013
|
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
1092
1014
|
if (result.exitCode === 0) {
|
|
1093
1015
|
successCount++;
|
|
1094
|
-
sections.push(`
|
|
1016
|
+
sections.push(` Completed in ${duration}s`);
|
|
1095
1017
|
const output = result.stdout.trim();
|
|
1096
1018
|
if (output) {
|
|
1097
1019
|
const outputLines = output.split("\n");
|
|
@@ -1110,7 +1032,7 @@ export function registerMetaTools(server) {
|
|
|
1110
1032
|
}
|
|
1111
1033
|
else {
|
|
1112
1034
|
failCount++;
|
|
1113
|
-
sections.push(`
|
|
1035
|
+
sections.push(` Failed (exit ${result.exitCode}) in ${duration}s`);
|
|
1114
1036
|
if (result.stderr)
|
|
1115
1037
|
sections.push(` Error: ${result.stderr.substring(0, 200)}`);
|
|
1116
1038
|
}
|
|
@@ -1120,7 +1042,7 @@ export function registerMetaTools(server) {
|
|
|
1120
1042
|
sections.push("── Workflow Summary ──");
|
|
1121
1043
|
sections.push(` Completed: ${successCount}/${steps.length}`);
|
|
1122
1044
|
sections.push(` Failed: ${failCount}/${steps.length}`);
|
|
1123
|
-
sections.push(failCount === 0 ? "
|
|
1045
|
+
sections.push(failCount === 0 ? " All steps completed successfully" : " WARNING: Some steps failed");
|
|
1124
1046
|
}
|
|
1125
1047
|
return { content: [createTextContent(sections.join("\n"))] };
|
|
1126
1048
|
}
|
|
@@ -1133,8 +1055,8 @@ export function registerMetaTools(server) {
|
|
|
1133
1055
|
const { limit, tool, since } = params;
|
|
1134
1056
|
try {
|
|
1135
1057
|
const sections = [];
|
|
1136
|
-
sections.push("
|
|
1137
|
-
sections.push("
|
|
1058
|
+
sections.push("Defense Change History");
|
|
1059
|
+
sections.push("");
|
|
1138
1060
|
let entries = getChangelog(limit * 5);
|
|
1139
1061
|
if (tool)
|
|
1140
1062
|
entries = entries.filter((e) => e.tool.toLowerCase().includes(tool.toLowerCase()));
|
|
@@ -1165,14 +1087,14 @@ export function registerMetaTools(server) {
|
|
|
1165
1087
|
if (since)
|
|
1166
1088
|
sections.push(` Filter: since '${since}'`);
|
|
1167
1089
|
for (const entry of entries) {
|
|
1168
|
-
sections.push("\n " + "
|
|
1090
|
+
sections.push("\n " + "");
|
|
1169
1091
|
sections.push(` ID: ${entry.id}`);
|
|
1170
1092
|
sections.push(` Time: ${entry.timestamp}`);
|
|
1171
1093
|
sections.push(` Tool: ${entry.tool}`);
|
|
1172
1094
|
sections.push(` Action: ${entry.action}`);
|
|
1173
1095
|
sections.push(` Target: ${entry.target}`);
|
|
1174
1096
|
sections.push(` Dry Run: ${entry.dryRun ? "Yes" : "No"}`);
|
|
1175
|
-
sections.push(` Success: ${entry.success ? "
|
|
1097
|
+
sections.push(` Success: ${entry.success ? "PASS" : "FAIL"}`);
|
|
1176
1098
|
if (entry.error)
|
|
1177
1099
|
sections.push(` Error: ${entry.error}`);
|
|
1178
1100
|
if (entry.before)
|
|
@@ -1546,12 +1468,12 @@ export function registerMetaTools(server) {
|
|
|
1546
1468
|
return { content: [formatToolOutput({ action: "plan", source: effectiveSource, severity_filter: effectiveSeverity, total_findings: findings.length, findings: findings.map(f => ({ finding_id: f.finding_id, description: f.description, severity: f.severity, remediation_command: `${f.remediation_command} ${f.remediation_args.join(" ")}`, risk_level: f.risk_level, category: f.category })) })] };
|
|
1547
1469
|
}
|
|
1548
1470
|
const sections = [];
|
|
1549
|
-
sections.push("
|
|
1550
|
-
sections.push("
|
|
1471
|
+
sections.push("Auto-Remediation Plan");
|
|
1472
|
+
sections.push("");
|
|
1551
1473
|
sections.push(`Source: ${effectiveSource} | Severity filter: >= ${effectiveSeverity}`);
|
|
1552
1474
|
sections.push(`Total findings: ${findings.length}`);
|
|
1553
1475
|
if (findings.length === 0) {
|
|
1554
|
-
sections.push("\
|
|
1476
|
+
sections.push("\nNo findings match the current filters. System looks good!");
|
|
1555
1477
|
return { content: [createTextContent(sections.join("\n"))] };
|
|
1556
1478
|
}
|
|
1557
1479
|
for (const f of findings) {
|
|
@@ -1585,29 +1507,29 @@ export function registerMetaTools(server) {
|
|
|
1585
1507
|
const msg = "No findings match the current filters. Nothing to remediate.";
|
|
1586
1508
|
if (params.output_format === "json")
|
|
1587
1509
|
return { content: [formatToolOutput({ action: "apply", dry_run: effectiveDryRun, message: msg, actions_taken: 0 })] };
|
|
1588
|
-
return { content: [createTextContent(
|
|
1510
|
+
return { content: [createTextContent(msg)] };
|
|
1589
1511
|
}
|
|
1590
1512
|
if (effectiveDryRun) {
|
|
1591
1513
|
if (params.output_format === "json") {
|
|
1592
1514
|
return { content: [formatToolOutput({ action: "apply", dry_run: true, total_findings: findings.length, would_execute: findings.filter(f => f.risk_level === "safe").map(f => ({ finding_id: f.finding_id, description: f.description, command: `${f.remediation_command} ${f.remediation_args.join(" ")}`, risk_level: f.risk_level })), would_skip: findings.filter(f => f.risk_level !== "safe").map(f => ({ finding_id: f.finding_id, description: f.description, risk_level: f.risk_level, reason: `risk_level is ${f.risk_level} (only safe actions auto-executed)` })) })] };
|
|
1593
1515
|
}
|
|
1594
1516
|
const sections = [];
|
|
1595
|
-
sections.push("
|
|
1596
|
-
sections.push("
|
|
1517
|
+
sections.push("Auto-Remediation — DRY RUN");
|
|
1518
|
+
sections.push("");
|
|
1597
1519
|
sections.push("[DRY RUN] No changes will be made.\n");
|
|
1598
1520
|
const safeFindings = findings.filter(f => f.risk_level === "safe");
|
|
1599
1521
|
const skippedFindings = findings.filter(f => f.risk_level !== "safe");
|
|
1600
1522
|
if (safeFindings.length > 0) {
|
|
1601
1523
|
sections.push("Would execute:");
|
|
1602
1524
|
for (const f of safeFindings) {
|
|
1603
|
-
sections.push(`
|
|
1525
|
+
sections.push(` ${f.finding_id}: ${f.remediation_command} ${f.remediation_args.join(" ")}`);
|
|
1604
1526
|
sections.push(` ${f.description}`);
|
|
1605
1527
|
}
|
|
1606
1528
|
}
|
|
1607
1529
|
if (skippedFindings.length > 0) {
|
|
1608
1530
|
sections.push("\nWould skip (too risky for auto-execution):");
|
|
1609
1531
|
for (const f of skippedFindings)
|
|
1610
|
-
sections.push(`
|
|
1532
|
+
sections.push(` SKIPPED: ${f.finding_id}: ${f.description} [${f.risk_level}]`);
|
|
1611
1533
|
}
|
|
1612
1534
|
sections.push("\nSet dry_run=false to execute safe remediations.");
|
|
1613
1535
|
return { content: [createTextContent(sections.join("\n"))] };
|
|
@@ -1621,46 +1543,46 @@ export function registerMetaTools(server) {
|
|
|
1621
1543
|
summary: { total: 0, successful: 0, failed: 0, skipped: 0, rolled_back: 0 },
|
|
1622
1544
|
};
|
|
1623
1545
|
const sections = [];
|
|
1624
|
-
sections.push("
|
|
1625
|
-
sections.push("
|
|
1546
|
+
sections.push("Auto-Remediation — LIVE EXECUTION");
|
|
1547
|
+
sections.push("");
|
|
1626
1548
|
sections.push(`Session ID: ${sessionId}\n`);
|
|
1627
1549
|
for (const f of findings) {
|
|
1628
1550
|
session.summary.total++;
|
|
1629
1551
|
if (f.risk_level !== "safe") {
|
|
1630
1552
|
session.actions.push({ finding_id: f.finding_id, description: f.description, remediation_command: f.remediation_command, remediation_args: f.remediation_args, rollback_command: f.rollback_command, rollback_args: f.rollback_args, before_state: "", after_state: "", status: "skipped", error: `risk_level is ${f.risk_level} (only safe actions auto-executed)`, timestamp: new Date().toISOString() });
|
|
1631
1553
|
session.summary.skipped++;
|
|
1632
|
-
sections.push(`
|
|
1554
|
+
sections.push(` SKIPPED: ${f.finding_id}: SKIPPED (${f.risk_level} risk)`);
|
|
1633
1555
|
continue;
|
|
1634
1556
|
}
|
|
1635
1557
|
if (!REMEDIATION_ALLOWLIST.has(f.remediation_command)) {
|
|
1636
1558
|
session.actions.push({ finding_id: f.finding_id, description: f.description, remediation_command: f.remediation_command, remediation_args: f.remediation_args, rollback_command: f.rollback_command, rollback_args: f.rollback_args, before_state: "", after_state: "", status: "skipped", error: `Command '${f.remediation_command}' not in remediation allowlist`, timestamp: new Date().toISOString() });
|
|
1637
1559
|
session.summary.skipped++;
|
|
1638
|
-
sections.push(`
|
|
1560
|
+
sections.push(` SKIPPED: ${f.finding_id}: SKIPPED (command not in remediation allowlist)`);
|
|
1639
1561
|
continue;
|
|
1640
1562
|
}
|
|
1641
1563
|
let beforeState = "";
|
|
1642
1564
|
const setArg = f.remediation_args.find(a => a.includes("="));
|
|
1643
1565
|
if (setArg && f.remediation_command === "sysctl") {
|
|
1644
1566
|
const key = setArg.substring(0, setArg.indexOf("="));
|
|
1645
|
-
const beforeResult = await
|
|
1567
|
+
const beforeResult = await runCommand("sysctl", ["-n", key]);
|
|
1646
1568
|
beforeState = beforeResult.stdout.trim();
|
|
1647
1569
|
}
|
|
1648
|
-
const result = await
|
|
1570
|
+
const result = await runCommand(f.remediation_command, f.remediation_args);
|
|
1649
1571
|
let afterState = "";
|
|
1650
1572
|
if (setArg && f.remediation_command === "sysctl") {
|
|
1651
1573
|
const key = setArg.substring(0, setArg.indexOf("="));
|
|
1652
|
-
const afterResult = await
|
|
1574
|
+
const afterResult = await runCommand("sysctl", ["-n", key]);
|
|
1653
1575
|
afterState = afterResult.stdout.trim();
|
|
1654
1576
|
}
|
|
1655
1577
|
if (result.exitCode === 0) {
|
|
1656
1578
|
session.actions.push({ finding_id: f.finding_id, description: f.description, remediation_command: f.remediation_command, remediation_args: f.remediation_args, rollback_command: f.rollback_command, rollback_args: f.rollback_args, before_state: beforeState, after_state: afterState, status: "success", timestamp: new Date().toISOString() });
|
|
1657
1579
|
session.summary.successful++;
|
|
1658
|
-
sections.push(`
|
|
1580
|
+
sections.push(` ${f.finding_id}: ${f.description}`);
|
|
1659
1581
|
}
|
|
1660
1582
|
else {
|
|
1661
1583
|
session.actions.push({ finding_id: f.finding_id, description: f.description, remediation_command: f.remediation_command, remediation_args: f.remediation_args, rollback_command: f.rollback_command, rollback_args: f.rollback_args, before_state: beforeState, after_state: afterState, status: "failed", error: result.stderr.substring(0, 200), timestamp: new Date().toISOString() });
|
|
1662
1584
|
session.summary.failed++;
|
|
1663
|
-
sections.push(`
|
|
1585
|
+
sections.push(` ${f.finding_id}: FAILED — ${result.stderr.substring(0, 100)}`);
|
|
1664
1586
|
}
|
|
1665
1587
|
}
|
|
1666
1588
|
session.status = session.summary.failed === 0 && session.summary.skipped === 0 ? "completed" : session.summary.successful > 0 ? "partial" : "completed";
|
|
@@ -1670,7 +1592,7 @@ export function registerMetaTools(server) {
|
|
|
1670
1592
|
sections.push(`\nSession saved: ${sessionPath}`);
|
|
1671
1593
|
}
|
|
1672
1594
|
catch (writeErr) {
|
|
1673
|
-
sections.push(`\
|
|
1595
|
+
sections.push(`\nWARNING: Failed to save session: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}`);
|
|
1674
1596
|
}
|
|
1675
1597
|
sections.push("\n── Summary ──");
|
|
1676
1598
|
sections.push(` Total: ${session.summary.total} | Success: ${session.summary.successful} | Failed: ${session.summary.failed} | Skipped: ${session.summary.skipped}`);
|
|
@@ -1701,8 +1623,8 @@ export function registerMetaTools(server) {
|
|
|
1701
1623
|
return { content: [createErrorContent(`Failed to parse session file: ${sessionPath}`)], isError: true };
|
|
1702
1624
|
}
|
|
1703
1625
|
const sections = [];
|
|
1704
|
-
sections.push("
|
|
1705
|
-
sections.push("
|
|
1626
|
+
sections.push("Rollback Session");
|
|
1627
|
+
sections.push("");
|
|
1706
1628
|
sections.push(`Session: ${session_id}`);
|
|
1707
1629
|
const actionsToRollback = session.actions.filter(a => a.status === "success").reverse();
|
|
1708
1630
|
if (actionsToRollback.length === 0) {
|
|
@@ -1713,15 +1635,15 @@ export function registerMetaTools(server) {
|
|
|
1713
1635
|
}
|
|
1714
1636
|
let rolledBack = 0, errors = 0;
|
|
1715
1637
|
for (const action of actionsToRollback) {
|
|
1716
|
-
const result = await
|
|
1638
|
+
const result = await runCommand(action.rollback_command, action.rollback_args);
|
|
1717
1639
|
if (result.exitCode === 0) {
|
|
1718
1640
|
action.status = "rolled_back";
|
|
1719
1641
|
rolledBack++;
|
|
1720
|
-
sections.push(`
|
|
1642
|
+
sections.push(` Rolled back: ${action.finding_id} — ${action.description}`);
|
|
1721
1643
|
}
|
|
1722
1644
|
else {
|
|
1723
1645
|
errors++;
|
|
1724
|
-
sections.push(`
|
|
1646
|
+
sections.push(` Rollback failed: ${action.finding_id} — ${result.stderr.substring(0, 100)}`);
|
|
1725
1647
|
}
|
|
1726
1648
|
}
|
|
1727
1649
|
session.status = "rolled_back";
|
|
@@ -1759,8 +1681,8 @@ export function registerMetaTools(server) {
|
|
|
1759
1681
|
if (output_format === "json")
|
|
1760
1682
|
return { content: [formatToolOutput(session)] };
|
|
1761
1683
|
const sections = [];
|
|
1762
|
-
sections.push("
|
|
1763
|
-
sections.push("
|
|
1684
|
+
sections.push("Remediation Session Detail");
|
|
1685
|
+
sections.push("");
|
|
1764
1686
|
sections.push(`Session: ${session.session_id}`);
|
|
1765
1687
|
sections.push(`Created: ${session.created_at}`);
|
|
1766
1688
|
sections.push(`Status: ${session.status}`);
|
|
@@ -1801,8 +1723,8 @@ export function registerMetaTools(server) {
|
|
|
1801
1723
|
if (output_format === "json")
|
|
1802
1724
|
return { content: [formatToolOutput({ total_sessions: sessionSummaries.length, sessions: sessionSummaries })] };
|
|
1803
1725
|
const sections = [];
|
|
1804
|
-
sections.push("
|
|
1805
|
-
sections.push("
|
|
1726
|
+
sections.push("Remediation Sessions");
|
|
1727
|
+
sections.push("");
|
|
1806
1728
|
sections.push(`Total sessions: ${sessionSummaries.length}\n`);
|
|
1807
1729
|
for (const s of sessionSummaries) {
|
|
1808
1730
|
sections.push(` ${s.session_id}`);
|
|
@@ -1909,8 +1831,8 @@ export function registerMetaTools(server) {
|
|
|
1909
1831
|
try {
|
|
1910
1832
|
const statuses = await listThirdPartyTools();
|
|
1911
1833
|
const sections = [];
|
|
1912
|
-
sections.push("
|
|
1913
|
-
sections.push("
|
|
1834
|
+
sections.push("Optional Third-Party Tool Status");
|
|
1835
|
+
sections.push("");
|
|
1914
1836
|
sections.push("");
|
|
1915
1837
|
// Table header
|
|
1916
1838
|
sections.push(" " +
|
|
@@ -1920,12 +1842,12 @@ export function registerMetaTools(server) {
|
|
|
1920
1842
|
"Target".padEnd(12) +
|
|
1921
1843
|
"Method".padEnd(16) +
|
|
1922
1844
|
"Verification");
|
|
1923
|
-
sections.push(" " + "
|
|
1845
|
+
sections.push(" " + "");
|
|
1924
1846
|
let installedCount = 0;
|
|
1925
1847
|
let missingCount = 0;
|
|
1926
1848
|
for (const status of statuses) {
|
|
1927
1849
|
const entry = THIRD_PARTY_MANIFEST.find(e => e.binary === status.binary);
|
|
1928
|
-
const installedStr = status.installed ? "
|
|
1850
|
+
const installedStr = status.installed ? "yes" : "no";
|
|
1929
1851
|
const currentVer = status.currentVersion ?? "—";
|
|
1930
1852
|
const targetVer = status.manifestVersion;
|
|
1931
1853
|
const method = entry?.installMethod ?? "unknown";
|
|
@@ -1956,7 +1878,7 @@ export function registerMetaTools(server) {
|
|
|
1956
1878
|
}
|
|
1957
1879
|
}
|
|
1958
1880
|
sections.push("");
|
|
1959
|
-
sections.push("
|
|
1881
|
+
sections.push(" Run: defense_mgmt → install_optional_deps (dry_run=true) to see full install plan");
|
|
1960
1882
|
}
|
|
1961
1883
|
return { content: [createTextContent(sections.join("\n"))] };
|
|
1962
1884
|
}
|
|
@@ -1974,8 +1896,8 @@ export function registerMetaTools(server) {
|
|
|
1974
1896
|
// Gate 1: Check DEFENSE_MCP_THIRD_PARTY_INSTALL env var
|
|
1975
1897
|
if (!isThirdPartyInstallEnabled()) {
|
|
1976
1898
|
const sections = [];
|
|
1977
|
-
sections.push("
|
|
1978
|
-
sections.push("
|
|
1899
|
+
sections.push("Third-Party Installation Not Enabled");
|
|
1900
|
+
sections.push("");
|
|
1979
1901
|
sections.push("");
|
|
1980
1902
|
sections.push("SECURITY: Third-party tool installation requires explicit opt-in.");
|
|
1981
1903
|
sections.push("Set the following environment variable in your MCP server configuration:");
|
|
@@ -1992,7 +1914,7 @@ export function registerMetaTools(server) {
|
|
|
1992
1914
|
if (missing.length > 0) {
|
|
1993
1915
|
sections.push("── Missing Optional Tools ──");
|
|
1994
1916
|
for (const status of missing) {
|
|
1995
|
-
sections.push(`\n
|
|
1917
|
+
sections.push(`\n ${status.name} (v${status.manifestVersion})`);
|
|
1996
1918
|
const instructions = getVerifiedInstallInstructions(status.binary);
|
|
1997
1919
|
// Show first few lines of instructions
|
|
1998
1920
|
const instrLines = instructions.split("\n").slice(0, 3);
|
|
@@ -2002,7 +1924,7 @@ export function registerMetaTools(server) {
|
|
|
2002
1924
|
}
|
|
2003
1925
|
}
|
|
2004
1926
|
else {
|
|
2005
|
-
sections.push("
|
|
1927
|
+
sections.push(" All optional tools are already installed.");
|
|
2006
1928
|
}
|
|
2007
1929
|
return { content: [createTextContent(sections.join("\n"))] };
|
|
2008
1930
|
}
|
|
@@ -2010,8 +1932,8 @@ export function registerMetaTools(server) {
|
|
|
2010
1932
|
if (effectiveDryRun) {
|
|
2011
1933
|
const statuses = await listThirdPartyTools();
|
|
2012
1934
|
const sections = [];
|
|
2013
|
-
sections.push("
|
|
2014
|
-
sections.push("
|
|
1935
|
+
sections.push("Third-Party Tool Installation Plan [DRY RUN]");
|
|
1936
|
+
sections.push("");
|
|
2015
1937
|
sections.push("");
|
|
2016
1938
|
let planCount = 0;
|
|
2017
1939
|
for (const status of statuses) {
|
|
@@ -2032,11 +1954,11 @@ export function registerMetaTools(server) {
|
|
|
2032
1954
|
}
|
|
2033
1955
|
}
|
|
2034
1956
|
if (planCount === 0) {
|
|
2035
|
-
sections.push("
|
|
1957
|
+
sections.push(" All optional tools are already installed. Nothing to do.");
|
|
2036
1958
|
if (toolName) {
|
|
2037
1959
|
const found = statuses.find(s => s.binary === toolName);
|
|
2038
1960
|
if (!found) {
|
|
2039
|
-
sections.push(`
|
|
1961
|
+
sections.push(` WARNING: Tool '${toolName}' is not in the third-party manifest.`);
|
|
2040
1962
|
}
|
|
2041
1963
|
}
|
|
2042
1964
|
}
|
|
@@ -2050,11 +1972,11 @@ export function registerMetaTools(server) {
|
|
|
2050
1972
|
// Gate 3: Live installation (dry_run=false AND DEFENSE_MCP_THIRD_PARTY_INSTALL=true)
|
|
2051
1973
|
const statuses = await listThirdPartyTools();
|
|
2052
1974
|
const sections = [];
|
|
2053
|
-
sections.push("
|
|
2054
|
-
sections.push("
|
|
1975
|
+
sections.push("Third-Party Tool Installation [LIVE]");
|
|
1976
|
+
sections.push("");
|
|
2055
1977
|
sections.push("");
|
|
2056
1978
|
if (!forceInstall) {
|
|
2057
|
-
sections.push("
|
|
1979
|
+
sections.push("WARNING: Note: The caller is responsible for confirming intent.");
|
|
2058
1980
|
sections.push(" Use force=true to skip this notice for automation.");
|
|
2059
1981
|
sections.push("");
|
|
2060
1982
|
}
|
|
@@ -2067,24 +1989,24 @@ export function registerMetaTools(server) {
|
|
|
2067
1989
|
continue;
|
|
2068
1990
|
if (status.installed && !forceInstall) {
|
|
2069
1991
|
skipCount++;
|
|
2070
|
-
sections.push(`
|
|
1992
|
+
sections.push(` SKIPPED: ${status.name} (${status.binary}) — already installed (v${status.currentVersion ?? "unknown"})`);
|
|
2071
1993
|
continue;
|
|
2072
1994
|
}
|
|
2073
|
-
sections.push(`
|
|
1995
|
+
sections.push(` Installing ${status.name} v${status.manifestVersion}...`);
|
|
2074
1996
|
const result = await installThirdPartyTool(status.binary, { force: forceInstall });
|
|
2075
1997
|
if (result.success) {
|
|
2076
1998
|
successCount++;
|
|
2077
|
-
sections.push(`
|
|
1999
|
+
sections.push(` ${result.message}`);
|
|
2078
2000
|
}
|
|
2079
2001
|
else {
|
|
2080
2002
|
failCount++;
|
|
2081
|
-
sections.push(`
|
|
2003
|
+
sections.push(` ${result.message}`);
|
|
2082
2004
|
}
|
|
2083
2005
|
}
|
|
2084
2006
|
if (toolName) {
|
|
2085
2007
|
const found = statuses.find(s => s.binary === toolName);
|
|
2086
2008
|
if (!found) {
|
|
2087
|
-
sections.push(`
|
|
2009
|
+
sections.push(` WARNING: Tool '${toolName}' is not in the third-party manifest.`);
|
|
2088
2010
|
sections.push(` Available tools: ${statuses.map(s => s.binary).join(", ")}`);
|
|
2089
2011
|
}
|
|
2090
2012
|
}
|
|
@@ -2171,7 +2093,6 @@ export function registerMetaTools(server) {
|
|
|
2171
2093
|
const binResults = await verifyAllBinaries();
|
|
2172
2094
|
const verified = binResults.filter(r => r.verified).length;
|
|
2173
2095
|
const warnBins = binResults.filter(r => !r.verified).length;
|
|
2174
|
-
const skipped = binResults.length === 0 ? 0 : 0;
|
|
2175
2096
|
sections.push(` Verified: ${verified}, Warnings: ${warnBins}, Total: ${binResults.length}`);
|
|
2176
2097
|
if (warnBins === 0) {
|
|
2177
2098
|
score++;
|