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,843 @@
1
+ /**
2
+ * WAF (Web Application Firewall) management tools for Kali Defense MCP Server.
3
+ *
4
+ * Registers 1 tool: waf_manage (actions: modsec_audit, modsec_rules,
5
+ * rate_limit_config, owasp_crs_deploy, blocked_requests)
6
+ *
7
+ * Provides ModSecurity WAF auditing, rule management, rate limiting
8
+ * configuration, OWASP CRS deployment checks, and blocked request analysis.
9
+ */
10
+ import { z } from "zod";
11
+ import { spawnSafe } from "../core/spawn-safe.js";
12
+ import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
13
+ // ── Constants ──────────────────────────────────────────────────────────────────
14
+ const MODSEC_CONF_PATHS = {
15
+ nginx: [
16
+ "/etc/modsecurity/modsecurity.conf",
17
+ "/etc/nginx/modsecurity.conf",
18
+ "/etc/nginx/modsec/modsecurity.conf",
19
+ ],
20
+ apache: [
21
+ "/etc/modsecurity/modsecurity.conf",
22
+ "/etc/apache2/mods-enabled/security2.conf",
23
+ "/etc/httpd/conf.d/mod_security.conf",
24
+ ],
25
+ };
26
+ const MODSEC_RULES_DIRS = [
27
+ "/etc/modsecurity/rules",
28
+ "/usr/share/modsecurity-crs/rules",
29
+ "/etc/modsecurity-crs/rules",
30
+ ];
31
+ const MODSEC_AUDIT_LOG_PATHS = [
32
+ "/var/log/modsecurity/modsec_audit.log",
33
+ "/var/log/modsec_audit.log",
34
+ "/var/log/apache2/modsec_audit.log",
35
+ "/var/log/nginx/modsec_audit.log",
36
+ ];
37
+ const OWASP_CRS_PATHS = [
38
+ "/usr/share/modsecurity-crs",
39
+ "/etc/modsecurity-crs",
40
+ "/opt/owasp-crs",
41
+ "/etc/modsecurity/crs",
42
+ ];
43
+ /**
44
+ * Run a command via spawnSafe and collect output as a promise.
45
+ * Handles errors gracefully — returns error info instead of throwing.
46
+ */
47
+ async function runCommand(command, args, timeoutMs = 30_000) {
48
+ return new Promise((resolve) => {
49
+ let child;
50
+ try {
51
+ child = spawnSafe(command, args);
52
+ }
53
+ catch (err) {
54
+ const msg = err instanceof Error ? err.message : String(err);
55
+ resolve({ stdout: "", stderr: msg, exitCode: -1 });
56
+ return;
57
+ }
58
+ let stdout = "";
59
+ let stderr = "";
60
+ let resolved = false;
61
+ const timer = setTimeout(() => {
62
+ if (!resolved) {
63
+ resolved = true;
64
+ child.kill("SIGTERM");
65
+ resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
66
+ }
67
+ }, timeoutMs);
68
+ child.stdout?.on("data", (data) => {
69
+ stdout += data.toString();
70
+ });
71
+ child.stderr?.on("data", (data) => {
72
+ stderr += data.toString();
73
+ });
74
+ child.on("close", (code) => {
75
+ if (!resolved) {
76
+ resolved = true;
77
+ clearTimeout(timer);
78
+ resolve({ stdout, stderr, exitCode: code ?? -1 });
79
+ }
80
+ });
81
+ child.on("error", (err) => {
82
+ if (!resolved) {
83
+ resolved = true;
84
+ clearTimeout(timer);
85
+ resolve({ stdout, stderr: err.message, exitCode: -1 });
86
+ }
87
+ });
88
+ });
89
+ }
90
+ /**
91
+ * Run a command via sudo through spawnSafe.
92
+ */
93
+ async function runSudoCommand(command, args, timeoutMs = 30_000) {
94
+ return runCommand("sudo", [command, ...args], timeoutMs);
95
+ }
96
+ // ── Action Implementations ─────────────────────────────────────────────────────
97
+ /**
98
+ * Audit ModSecurity WAF configuration.
99
+ */
100
+ async function handleModsecAudit(webServer, outputFormat) {
101
+ try {
102
+ const sections = [];
103
+ const jsonData = {};
104
+ sections.push("🛡️ ModSecurity WAF Audit");
105
+ sections.push("=".repeat(55));
106
+ sections.push(`Web Server: ${webServer}`);
107
+ // Check if ModSecurity is installed
108
+ const dpkgCheck = await runCommand("dpkg", ["-l", "libapache2-mod-security2"]);
109
+ const modsecNginxCheck = await runCommand("dpkg", ["-l", "libnginx-mod-security"]);
110
+ const whichCheck = await runCommand("which", ["modsecurity-check"]);
111
+ const isInstalled = dpkgCheck.exitCode === 0 ||
112
+ modsecNginxCheck.exitCode === 0 ||
113
+ whichCheck.exitCode === 0;
114
+ jsonData.installed = isInstalled;
115
+ sections.push(`\n── Installation Status ──`);
116
+ if (!isInstalled) {
117
+ // Check for config file existence as fallback
118
+ const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
119
+ let configFound = false;
120
+ for (const confPath of confPaths) {
121
+ const testResult = await runCommand("test", ["-f", confPath]);
122
+ if (testResult.exitCode === 0) {
123
+ configFound = true;
124
+ sections.push(` ⚠️ ModSecurity package not found via dpkg, but config exists: ${confPath}`);
125
+ break;
126
+ }
127
+ }
128
+ if (!configFound) {
129
+ sections.push(" ❌ ModSecurity is NOT installed");
130
+ sections.push(" Install with:");
131
+ if (webServer === "nginx") {
132
+ sections.push(" sudo apt install libnginx-mod-security");
133
+ }
134
+ else {
135
+ sections.push(" sudo apt install libapache2-mod-security2");
136
+ }
137
+ jsonData.engine_mode = "not_installed";
138
+ if (outputFormat === "json") {
139
+ return { content: [formatToolOutput(jsonData)] };
140
+ }
141
+ return { content: [createTextContent(sections.join("\n"))] };
142
+ }
143
+ }
144
+ else {
145
+ sections.push(" ✅ ModSecurity is installed");
146
+ }
147
+ // Read ModSecurity configuration
148
+ const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
149
+ let configContent = "";
150
+ let activeConfPath = "";
151
+ for (const confPath of confPaths) {
152
+ const catResult = await runSudoCommand("cat", [confPath]);
153
+ if (catResult.exitCode === 0 && catResult.stdout.trim().length > 0) {
154
+ configContent = catResult.stdout;
155
+ activeConfPath = confPath;
156
+ break;
157
+ }
158
+ }
159
+ sections.push(`\n── Configuration ──`);
160
+ if (activeConfPath) {
161
+ sections.push(` Config file: ${activeConfPath}`);
162
+ jsonData.config_path = activeConfPath;
163
+ // Check SecRuleEngine status
164
+ const engineMatch = configContent.match(/^\s*SecRuleEngine\s+(\S+)/m);
165
+ const engineMode = engineMatch ? engineMatch[1] : "unknown";
166
+ jsonData.engine_mode = engineMode;
167
+ if (engineMode === "On") {
168
+ sections.push(" ✅ SecRuleEngine: On (active protection)");
169
+ }
170
+ else if (engineMode === "DetectionOnly") {
171
+ sections.push(" ⚠️ SecRuleEngine: DetectionOnly (logging only, not blocking)");
172
+ }
173
+ else if (engineMode === "Off") {
174
+ sections.push(" ❌ SecRuleEngine: Off (WAF disabled!)");
175
+ }
176
+ else {
177
+ sections.push(` ❓ SecRuleEngine: ${engineMode}`);
178
+ }
179
+ // Check audit logging
180
+ const auditLogMatch = configContent.match(/^\s*SecAuditLog\s+(\S+)/m);
181
+ const auditLog = auditLogMatch ? auditLogMatch[1] : "not configured";
182
+ jsonData.audit_log = auditLog;
183
+ sections.push(` Audit Log: ${auditLog}`);
184
+ const auditEngineMatch = configContent.match(/^\s*SecAuditEngine\s+(\S+)/m);
185
+ const auditEngine = auditEngineMatch ? auditEngineMatch[1] : "not set";
186
+ jsonData.audit_engine = auditEngine;
187
+ sections.push(` Audit Engine: ${auditEngine}`);
188
+ // Count rules
189
+ const ruleCount = (configContent.match(/SecRule\s/g) || []).length;
190
+ jsonData.inline_rule_count = ruleCount;
191
+ sections.push(` Inline Rules: ${ruleCount}`);
192
+ // Check for common misconfigurations
193
+ sections.push(`\n── Misconfiguration Checks ──`);
194
+ const issues = [];
195
+ if (engineMode === "Off") {
196
+ issues.push("SecRuleEngine is Off — WAF provides no protection");
197
+ }
198
+ if (auditEngine === "Off" || auditEngine === "not set") {
199
+ issues.push("Audit logging is disabled — no visibility into blocked requests");
200
+ }
201
+ if (!configContent.includes("SecRequestBodyAccess")) {
202
+ issues.push("SecRequestBodyAccess not configured — POST body inspection may be disabled");
203
+ }
204
+ if (!configContent.includes("SecResponseBodyAccess")) {
205
+ issues.push("SecResponseBodyAccess not configured — response inspection may be disabled");
206
+ }
207
+ if (configContent.includes("SecRuleRemoveById")) {
208
+ const removedCount = (configContent.match(/SecRuleRemoveById/g) || []).length;
209
+ issues.push(`${removedCount} rule(s) disabled via SecRuleRemoveById — review for necessity`);
210
+ }
211
+ jsonData.issues = issues;
212
+ if (issues.length === 0) {
213
+ sections.push(" ✅ No common misconfigurations detected");
214
+ }
215
+ else {
216
+ for (const issue of issues) {
217
+ sections.push(` ⚠️ ${issue}`);
218
+ }
219
+ }
220
+ }
221
+ else {
222
+ sections.push(" ❌ No ModSecurity configuration file found");
223
+ sections.push(" Searched paths:");
224
+ for (const p of confPaths) {
225
+ sections.push(` - ${p}`);
226
+ }
227
+ jsonData.config_path = null;
228
+ jsonData.engine_mode = "no_config";
229
+ }
230
+ if (outputFormat === "json") {
231
+ return { content: [formatToolOutput(jsonData)] };
232
+ }
233
+ return { content: [createTextContent(sections.join("\n"))] };
234
+ }
235
+ catch (err) {
236
+ const msg = err instanceof Error ? err.message : String(err);
237
+ return { content: [createErrorContent(`ModSecurity audit failed: ${msg}`)], isError: true };
238
+ }
239
+ }
240
+ /**
241
+ * Manage ModSecurity rules.
242
+ */
243
+ async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
244
+ try {
245
+ const sections = [];
246
+ const jsonData = {};
247
+ sections.push("📋 ModSecurity Rules Management");
248
+ sections.push("=".repeat(55));
249
+ switch (ruleAction) {
250
+ case "list": {
251
+ sections.push("\n── Loaded Rule Files ──");
252
+ const allFiles = [];
253
+ for (const rulesDir of MODSEC_RULES_DIRS) {
254
+ const lsResult = await runSudoCommand("ls", ["-la", rulesDir]);
255
+ if (lsResult.exitCode === 0) {
256
+ sections.push(`\n Directory: ${rulesDir}`);
257
+ const files = lsResult.stdout
258
+ .split("\n")
259
+ .filter((line) => line.includes(".conf"))
260
+ .map((line) => {
261
+ const parts = line.trim().split(/\s+/);
262
+ return parts[parts.length - 1];
263
+ })
264
+ .filter((f) => f && f.endsWith(".conf"));
265
+ for (const file of files) {
266
+ sections.push(` 📄 ${file}`);
267
+ allFiles.push(`${rulesDir}/${file}`);
268
+ }
269
+ if (files.length === 0) {
270
+ sections.push(" (no .conf rule files found)");
271
+ }
272
+ }
273
+ }
274
+ jsonData.rule_files = allFiles;
275
+ jsonData.total_files = allFiles.length;
276
+ if (allFiles.length === 0) {
277
+ sections.push("\n ❌ No rule files found in standard directories");
278
+ sections.push(" Searched:");
279
+ for (const d of MODSEC_RULES_DIRS) {
280
+ sections.push(` - ${d}`);
281
+ }
282
+ }
283
+ else {
284
+ sections.push(`\n Total rule files: ${allFiles.length}`);
285
+ }
286
+ break;
287
+ }
288
+ case "enable":
289
+ case "disable": {
290
+ if (!ruleId) {
291
+ return {
292
+ content: [createErrorContent("rule_id is required for enable/disable actions")],
293
+ isError: true,
294
+ };
295
+ }
296
+ sections.push(`\n Action: ${ruleAction} rule ${ruleId}`);
297
+ jsonData.rule_id = ruleId;
298
+ jsonData.action = ruleAction;
299
+ // Find the active config file
300
+ const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
301
+ let activeConfPath = "";
302
+ let configContent = "";
303
+ for (const confPath of confPaths) {
304
+ const catResult = await runSudoCommand("cat", [confPath]);
305
+ if (catResult.exitCode === 0 && catResult.stdout.trim().length > 0) {
306
+ configContent = catResult.stdout;
307
+ activeConfPath = confPath;
308
+ break;
309
+ }
310
+ }
311
+ if (!activeConfPath) {
312
+ return {
313
+ content: [createErrorContent("No ModSecurity configuration file found")],
314
+ isError: true,
315
+ };
316
+ }
317
+ const removeDirective = `SecRuleRemoveById ${ruleId}`;
318
+ const hasRemoveDirective = configContent.includes(removeDirective);
319
+ if (ruleAction === "disable") {
320
+ if (hasRemoveDirective) {
321
+ sections.push(` ℹ️ Rule ${ruleId} is already disabled`);
322
+ jsonData.status = "already_disabled";
323
+ }
324
+ else {
325
+ // Append SecRuleRemoveById to config
326
+ const appendResult = await runSudoCommand("sh", [
327
+ "-c",
328
+ `echo '${removeDirective}' >> ${activeConfPath}`,
329
+ ]);
330
+ if (appendResult.exitCode === 0) {
331
+ sections.push(` ✅ Rule ${ruleId} disabled (added ${removeDirective})`);
332
+ sections.push(` Config: ${activeConfPath}`);
333
+ sections.push(` ⚠️ Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
334
+ jsonData.status = "disabled";
335
+ }
336
+ else {
337
+ sections.push(` ❌ Failed to disable rule: ${appendResult.stderr}`);
338
+ jsonData.status = "error";
339
+ jsonData.error = appendResult.stderr;
340
+ }
341
+ }
342
+ }
343
+ else {
344
+ // enable — remove the SecRuleRemoveById directive
345
+ if (!hasRemoveDirective) {
346
+ sections.push(` ℹ️ Rule ${ruleId} is already enabled (no removal directive found)`);
347
+ jsonData.status = "already_enabled";
348
+ }
349
+ else {
350
+ const sedResult = await runSudoCommand("sed", [
351
+ "-i",
352
+ `/${removeDirective}/d`,
353
+ activeConfPath,
354
+ ]);
355
+ if (sedResult.exitCode === 0) {
356
+ sections.push(` ✅ Rule ${ruleId} enabled (removed ${removeDirective})`);
357
+ sections.push(` Config: ${activeConfPath}`);
358
+ sections.push(` ⚠️ Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
359
+ jsonData.status = "enabled";
360
+ }
361
+ else {
362
+ sections.push(` ❌ Failed to enable rule: ${sedResult.stderr}`);
363
+ jsonData.status = "error";
364
+ jsonData.error = sedResult.stderr;
365
+ }
366
+ }
367
+ }
368
+ break;
369
+ }
370
+ default:
371
+ return {
372
+ content: [createErrorContent(`Unknown rule_action: ${ruleAction}. Use list, enable, or disable.`)],
373
+ isError: true,
374
+ };
375
+ }
376
+ if (outputFormat === "json") {
377
+ return { content: [formatToolOutput(jsonData)] };
378
+ }
379
+ return { content: [createTextContent(sections.join("\n"))] };
380
+ }
381
+ catch (err) {
382
+ const msg = err instanceof Error ? err.message : String(err);
383
+ return { content: [createErrorContent(`ModSecurity rules management failed: ${msg}`)], isError: true };
384
+ }
385
+ }
386
+ /**
387
+ * Configure rate limiting at the web server level.
388
+ */
389
+ async function handleRateLimitConfig(webServer, rateLimit, rateLimitZone, outputFormat) {
390
+ try {
391
+ const sections = [];
392
+ const jsonData = {};
393
+ sections.push("⚡ Rate Limiting Configuration");
394
+ sections.push("=".repeat(55));
395
+ sections.push(`Web Server: ${webServer}`);
396
+ jsonData.web_server = webServer;
397
+ if (webServer === "nginx") {
398
+ // Check current nginx rate limiting configuration
399
+ const nginxConf = await runSudoCommand("cat", ["/etc/nginx/nginx.conf"]);
400
+ const sitesResult = await runSudoCommand("ls", ["/etc/nginx/sites-enabled/"]);
401
+ sections.push("\n── Current Rate Limiting (nginx) ──");
402
+ if (nginxConf.exitCode === 0) {
403
+ const content = nginxConf.stdout;
404
+ // Find limit_req_zone directives
405
+ const zoneMatches = content.match(/limit_req_zone\s+[^;]+;/g) || [];
406
+ const reqMatches = content.match(/limit_req\s+[^;]+;/g) || [];
407
+ jsonData.limit_req_zones = zoneMatches.map((z) => z.trim());
408
+ jsonData.limit_req_directives = reqMatches.map((r) => r.trim());
409
+ if (zoneMatches.length > 0) {
410
+ sections.push(" Rate limit zones defined:");
411
+ for (const zone of zoneMatches) {
412
+ sections.push(` 📊 ${zone.trim()}`);
413
+ }
414
+ }
415
+ else {
416
+ sections.push(" ❌ No limit_req_zone directives found");
417
+ }
418
+ if (reqMatches.length > 0) {
419
+ sections.push(" Rate limit enforcement:");
420
+ for (const req of reqMatches) {
421
+ sections.push(` 🔒 ${req.trim()}`);
422
+ }
423
+ }
424
+ else {
425
+ sections.push(" ❌ No limit_req directives found");
426
+ }
427
+ }
428
+ else {
429
+ sections.push(" ❌ Could not read /etc/nginx/nginx.conf");
430
+ jsonData.error = "Could not read nginx config";
431
+ }
432
+ // Suggest configuration
433
+ sections.push("\n── Recommended Configuration ──");
434
+ const effectiveRate = rateLimit ?? 10;
435
+ const effectiveZone = rateLimitZone ?? "default";
436
+ sections.push(" Add to http {} block in nginx.conf:");
437
+ sections.push(` limit_req_zone $binary_remote_addr zone=${effectiveZone}:10m rate=${effectiveRate}r/s;`);
438
+ sections.push(" Add to server {} or location {} block:");
439
+ sections.push(` limit_req zone=${effectiveZone} burst=${effectiveRate * 2} nodelay;`);
440
+ sections.push(" limit_req_status 429;");
441
+ jsonData.suggested_rate = effectiveRate;
442
+ jsonData.suggested_zone = effectiveZone;
443
+ }
444
+ else {
445
+ // Apache rate limiting
446
+ sections.push("\n── Current Rate Limiting (Apache) ──");
447
+ // Check for mod_ratelimit
448
+ const ratelimitCheck = await runCommand("apache2ctl", ["-M"]);
449
+ let hasRatelimit = false;
450
+ let hasEvasive = false;
451
+ if (ratelimitCheck.exitCode === 0) {
452
+ hasRatelimit = ratelimitCheck.stdout.includes("ratelimit_module");
453
+ hasEvasive = ratelimitCheck.stdout.includes("evasive");
454
+ }
455
+ jsonData.mod_ratelimit = hasRatelimit;
456
+ jsonData.mod_evasive = hasEvasive;
457
+ sections.push(` mod_ratelimit: ${hasRatelimit ? "✅ loaded" : "❌ not loaded"}`);
458
+ sections.push(` mod_evasive: ${hasEvasive ? "✅ loaded" : "❌ not loaded"}`);
459
+ if (hasEvasive) {
460
+ const evasiveConf = await runSudoCommand("cat", ["/etc/apache2/mods-enabled/evasive.conf"]);
461
+ if (evasiveConf.exitCode === 0) {
462
+ sections.push("\n mod_evasive configuration:");
463
+ for (const line of evasiveConf.stdout.split("\n")) {
464
+ const trimmed = line.trim();
465
+ if (trimmed && !trimmed.startsWith("#")) {
466
+ sections.push(` ${trimmed}`);
467
+ }
468
+ }
469
+ }
470
+ }
471
+ sections.push("\n── Recommended Configuration ──");
472
+ const effectiveRate = rateLimit ?? 10;
473
+ if (!hasEvasive) {
474
+ sections.push(" Install mod_evasive:");
475
+ sections.push(" sudo apt install libapache2-mod-evasive");
476
+ sections.push(" sudo a2enmod evasive");
477
+ }
478
+ sections.push(" Recommended /etc/apache2/mods-enabled/evasive.conf:");
479
+ sections.push(" <IfModule mod_evasive20.c>");
480
+ sections.push(` DOSPageCount ${effectiveRate}`);
481
+ sections.push(` DOSSiteCount ${effectiveRate * 5}`);
482
+ sections.push(" DOSPageInterval 1");
483
+ sections.push(" DOSSiteInterval 1");
484
+ sections.push(" DOSBlockingPeriod 60");
485
+ sections.push(" </IfModule>");
486
+ jsonData.suggested_rate = effectiveRate;
487
+ }
488
+ if (outputFormat === "json") {
489
+ return { content: [formatToolOutput(jsonData)] };
490
+ }
491
+ return { content: [createTextContent(sections.join("\n"))] };
492
+ }
493
+ catch (err) {
494
+ const msg = err instanceof Error ? err.message : String(err);
495
+ return { content: [createErrorContent(`Rate limit configuration failed: ${msg}`)], isError: true };
496
+ }
497
+ }
498
+ /**
499
+ * Check OWASP Core Rule Set deployment status.
500
+ */
501
+ async function handleOwaspCrsDeploy(webServer, outputFormat) {
502
+ try {
503
+ const sections = [];
504
+ const jsonData = {};
505
+ sections.push("🌐 OWASP Core Rule Set (CRS) Status");
506
+ sections.push("=".repeat(55));
507
+ // Check if CRS is installed
508
+ let crsPath = "";
509
+ for (const path of OWASP_CRS_PATHS) {
510
+ const testResult = await runCommand("test", ["-d", path]);
511
+ if (testResult.exitCode === 0) {
512
+ crsPath = path;
513
+ break;
514
+ }
515
+ }
516
+ jsonData.installed = !!crsPath;
517
+ jsonData.crs_path = crsPath || null;
518
+ if (!crsPath) {
519
+ sections.push("\n ❌ OWASP CRS is NOT installed");
520
+ sections.push("\n── Installation Instructions ──");
521
+ sections.push(" Option 1 — Package manager:");
522
+ sections.push(" sudo apt install modsecurity-crs");
523
+ sections.push(" Option 2 — Git (latest version):");
524
+ sections.push(" sudo git clone https://github.com/coreruleset/coreruleset.git /usr/share/modsecurity-crs");
525
+ sections.push(" sudo cp /usr/share/modsecurity-crs/crs-setup.conf.example /usr/share/modsecurity-crs/crs-setup.conf");
526
+ sections.push("\n After installation, add to ModSecurity config:");
527
+ sections.push(" Include /usr/share/modsecurity-crs/crs-setup.conf");
528
+ sections.push(" Include /usr/share/modsecurity-crs/rules/*.conf");
529
+ if (outputFormat === "json") {
530
+ return { content: [formatToolOutput(jsonData)] };
531
+ }
532
+ return { content: [createTextContent(sections.join("\n"))] };
533
+ }
534
+ sections.push(`\n ✅ CRS found at: ${crsPath}`);
535
+ // Check version
536
+ const changelogResult = await runCommand("head", ["-5", `${crsPath}/CHANGES`]);
537
+ const versionFileResult = await runCommand("cat", [`${crsPath}/VERSION`]);
538
+ let version = "unknown";
539
+ if (versionFileResult.exitCode === 0 && versionFileResult.stdout.trim()) {
540
+ version = versionFileResult.stdout.trim();
541
+ }
542
+ else if (changelogResult.exitCode === 0) {
543
+ const versionMatch = changelogResult.stdout.match(/(\d+\.\d+\.\d+)/);
544
+ if (versionMatch)
545
+ version = versionMatch[1];
546
+ }
547
+ jsonData.version = version;
548
+ sections.push(` Version: ${version}`);
549
+ // Check CRS setup file for paranoia level
550
+ const setupConf = await runSudoCommand("cat", [`${crsPath}/crs-setup.conf`]);
551
+ let paranoiaLevel = "1 (default)";
552
+ if (setupConf.exitCode === 0) {
553
+ const plMatch = setupConf.stdout.match(/^\s*SecAction\s.*setvar:'tx\.paranoia_level=(\d+)'/m);
554
+ if (plMatch) {
555
+ paranoiaLevel = plMatch[1];
556
+ }
557
+ }
558
+ jsonData.paranoia_level = paranoiaLevel;
559
+ sections.push(` Paranoia Level: ${paranoiaLevel}`);
560
+ // Check integration with ModSecurity
561
+ sections.push("\n── Integration Check ──");
562
+ const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
563
+ let integrated = false;
564
+ for (const confPath of confPaths) {
565
+ const confContent = await runSudoCommand("cat", [confPath]);
566
+ if (confContent.exitCode === 0) {
567
+ if (confContent.stdout.includes("modsecurity-crs") || confContent.stdout.includes("crs-setup")) {
568
+ integrated = true;
569
+ sections.push(` ✅ CRS Include directives found in ${confPath}`);
570
+ break;
571
+ }
572
+ }
573
+ }
574
+ // Also check for include in main nginx/apache conf
575
+ if (!integrated) {
576
+ const mainConf = webServer === "nginx"
577
+ ? await runSudoCommand("cat", ["/etc/nginx/nginx.conf"])
578
+ : await runSudoCommand("cat", ["/etc/apache2/apache2.conf"]);
579
+ if (mainConf.exitCode === 0 && (mainConf.stdout.includes("modsecurity-crs") || mainConf.stdout.includes("crs-setup"))) {
580
+ integrated = true;
581
+ sections.push(" ✅ CRS Include directives found in main config");
582
+ }
583
+ }
584
+ jsonData.integrated = integrated;
585
+ if (!integrated) {
586
+ sections.push(" ❌ CRS Include directives NOT found in ModSecurity configuration");
587
+ sections.push(" Add these lines to your ModSecurity configuration:");
588
+ sections.push(` Include ${crsPath}/crs-setup.conf`);
589
+ sections.push(` Include ${crsPath}/rules/*.conf`);
590
+ }
591
+ // List active rule categories
592
+ sections.push("\n── Active Rule Categories ──");
593
+ const rulesDir = `${crsPath}/rules`;
594
+ const rulesList = await runCommand("ls", [rulesDir]);
595
+ const categories = [];
596
+ if (rulesList.exitCode === 0) {
597
+ const ruleFiles = rulesList.stdout
598
+ .split("\n")
599
+ .filter((f) => f.endsWith(".conf"))
600
+ .sort();
601
+ for (const file of ruleFiles) {
602
+ categories.push(file);
603
+ sections.push(` 📄 ${file}`);
604
+ }
605
+ }
606
+ jsonData.rule_categories = categories;
607
+ jsonData.total_categories = categories.length;
608
+ sections.push(`\n Total rule files: ${categories.length}`);
609
+ if (outputFormat === "json") {
610
+ return { content: [formatToolOutput(jsonData)] };
611
+ }
612
+ return { content: [createTextContent(sections.join("\n"))] };
613
+ }
614
+ catch (err) {
615
+ const msg = err instanceof Error ? err.message : String(err);
616
+ return { content: [createErrorContent(`OWASP CRS check failed: ${msg}`)], isError: true };
617
+ }
618
+ }
619
+ /**
620
+ * Analyze WAF logs for blocked requests.
621
+ */
622
+ async function handleBlockedRequests(logPath, outputFormat) {
623
+ try {
624
+ const sections = [];
625
+ const jsonData = {};
626
+ sections.push("🚫 WAF Blocked Requests Analysis");
627
+ sections.push("=".repeat(55));
628
+ // Find the log file
629
+ let activeLogPath = logPath;
630
+ if (!activeLogPath) {
631
+ for (const path of MODSEC_AUDIT_LOG_PATHS) {
632
+ const testResult = await runCommand("test", ["-f", path]);
633
+ if (testResult.exitCode === 0) {
634
+ activeLogPath = path;
635
+ break;
636
+ }
637
+ }
638
+ }
639
+ if (!activeLogPath) {
640
+ sections.push("\n ❌ No ModSecurity audit log found");
641
+ sections.push(" Searched paths:");
642
+ for (const p of MODSEC_AUDIT_LOG_PATHS) {
643
+ sections.push(` - ${p}`);
644
+ }
645
+ sections.push("\n Ensure SecAuditLog is configured in modsecurity.conf");
646
+ jsonData.log_found = false;
647
+ if (outputFormat === "json") {
648
+ return { content: [formatToolOutput(jsonData)] };
649
+ }
650
+ return { content: [createTextContent(sections.join("\n"))] };
651
+ }
652
+ jsonData.log_path = activeLogPath;
653
+ jsonData.log_found = true;
654
+ sections.push(` Log file: ${activeLogPath}`);
655
+ // Get log file size
656
+ const statResult = await runCommand("stat", ["--format=%s", activeLogPath]);
657
+ if (statResult.exitCode === 0) {
658
+ const sizeBytes = parseInt(statResult.stdout.trim(), 10);
659
+ const sizeMB = (sizeBytes / 1024 / 1024).toFixed(2);
660
+ sections.push(` Log size: ${sizeMB} MB`);
661
+ jsonData.log_size_bytes = sizeBytes;
662
+ }
663
+ // Analyze blocked IPs (top 10)
664
+ sections.push("\n── Top Blocked IPs ──");
665
+ const ipResult = await runSudoCommand("grep", ["-oP", "\\d+\\.\\d+\\.\\d+\\.\\d+", activeLogPath]);
666
+ if (ipResult.exitCode === 0 && ipResult.stdout.trim()) {
667
+ // Count IP occurrences manually
668
+ const ipCounts = {};
669
+ for (const ip of ipResult.stdout.trim().split("\n")) {
670
+ const trimmedIp = ip.trim();
671
+ if (trimmedIp) {
672
+ ipCounts[trimmedIp] = (ipCounts[trimmedIp] || 0) + 1;
673
+ }
674
+ }
675
+ const sortedIps = Object.entries(ipCounts)
676
+ .sort((a, b) => b[1] - a[1])
677
+ .slice(0, 10);
678
+ jsonData.top_blocked_ips = sortedIps.map(([ip, count]) => ({ ip, count }));
679
+ for (const [ip, count] of sortedIps) {
680
+ sections.push(` ${String(count).padStart(6)} │ ${ip}`);
681
+ }
682
+ }
683
+ else {
684
+ sections.push(" No blocked IPs found");
685
+ jsonData.top_blocked_ips = [];
686
+ }
687
+ // Analyze triggered rules (top 10)
688
+ sections.push("\n── Top Triggered Rules ──");
689
+ const ruleResult = await runSudoCommand("grep", ["-oP", 'id "\\d+"', activeLogPath]);
690
+ if (ruleResult.exitCode === 0 && ruleResult.stdout.trim()) {
691
+ const ruleCounts = {};
692
+ for (const match of ruleResult.stdout.trim().split("\n")) {
693
+ const ruleIdMatch = match.match(/\d+/);
694
+ if (ruleIdMatch) {
695
+ const rid = ruleIdMatch[0];
696
+ ruleCounts[rid] = (ruleCounts[rid] || 0) + 1;
697
+ }
698
+ }
699
+ const sortedRules = Object.entries(ruleCounts)
700
+ .sort((a, b) => b[1] - a[1])
701
+ .slice(0, 10);
702
+ jsonData.top_triggered_rules = sortedRules.map(([ruleId, count]) => ({ rule_id: ruleId, count }));
703
+ for (const [ruleId, count] of sortedRules) {
704
+ sections.push(` ${String(count).padStart(6)} │ Rule ${ruleId}`);
705
+ }
706
+ // Identify false positive candidates (rules triggered > 100 times)
707
+ sections.push("\n── Possible False Positives ──");
708
+ const fpCandidates = sortedRules.filter(([, count]) => count > 100);
709
+ jsonData.false_positive_candidates = fpCandidates.map(([ruleId, count]) => ({ rule_id: ruleId, count }));
710
+ if (fpCandidates.length > 0) {
711
+ sections.push(" Rules triggered excessively (may be false positives):");
712
+ for (const [ruleId, count] of fpCandidates) {
713
+ sections.push(` ⚠️ Rule ${ruleId}: ${count} hits — review for tuning`);
714
+ }
715
+ }
716
+ else {
717
+ sections.push(" No obvious false positive candidates detected");
718
+ }
719
+ }
720
+ else {
721
+ sections.push(" No triggered rules found");
722
+ jsonData.top_triggered_rules = [];
723
+ jsonData.false_positive_candidates = [];
724
+ }
725
+ // Attack categories
726
+ sections.push("\n── Attack Categories ──");
727
+ const categories = {};
728
+ const categoryPatterns = {
729
+ "SQL Injection": "sql|sqli|SQL",
730
+ "XSS": "xss|cross-site scripting",
731
+ "Path Traversal": "traversal|path-traversal|directory",
732
+ "Remote File Inclusion": "rfi|remote file",
733
+ "Local File Inclusion": "lfi|local file",
734
+ "Command Injection": "command injection|cmd|rce",
735
+ "Protocol Violation": "protocol|violation|request-",
736
+ "Scanner Detection": "scanner|nikto|nmap|acunetix",
737
+ };
738
+ for (const [category, pattern] of Object.entries(categoryPatterns)) {
739
+ const grepResult = await runSudoCommand("grep", ["-ciP", pattern, activeLogPath]);
740
+ if (grepResult.exitCode === 0) {
741
+ const count = parseInt(grepResult.stdout.trim(), 10);
742
+ if (count > 0) {
743
+ categories[category] = count;
744
+ }
745
+ }
746
+ }
747
+ jsonData.attack_categories = categories;
748
+ const sortedCategories = Object.entries(categories).sort((a, b) => b[1] - a[1]);
749
+ if (sortedCategories.length > 0) {
750
+ for (const [category, count] of sortedCategories) {
751
+ sections.push(` ${String(count).padStart(6)} │ ${category}`);
752
+ }
753
+ }
754
+ else {
755
+ sections.push(" No categorized attacks found");
756
+ }
757
+ // Timeline (recent blocks count by hour — last 24h)
758
+ sections.push("\n── Recent Activity (last 24 lines) ──");
759
+ const tailResult = await runSudoCommand("tail", ["-24", activeLogPath]);
760
+ if (tailResult.exitCode === 0 && tailResult.stdout.trim()) {
761
+ const recentLines = tailResult.stdout.trim().split("\n").slice(0, 10);
762
+ for (const line of recentLines) {
763
+ sections.push(` ${line.substring(0, 120)}`);
764
+ }
765
+ if (tailResult.stdout.trim().split("\n").length > 10) {
766
+ sections.push(" ...");
767
+ }
768
+ }
769
+ else {
770
+ sections.push(" No recent entries");
771
+ }
772
+ if (outputFormat === "json") {
773
+ return { content: [formatToolOutput(jsonData)] };
774
+ }
775
+ return { content: [createTextContent(sections.join("\n"))] };
776
+ }
777
+ catch (err) {
778
+ const msg = err instanceof Error ? err.message : String(err);
779
+ return { content: [createErrorContent(`Blocked requests analysis failed: ${msg}`)], isError: true };
780
+ }
781
+ }
782
+ // ── Registration entry point ───────────────────────────────────────────────
783
+ export function registerWafTools(server) {
784
+ server.tool("waf_manage", "Web Application Firewall management: audit ModSecurity, manage rules, configure rate limiting, deploy OWASP CRS, analyze blocked requests.", {
785
+ action: z
786
+ .enum(["modsec_audit", "modsec_rules", "rate_limit_config", "owasp_crs_deploy", "blocked_requests"])
787
+ .describe("Action: modsec_audit=audit WAF config, modsec_rules=manage rules, rate_limit_config=rate limiting, owasp_crs_deploy=CRS status, blocked_requests=log analysis"),
788
+ web_server: z
789
+ .enum(["nginx", "apache"])
790
+ .optional()
791
+ .default("nginx")
792
+ .describe("Web server type (default: nginx)"),
793
+ rule_id: z
794
+ .string()
795
+ .optional()
796
+ .describe("ModSecurity rule ID (used with modsec_rules action)"),
797
+ rule_action: z
798
+ .enum(["enable", "disable", "list"])
799
+ .optional()
800
+ .default("list")
801
+ .describe("Rule action: enable, disable, or list (used with modsec_rules)"),
802
+ rate_limit: z
803
+ .number()
804
+ .optional()
805
+ .describe("Requests per second for rate limiting (used with rate_limit_config)"),
806
+ rate_limit_zone: z
807
+ .string()
808
+ .optional()
809
+ .describe("Zone name for rate limiting (used with rate_limit_config)"),
810
+ log_path: z
811
+ .string()
812
+ .optional()
813
+ .describe("Path to WAF log file (used with blocked_requests)"),
814
+ output_format: z
815
+ .enum(["text", "json"])
816
+ .optional()
817
+ .default("text")
818
+ .describe("Output format: text or json (default: text)"),
819
+ }, async (params) => {
820
+ const { action } = params;
821
+ const webServer = params.web_server ?? "nginx";
822
+ const outputFormat = params.output_format ?? "text";
823
+ switch (action) {
824
+ case "modsec_audit":
825
+ return handleModsecAudit(webServer, outputFormat);
826
+ case "modsec_rules": {
827
+ const ruleAction = params.rule_action ?? "list";
828
+ return handleModsecRules(ruleAction, params.rule_id, webServer, outputFormat);
829
+ }
830
+ case "rate_limit_config":
831
+ return handleRateLimitConfig(webServer, params.rate_limit, params.rate_limit_zone, outputFormat);
832
+ case "owasp_crs_deploy":
833
+ return handleOwaspCrsDeploy(webServer, outputFormat);
834
+ case "blocked_requests":
835
+ return handleBlockedRequests(params.log_path, outputFormat);
836
+ default:
837
+ return {
838
+ content: [createErrorContent(`Unknown action: ${action}`)],
839
+ isError: true,
840
+ };
841
+ }
842
+ });
843
+ }