defense-mcp-server 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { executeCommand } from "../core/executor.js";
|
|
3
|
+
import { getConfig } from "../core/config.js";
|
|
4
|
+
import { createTextContent, createErrorContent, formatToolOutput } from "../core/parsers.js";
|
|
5
|
+
import { getDistroAdapter } from "../core/distro-adapter.js";
|
|
6
|
+
import { detectDistro } from "../core/distro.js";
|
|
7
|
+
import * as https from "node:https";
|
|
8
|
+
/** Simple HTTPS GET that returns the response body as a string. Uses networkTimeout from config. */
|
|
9
|
+
function httpsGet(url) {
|
|
10
|
+
const config = getConfig();
|
|
11
|
+
const timeoutMs = config.networkTimeout;
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const req = https.get(url, { timeout: timeoutMs }, (res) => {
|
|
14
|
+
if (res.statusCode === 403) {
|
|
15
|
+
reject(new Error("NVD API rate limit exceeded (HTTP 403). Wait 30s or use an API key."));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (res.statusCode && res.statusCode >= 300) {
|
|
19
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const chunks = [];
|
|
23
|
+
res.on("data", (c) => chunks.push(c));
|
|
24
|
+
res.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
25
|
+
});
|
|
26
|
+
req.on("error", reject);
|
|
27
|
+
req.on("timeout", () => {
|
|
28
|
+
req.destroy();
|
|
29
|
+
const timeoutSec = Math.round(timeoutMs / 1000);
|
|
30
|
+
reject(new Error(`Network request timed out after ${timeoutSec} seconds. ` +
|
|
31
|
+
`The target may be unreachable or the network is slow. ` +
|
|
32
|
+
`Consider increasing KALI_DEFENSE_NETWORK_TIMEOUT (current: ${timeoutSec}s).`));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function registerPatchManagementTools(server) {
|
|
37
|
+
// Tool 1: patch_update_audit - Check for pending updates
|
|
38
|
+
server.tool("patch_update_audit", "Audit system for pending security updates, held-back packages, and overall patch status. Checks apt/dpkg on Debian-based or dnf/yum on RHEL-based systems.", {
|
|
39
|
+
security_only: z.boolean().optional().default(false).describe("Only show security-relevant updates"),
|
|
40
|
+
}, async (params) => {
|
|
41
|
+
try {
|
|
42
|
+
const da = await getDistroAdapter();
|
|
43
|
+
const pq = da.pkgQuery;
|
|
44
|
+
// Update package cache first (read-only)
|
|
45
|
+
const updateCmd = da.pkg.updateCmd();
|
|
46
|
+
await executeCommand({
|
|
47
|
+
command: "sudo",
|
|
48
|
+
args: updateCmd,
|
|
49
|
+
timeout: 60000,
|
|
50
|
+
toolName: "patch_update_audit",
|
|
51
|
+
});
|
|
52
|
+
// Get upgradable packages
|
|
53
|
+
const upgradeResult = await executeCommand({
|
|
54
|
+
command: pq.listUpgradableCmd[0],
|
|
55
|
+
args: pq.listUpgradableCmd.slice(1),
|
|
56
|
+
timeout: 30000,
|
|
57
|
+
toolName: "patch_update_audit",
|
|
58
|
+
});
|
|
59
|
+
// Parse upgradable packages based on distro family
|
|
60
|
+
let packages = [];
|
|
61
|
+
if (da.isDebian) {
|
|
62
|
+
// apt list --upgradable format: package/repo version arch [upgradable from: old]
|
|
63
|
+
const lines = upgradeResult.stdout.split("\n").filter(l => l.includes("/"));
|
|
64
|
+
packages = lines.map(l => {
|
|
65
|
+
const match = l.match(/^(\S+)\/(\S+)\s+(\S+)\s+(\S+)\s+\[upgradable from: (\S+)\]/);
|
|
66
|
+
if (match) {
|
|
67
|
+
return {
|
|
68
|
+
name: match[1], repo: match[2], newVersion: match[3],
|
|
69
|
+
arch: match[4], currentVersion: match[5],
|
|
70
|
+
security: match[2].includes("security"),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}).filter((p) => p !== null);
|
|
75
|
+
}
|
|
76
|
+
else if (da.isRhel) {
|
|
77
|
+
// dnf/yum check-update format: package.arch version repo
|
|
78
|
+
const lines = upgradeResult.stdout.split("\n").filter(l => l.trim() && !l.startsWith("Last") && !l.startsWith("Obsoleting") && l.includes("."));
|
|
79
|
+
packages = lines.map(l => {
|
|
80
|
+
const parts = l.trim().split(/\s+/);
|
|
81
|
+
if (parts.length >= 3) {
|
|
82
|
+
const [nameArch, version, repo] = parts;
|
|
83
|
+
const dotIdx = nameArch.lastIndexOf(".");
|
|
84
|
+
return {
|
|
85
|
+
name: dotIdx > 0 ? nameArch.substring(0, dotIdx) : nameArch,
|
|
86
|
+
arch: dotIdx > 0 ? nameArch.substring(dotIdx + 1) : "",
|
|
87
|
+
newVersion: version, repo,
|
|
88
|
+
security: repo.toLowerCase().includes("security") || repo.toLowerCase().includes("update"),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}).filter((p) => p !== null);
|
|
93
|
+
}
|
|
94
|
+
else if (da.isSuse) {
|
|
95
|
+
// zypper list-updates format: table output
|
|
96
|
+
const lines = upgradeResult.stdout.split("\n").filter(l => l.includes("|"));
|
|
97
|
+
packages = lines.slice(2).map(l => {
|
|
98
|
+
const cols = l.split("|").map(c => c.trim());
|
|
99
|
+
if (cols.length >= 5) {
|
|
100
|
+
return {
|
|
101
|
+
name: cols[2], newVersion: cols[4], repo: cols[1],
|
|
102
|
+
security: cols[1]?.toLowerCase().includes("update") ?? false,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}).filter((p) => p !== null);
|
|
107
|
+
}
|
|
108
|
+
else if (da.isArch) {
|
|
109
|
+
// pacman -Qu format: package oldversion -> newversion
|
|
110
|
+
const lines = upgradeResult.stdout.split("\n").filter(l => l.trim());
|
|
111
|
+
packages = lines.map(l => {
|
|
112
|
+
const parts = l.trim().split(/\s+/);
|
|
113
|
+
return {
|
|
114
|
+
name: parts[0], currentVersion: parts[1], newVersion: parts[3],
|
|
115
|
+
security: false, // Arch doesn't differentiate security updates
|
|
116
|
+
};
|
|
117
|
+
}).filter(p => p.name);
|
|
118
|
+
}
|
|
119
|
+
else if (da.isAlpine) {
|
|
120
|
+
// apk version -l '<' format: package-version < newversion
|
|
121
|
+
const lines = upgradeResult.stdout.split("\n").filter(l => l.includes("<"));
|
|
122
|
+
packages = lines.map(l => {
|
|
123
|
+
const parts = l.trim().split(/\s+/);
|
|
124
|
+
return { name: parts[0], security: false };
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const securityPkgs = packages.filter(p => p.security);
|
|
128
|
+
const displayPkgs = params.security_only ? securityPkgs : packages;
|
|
129
|
+
// Check held-back packages
|
|
130
|
+
const heldResult = await executeCommand({
|
|
131
|
+
command: pq.showHeldCmd[0],
|
|
132
|
+
args: pq.showHeldCmd.slice(1),
|
|
133
|
+
timeout: 10000,
|
|
134
|
+
toolName: "patch_update_audit",
|
|
135
|
+
});
|
|
136
|
+
const heldPackages = heldResult.stdout.trim().split("\n").filter(l => l.trim());
|
|
137
|
+
// Check auto-remove candidates
|
|
138
|
+
const autoRemoveResult = await executeCommand({
|
|
139
|
+
command: pq.autoRemoveCmd[0],
|
|
140
|
+
args: pq.autoRemoveCmd.slice(1),
|
|
141
|
+
timeout: 15000,
|
|
142
|
+
toolName: "patch_update_audit",
|
|
143
|
+
});
|
|
144
|
+
const autoRemoveCount = (() => {
|
|
145
|
+
const match = autoRemoveResult.stdout.match(/(\d+)\s+(?:to remove|packages? will be removed|packages? can be autoremoved)/i);
|
|
146
|
+
return match?.[1] ?? "0";
|
|
147
|
+
})();
|
|
148
|
+
// Kernel version check
|
|
149
|
+
const kernelResult = await executeCommand({
|
|
150
|
+
command: "uname",
|
|
151
|
+
args: ["-r"],
|
|
152
|
+
timeout: 5000,
|
|
153
|
+
toolName: "patch_update_audit",
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
content: [createTextContent(JSON.stringify({
|
|
157
|
+
distro: da.summary,
|
|
158
|
+
summary: {
|
|
159
|
+
totalUpgradable: packages.length,
|
|
160
|
+
securityUpdates: securityPkgs.length,
|
|
161
|
+
heldBack: heldPackages.length > 0 ? heldPackages : [],
|
|
162
|
+
autoRemoveCandidates: parseInt(autoRemoveCount),
|
|
163
|
+
currentKernel: kernelResult.stdout.trim(),
|
|
164
|
+
status: packages.length === 0 ? "UP_TO_DATE" : securityPkgs.length > 0 ? "SECURITY_UPDATES_PENDING" : "UPDATES_AVAILABLE",
|
|
165
|
+
},
|
|
166
|
+
packages: displayPkgs.slice(0, 100),
|
|
167
|
+
}, null, 2))],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
return {
|
|
172
|
+
content: [createErrorContent(error instanceof Error ? error.message : String(error))],
|
|
173
|
+
isError: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// Tool 2: patch_unattended_audit - Check auto-update config
|
|
178
|
+
server.tool("patch_unattended_audit", "Audit unattended-upgrades configuration to ensure automatic security patching is properly configured.", {}, async () => {
|
|
179
|
+
try {
|
|
180
|
+
const da = await getDistroAdapter();
|
|
181
|
+
const au = da.autoUpdate;
|
|
182
|
+
const findings = [];
|
|
183
|
+
if (!au.supported) {
|
|
184
|
+
return {
|
|
185
|
+
content: [createTextContent(JSON.stringify({
|
|
186
|
+
distro: da.summary,
|
|
187
|
+
supported: false,
|
|
188
|
+
message: `Automatic updates are not natively supported on ${da.distro.name}.`,
|
|
189
|
+
recommendation: au.installHint,
|
|
190
|
+
}, null, 2))],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// Check if auto-update package is installed
|
|
194
|
+
const pkgCheckResult = await executeCommand({
|
|
195
|
+
command: au.checkInstalledCmd[0],
|
|
196
|
+
args: au.checkInstalledCmd.slice(1),
|
|
197
|
+
timeout: 10000,
|
|
198
|
+
toolName: "patch_unattended_audit",
|
|
199
|
+
});
|
|
200
|
+
const installed = da.isDebian
|
|
201
|
+
? pkgCheckResult.stdout.includes("ii")
|
|
202
|
+
: pkgCheckResult.exitCode === 0;
|
|
203
|
+
findings.push({
|
|
204
|
+
check: "auto_update_installed",
|
|
205
|
+
status: installed ? "PASS" : "FAIL",
|
|
206
|
+
value: installed ? "installed" : "not installed",
|
|
207
|
+
description: `${au.packageName} package`,
|
|
208
|
+
});
|
|
209
|
+
if (installed && au.serviceName) {
|
|
210
|
+
// Check service status
|
|
211
|
+
const serviceResult = await executeCommand({
|
|
212
|
+
command: "systemctl",
|
|
213
|
+
args: ["is-enabled", au.serviceName],
|
|
214
|
+
timeout: 10000,
|
|
215
|
+
toolName: "patch_unattended_audit",
|
|
216
|
+
});
|
|
217
|
+
const enabled = serviceResult.stdout.trim() === "enabled";
|
|
218
|
+
findings.push({
|
|
219
|
+
check: "service_enabled",
|
|
220
|
+
status: enabled ? "PASS" : "FAIL",
|
|
221
|
+
value: serviceResult.stdout.trim(),
|
|
222
|
+
description: `${au.serviceName} service enabled`,
|
|
223
|
+
});
|
|
224
|
+
// Check config files
|
|
225
|
+
for (const configFile of au.configFiles) {
|
|
226
|
+
const configResult = await executeCommand({
|
|
227
|
+
command: "cat",
|
|
228
|
+
args: [configFile],
|
|
229
|
+
timeout: 5000,
|
|
230
|
+
toolName: "patch_unattended_audit",
|
|
231
|
+
});
|
|
232
|
+
if (configResult.exitCode === 0) {
|
|
233
|
+
const content = configResult.stdout;
|
|
234
|
+
if (da.isDebian) {
|
|
235
|
+
// Debian-specific config parsing
|
|
236
|
+
if (configFile.includes("20auto-upgrades")) {
|
|
237
|
+
const updateEnabled = content.includes('APT::Periodic::Update-Package-Lists "1"');
|
|
238
|
+
const upgradeEnabled = content.includes('APT::Periodic::Unattended-Upgrade "1"');
|
|
239
|
+
findings.push({
|
|
240
|
+
check: "auto_update_lists",
|
|
241
|
+
status: updateEnabled ? "PASS" : "FAIL",
|
|
242
|
+
value: updateEnabled ? "enabled" : "disabled",
|
|
243
|
+
description: "Automatic package list updates",
|
|
244
|
+
});
|
|
245
|
+
findings.push({
|
|
246
|
+
check: "auto_upgrade",
|
|
247
|
+
status: upgradeEnabled ? "PASS" : "FAIL",
|
|
248
|
+
value: upgradeEnabled ? "enabled" : "disabled",
|
|
249
|
+
description: "Automatic unattended upgrades",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else if (configFile.includes("50unattended")) {
|
|
253
|
+
const hasSecurityOrigin = content.includes("security") || content.includes("Security");
|
|
254
|
+
findings.push({
|
|
255
|
+
check: "security_origins",
|
|
256
|
+
status: hasSecurityOrigin ? "PASS" : "WARN",
|
|
257
|
+
value: hasSecurityOrigin ? "configured" : "not found",
|
|
258
|
+
description: "Security origins in unattended-upgrades config",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (da.isRhel) {
|
|
263
|
+
// RHEL-specific: /etc/dnf/automatic.conf
|
|
264
|
+
const applyUpdates = content.includes("apply_updates = yes");
|
|
265
|
+
findings.push({
|
|
266
|
+
check: "apply_updates",
|
|
267
|
+
status: applyUpdates ? "PASS" : "FAIL",
|
|
268
|
+
value: applyUpdates ? "enabled" : "disabled",
|
|
269
|
+
description: "Automatic application of updates",
|
|
270
|
+
});
|
|
271
|
+
const upgradeType = content.match(/upgrade_type\s*=\s*(\S+)/)?.[1] ?? "unknown";
|
|
272
|
+
findings.push({
|
|
273
|
+
check: "upgrade_type",
|
|
274
|
+
status: upgradeType === "security" ? "PASS" : "WARN",
|
|
275
|
+
value: upgradeType,
|
|
276
|
+
description: "Update type (security recommended)",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else if (da.isSuse) {
|
|
280
|
+
findings.push({
|
|
281
|
+
check: "config_exists",
|
|
282
|
+
status: "PASS",
|
|
283
|
+
value: "present",
|
|
284
|
+
description: `Config file ${configFile} exists`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
findings.push({
|
|
290
|
+
check: "config_file",
|
|
291
|
+
status: "FAIL",
|
|
292
|
+
value: "missing",
|
|
293
|
+
description: `${configFile} not found`,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const passCount = findings.filter(f => f.status === "PASS").length;
|
|
299
|
+
const failCount = findings.filter(f => f.status === "FAIL").length;
|
|
300
|
+
return {
|
|
301
|
+
content: [createTextContent(JSON.stringify({
|
|
302
|
+
distro: da.summary,
|
|
303
|
+
summary: {
|
|
304
|
+
total: findings.length,
|
|
305
|
+
pass: passCount,
|
|
306
|
+
fail: failCount,
|
|
307
|
+
warn: findings.filter(f => f.status === "WARN").length,
|
|
308
|
+
},
|
|
309
|
+
findings,
|
|
310
|
+
recommendation: !installed
|
|
311
|
+
? `CRITICAL: Install auto-updates: ${au.installHint}`
|
|
312
|
+
: failCount > 0
|
|
313
|
+
? "WARNING: Automatic security updates not fully configured"
|
|
314
|
+
: "PASS: Automatic security updates properly configured",
|
|
315
|
+
}, null, 2))],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
return {
|
|
320
|
+
content: [createErrorContent(error instanceof Error ? error.message : String(error))],
|
|
321
|
+
isError: true,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
// Tool 3: patch_integrity_check - Verify installed package integrity
|
|
326
|
+
server.tool("patch_integrity_check", "Verify integrity of installed packages using debsums (Debian/Ubuntu) or rpm -V (RHEL). Detects modified system files that may indicate compromise.", {
|
|
327
|
+
package_name: z.string().optional().describe("Specific package to check, or omit for all"),
|
|
328
|
+
changed_only: z.boolean().optional().default(true).describe("Only show files that have changed"),
|
|
329
|
+
}, async (params) => {
|
|
330
|
+
try {
|
|
331
|
+
const da = await getDistroAdapter();
|
|
332
|
+
const ic = da.integrity;
|
|
333
|
+
if (!ic.supported) {
|
|
334
|
+
return {
|
|
335
|
+
content: [createTextContent(JSON.stringify({
|
|
336
|
+
distro: da.summary,
|
|
337
|
+
error: "Package integrity checking not supported on this distribution",
|
|
338
|
+
recommendation: ic.installHint,
|
|
339
|
+
}, null, 2))],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
// Build the command based on distro
|
|
343
|
+
let cmd;
|
|
344
|
+
if (params.package_name) {
|
|
345
|
+
cmd = ic.checkPackageCmd(params.package_name);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
cmd = [...ic.checkCmd];
|
|
349
|
+
}
|
|
350
|
+
// For debsums, add --changed flag if not already there
|
|
351
|
+
if (da.isDebian && params.changed_only && !cmd.includes("--changed")) {
|
|
352
|
+
// Replace -s with --changed for changed_only mode
|
|
353
|
+
const idx = cmd.indexOf("-s");
|
|
354
|
+
if (idx >= 0)
|
|
355
|
+
cmd[idx] = "--changed";
|
|
356
|
+
}
|
|
357
|
+
const result = await executeCommand({
|
|
358
|
+
command: "sudo",
|
|
359
|
+
args: cmd,
|
|
360
|
+
timeout: 120000,
|
|
361
|
+
toolName: "patch_integrity_check",
|
|
362
|
+
});
|
|
363
|
+
const lines = (result.stdout + result.stderr).split("\n").filter(l => l.trim());
|
|
364
|
+
let changes = [];
|
|
365
|
+
if (da.isDebian) {
|
|
366
|
+
// debsums output: "file CHANGED" or "file OK" or "file MISSING"
|
|
367
|
+
changes = lines.map(l => {
|
|
368
|
+
const match = l.match(/^(\S+)\s+(OK|CHANGED|MISSING|REPLACED)/i);
|
|
369
|
+
if (match)
|
|
370
|
+
return { file: match[1], status: match[2] };
|
|
371
|
+
return null;
|
|
372
|
+
}).filter((c) => c !== null);
|
|
373
|
+
}
|
|
374
|
+
else if (da.isRhel || da.isSuse) {
|
|
375
|
+
// rpm -V output: SM5DLUGTP c /path/to/file
|
|
376
|
+
// Dots mean OK, letters mean changes
|
|
377
|
+
changes = lines.map(l => {
|
|
378
|
+
const match = l.match(/^([.SM5DLUGTP]{9})\s+\S?\s*(.+)/);
|
|
379
|
+
if (match) {
|
|
380
|
+
const flags = match[1];
|
|
381
|
+
const file = match[2].trim();
|
|
382
|
+
const isChanged = flags !== ".........";
|
|
383
|
+
return { file, status: isChanged ? `CHANGED (${flags})` : "OK" };
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}).filter((c) => c !== null);
|
|
387
|
+
}
|
|
388
|
+
else if (da.isArch) {
|
|
389
|
+
// pacman -Qk output: package: /path (Modification)
|
|
390
|
+
changes = lines.map(l => {
|
|
391
|
+
if (l.includes("warning:")) {
|
|
392
|
+
const match = l.match(/warning:\s+(\S+):\s+(.+)/);
|
|
393
|
+
if (match)
|
|
394
|
+
return { file: match[1], status: match[2] };
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}).filter((c) => c !== null);
|
|
398
|
+
}
|
|
399
|
+
const changed = changes.filter(c => c.status !== "OK");
|
|
400
|
+
return {
|
|
401
|
+
content: [createTextContent(JSON.stringify({
|
|
402
|
+
distro: da.summary,
|
|
403
|
+
tool: ic.toolName,
|
|
404
|
+
summary: {
|
|
405
|
+
totalChecked: changes.length,
|
|
406
|
+
changed: changed.length,
|
|
407
|
+
status: changed.length === 0 ? "PASS" : "WARN",
|
|
408
|
+
},
|
|
409
|
+
changedFiles: changed,
|
|
410
|
+
note: changed.length > 0
|
|
411
|
+
? "Modified files detected. Review if changes are legitimate (config edits) or suspicious (potential compromise)."
|
|
412
|
+
: "All checked files match their package checksums.",
|
|
413
|
+
}, null, 2))],
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
418
|
+
const da = await getDistroAdapter().catch(() => null);
|
|
419
|
+
if (errMsg.includes("not found") || errMsg.includes("ENOENT")) {
|
|
420
|
+
return {
|
|
421
|
+
content: [createTextContent(JSON.stringify({
|
|
422
|
+
error: `${da?.integrity.toolName ?? "Integrity tool"} not available`,
|
|
423
|
+
recommendation: da?.integrity.installHint ?? "Install the appropriate integrity checking tool",
|
|
424
|
+
}, null, 2))],
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
content: [createErrorContent(errMsg)],
|
|
429
|
+
isError: true,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
// Tool 4: patch_kernel_audit - Audit kernel version and livepatch
|
|
434
|
+
server.tool("patch_kernel_audit", "Audit kernel version, check for available kernel updates, livepatch status, and kernel support timeline.", {}, async () => {
|
|
435
|
+
try {
|
|
436
|
+
const da = await getDistroAdapter();
|
|
437
|
+
const pq = da.pkgQuery;
|
|
438
|
+
// Current kernel
|
|
439
|
+
const unameResult = await executeCommand({
|
|
440
|
+
command: "uname",
|
|
441
|
+
args: ["-r"],
|
|
442
|
+
timeout: 5000,
|
|
443
|
+
toolName: "patch_kernel_audit",
|
|
444
|
+
});
|
|
445
|
+
const currentKernel = unameResult.stdout.trim();
|
|
446
|
+
// All installed kernels — distro-aware
|
|
447
|
+
const kernelResult = await executeCommand({
|
|
448
|
+
command: pq.listKernelsCmd[0],
|
|
449
|
+
args: pq.listKernelsCmd.slice(1),
|
|
450
|
+
timeout: 10000,
|
|
451
|
+
toolName: "patch_kernel_audit",
|
|
452
|
+
});
|
|
453
|
+
let installedKernels = [];
|
|
454
|
+
if (da.isDebian) {
|
|
455
|
+
installedKernels = kernelResult.stdout.split("\n")
|
|
456
|
+
.filter(l => l.startsWith("ii") && l.includes("linux-image"))
|
|
457
|
+
.map(l => {
|
|
458
|
+
const parts = l.split(/\s+/);
|
|
459
|
+
return { package: parts[1], version: parts[2] };
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
else if (da.isRhel || da.isSuse) {
|
|
463
|
+
installedKernels = kernelResult.stdout.split("\n")
|
|
464
|
+
.filter(l => l.trim() && l.includes("kernel"))
|
|
465
|
+
.map(l => ({ package: l.trim() }));
|
|
466
|
+
}
|
|
467
|
+
else if (da.isArch) {
|
|
468
|
+
installedKernels = kernelResult.stdout.split("\n")
|
|
469
|
+
.filter(l => l.trim())
|
|
470
|
+
.map(l => {
|
|
471
|
+
const parts = l.split(/\s+/);
|
|
472
|
+
return { package: parts[0], version: parts[1] };
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
// Check CPU vulnerabilities (works on all Linux distros)
|
|
476
|
+
const vulnResult = await executeCommand({
|
|
477
|
+
command: "ls",
|
|
478
|
+
args: ["/sys/devices/system/cpu/vulnerabilities/"],
|
|
479
|
+
timeout: 5000,
|
|
480
|
+
toolName: "patch_kernel_audit",
|
|
481
|
+
});
|
|
482
|
+
const vulns = [];
|
|
483
|
+
if (vulnResult.exitCode === 0) {
|
|
484
|
+
for (const vuln of vulnResult.stdout.trim().split("\n").filter(v => v.trim())) {
|
|
485
|
+
const catResult = await executeCommand({
|
|
486
|
+
command: "cat",
|
|
487
|
+
args: [`/sys/devices/system/cpu/vulnerabilities/${vuln.trim()}`],
|
|
488
|
+
timeout: 5000,
|
|
489
|
+
toolName: "patch_kernel_audit",
|
|
490
|
+
});
|
|
491
|
+
const status = catResult.stdout.trim();
|
|
492
|
+
vulns.push({
|
|
493
|
+
name: vuln.trim(),
|
|
494
|
+
status,
|
|
495
|
+
mitigated: status.toLowerCase().includes("not affected") || status.toLowerCase().includes("mitigat"),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Boot time (works everywhere)
|
|
500
|
+
const uptimeResult = await executeCommand({
|
|
501
|
+
command: "uptime",
|
|
502
|
+
args: ["-s"],
|
|
503
|
+
timeout: 5000,
|
|
504
|
+
toolName: "patch_kernel_audit",
|
|
505
|
+
});
|
|
506
|
+
// Livepatch status — Debian/Ubuntu specific
|
|
507
|
+
let livepatchActive = false;
|
|
508
|
+
let livepatchStatus = "not available";
|
|
509
|
+
if (da.isDebian) {
|
|
510
|
+
const livepatchResult = await executeCommand({
|
|
511
|
+
command: "canonical-livepatch",
|
|
512
|
+
args: ["status"],
|
|
513
|
+
timeout: 10000,
|
|
514
|
+
toolName: "patch_kernel_audit",
|
|
515
|
+
});
|
|
516
|
+
livepatchActive = livepatchResult.exitCode === 0;
|
|
517
|
+
livepatchStatus = livepatchActive ? livepatchResult.stdout.trim().substring(0, 500) : "not installed";
|
|
518
|
+
}
|
|
519
|
+
else if (da.isRhel) {
|
|
520
|
+
// RHEL has kpatch
|
|
521
|
+
const kpatchResult = await executeCommand({
|
|
522
|
+
command: "kpatch",
|
|
523
|
+
args: ["list"],
|
|
524
|
+
timeout: 10000,
|
|
525
|
+
toolName: "patch_kernel_audit",
|
|
526
|
+
});
|
|
527
|
+
livepatchActive = kpatchResult.exitCode === 0;
|
|
528
|
+
livepatchStatus = livepatchActive ? kpatchResult.stdout.trim().substring(0, 500) : "kpatch not installed";
|
|
529
|
+
}
|
|
530
|
+
else if (da.isSuse) {
|
|
531
|
+
const klpResult = await executeCommand({
|
|
532
|
+
command: "klp",
|
|
533
|
+
args: ["status"],
|
|
534
|
+
timeout: 10000,
|
|
535
|
+
toolName: "patch_kernel_audit",
|
|
536
|
+
});
|
|
537
|
+
livepatchActive = klpResult.exitCode === 0;
|
|
538
|
+
livepatchStatus = livepatchActive ? klpResult.stdout.trim().substring(0, 500) : "kernel livepatch not installed";
|
|
539
|
+
}
|
|
540
|
+
const unmitigated = vulns.filter(v => !v.mitigated);
|
|
541
|
+
// Distro-aware cleanup recommendation
|
|
542
|
+
const cleanupHint = da.isDebian ? "sudo apt autoremove"
|
|
543
|
+
: da.isRhel ? "sudo dnf remove --oldinstallonly"
|
|
544
|
+
: da.isArch ? "manually remove old kernels"
|
|
545
|
+
: "remove unused kernel packages";
|
|
546
|
+
return {
|
|
547
|
+
content: [createTextContent(JSON.stringify({
|
|
548
|
+
distro: da.summary,
|
|
549
|
+
currentKernel,
|
|
550
|
+
bootTime: uptimeResult.stdout.trim(),
|
|
551
|
+
installedKernels,
|
|
552
|
+
cpuVulnerabilities: {
|
|
553
|
+
total: vulns.length,
|
|
554
|
+
mitigated: vulns.length - unmitigated.length,
|
|
555
|
+
unmitigated: unmitigated.length,
|
|
556
|
+
details: vulns,
|
|
557
|
+
},
|
|
558
|
+
livepatch: {
|
|
559
|
+
available: livepatchActive,
|
|
560
|
+
status: livepatchStatus,
|
|
561
|
+
},
|
|
562
|
+
recommendations: [
|
|
563
|
+
...(unmitigated.length > 0 ? [`${unmitigated.length} CPU vulnerabilities not fully mitigated`] : []),
|
|
564
|
+
...(installedKernels.length > 3 ? [`Multiple old kernels installed — consider removing unused: ${cleanupHint}`] : []),
|
|
565
|
+
],
|
|
566
|
+
}, null, 2))],
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
return {
|
|
571
|
+
content: [createErrorContent(error instanceof Error ? error.message : String(error))],
|
|
572
|
+
isError: true,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
// Tool 5: vulnerability_intel (merged: lookup_cve, scan_packages_cves, get_patch_urgency)
|
|
577
|
+
server.tool("patch_vulnerability_intel", "Vulnerability intelligence: look up CVEs, scan packages for known CVEs, or check patch urgency for a package.", {
|
|
578
|
+
action: z.enum(["lookup", "scan", "urgency"]).describe("Action: lookup=CVE lookup, scan=scan packages for CVEs, urgency=check patch urgency"),
|
|
579
|
+
// lookup params
|
|
580
|
+
cveId: z.string().optional().describe("CVE ID e.g. CVE-2024-1234 (lookup action)"),
|
|
581
|
+
// scan params
|
|
582
|
+
maxPackages: z.number().optional().default(50).describe("Maximum packages to check (scan action)"),
|
|
583
|
+
// urgency params
|
|
584
|
+
packageName: z.string().optional().describe("Package name to check (urgency action)"),
|
|
585
|
+
// shared
|
|
586
|
+
dryRun: z.boolean().optional().default(true).describe("Preview only"),
|
|
587
|
+
}, async (params) => {
|
|
588
|
+
const { action } = params;
|
|
589
|
+
switch (action) {
|
|
590
|
+
case "lookup": {
|
|
591
|
+
const { cveId, dryRun } = params;
|
|
592
|
+
try {
|
|
593
|
+
if (!cveId) {
|
|
594
|
+
return { content: [createErrorContent("cveId is required for lookup action")], isError: true };
|
|
595
|
+
}
|
|
596
|
+
if (!/^CVE-\d{4}-\d{4,}$/.test(cveId)) {
|
|
597
|
+
return { content: [createErrorContent("cveId must match format CVE-YYYY-NNNN+")], isError: true };
|
|
598
|
+
}
|
|
599
|
+
if (dryRun) {
|
|
600
|
+
return { content: [formatToolOutput({ dryRun: true, url: `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${cveId}` })] };
|
|
601
|
+
}
|
|
602
|
+
const url = `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${cveId}`;
|
|
603
|
+
const body = await httpsGet(url);
|
|
604
|
+
const data = JSON.parse(body);
|
|
605
|
+
const vuln = data?.vulnerabilities?.[0]?.cve;
|
|
606
|
+
if (!vuln) {
|
|
607
|
+
return { content: [formatToolOutput({ cveId, found: false })] };
|
|
608
|
+
}
|
|
609
|
+
const description = vuln.descriptions?.find((d) => d.lang === "en")?.value ?? "No description";
|
|
610
|
+
const metrics = vuln.metrics ?? {};
|
|
611
|
+
const cvss31 = metrics.cvssMetricV31?.[0]?.cvssData;
|
|
612
|
+
const cvss2 = metrics.cvssMetricV2?.[0]?.cvssData;
|
|
613
|
+
return {
|
|
614
|
+
content: [formatToolOutput({
|
|
615
|
+
cveId: vuln.id,
|
|
616
|
+
description,
|
|
617
|
+
published: vuln.published,
|
|
618
|
+
lastModified: vuln.lastModified,
|
|
619
|
+
cvssV31: cvss31 ? { score: cvss31.baseScore, severity: cvss31.baseSeverity, vector: cvss31.vectorString } : null,
|
|
620
|
+
cvssV2: cvss2 ? { score: cvss2.baseScore, vector: cvss2.vectorString } : null,
|
|
621
|
+
references: (vuln.references ?? []).slice(0, 10).map((r) => r.url),
|
|
622
|
+
})],
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
627
|
+
return { content: [createErrorContent(`CVE lookup failed: ${msg}`)], isError: true };
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
case "scan": {
|
|
631
|
+
const { maxPackages, dryRun } = params;
|
|
632
|
+
try {
|
|
633
|
+
const distro = await detectDistro();
|
|
634
|
+
if (dryRun) {
|
|
635
|
+
return { content: [formatToolOutput({ dryRun: true, distro: distro.id, method: distro.family === "debian" ? "apt-get upgrade -s / debsecan" : "dnf updateinfo" })] };
|
|
636
|
+
}
|
|
637
|
+
if (distro.family === "debian") {
|
|
638
|
+
const debsecan = await executeCommand({ command: "which", args: ["debsecan"], timeout: 5000 });
|
|
639
|
+
if (debsecan.exitCode === 0) {
|
|
640
|
+
const result = await executeCommand({ command: "debsecan", args: ["--format", "detail"], timeout: 60000 });
|
|
641
|
+
const lines = result.stdout.trim().split("\n").filter(Boolean);
|
|
642
|
+
return { content: [formatToolOutput({ tool: "debsecan", totalFindings: lines.length, findings: lines.slice(0, maxPackages) })] };
|
|
643
|
+
}
|
|
644
|
+
const result = await executeCommand({ command: "apt-get", args: ["upgrade", "-s"], timeout: 30000 });
|
|
645
|
+
const upgradable = result.stdout.split("\n")
|
|
646
|
+
.filter((l) => l.startsWith("Inst "))
|
|
647
|
+
.slice(0, maxPackages)
|
|
648
|
+
.map((l) => { const match = l.match(/^Inst\s+(\S+)\s+\[(\S+)\]\s+\((\S+)/); return match ? { package: match[1], current: match[2], available: match[3] } : null; })
|
|
649
|
+
.filter(Boolean);
|
|
650
|
+
return { content: [formatToolOutput({ tool: "apt-get upgrade -s", upgradablePackages: upgradable.length, packages: upgradable })] };
|
|
651
|
+
}
|
|
652
|
+
if (distro.family === "rhel") {
|
|
653
|
+
const result = await executeCommand({ command: "dnf", args: ["updateinfo", "list", "--security"], timeout: 30000 });
|
|
654
|
+
const lines = result.stdout.trim().split("\n").filter(Boolean).slice(0, maxPackages);
|
|
655
|
+
return { content: [formatToolOutput({ tool: "dnf updateinfo", findings: lines.length, details: lines })] };
|
|
656
|
+
}
|
|
657
|
+
return { content: [createErrorContent(`CVE scanning not supported for distro family: ${distro.family}`)], isError: true };
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
661
|
+
return { content: [createErrorContent(`Package CVE scan failed: ${msg}`)], isError: true };
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
case "urgency": {
|
|
665
|
+
const { packageName, dryRun } = params;
|
|
666
|
+
try {
|
|
667
|
+
if (!packageName) {
|
|
668
|
+
return { content: [createErrorContent("packageName is required for urgency action")], isError: true };
|
|
669
|
+
}
|
|
670
|
+
const distro = await detectDistro();
|
|
671
|
+
if (dryRun) {
|
|
672
|
+
return { content: [formatToolOutput({ dryRun: true, package: packageName, distro: distro.id })] };
|
|
673
|
+
}
|
|
674
|
+
const info = { package: packageName, distro: distro.id };
|
|
675
|
+
if (distro.family === "debian") {
|
|
676
|
+
const dpkg = await executeCommand({ command: "dpkg-query", args: ["-W", "-f", "${Version}", packageName], timeout: 10000 });
|
|
677
|
+
info.installedVersion = dpkg.exitCode === 0 ? dpkg.stdout.trim() : "not installed";
|
|
678
|
+
const apt = await executeCommand({ command: "apt-cache", args: ["policy", packageName], timeout: 10000 });
|
|
679
|
+
if (apt.exitCode === 0) {
|
|
680
|
+
const candidate = apt.stdout.match(/Candidate:\s*(\S+)/)?.[1];
|
|
681
|
+
info.candidateVersion = candidate ?? "unknown";
|
|
682
|
+
info.updateAvailable = candidate && candidate !== info.installedVersion;
|
|
683
|
+
}
|
|
684
|
+
const changelog = await executeCommand({ command: "apt-get", args: ["changelog", packageName], timeout: 15000 });
|
|
685
|
+
if (changelog.exitCode === 0) {
|
|
686
|
+
info.securityEntries = changelog.stdout.split("\n").filter((l) => /CVE-\d{4}-\d{4,}|security/i.test(l)).slice(0, 10);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else if (distro.family === "rhel") {
|
|
690
|
+
const rpm = await executeCommand({ command: "rpm", args: ["-q", packageName], timeout: 10000 });
|
|
691
|
+
info.installedVersion = rpm.exitCode === 0 ? rpm.stdout.trim() : "not installed";
|
|
692
|
+
const updateinfo = await executeCommand({ command: "dnf", args: ["updateinfo", "info", packageName], timeout: 15000 });
|
|
693
|
+
if (updateinfo.exitCode === 0) {
|
|
694
|
+
info.advisories = updateinfo.stdout.slice(0, 5000);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return { content: [formatToolOutput(info)] };
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
701
|
+
return { content: [createErrorContent(`Patch urgency check failed: ${msg}`)], isError: true };
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
default:
|
|
705
|
+
return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|