defense-mcp-server 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,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
|
+
}
|