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,8 @@
1
+ /**
2
+ * Drift detection tools.
3
+ *
4
+ * Registers 1 tool: drift_baseline (actions: create, compare, list).
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ export declare function registerDriftDetectionTools(server: McpServer): void;
8
+ //# sourceMappingURL=drift-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift-detection.d.ts","sourceRoot":"","sources":["../../src/tools/drift-detection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwFpE,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkSnE"}
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Drift detection tools.
3
+ *
4
+ * Registers 1 tool: drift_baseline (actions: create, compare, list).
5
+ */
6
+ import { z } from "zod";
7
+ import { executeCommand } from "../core/executor.js";
8
+ import { createErrorContent, formatToolOutput } from "../core/parsers.js";
9
+ import { existsSync, readFileSync, mkdirSync, readdirSync, statSync } from "node:fs";
10
+ import { join, extname, resolve } from "node:path";
11
+ import { homedir } from "node:os";
12
+ import { createHash } from "node:crypto";
13
+ import { secureWriteFileSync } from "../core/secure-fs.js";
14
+ const BASELINE_DIR = join(homedir(), ".kali-mcp-baselines");
15
+ // ── TOOL-024 remediation: baseline path validation ─────────────────────────
16
+ /** Allowed directories for drift baseline files */
17
+ const ALLOWED_BASELINE_DIRS = [BASELINE_DIR, "/tmp", "/var/lib", "/home", "/root", "/opt"];
18
+ /** Allowed file extensions for baseline files */
19
+ const ALLOWED_BASELINE_EXTENSIONS = new Set([".json", ".yaml", ".yml"]);
20
+ /**
21
+ * Validate a baseline file path:
22
+ * 1. Reject `..` sequences
23
+ * 2. Resolve and verify within allowed directories
24
+ * 3. Validate file extension
25
+ */
26
+ function validateBaselinePath(inputPath) {
27
+ if (!inputPath || typeof inputPath !== "string") {
28
+ throw new Error("Baseline path must be a non-empty string");
29
+ }
30
+ if (inputPath.includes("..")) {
31
+ throw new Error(`Baseline path contains forbidden traversal sequence (..): '${inputPath}'`);
32
+ }
33
+ const resolved = resolve(inputPath);
34
+ const isAllowed = ALLOWED_BASELINE_DIRS.some((dir) => resolved === dir || resolved.startsWith(dir + "/"));
35
+ if (!isAllowed) {
36
+ throw new Error(`Baseline path '${resolved}' is not within allowed directories: ${ALLOWED_BASELINE_DIRS.join(", ")}`);
37
+ }
38
+ const ext = extname(resolved).toLowerCase();
39
+ if (ext && !ALLOWED_BASELINE_EXTENSIONS.has(ext)) {
40
+ throw new Error(`Baseline file has invalid extension '${ext}'. Allowed: ${[...ALLOWED_BASELINE_EXTENSIONS].join(", ")}`);
41
+ }
42
+ return resolved;
43
+ }
44
+ function ensureBaselineDir() {
45
+ if (!existsSync(BASELINE_DIR)) {
46
+ mkdirSync(BASELINE_DIR, { recursive: true });
47
+ }
48
+ }
49
+ function hashFile(filePath) {
50
+ try {
51
+ const content = readFileSync(filePath);
52
+ return createHash("sha256").update(content).digest("hex");
53
+ }
54
+ catch {
55
+ return "unreadable";
56
+ }
57
+ }
58
+ export function registerDriftDetectionTools(server) {
59
+ server.tool("drift_baseline", "Drift detection: create system baselines, compare current state against baselines, or list available baselines.", {
60
+ action: z.enum(["create", "compare", "list"]).describe("Action: create=create baseline, compare=compare against baseline, list=list baselines"),
61
+ // create/compare params
62
+ name: z.string().optional().default("default").describe("Baseline name"),
63
+ directories: z.array(z.string()).optional().default(["/etc"]).describe("Directories to hash (create action)"),
64
+ // shared
65
+ dryRun: z.boolean().optional().default(true).describe("Preview only"),
66
+ }, async (params) => {
67
+ const { action } = params;
68
+ switch (action) {
69
+ // ── create ──────────────────────────────────────────────────
70
+ case "create": {
71
+ const { directories, name, dryRun } = params;
72
+ try {
73
+ ensureBaselineDir();
74
+ if (dryRun) {
75
+ return {
76
+ content: [formatToolOutput({
77
+ dryRun: true,
78
+ directories,
79
+ baselineName: name,
80
+ storagePath: join(BASELINE_DIR, `${name}.json`),
81
+ })],
82
+ };
83
+ }
84
+ // Hash files in directories
85
+ const files = [];
86
+ for (const dir of directories) {
87
+ if (!existsSync(dir))
88
+ continue;
89
+ const findResult = await executeCommand({
90
+ command: "find",
91
+ args: [dir, "-maxdepth", "3", "-type", "f", "-not", "-path", "*/proc/*", "-not", "-path", "*/sys/*"],
92
+ timeout: 30000,
93
+ });
94
+ // find may return non-zero due to permission errors but still output valid paths
95
+ if (findResult.stdout.trim()) {
96
+ const paths = findResult.stdout.trim().split("\n").filter(Boolean).slice(0, 5000);
97
+ for (const p of paths) {
98
+ try {
99
+ const stat = statSync(p);
100
+ files.push({
101
+ path: p,
102
+ hash: hashFile(p),
103
+ size: stat.size,
104
+ mtime: stat.mtime.toISOString(),
105
+ });
106
+ }
107
+ catch { /* skip unreadable */ }
108
+ }
109
+ }
110
+ }
111
+ // Capture sysctl state
112
+ const sysctlState = {};
113
+ const sysctlResult = await executeCommand({
114
+ command: "sysctl",
115
+ args: ["-a"],
116
+ timeout: 10000,
117
+ });
118
+ if (sysctlResult.exitCode === 0 && sysctlResult.stdout.trim()) {
119
+ for (const line of sysctlResult.stdout.split("\n")) {
120
+ const idx = line.indexOf("=");
121
+ if (idx > 0) {
122
+ sysctlState[line.substring(0, idx).trim()] = line.substring(idx + 1).trim();
123
+ }
124
+ }
125
+ }
126
+ // Fallback: read key sysctl values from /proc/sys/ if sysctl binary failed or returned nothing
127
+ if (Object.keys(sysctlState).length === 0) {
128
+ const procSysKeys = [
129
+ ["net.ipv4.ip_forward", "/proc/sys/net/ipv4/ip_forward"],
130
+ ["net.ipv4.conf.all.accept_redirects", "/proc/sys/net/ipv4/conf/all/accept_redirects"],
131
+ ["net.ipv4.conf.all.send_redirects", "/proc/sys/net/ipv4/conf/all/send_redirects"],
132
+ ["net.ipv4.conf.all.accept_source_route", "/proc/sys/net/ipv4/conf/all/accept_source_route"],
133
+ ["net.ipv4.conf.all.log_martians", "/proc/sys/net/ipv4/conf/all/log_martians"],
134
+ ["net.ipv4.icmp_echo_ignore_broadcasts", "/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts"],
135
+ ["net.ipv4.tcp_syncookies", "/proc/sys/net/ipv4/tcp_syncookies"],
136
+ ["net.ipv6.conf.all.accept_redirects", "/proc/sys/net/ipv6/conf/all/accept_redirects"],
137
+ ["net.ipv6.conf.all.accept_source_route", "/proc/sys/net/ipv6/conf/all/accept_source_route"],
138
+ ["kernel.randomize_va_space", "/proc/sys/kernel/randomize_va_space"],
139
+ ["kernel.dmesg_restrict", "/proc/sys/kernel/dmesg_restrict"],
140
+ ["kernel.kptr_restrict", "/proc/sys/kernel/kptr_restrict"],
141
+ ["kernel.yama.ptrace_scope", "/proc/sys/kernel/yama/ptrace_scope"],
142
+ ["kernel.sysrq", "/proc/sys/kernel/sysrq"],
143
+ ["fs.protected_hardlinks", "/proc/sys/fs/protected_hardlinks"],
144
+ ["fs.protected_symlinks", "/proc/sys/fs/protected_symlinks"],
145
+ ["fs.suid_dumpable", "/proc/sys/fs/suid_dumpable"],
146
+ ];
147
+ for (const [key, procPath] of procSysKeys) {
148
+ try {
149
+ if (existsSync(procPath)) {
150
+ const val = readFileSync(procPath, "utf-8").trim();
151
+ sysctlState[key] = val;
152
+ }
153
+ }
154
+ catch { /* skip unreadable */ }
155
+ }
156
+ if (Object.keys(sysctlState).length > 0) {
157
+ console.error(`[drift-detection] sysctl binary unavailable; read ${Object.keys(sysctlState).length} keys from /proc/sys/`);
158
+ }
159
+ else {
160
+ console.error("[drift-detection] Warning: could not capture any sysctl state (sysctl binary unavailable and /proc/sys/ read failed)");
161
+ }
162
+ }
163
+ // Capture service states
164
+ const services = {};
165
+ const svcResult = await executeCommand({
166
+ command: "systemctl",
167
+ args: ["list-units", "--type=service", "--no-pager", "--plain", "--no-legend"],
168
+ timeout: 10000,
169
+ });
170
+ if (svcResult.exitCode === 0) {
171
+ for (const line of svcResult.stdout.split("\n")) {
172
+ const parts = line.trim().split(/\s+/);
173
+ if (parts.length >= 3) {
174
+ services[parts[0]] = parts[2]; // active/inactive/failed
175
+ }
176
+ }
177
+ }
178
+ const baseline = {
179
+ id: name,
180
+ timestamp: new Date().toISOString(),
181
+ directories,
182
+ files,
183
+ sysctlState,
184
+ services,
185
+ };
186
+ const outPath = join(BASELINE_DIR, `${name}.json`);
187
+ // TOOL-024: Validate baseline output path and use secure write
188
+ validateBaselinePath(outPath);
189
+ secureWriteFileSync(outPath, JSON.stringify(baseline, null, 2), "utf-8");
190
+ return {
191
+ content: [formatToolOutput({
192
+ baselineName: name,
193
+ timestamp: baseline.timestamp,
194
+ filesHashed: files.length,
195
+ sysctlKeys: Object.keys(sysctlState).length,
196
+ servicesTracked: Object.keys(services).length,
197
+ savedTo: outPath,
198
+ })],
199
+ };
200
+ }
201
+ catch (err) {
202
+ return { content: [createErrorContent(`Baseline creation failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
203
+ }
204
+ }
205
+ // ── compare ─────────────────────────────────────────────────
206
+ case "compare": {
207
+ const { name, dryRun } = params;
208
+ try {
209
+ const baselinePath = join(BASELINE_DIR, `${name}.json`);
210
+ // TOOL-024: Validate baseline path before reading
211
+ validateBaselinePath(baselinePath);
212
+ if (!existsSync(baselinePath)) {
213
+ return { content: [createErrorContent(`Baseline '${name}' not found at ${baselinePath}`)], isError: true };
214
+ }
215
+ const baseline = JSON.parse(readFileSync(baselinePath, "utf-8"));
216
+ const fileChanges = [];
217
+ const sysctlChanges = [];
218
+ const serviceChanges = [];
219
+ // Compare files
220
+ for (const entry of baseline.files.slice(0, 2000)) {
221
+ if (!existsSync(entry.path)) {
222
+ fileChanges.push({ path: entry.path, type: "deleted", detail: "File no longer exists" });
223
+ continue;
224
+ }
225
+ const currentHash = hashFile(entry.path);
226
+ if (currentHash !== entry.hash && currentHash !== "unreadable") {
227
+ fileChanges.push({ path: entry.path, type: "modified", detail: `hash changed: ${entry.hash.slice(0, 12)}... → ${currentHash.slice(0, 12)}...` });
228
+ }
229
+ }
230
+ // Compare sysctl
231
+ const sysctlResult = await executeCommand({
232
+ command: "sysctl",
233
+ args: ["-a"],
234
+ timeout: 10000,
235
+ });
236
+ if (sysctlResult.exitCode === 0) {
237
+ const currentSysctl = {};
238
+ for (const line of sysctlResult.stdout.split("\n")) {
239
+ const idx = line.indexOf("=");
240
+ if (idx > 0) {
241
+ currentSysctl[line.substring(0, idx).trim()] = line.substring(idx + 1).trim();
242
+ }
243
+ }
244
+ for (const [key, val] of Object.entries(baseline.sysctlState)) {
245
+ const current = currentSysctl[key];
246
+ if (current !== undefined && current !== val) {
247
+ sysctlChanges.push({ key, baseline: val, current });
248
+ }
249
+ }
250
+ }
251
+ // Compare services
252
+ const svcResult = await executeCommand({
253
+ command: "systemctl",
254
+ args: ["list-units", "--type=service", "--no-pager", "--plain", "--no-legend"],
255
+ timeout: 10000,
256
+ });
257
+ if (svcResult.exitCode === 0) {
258
+ const currentServices = {};
259
+ for (const line of svcResult.stdout.split("\n")) {
260
+ const parts = line.trim().split(/\s+/);
261
+ if (parts.length >= 3) {
262
+ currentServices[parts[0]] = parts[2];
263
+ }
264
+ }
265
+ for (const [svc, state] of Object.entries(baseline.services)) {
266
+ const current = currentServices[svc];
267
+ if (current !== undefined && current !== state) {
268
+ serviceChanges.push({ service: svc, baseline: state, current });
269
+ }
270
+ }
271
+ }
272
+ const totalDrifts = fileChanges.length + sysctlChanges.length + serviceChanges.length;
273
+ return {
274
+ content: [formatToolOutput({
275
+ baselineName: name,
276
+ baselineTimestamp: baseline.timestamp,
277
+ comparedAt: new Date().toISOString(),
278
+ totalDrifts,
279
+ fileChanges: fileChanges.slice(0, 50),
280
+ sysctlChanges: sysctlChanges.slice(0, 50),
281
+ serviceChanges: serviceChanges.slice(0, 50),
282
+ status: totalDrifts === 0 ? "NO_DRIFT" : "DRIFT_DETECTED",
283
+ })],
284
+ };
285
+ }
286
+ catch (err) {
287
+ return { content: [createErrorContent(`Baseline comparison failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
288
+ }
289
+ }
290
+ // ── list ────────────────────────────────────────────────────
291
+ case "list": {
292
+ try {
293
+ ensureBaselineDir();
294
+ const files = readdirSync(BASELINE_DIR).filter((f) => f.endsWith(".json") && f !== "manifest.json");
295
+ const baselines = files.map((f) => {
296
+ try {
297
+ const data = JSON.parse(readFileSync(join(BASELINE_DIR, f), "utf-8"));
298
+ return {
299
+ name: data.id,
300
+ timestamp: data.timestamp,
301
+ filesTracked: data.files.length,
302
+ sysctlKeys: Object.keys(data.sysctlState).length,
303
+ services: Object.keys(data.services).length,
304
+ };
305
+ }
306
+ catch {
307
+ return { name: f, timestamp: "unknown", filesTracked: 0, sysctlKeys: 0, services: 0 };
308
+ }
309
+ });
310
+ return {
311
+ content: [formatToolOutput({
312
+ baselineDir: BASELINE_DIR,
313
+ totalBaselines: baselines.length,
314
+ baselines,
315
+ })],
316
+ };
317
+ }
318
+ catch (err) {
319
+ return { content: [createErrorContent(`Drift listing failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
320
+ }
321
+ }
322
+ default:
323
+ return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
324
+ }
325
+ });
326
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * eBPF and Falco security tools.
3
+ *
4
+ * Registers 2 tools: list_ebpf_programs, falco (actions: status, deploy_rules, events).
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ /**
8
+ * Validate a BPF filter expression.
9
+ * Rejects shell metacharacters, enforces length limits, and allows only
10
+ * safe characters (alphanumeric, spaces, dots, colons, slashes, hyphens,
11
+ * underscores, brackets, comparison operators, and quotes).
12
+ */
13
+ export declare function validateBpfFilter(filter: string): string;
14
+ export declare function registerEbpfSecurityTools(server: McpServer): void;
15
+ //# sourceMappingURL=ebpf-security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ebpf-security.d.ts","sourceRoot":"","sources":["../../src/tools/ebpf-security.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqBpE;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0BxD;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwRjE"}
@@ -0,0 +1,294 @@
1
+ /**
2
+ * eBPF and Falco security tools.
3
+ *
4
+ * Registers 2 tools: list_ebpf_programs, falco (actions: status, deploy_rules, events).
5
+ */
6
+ import { z } from "zod";
7
+ import { executeCommand } from "../core/executor.js";
8
+ import { createErrorContent, formatToolOutput } from "../core/parsers.js";
9
+ import { logChange, createChangeEntry } from "../core/changelog.js";
10
+ import { SafeguardRegistry } from "../core/safeguards.js";
11
+ import { existsSync, mkdirSync } from "node:fs";
12
+ import { secureWriteFileSync } from "../core/secure-fs.js";
13
+ // ── TOOL-018 remediation: BPF filter expression validation ─────────────────
14
+ /** Characters allowed in BPF filter expressions */
15
+ const BPF_FILTER_ALLOWED_RE = /^[a-zA-Z0-9\s.\/:_\-\[\]!=<>'"]+$/;
16
+ /** Shell metacharacters that must be rejected in BPF filters */
17
+ const BPF_SHELL_METACHAR_RE = /[;|&`$(){}<>]/;
18
+ /** Maximum BPF filter expression length */
19
+ const BPF_FILTER_MAX_LENGTH = 500;
20
+ /**
21
+ * Validate a BPF filter expression.
22
+ * Rejects shell metacharacters, enforces length limits, and allows only
23
+ * safe characters (alphanumeric, spaces, dots, colons, slashes, hyphens,
24
+ * underscores, brackets, comparison operators, and quotes).
25
+ */
26
+ export function validateBpfFilter(filter) {
27
+ if (!filter || typeof filter !== "string") {
28
+ throw new Error("BPF filter must be a non-empty string");
29
+ }
30
+ const trimmed = filter.trim();
31
+ if (trimmed.length > BPF_FILTER_MAX_LENGTH) {
32
+ throw new Error(`BPF filter expression too long (${trimmed.length} chars). Maximum is ${BPF_FILTER_MAX_LENGTH} characters.`);
33
+ }
34
+ if (BPF_SHELL_METACHAR_RE.test(trimmed)) {
35
+ throw new Error(`BPF filter contains forbidden shell metacharacters: '${trimmed}'`);
36
+ }
37
+ if (!BPF_FILTER_ALLOWED_RE.test(trimmed)) {
38
+ throw new Error(`BPF filter contains invalid characters. Only alphanumeric, spaces, dots, colons, slashes, hyphens, underscores, brackets, comparison operators, and quotes are allowed.`);
39
+ }
40
+ return trimmed;
41
+ }
42
+ export function registerEbpfSecurityTools(server) {
43
+ // ── list_ebpf_programs (kept as-is) ────────────────────────────────────────
44
+ server.tool("ebpf_list_programs", "List loaded eBPF programs and pinned maps.", {
45
+ dryRun: z.boolean().optional().default(true).describe("Preview only"),
46
+ }, async ({ dryRun }) => {
47
+ try {
48
+ const results = {};
49
+ // List BPF filesystem
50
+ if (existsSync("/sys/fs/bpf")) {
51
+ const ls = await executeCommand({
52
+ command: "ls",
53
+ args: ["-la", "/sys/fs/bpf"],
54
+ timeout: 5000,
55
+ });
56
+ results.bpfFs = ls.stdout.trim().split("\n");
57
+ }
58
+ else {
59
+ results.bpfFs = "Not mounted";
60
+ }
61
+ // Try bpftool
62
+ const bpftool = await executeCommand({
63
+ command: "bpftool",
64
+ args: ["prog", "list", "--json"],
65
+ timeout: 10000,
66
+ });
67
+ if (bpftool.exitCode === 0) {
68
+ try {
69
+ results.programs = JSON.parse(bpftool.stdout);
70
+ }
71
+ catch {
72
+ results.programs = bpftool.stdout.split("\n").filter(Boolean);
73
+ }
74
+ }
75
+ else {
76
+ // Fallback to non-JSON
77
+ const fallback = await executeCommand({
78
+ command: "bpftool",
79
+ args: ["prog", "list"],
80
+ timeout: 10000,
81
+ });
82
+ results.programs = fallback.exitCode === 0
83
+ ? fallback.stdout.split("\n").filter(Boolean)
84
+ : "bpftool not available or insufficient permissions";
85
+ }
86
+ // List maps
87
+ const maps = await executeCommand({
88
+ command: "bpftool",
89
+ args: ["map", "list", "--json"],
90
+ timeout: 10000,
91
+ });
92
+ if (maps.exitCode === 0) {
93
+ try {
94
+ results.maps = JSON.parse(maps.stdout);
95
+ }
96
+ catch {
97
+ results.maps = maps.stdout.split("\n").filter(Boolean);
98
+ }
99
+ }
100
+ return { content: [formatToolOutput(results)] };
101
+ }
102
+ catch (err) {
103
+ return { content: [createErrorContent(`eBPF listing failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
104
+ }
105
+ });
106
+ // ── falco (merged: check_falco + deploy_falco_rules + get_ebpf_events) ────
107
+ server.tool("ebpf_falco", "Falco runtime security: check status, deploy custom rules, or read recent events.", {
108
+ action: z.enum(["status", "deploy_rules", "events"]).describe("Action: status=check Falco, deploy_rules=deploy custom rules, events=read recent events"),
109
+ // deploy_rules params
110
+ ruleName: z.string().optional().describe("Rule file name without .yaml extension (deploy_rules action)"),
111
+ ruleContent: z.string().optional().describe("YAML rule content (deploy_rules action)"),
112
+ // events params
113
+ lines: z.number().optional().default(50).describe("Number of recent events to return (events action)"),
114
+ priority: z.enum(["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]).optional().describe("Filter by minimum priority (events action)"),
115
+ // shared
116
+ dryRun: z.boolean().optional().default(true).describe("Preview only"),
117
+ }, async (params) => {
118
+ const { action } = params;
119
+ switch (action) {
120
+ // ── status ──────────────────────────────────────────────────
121
+ case "status": {
122
+ try {
123
+ const info = {};
124
+ // Check if falco is installed
125
+ const which = await executeCommand({ command: "which", args: ["falco"], timeout: 5000 });
126
+ info.installed = which.exitCode === 0;
127
+ if (!info.installed) {
128
+ return {
129
+ content: [formatToolOutput({
130
+ installed: false,
131
+ message: "Falco not installed. Install from https://falco.org/docs/install-operate/installation/",
132
+ })],
133
+ };
134
+ }
135
+ // Version
136
+ const version = await executeCommand({ command: "falco", args: ["--version"], timeout: 5000 });
137
+ info.version = version.stdout.trim();
138
+ // Service status
139
+ const status = await executeCommand({
140
+ command: "systemctl",
141
+ args: ["status", "falco", "--no-pager"],
142
+ timeout: 10000,
143
+ });
144
+ info.serviceStatus = status.stdout.trim().split("\n").slice(0, 10);
145
+ info.active = status.stdout.includes("active (running)");
146
+ // Check config
147
+ if (existsSync("/etc/falco/falco.yaml")) {
148
+ info.configExists = true;
149
+ }
150
+ // Check custom rules
151
+ if (existsSync("/etc/falco/rules.d")) {
152
+ const ls = await executeCommand({ command: "ls", args: ["/etc/falco/rules.d"], timeout: 5000 });
153
+ info.customRules = ls.stdout.trim().split("\n").filter(Boolean);
154
+ }
155
+ return { content: [formatToolOutput(info)] };
156
+ }
157
+ catch (err) {
158
+ return { content: [createErrorContent(`Falco check failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
159
+ }
160
+ }
161
+ // ── deploy_rules ────────────────────────────────────────────
162
+ case "deploy_rules": {
163
+ const { ruleName, ruleContent, dryRun } = params;
164
+ try {
165
+ if (!ruleName) {
166
+ return { content: [createErrorContent("ruleName is required for deploy_rules action")], isError: true };
167
+ }
168
+ if (!ruleContent) {
169
+ return { content: [createErrorContent("ruleContent is required for deploy_rules action")], isError: true };
170
+ }
171
+ const safety = await SafeguardRegistry.getInstance().checkSafety("deploy_falco_rules", { ruleName });
172
+ const rulesDir = "/etc/falco/rules.d";
173
+ const rulePath = `${rulesDir}/${ruleName}.yaml`;
174
+ if (dryRun) {
175
+ return {
176
+ content: [formatToolOutput({
177
+ dryRun: true,
178
+ rulePath,
179
+ ruleContent,
180
+ warnings: safety.warnings,
181
+ restartCommand: "systemctl restart falco",
182
+ })],
183
+ };
184
+ }
185
+ if (!existsSync(rulesDir)) {
186
+ mkdirSync(rulesDir, { recursive: true });
187
+ }
188
+ // TOOL-010: Use secure-fs instead of direct writeFileSync for audit trail
189
+ secureWriteFileSync(rulePath, ruleContent, "utf-8");
190
+ // Validate rules
191
+ const validate = await executeCommand({
192
+ command: "falco",
193
+ args: ["--validate", rulePath],
194
+ timeout: 15000,
195
+ });
196
+ const entry = createChangeEntry({
197
+ tool: "ebpf_falco",
198
+ action: `Deploy Falco rule ${ruleName}`,
199
+ target: rulePath,
200
+ dryRun: false,
201
+ success: validate.exitCode === 0,
202
+ rollbackCommand: `rm ${rulePath}`,
203
+ });
204
+ logChange(entry);
205
+ return {
206
+ content: [formatToolOutput({
207
+ success: validate.exitCode === 0,
208
+ rulePath,
209
+ validated: validate.exitCode === 0,
210
+ validationOutput: validate.stdout || validate.stderr,
211
+ nextStep: "systemctl restart falco",
212
+ })],
213
+ };
214
+ }
215
+ catch (err) {
216
+ return { content: [createErrorContent(`Falco rule deployment failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
217
+ }
218
+ }
219
+ // ── events ──────────────────────────────────────────────────
220
+ case "events": {
221
+ const { lines, priority } = params;
222
+ try {
223
+ const logPaths = [
224
+ "/var/log/falco/falco_events.json",
225
+ "/var/log/falco/events.json",
226
+ "/var/log/falco.log",
227
+ ];
228
+ let logPath = null;
229
+ for (const p of logPaths) {
230
+ if (existsSync(p)) {
231
+ logPath = p;
232
+ break;
233
+ }
234
+ }
235
+ if (!logPath) {
236
+ // Try journalctl
237
+ const journalResult = await executeCommand({
238
+ command: "journalctl",
239
+ args: ["-u", "falco", "--no-pager", "-n", String(lines), "-o", "json"],
240
+ timeout: 10000,
241
+ });
242
+ if (journalResult.exitCode === 0) {
243
+ const events = journalResult.stdout.trim().split("\n").filter(Boolean).map((l) => {
244
+ try {
245
+ return JSON.parse(l);
246
+ }
247
+ catch {
248
+ return { raw: l };
249
+ }
250
+ });
251
+ return { content: [formatToolOutput({ source: "journalctl", events: events.slice(-lines) })] };
252
+ }
253
+ return { content: [createErrorContent("No Falco log file found and journalctl query failed")], isError: true };
254
+ }
255
+ const result = await executeCommand({
256
+ command: "tail",
257
+ args: ["-n", String(lines), logPath],
258
+ timeout: 10000,
259
+ });
260
+ const events = result.stdout.trim().split("\n").filter(Boolean).map((l) => {
261
+ try {
262
+ return JSON.parse(l);
263
+ }
264
+ catch {
265
+ return { raw: l };
266
+ }
267
+ });
268
+ let filtered = events;
269
+ if (priority) {
270
+ const priorities = ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"];
271
+ const minIdx = priorities.indexOf(priority);
272
+ filtered = events.filter((e) => {
273
+ const p = typeof e.priority === "string" ? e.priority.toLowerCase() : "";
274
+ const idx = priorities.indexOf(p);
275
+ return idx >= 0 && idx <= minIdx;
276
+ });
277
+ }
278
+ return {
279
+ content: [formatToolOutput({
280
+ source: logPath,
281
+ totalEvents: filtered.length,
282
+ events: filtered,
283
+ })],
284
+ };
285
+ }
286
+ catch (err) {
287
+ return { content: [createErrorContent(`eBPF events retrieval failed: ${err instanceof Error ? err.message : String(err)}`)], isError: true };
288
+ }
289
+ }
290
+ default:
291
+ return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
292
+ }
293
+ });
294
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Encryption and cryptography tools for Kali Defense MCP Server.
3
+ *
4
+ * Registers 4 tools: crypto_tls (actions: remote_audit, cert_expiry, config_audit),
5
+ * crypto_gpg_keys, crypto_luks_manage, crypto_file_hash.
6
+ */
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ export declare function registerEncryptionTools(server: McpServer): void;
9
+ //# sourceMappingURL=encryption.d.ts.map