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,826 @@
1
+ /**
2
+ * Wireless security tools for Kali Defense MCP Server.
3
+ *
4
+ * Registers 1 tool: wireless_security (actions: bt_audit, wifi_audit,
5
+ * rogue_ap_detect, disable_unused)
6
+ *
7
+ * Provides Bluetooth adapter auditing, WiFi configuration assessment,
8
+ * rogue access point detection with evil twin analysis, and unused
9
+ * wireless interface disabling with kernel module blacklist recommendations.
10
+ */
11
+ import { z } from "zod";
12
+ import { spawnSafe } from "../core/spawn-safe.js";
13
+ import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ // ── Constants ──────────────────────────────────────────────────────────────────
16
+ /** Path to known APs configuration file */
17
+ const KNOWN_APS_PATH = "/var/lib/kali-defense/wireless/known-aps.json";
18
+ /** Wireless kernel modules that can be blacklisted */
19
+ const WIRELESS_MODULES = ["bluetooth", "btusb", "iwlwifi", "ath9k", "ath10k_pci", "rt2800usb"];
20
+ /**
21
+ * Run a command via spawnSafe and collect output as a promise.
22
+ * Handles errors gracefully — returns error info instead of throwing.
23
+ */
24
+ async function runCommand(command, args, timeoutMs = 30_000) {
25
+ return new Promise((resolve) => {
26
+ let child;
27
+ try {
28
+ child = spawnSafe(command, args);
29
+ }
30
+ catch (err) {
31
+ const msg = err instanceof Error ? err.message : String(err);
32
+ resolve({ stdout: "", stderr: msg, exitCode: -1 });
33
+ return;
34
+ }
35
+ let stdout = "";
36
+ let stderr = "";
37
+ let resolved = false;
38
+ const timer = setTimeout(() => {
39
+ if (!resolved) {
40
+ resolved = true;
41
+ child.kill("SIGTERM");
42
+ resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
43
+ }
44
+ }, timeoutMs);
45
+ child.stdout?.on("data", (data) => {
46
+ stdout += data.toString();
47
+ });
48
+ child.stderr?.on("data", (data) => {
49
+ stderr += data.toString();
50
+ });
51
+ child.on("close", (code) => {
52
+ if (!resolved) {
53
+ resolved = true;
54
+ clearTimeout(timer);
55
+ resolve({ stdout, stderr, exitCode: code ?? -1 });
56
+ }
57
+ });
58
+ child.on("error", (err) => {
59
+ if (!resolved) {
60
+ resolved = true;
61
+ clearTimeout(timer);
62
+ resolve({ stdout, stderr: err.message, exitCode: -1 });
63
+ }
64
+ });
65
+ });
66
+ }
67
+ /**
68
+ * Load known APs from the configuration file.
69
+ * Returns empty array if file doesn't exist or is invalid.
70
+ */
71
+ function loadKnownAps() {
72
+ try {
73
+ if (existsSync(KNOWN_APS_PATH)) {
74
+ const data = readFileSync(KNOWN_APS_PATH, "utf-8");
75
+ const parsed = JSON.parse(data);
76
+ if (Array.isArray(parsed))
77
+ return parsed;
78
+ if (parsed && Array.isArray(parsed.aps))
79
+ return parsed.aps;
80
+ }
81
+ }
82
+ catch {
83
+ // Fall through to default
84
+ }
85
+ return [];
86
+ }
87
+ /**
88
+ * Check if two SSIDs are similar enough to be an evil twin.
89
+ * Considers: exact match, case differences, character substitutions,
90
+ * appended/prepended characters.
91
+ */
92
+ export function isEvilTwin(knownSsid, candidateSsid) {
93
+ if (knownSsid === candidateSsid)
94
+ return false; // same SSID is not evil twin
95
+ const kLower = knownSsid.toLowerCase();
96
+ const cLower = candidateSsid.toLowerCase();
97
+ // Case-insensitive exact match
98
+ if (kLower === cLower)
99
+ return true;
100
+ // One is a substring of the other with minor additions
101
+ if (cLower.includes(kLower) || kLower.includes(cLower)) {
102
+ const lenDiff = Math.abs(kLower.length - cLower.length);
103
+ if (lenDiff <= 3)
104
+ return true;
105
+ }
106
+ // Levenshtein distance <= 2 for short SSIDs
107
+ if (kLower.length <= 20 && cLower.length <= 20) {
108
+ const dist = levenshteinDistance(kLower, cLower);
109
+ if (dist <= 2)
110
+ return true;
111
+ }
112
+ // Common substitutions (0 for O, 1 for l, etc.)
113
+ const normalized = cLower
114
+ .replace(/0/g, "o")
115
+ .replace(/1/g, "l")
116
+ .replace(/3/g, "e")
117
+ .replace(/5/g, "s");
118
+ const knownNormalized = kLower
119
+ .replace(/0/g, "o")
120
+ .replace(/1/g, "l")
121
+ .replace(/3/g, "e")
122
+ .replace(/5/g, "s");
123
+ if (normalized === knownNormalized && kLower !== cLower)
124
+ return true;
125
+ return false;
126
+ }
127
+ /**
128
+ * Simple Levenshtein distance implementation.
129
+ */
130
+ function levenshteinDistance(a, b) {
131
+ const matrix = [];
132
+ for (let i = 0; i <= a.length; i++) {
133
+ matrix[i] = [i];
134
+ }
135
+ for (let j = 0; j <= b.length; j++) {
136
+ matrix[0][j] = j;
137
+ }
138
+ for (let i = 1; i <= a.length; i++) {
139
+ for (let j = 1; j <= b.length; j++) {
140
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
141
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
142
+ }
143
+ }
144
+ return matrix[a.length][b.length];
145
+ }
146
+ async function btAudit() {
147
+ const result = {
148
+ adapterFound: false,
149
+ adapterStatus: "not found",
150
+ powered: false,
151
+ discoverable: false,
152
+ pairedDevices: [],
153
+ pairedDevicesCount: 0,
154
+ serviceRunning: false,
155
+ serviceStatus: "unknown",
156
+ riskLevel: "INFO",
157
+ recommendations: [],
158
+ };
159
+ // Check if Bluetooth adapter exists via hciconfig
160
+ const hciResult = await runCommand("hciconfig", ["-a"], 10_000);
161
+ if (hciResult.exitCode === 0 && hciResult.stdout.trim().length > 0) {
162
+ result.adapterFound = true;
163
+ result.adapterStatus = hciResult.stdout.trim().includes("UP RUNNING")
164
+ ? "up and running"
165
+ : hciResult.stdout.trim().includes("DOWN")
166
+ ? "down"
167
+ : "present";
168
+ result.powered = hciResult.stdout.includes("UP RUNNING");
169
+ }
170
+ else {
171
+ // Try bluetoothctl as fallback
172
+ const btctlResult = await runCommand("bluetoothctl", ["show"], 10_000);
173
+ if (btctlResult.exitCode === 0 && btctlResult.stdout.trim().length > 0) {
174
+ result.adapterFound = true;
175
+ result.powered = btctlResult.stdout.includes("Powered: yes");
176
+ result.adapterStatus = result.powered ? "powered on" : "powered off";
177
+ result.discoverable = btctlResult.stdout.includes("Discoverable: yes");
178
+ }
179
+ else if (hciResult.stderr.includes("not found") ||
180
+ hciResult.stderr.includes("No such file") ||
181
+ btctlResult.stderr.includes("not found")) {
182
+ // No bluetooth tools installed
183
+ result.adapterStatus = "no bluetooth tools installed";
184
+ result.riskLevel = "INFO";
185
+ result.recommendations.push("Bluetooth tools not installed — no Bluetooth audit possible");
186
+ return result;
187
+ }
188
+ }
189
+ if (!result.adapterFound) {
190
+ result.adapterStatus = "no adapter found";
191
+ result.riskLevel = "LOW";
192
+ result.recommendations.push("No Bluetooth adapter detected — low risk");
193
+ return result;
194
+ }
195
+ // Check discoverability via bluetoothctl if not already checked
196
+ if (!result.discoverable) {
197
+ const discoverResult = await runCommand("bluetoothctl", ["show"], 10_000);
198
+ if (discoverResult.exitCode === 0) {
199
+ result.discoverable = discoverResult.stdout.includes("Discoverable: yes");
200
+ }
201
+ }
202
+ // List paired devices
203
+ const pairedResult = await runCommand("bluetoothctl", ["paired-devices"], 10_000);
204
+ if (pairedResult.exitCode === 0 && pairedResult.stdout.trim().length > 0) {
205
+ result.pairedDevices = pairedResult.stdout
206
+ .trim()
207
+ .split("\n")
208
+ .filter((l) => l.trim().length > 0);
209
+ result.pairedDevicesCount = result.pairedDevices.length;
210
+ }
211
+ // Check Bluetooth service status
212
+ const serviceResult = await runCommand("systemctl", ["status", "bluetooth"], 10_000);
213
+ if (serviceResult.exitCode === 0 || serviceResult.exitCode === 3) {
214
+ result.serviceStatus = serviceResult.stdout.trim();
215
+ result.serviceRunning = serviceResult.stdout.includes("active (running)");
216
+ }
217
+ // Determine risk level and recommendations
218
+ if (result.powered) {
219
+ result.riskLevel = "MEDIUM";
220
+ result.recommendations.push("Bluetooth is enabled — disable if not needed (especially on servers)");
221
+ }
222
+ if (result.discoverable) {
223
+ result.riskLevel = "HIGH";
224
+ result.recommendations.push("CRITICAL: Bluetooth is discoverable — disable discoverability immediately");
225
+ }
226
+ if (result.pairedDevicesCount > 0) {
227
+ result.recommendations.push(`${result.pairedDevicesCount} paired device(s) found — review and remove unnecessary pairings`);
228
+ }
229
+ if (result.serviceRunning) {
230
+ result.recommendations.push("Bluetooth service is running — consider disabling: systemctl disable --now bluetooth");
231
+ }
232
+ if (!result.powered && !result.serviceRunning) {
233
+ result.riskLevel = "LOW";
234
+ }
235
+ return result;
236
+ }
237
+ async function wifiAudit(iface) {
238
+ const result = {
239
+ interfacesFound: [],
240
+ interfaceCount: 0,
241
+ activeConnection: "none",
242
+ securityType: "unknown",
243
+ savedNetworks: [],
244
+ savedNetworkCount: 0,
245
+ wifiNeeded: false,
246
+ riskLevel: "INFO",
247
+ recommendations: [],
248
+ };
249
+ // List wireless interfaces via iw dev
250
+ const iwResult = await runCommand("iw", ["dev"], 10_000);
251
+ if (iwResult.exitCode === 0 && iwResult.stdout.trim().length > 0) {
252
+ const ifaceMatches = iwResult.stdout.match(/Interface\s+(\S+)/g);
253
+ if (ifaceMatches) {
254
+ result.interfacesFound = ifaceMatches.map((m) => m.replace("Interface ", "").trim());
255
+ }
256
+ }
257
+ else {
258
+ // Fallback to iwconfig
259
+ const iwconfigResult = await runCommand("iwconfig", [], 10_000);
260
+ if (iwconfigResult.exitCode === 0) {
261
+ const lines = iwconfigResult.stdout.split("\n");
262
+ for (const line of lines) {
263
+ if (line.includes("IEEE 802.11") || line.includes("ESSID")) {
264
+ const match = line.match(/^(\S+)/);
265
+ if (match)
266
+ result.interfacesFound.push(match[1]);
267
+ }
268
+ }
269
+ }
270
+ else if (iwResult.stderr.includes("not found") &&
271
+ iwconfigResult.stderr.includes("not found")) {
272
+ result.recommendations.push("No wireless tools installed (iw, iwconfig) — cannot audit WiFi");
273
+ return result;
274
+ }
275
+ }
276
+ result.interfaceCount = result.interfacesFound.length;
277
+ if (result.interfaceCount === 0) {
278
+ result.riskLevel = "LOW";
279
+ result.recommendations.push("No wireless interfaces found — low risk");
280
+ return result;
281
+ }
282
+ // Check active connection via nmcli
283
+ const nmcliActiveResult = await runCommand("nmcli", ["connection", "show", "--active"], 10_000);
284
+ if (nmcliActiveResult.exitCode === 0 && nmcliActiveResult.stdout.trim().length > 0) {
285
+ const lines = nmcliActiveResult.stdout.trim().split("\n");
286
+ for (const line of lines) {
287
+ if (line.includes("wifi") || line.includes("wireless")) {
288
+ result.activeConnection = line.trim();
289
+ result.wifiNeeded = true;
290
+ }
291
+ }
292
+ }
293
+ // Check WiFi security type
294
+ if (result.activeConnection !== "none") {
295
+ const nmcliDetailResult = await runCommand("nmcli", ["-t", "-f", "NAME,TYPE,DEVICE,802-11-wireless-security.key-mgmt", "connection", "show", "--active"], 10_000);
296
+ if (nmcliDetailResult.exitCode === 0) {
297
+ const output = nmcliDetailResult.stdout;
298
+ if (output.includes("wpa-psk") || output.includes("wpa-eap")) {
299
+ result.securityType = "WPA2/WPA3";
300
+ }
301
+ else if (output.includes("wep")) {
302
+ result.securityType = "WEP";
303
+ result.riskLevel = "HIGH";
304
+ result.recommendations.push("CRITICAL: Using WEP encryption — upgrade to WPA2/WPA3 immediately");
305
+ }
306
+ else if (output.includes("sae")) {
307
+ result.securityType = "WPA3-SAE";
308
+ }
309
+ else if (output.includes("none") || output.includes("open")) {
310
+ result.securityType = "Open/None";
311
+ result.riskLevel = "HIGH";
312
+ result.recommendations.push("CRITICAL: Connected to an open network with no encryption");
313
+ }
314
+ else {
315
+ result.securityType = "WPA2/WPA3";
316
+ }
317
+ }
318
+ }
319
+ // Check saved networks
320
+ const savedResult = await runCommand("nmcli", ["connection", "show"], 10_000);
321
+ if (savedResult.exitCode === 0 && savedResult.stdout.trim().length > 0) {
322
+ const lines = savedResult.stdout.trim().split("\n").slice(1); // skip header
323
+ for (const line of lines) {
324
+ if (line.includes("wifi") || line.includes("wireless")) {
325
+ result.savedNetworks.push(line.trim());
326
+ }
327
+ }
328
+ result.savedNetworkCount = result.savedNetworks.length;
329
+ }
330
+ // Recommendations
331
+ if (result.interfaceCount > 0 && !result.wifiNeeded) {
332
+ result.riskLevel = result.riskLevel === "HIGH" ? "HIGH" : "MEDIUM";
333
+ result.recommendations.push("WiFi interfaces found but no active connection — consider disabling if not needed");
334
+ }
335
+ if (result.savedNetworkCount > 5) {
336
+ result.recommendations.push(`${result.savedNetworkCount} saved WiFi networks — review and remove unnecessary entries`);
337
+ }
338
+ result.recommendations.push("Servers typically should not use WiFi — use wired Ethernet for production systems");
339
+ return result;
340
+ }
341
+ async function rogueApDetect(iface) {
342
+ const result = {
343
+ totalApsFound: 0,
344
+ aps: [],
345
+ knownAps: [],
346
+ unknownAps: [],
347
+ openAps: [],
348
+ potentialEvilTwins: [],
349
+ scanInterface: iface ?? "auto",
350
+ recommendations: [],
351
+ };
352
+ // Determine interface to scan
353
+ let scanIface = iface;
354
+ if (!scanIface) {
355
+ const iwResult = await runCommand("iw", ["dev"], 10_000);
356
+ if (iwResult.exitCode === 0) {
357
+ const match = iwResult.stdout.match(/Interface\s+(\S+)/);
358
+ if (match)
359
+ scanIface = match[1];
360
+ }
361
+ }
362
+ // Scan using nmcli device wifi list (more reliable, doesn't need root)
363
+ const scanResult = await runCommand("nmcli", ["-t", "-f", "SSID,BSSID,SIGNAL,SECURITY,FREQ", "device", "wifi", "list"], 30_000);
364
+ if (scanResult.exitCode === 0 && scanResult.stdout.trim().length > 0) {
365
+ const lines = scanResult.stdout.trim().split("\n");
366
+ for (const line of lines) {
367
+ const parts = line.split(":");
368
+ if (parts.length >= 5) {
369
+ // nmcli -t uses : as separator; BSSID contains \ escaped colons
370
+ // Reassemble BSSID from parts
371
+ const ssid = parts[0].trim();
372
+ // BSSID is in parts 1-6 (MAC address with escaped colons)
373
+ const bssidParts = parts.slice(1, 7);
374
+ const bssid = bssidParts.join(":").replace(/\\\\/g, "").trim();
375
+ const remaining = parts.slice(7);
376
+ const signal = remaining[0]?.trim() ?? "";
377
+ const security = remaining[1]?.trim() ?? "";
378
+ const frequency = remaining[2]?.trim() ?? "";
379
+ if (ssid || bssid) {
380
+ result.aps.push({ ssid, bssid, signal, security, frequency });
381
+ }
382
+ }
383
+ }
384
+ }
385
+ else if (scanIface) {
386
+ // Fallback: use iw scan (needs root)
387
+ const iwScanResult = await runCommand("iw", ["dev", scanIface, "scan"], 30_000);
388
+ if (iwScanResult.exitCode === 0) {
389
+ let currentAp = {};
390
+ const lines = iwScanResult.stdout.split("\n");
391
+ for (const line of lines) {
392
+ const bssidMatch = line.match(/BSS\s+([0-9a-fA-F:]+)/);
393
+ if (bssidMatch) {
394
+ if (currentAp.bssid) {
395
+ result.aps.push({
396
+ ssid: currentAp.ssid ?? "",
397
+ bssid: currentAp.bssid,
398
+ signal: currentAp.signal ?? "",
399
+ security: currentAp.security ?? "Open",
400
+ frequency: currentAp.frequency ?? "",
401
+ });
402
+ }
403
+ currentAp = { bssid: bssidMatch[1] };
404
+ }
405
+ const ssidMatch = line.match(/SSID:\s*(.+)/);
406
+ if (ssidMatch)
407
+ currentAp.ssid = ssidMatch[1].trim();
408
+ const signalMatch = line.match(/signal:\s*(.+)/);
409
+ if (signalMatch)
410
+ currentAp.signal = signalMatch[1].trim();
411
+ const freqMatch = line.match(/freq:\s*(\d+)/);
412
+ if (freqMatch)
413
+ currentAp.frequency = freqMatch[1];
414
+ if (line.includes("WPA") || line.includes("RSN")) {
415
+ currentAp.security = line.includes("RSN") ? "WPA2" : "WPA";
416
+ }
417
+ }
418
+ // Push last AP
419
+ if (currentAp.bssid) {
420
+ result.aps.push({
421
+ ssid: currentAp.ssid ?? "",
422
+ bssid: currentAp.bssid,
423
+ signal: currentAp.signal ?? "",
424
+ security: currentAp.security ?? "Open",
425
+ frequency: currentAp.frequency ?? "",
426
+ });
427
+ }
428
+ }
429
+ else {
430
+ result.recommendations.push("WiFi scan failed — may need root privileges or wireless tools installed");
431
+ return result;
432
+ }
433
+ }
434
+ else {
435
+ result.recommendations.push("No wireless interface available for scanning");
436
+ return result;
437
+ }
438
+ result.totalApsFound = result.aps.length;
439
+ if (result.totalApsFound === 0) {
440
+ result.recommendations.push("No access points found — interface may not support scanning");
441
+ return result;
442
+ }
443
+ // Load known APs
444
+ const knownAps = loadKnownAps();
445
+ result.knownAps = knownAps.map((ap) => ap.ssid);
446
+ // Classify APs
447
+ for (const ap of result.aps) {
448
+ // Check if open (no security)
449
+ if (!ap.security || ap.security === "" || ap.security.toLowerCase() === "open" || ap.security === "--") {
450
+ result.openAps.push(ap);
451
+ }
452
+ // Check if unknown
453
+ if (knownAps.length > 0) {
454
+ const isKnown = knownAps.some((known) => known.ssid === ap.ssid &&
455
+ (!known.bssid || known.bssid === ap.bssid));
456
+ if (!isKnown) {
457
+ result.unknownAps.push(ap);
458
+ }
459
+ // Check for evil twins
460
+ for (const known of knownAps) {
461
+ if (isEvilTwin(known.ssid, ap.ssid)) {
462
+ result.potentialEvilTwins.push({ ap, matchedKnown: known.ssid });
463
+ }
464
+ // Also flag if same SSID but different BSSID
465
+ if (known.ssid === ap.ssid && known.bssid && known.bssid !== ap.bssid) {
466
+ result.potentialEvilTwins.push({ ap, matchedKnown: known.ssid });
467
+ }
468
+ }
469
+ }
470
+ }
471
+ // Recommendations
472
+ if (result.openAps.length > 0) {
473
+ result.recommendations.push(`${result.openAps.length} open (unencrypted) AP(s) detected — avoid connecting`);
474
+ }
475
+ if (result.unknownAps.length > 0) {
476
+ result.recommendations.push(`${result.unknownAps.length} unknown AP(s) detected — review for unauthorized devices`);
477
+ }
478
+ if (result.potentialEvilTwins.length > 0) {
479
+ result.recommendations.push(`WARNING: ${result.potentialEvilTwins.length} potential evil twin(s) detected — investigate immediately`);
480
+ }
481
+ if (knownAps.length === 0) {
482
+ result.recommendations.push(`No known AP list found at ${KNOWN_APS_PATH} — create one to enable evil twin detection`);
483
+ }
484
+ return result;
485
+ }
486
+ async function disableUnused(iface) {
487
+ const result = {
488
+ wirelessInterfaces: [],
489
+ loadedModules: [],
490
+ interfacesDisabled: 0,
491
+ modulesBlacklistable: 0,
492
+ rfkillAvailable: false,
493
+ cisBenchmark: "CIS Benchmark 3.1.2 — Ensure wireless interfaces are disabled",
494
+ recommendations: [],
495
+ };
496
+ // Check rfkill availability
497
+ const rfkillCheck = await runCommand("which", ["rfkill"], 5_000);
498
+ result.rfkillAvailable = rfkillCheck.exitCode === 0;
499
+ // List all wireless interfaces
500
+ const iwResult = await runCommand("iw", ["dev"], 10_000);
501
+ const interfaces = [];
502
+ if (iwResult.exitCode === 0 && iwResult.stdout.trim().length > 0) {
503
+ const ifaceMatches = iwResult.stdout.match(/Interface\s+(\S+)/g);
504
+ if (ifaceMatches) {
505
+ for (const m of ifaceMatches) {
506
+ interfaces.push(m.replace("Interface ", "").trim());
507
+ }
508
+ }
509
+ }
510
+ // Check active connections to determine which interfaces are in use
511
+ const nmcliResult = await runCommand("nmcli", ["-t", "-f", "DEVICE,TYPE,STATE", "device"], 10_000);
512
+ const activeDevices = new Set();
513
+ if (nmcliResult.exitCode === 0) {
514
+ const lines = nmcliResult.stdout.trim().split("\n");
515
+ for (const line of lines) {
516
+ const parts = line.split(":");
517
+ if (parts.length >= 3 && parts[2]?.trim() === "connected") {
518
+ activeDevices.add(parts[0].trim());
519
+ }
520
+ }
521
+ }
522
+ // If a specific interface is requested, filter to just that one
523
+ const targetInterfaces = iface ? [iface] : interfaces;
524
+ for (const ifaceName of targetInterfaces) {
525
+ const inUse = activeDevices.has(ifaceName);
526
+ let disabled = false;
527
+ if (!inUse) {
528
+ // Try to disable via rfkill
529
+ if (result.rfkillAvailable) {
530
+ const rfkillResult = await runCommand("rfkill", ["block", ifaceName], 10_000);
531
+ if (rfkillResult.exitCode === 0) {
532
+ disabled = true;
533
+ result.interfacesDisabled++;
534
+ }
535
+ else {
536
+ // Try ip link set down as fallback
537
+ const ipResult = await runCommand("ip", ["link", "set", ifaceName, "down"], 10_000);
538
+ disabled = ipResult.exitCode === 0;
539
+ if (disabled)
540
+ result.interfacesDisabled++;
541
+ }
542
+ }
543
+ else {
544
+ // Try ip link set down
545
+ const ipResult = await runCommand("ip", ["link", "set", ifaceName, "down"], 10_000);
546
+ disabled = ipResult.exitCode === 0;
547
+ if (disabled)
548
+ result.interfacesDisabled++;
549
+ }
550
+ }
551
+ result.wirelessInterfaces.push({ name: ifaceName, inUse, disabled });
552
+ }
553
+ // Check loaded wireless kernel modules
554
+ const lsmodResult = await runCommand("lsmod", [], 10_000);
555
+ if (lsmodResult.exitCode === 0) {
556
+ const lsmodOutput = lsmodResult.stdout;
557
+ for (const modName of WIRELESS_MODULES) {
558
+ const loaded = new RegExp(`^${modName}\\s`, "m").test(lsmodOutput);
559
+ const canBlacklist = loaded;
560
+ result.loadedModules.push({ name: modName, loaded, canBlacklist });
561
+ if (canBlacklist)
562
+ result.modulesBlacklistable++;
563
+ }
564
+ }
565
+ else {
566
+ result.recommendations.push("lsmod not available — cannot check loaded kernel modules");
567
+ }
568
+ // Recommendations
569
+ if (result.wirelessInterfaces.length === 0 && interfaces.length === 0) {
570
+ result.recommendations.push("No wireless interfaces found — system complies with CIS wireless requirements");
571
+ }
572
+ for (const wi of result.wirelessInterfaces) {
573
+ if (wi.inUse) {
574
+ result.recommendations.push(`Interface ${wi.name} is in use — cannot disable while active`);
575
+ }
576
+ else if (wi.disabled) {
577
+ result.recommendations.push(`Interface ${wi.name} disabled successfully`);
578
+ }
579
+ else {
580
+ result.recommendations.push(`Failed to disable interface ${wi.name} — may need root privileges`);
581
+ }
582
+ }
583
+ if (result.modulesBlacklistable > 0) {
584
+ const moduleNames = result.loadedModules
585
+ .filter((m) => m.canBlacklist)
586
+ .map((m) => m.name)
587
+ .join(", ");
588
+ result.recommendations.push(`${result.modulesBlacklistable} wireless module(s) loaded (${moduleNames}) — ` +
589
+ "consider blacklisting in /etc/modprobe.d/disable-wireless.conf");
590
+ }
591
+ return result;
592
+ }
593
+ // ── Registration entry point ───────────────────────────────────────────────
594
+ export function registerWirelessSecurityTools(server) {
595
+ server.tool("wireless_security", "Wireless security: audit Bluetooth adapters, assess WiFi configuration, detect rogue access points with evil twin analysis, and disable unused wireless interfaces with kernel module recommendations.", {
596
+ action: z
597
+ .enum(["bt_audit", "wifi_audit", "rogue_ap_detect", "disable_unused"])
598
+ .describe("Action: bt_audit=audit Bluetooth security, wifi_audit=audit WiFi configuration, rogue_ap_detect=scan for rogue APs, disable_unused=disable unused wireless interfaces"),
599
+ interface: z
600
+ .string()
601
+ .optional()
602
+ .describe("Specific wireless interface to audit (e.g., wlan0)"),
603
+ output_format: z
604
+ .enum(["text", "json"])
605
+ .optional()
606
+ .default("text")
607
+ .describe("Output format (default text)"),
608
+ }, async (params) => {
609
+ const { action } = params;
610
+ const outputFormat = params.output_format ?? "text";
611
+ switch (action) {
612
+ // ── bt_audit ──────────────────────────────────────────────────────
613
+ case "bt_audit": {
614
+ try {
615
+ const audit = await btAudit();
616
+ const output = {
617
+ action: "bt_audit",
618
+ adapterFound: audit.adapterFound,
619
+ adapterStatus: audit.adapterStatus,
620
+ powered: audit.powered,
621
+ discoverable: audit.discoverable,
622
+ pairedDevicesCount: audit.pairedDevicesCount,
623
+ pairedDevices: audit.pairedDevices,
624
+ serviceRunning: audit.serviceRunning,
625
+ serviceStatus: audit.serviceStatus,
626
+ riskLevel: audit.riskLevel,
627
+ recommendations: audit.recommendations,
628
+ };
629
+ if (outputFormat === "json") {
630
+ return { content: [formatToolOutput(output)] };
631
+ }
632
+ let text = "Wireless Security — Bluetooth Audit\n\n";
633
+ text += `Adapter Found: ${audit.adapterFound ? "yes" : "no"}\n`;
634
+ text += `Adapter Status: ${audit.adapterStatus}\n`;
635
+ text += `Powered: ${audit.powered ? "YES ⚠" : "no"}\n`;
636
+ text += `Discoverable: ${audit.discoverable ? "YES ⚠⚠" : "no ✓"}\n`;
637
+ text += `Paired Devices: ${audit.pairedDevicesCount}\n`;
638
+ if (audit.pairedDevices.length > 0) {
639
+ text += "\nPaired Devices:\n";
640
+ for (const dev of audit.pairedDevices) {
641
+ text += ` • ${dev}\n`;
642
+ }
643
+ }
644
+ text += `\nBluetooth Service: ${audit.serviceRunning ? "running ⚠" : "not running ✓"}\n`;
645
+ text += `Risk Level: ${audit.riskLevel}\n`;
646
+ if (audit.recommendations.length > 0) {
647
+ text += "\nRecommendations:\n";
648
+ for (const rec of audit.recommendations) {
649
+ text += ` • ${rec}\n`;
650
+ }
651
+ }
652
+ return { content: [createTextContent(text)] };
653
+ }
654
+ catch (err) {
655
+ const msg = err instanceof Error ? err.message : String(err);
656
+ return { content: [createErrorContent(`bt_audit failed: ${msg}`)], isError: true };
657
+ }
658
+ }
659
+ // ── wifi_audit ────────────────────────────────────────────────────
660
+ case "wifi_audit": {
661
+ try {
662
+ const audit = await wifiAudit(params.interface);
663
+ const output = {
664
+ action: "wifi_audit",
665
+ interfacesFound: audit.interfacesFound,
666
+ interfaceCount: audit.interfaceCount,
667
+ activeConnection: audit.activeConnection,
668
+ securityType: audit.securityType,
669
+ savedNetworkCount: audit.savedNetworkCount,
670
+ savedNetworks: audit.savedNetworks,
671
+ wifiNeeded: audit.wifiNeeded,
672
+ riskLevel: audit.riskLevel,
673
+ recommendations: audit.recommendations,
674
+ };
675
+ if (outputFormat === "json") {
676
+ return { content: [formatToolOutput(output)] };
677
+ }
678
+ let text = "Wireless Security — WiFi Audit\n\n";
679
+ text += `Wireless Interfaces: ${audit.interfaceCount}\n`;
680
+ if (audit.interfacesFound.length > 0) {
681
+ text += `Interfaces: ${audit.interfacesFound.join(", ")}\n`;
682
+ }
683
+ text += `Active Connection: ${audit.activeConnection}\n`;
684
+ text += `Security Type: ${audit.securityType}\n`;
685
+ text += `Saved Networks: ${audit.savedNetworkCount}\n`;
686
+ text += `WiFi Needed: ${audit.wifiNeeded ? "yes" : "no"}\n`;
687
+ text += `Risk Level: ${audit.riskLevel}\n`;
688
+ if (audit.savedNetworks.length > 0) {
689
+ text += "\nSaved WiFi Networks:\n";
690
+ for (const net of audit.savedNetworks) {
691
+ text += ` • ${net}\n`;
692
+ }
693
+ }
694
+ if (audit.recommendations.length > 0) {
695
+ text += "\nRecommendations:\n";
696
+ for (const rec of audit.recommendations) {
697
+ text += ` • ${rec}\n`;
698
+ }
699
+ }
700
+ return { content: [createTextContent(text)] };
701
+ }
702
+ catch (err) {
703
+ const msg = err instanceof Error ? err.message : String(err);
704
+ return { content: [createErrorContent(`wifi_audit failed: ${msg}`)], isError: true };
705
+ }
706
+ }
707
+ // ── rogue_ap_detect ───────────────────────────────────────────────
708
+ case "rogue_ap_detect": {
709
+ try {
710
+ const scan = await rogueApDetect(params.interface);
711
+ const output = {
712
+ action: "rogue_ap_detect",
713
+ totalApsFound: scan.totalApsFound,
714
+ knownAps: scan.knownAps,
715
+ unknownApsCount: scan.unknownAps.length,
716
+ unknownAps: scan.unknownAps,
717
+ openApsCount: scan.openAps.length,
718
+ openAps: scan.openAps,
719
+ potentialEvilTwins: scan.potentialEvilTwins,
720
+ scanInterface: scan.scanInterface,
721
+ recommendations: scan.recommendations,
722
+ };
723
+ if (outputFormat === "json") {
724
+ return { content: [formatToolOutput(output)] };
725
+ }
726
+ let text = "Wireless Security — Rogue AP Detection\n\n";
727
+ text += `Scan Interface: ${scan.scanInterface}\n`;
728
+ text += `Total APs Found: ${scan.totalApsFound}\n`;
729
+ text += `Known APs: ${scan.knownAps.length}\n`;
730
+ text += `Unknown APs: ${scan.unknownAps.length}\n`;
731
+ text += `Open APs: ${scan.openAps.length}\n`;
732
+ text += `Potential Evil Twins: ${scan.potentialEvilTwins.length}\n`;
733
+ if (scan.unknownAps.length > 0) {
734
+ text += "\nUnknown Access Points:\n";
735
+ for (const ap of scan.unknownAps) {
736
+ text += ` • SSID: ${ap.ssid || "(hidden)"} | BSSID: ${ap.bssid} | Signal: ${ap.signal} | Security: ${ap.security}\n`;
737
+ }
738
+ }
739
+ if (scan.openAps.length > 0) {
740
+ text += "\nOpen (Unencrypted) Access Points:\n";
741
+ for (const ap of scan.openAps) {
742
+ text += ` • SSID: ${ap.ssid || "(hidden)"} | BSSID: ${ap.bssid} | Signal: ${ap.signal}\n`;
743
+ }
744
+ }
745
+ if (scan.potentialEvilTwins.length > 0) {
746
+ text += "\n⚠ Potential Evil Twins:\n";
747
+ for (const twin of scan.potentialEvilTwins) {
748
+ text += ` • SSID: ${twin.ap.ssid} | BSSID: ${twin.ap.bssid} — mimics known AP: ${twin.matchedKnown}\n`;
749
+ }
750
+ }
751
+ if (scan.recommendations.length > 0) {
752
+ text += "\nRecommendations:\n";
753
+ for (const rec of scan.recommendations) {
754
+ text += ` • ${rec}\n`;
755
+ }
756
+ }
757
+ return { content: [createTextContent(text)] };
758
+ }
759
+ catch (err) {
760
+ const msg = err instanceof Error ? err.message : String(err);
761
+ return { content: [createErrorContent(`rogue_ap_detect failed: ${msg}`)], isError: true };
762
+ }
763
+ }
764
+ // ── disable_unused ────────────────────────────────────────────────
765
+ case "disable_unused": {
766
+ try {
767
+ const disable = await disableUnused(params.interface);
768
+ const output = {
769
+ action: "disable_unused",
770
+ wirelessInterfaces: disable.wirelessInterfaces,
771
+ loadedModules: disable.loadedModules,
772
+ interfacesDisabled: disable.interfacesDisabled,
773
+ modulesBlacklistable: disable.modulesBlacklistable,
774
+ rfkillAvailable: disable.rfkillAvailable,
775
+ cisBenchmark: disable.cisBenchmark,
776
+ recommendations: disable.recommendations,
777
+ };
778
+ if (outputFormat === "json") {
779
+ return { content: [formatToolOutput(output)] };
780
+ }
781
+ let text = "Wireless Security — Disable Unused Interfaces\n\n";
782
+ text += `CIS Reference: ${disable.cisBenchmark}\n\n`;
783
+ text += "Wireless Interfaces:\n";
784
+ if (disable.wirelessInterfaces.length === 0) {
785
+ text += " No wireless interfaces found\n";
786
+ }
787
+ else {
788
+ for (const wi of disable.wirelessInterfaces) {
789
+ const status = wi.inUse
790
+ ? "IN USE (not disabled)"
791
+ : wi.disabled
792
+ ? "DISABLED ✓"
793
+ : "could not disable ⚠";
794
+ text += ` • ${wi.name}: ${status}\n`;
795
+ }
796
+ }
797
+ text += "\nKernel Modules:\n";
798
+ if (disable.loadedModules.length === 0) {
799
+ text += " Could not check kernel modules\n";
800
+ }
801
+ else {
802
+ for (const mod of disable.loadedModules) {
803
+ text += ` • ${mod.name}: ${mod.loaded ? "LOADED" : "not loaded"}${mod.canBlacklist ? " ⚠ can be blacklisted" : ""}\n`;
804
+ }
805
+ }
806
+ text += `\nInterfaces Disabled: ${disable.interfacesDisabled}\n`;
807
+ text += `Modules Blacklistable: ${disable.modulesBlacklistable}\n`;
808
+ text += `rfkill Available: ${disable.rfkillAvailable ? "yes" : "no"}\n`;
809
+ if (disable.recommendations.length > 0) {
810
+ text += "\nRecommendations:\n";
811
+ for (const rec of disable.recommendations) {
812
+ text += ` • ${rec}\n`;
813
+ }
814
+ }
815
+ return { content: [createTextContent(text)] };
816
+ }
817
+ catch (err) {
818
+ const msg = err instanceof Error ? err.message : String(err);
819
+ return { content: [createErrorContent(`disable_unused failed: ${msg}`)], isError: true };
820
+ }
821
+ }
822
+ default:
823
+ return { content: [createErrorContent(`Unknown action: ${action}`)], isError: true };
824
+ }
825
+ });
826
+ }