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.
Files changed (186) hide show
  1. package/CHANGELOG.md +471 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/build/core/auto-installer.d.ts +102 -0
  5. package/build/core/auto-installer.d.ts.map +1 -0
  6. package/build/core/auto-installer.js +833 -0
  7. package/build/core/backup-manager.d.ts +63 -0
  8. package/build/core/backup-manager.d.ts.map +1 -0
  9. package/build/core/backup-manager.js +189 -0
  10. package/build/core/changelog.d.ts +75 -0
  11. package/build/core/changelog.d.ts.map +1 -0
  12. package/build/core/changelog.js +123 -0
  13. package/build/core/command-allowlist.d.ts +129 -0
  14. package/build/core/command-allowlist.d.ts.map +1 -0
  15. package/build/core/command-allowlist.js +849 -0
  16. package/build/core/config.d.ts +79 -0
  17. package/build/core/config.d.ts.map +1 -0
  18. package/build/core/config.js +193 -0
  19. package/build/core/dependency-validator.d.ts +106 -0
  20. package/build/core/dependency-validator.d.ts.map +1 -0
  21. package/build/core/dependency-validator.js +405 -0
  22. package/build/core/distro-adapter.d.ts +177 -0
  23. package/build/core/distro-adapter.d.ts.map +1 -0
  24. package/build/core/distro-adapter.js +481 -0
  25. package/build/core/distro.d.ts +68 -0
  26. package/build/core/distro.d.ts.map +1 -0
  27. package/build/core/distro.js +457 -0
  28. package/build/core/encrypted-state.d.ts +76 -0
  29. package/build/core/encrypted-state.d.ts.map +1 -0
  30. package/build/core/encrypted-state.js +209 -0
  31. package/build/core/executor.d.ts +56 -0
  32. package/build/core/executor.d.ts.map +1 -0
  33. package/build/core/executor.js +350 -0
  34. package/build/core/installer.d.ts +92 -0
  35. package/build/core/installer.d.ts.map +1 -0
  36. package/build/core/installer.js +1072 -0
  37. package/build/core/logger.d.ts +102 -0
  38. package/build/core/logger.d.ts.map +1 -0
  39. package/build/core/logger.js +132 -0
  40. package/build/core/parsers.d.ts +151 -0
  41. package/build/core/parsers.d.ts.map +1 -0
  42. package/build/core/parsers.js +479 -0
  43. package/build/core/policy-engine.d.ts +170 -0
  44. package/build/core/policy-engine.d.ts.map +1 -0
  45. package/build/core/policy-engine.js +656 -0
  46. package/build/core/preflight.d.ts +157 -0
  47. package/build/core/preflight.d.ts.map +1 -0
  48. package/build/core/preflight.js +638 -0
  49. package/build/core/privilege-manager.d.ts +108 -0
  50. package/build/core/privilege-manager.d.ts.map +1 -0
  51. package/build/core/privilege-manager.js +363 -0
  52. package/build/core/rate-limiter.d.ts +67 -0
  53. package/build/core/rate-limiter.d.ts.map +1 -0
  54. package/build/core/rate-limiter.js +129 -0
  55. package/build/core/rollback.d.ts +73 -0
  56. package/build/core/rollback.d.ts.map +1 -0
  57. package/build/core/rollback.js +278 -0
  58. package/build/core/safeguards.d.ts +58 -0
  59. package/build/core/safeguards.d.ts.map +1 -0
  60. package/build/core/safeguards.js +448 -0
  61. package/build/core/sanitizer.d.ts +118 -0
  62. package/build/core/sanitizer.d.ts.map +1 -0
  63. package/build/core/sanitizer.js +459 -0
  64. package/build/core/secure-fs.d.ts +67 -0
  65. package/build/core/secure-fs.d.ts.map +1 -0
  66. package/build/core/secure-fs.js +143 -0
  67. package/build/core/spawn-safe.d.ts +55 -0
  68. package/build/core/spawn-safe.d.ts.map +1 -0
  69. package/build/core/spawn-safe.js +146 -0
  70. package/build/core/sudo-guard.d.ts +145 -0
  71. package/build/core/sudo-guard.d.ts.map +1 -0
  72. package/build/core/sudo-guard.js +349 -0
  73. package/build/core/sudo-session.d.ts +100 -0
  74. package/build/core/sudo-session.d.ts.map +1 -0
  75. package/build/core/sudo-session.js +319 -0
  76. package/build/core/tool-dependencies.d.ts +61 -0
  77. package/build/core/tool-dependencies.d.ts.map +1 -0
  78. package/build/core/tool-dependencies.js +571 -0
  79. package/build/core/tool-registry.d.ts +111 -0
  80. package/build/core/tool-registry.d.ts.map +1 -0
  81. package/build/core/tool-registry.js +656 -0
  82. package/build/core/tool-wrapper.d.ts +73 -0
  83. package/build/core/tool-wrapper.d.ts.map +1 -0
  84. package/build/core/tool-wrapper.js +296 -0
  85. package/build/index.d.ts +3 -0
  86. package/build/index.d.ts.map +1 -0
  87. package/build/index.js +247 -0
  88. package/build/tools/access-control.d.ts +9 -0
  89. package/build/tools/access-control.d.ts.map +1 -0
  90. package/build/tools/access-control.js +1818 -0
  91. package/build/tools/api-security.d.ts +12 -0
  92. package/build/tools/api-security.d.ts.map +1 -0
  93. package/build/tools/api-security.js +901 -0
  94. package/build/tools/app-hardening.d.ts +11 -0
  95. package/build/tools/app-hardening.d.ts.map +1 -0
  96. package/build/tools/app-hardening.js +768 -0
  97. package/build/tools/backup.d.ts +8 -0
  98. package/build/tools/backup.d.ts.map +1 -0
  99. package/build/tools/backup.js +381 -0
  100. package/build/tools/cloud-security.d.ts +17 -0
  101. package/build/tools/cloud-security.d.ts.map +1 -0
  102. package/build/tools/cloud-security.js +739 -0
  103. package/build/tools/compliance.d.ts +10 -0
  104. package/build/tools/compliance.d.ts.map +1 -0
  105. package/build/tools/compliance.js +1225 -0
  106. package/build/tools/container-security.d.ts +14 -0
  107. package/build/tools/container-security.d.ts.map +1 -0
  108. package/build/tools/container-security.js +788 -0
  109. package/build/tools/deception.d.ts +13 -0
  110. package/build/tools/deception.d.ts.map +1 -0
  111. package/build/tools/deception.js +763 -0
  112. package/build/tools/dns-security.d.ts +93 -0
  113. package/build/tools/dns-security.d.ts.map +1 -0
  114. package/build/tools/dns-security.js +745 -0
  115. package/build/tools/drift-detection.d.ts +8 -0
  116. package/build/tools/drift-detection.d.ts.map +1 -0
  117. package/build/tools/drift-detection.js +326 -0
  118. package/build/tools/ebpf-security.d.ts +15 -0
  119. package/build/tools/ebpf-security.d.ts.map +1 -0
  120. package/build/tools/ebpf-security.js +294 -0
  121. package/build/tools/encryption.d.ts +9 -0
  122. package/build/tools/encryption.d.ts.map +1 -0
  123. package/build/tools/encryption.js +1667 -0
  124. package/build/tools/firewall.d.ts +9 -0
  125. package/build/tools/firewall.d.ts.map +1 -0
  126. package/build/tools/firewall.js +1398 -0
  127. package/build/tools/hardening.d.ts +10 -0
  128. package/build/tools/hardening.d.ts.map +1 -0
  129. package/build/tools/hardening.js +2654 -0
  130. package/build/tools/ids.d.ts +9 -0
  131. package/build/tools/ids.d.ts.map +1 -0
  132. package/build/tools/ids.js +624 -0
  133. package/build/tools/incident-response.d.ts +10 -0
  134. package/build/tools/incident-response.d.ts.map +1 -0
  135. package/build/tools/incident-response.js +1180 -0
  136. package/build/tools/logging.d.ts +12 -0
  137. package/build/tools/logging.d.ts.map +1 -0
  138. package/build/tools/logging.js +454 -0
  139. package/build/tools/malware.d.ts +10 -0
  140. package/build/tools/malware.d.ts.map +1 -0
  141. package/build/tools/malware.js +532 -0
  142. package/build/tools/meta.d.ts +11 -0
  143. package/build/tools/meta.d.ts.map +1 -0
  144. package/build/tools/meta.js +2278 -0
  145. package/build/tools/network-defense.d.ts +12 -0
  146. package/build/tools/network-defense.d.ts.map +1 -0
  147. package/build/tools/network-defense.js +760 -0
  148. package/build/tools/patch-management.d.ts +3 -0
  149. package/build/tools/patch-management.d.ts.map +1 -0
  150. package/build/tools/patch-management.js +708 -0
  151. package/build/tools/process-security.d.ts +12 -0
  152. package/build/tools/process-security.d.ts.map +1 -0
  153. package/build/tools/process-security.js +784 -0
  154. package/build/tools/reporting.d.ts +11 -0
  155. package/build/tools/reporting.d.ts.map +1 -0
  156. package/build/tools/reporting.js +559 -0
  157. package/build/tools/secrets.d.ts +9 -0
  158. package/build/tools/secrets.d.ts.map +1 -0
  159. package/build/tools/secrets.js +596 -0
  160. package/build/tools/siem-integration.d.ts +18 -0
  161. package/build/tools/siem-integration.d.ts.map +1 -0
  162. package/build/tools/siem-integration.js +754 -0
  163. package/build/tools/sudo-management.d.ts +18 -0
  164. package/build/tools/sudo-management.d.ts.map +1 -0
  165. package/build/tools/sudo-management.js +737 -0
  166. package/build/tools/supply-chain-security.d.ts +8 -0
  167. package/build/tools/supply-chain-security.d.ts.map +1 -0
  168. package/build/tools/supply-chain-security.js +256 -0
  169. package/build/tools/threat-intel.d.ts +22 -0
  170. package/build/tools/threat-intel.d.ts.map +1 -0
  171. package/build/tools/threat-intel.js +749 -0
  172. package/build/tools/vulnerability-management.d.ts +11 -0
  173. package/build/tools/vulnerability-management.d.ts.map +1 -0
  174. package/build/tools/vulnerability-management.js +667 -0
  175. package/build/tools/waf.d.ts +12 -0
  176. package/build/tools/waf.d.ts.map +1 -0
  177. package/build/tools/waf.js +843 -0
  178. package/build/tools/wireless-security.d.ts +19 -0
  179. package/build/tools/wireless-security.d.ts.map +1 -0
  180. package/build/tools/wireless-security.js +826 -0
  181. package/build/tools/zero-trust-network.d.ts +8 -0
  182. package/build/tools/zero-trust-network.d.ts.map +1 -0
  183. package/build/tools/zero-trust-network.js +367 -0
  184. package/docs/SAFEGUARDS.md +518 -0
  185. package/docs/TOOLS-REFERENCE.md +665 -0
  186. 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
+ }