mirror of
https://github.com/smittix/intercept.git
synced 2026-07-06 00:28:12 -07:00
96172ca593
First-time run of ruff-format via pre-commit hook normalises quote style, trailing commas, and whitespace across 188 Python files. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
699 lines
25 KiB
Python
699 lines
25 KiB
Python
"""
|
|
TSCM Threat Detection Engine
|
|
|
|
Analyzes WiFi, Bluetooth, and RF data to identify potential surveillance devices
|
|
and classify threats based on known patterns and baseline comparison.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from data.tscm_frequencies import (
|
|
get_frequency_risk,
|
|
get_threat_severity,
|
|
is_known_tracker,
|
|
is_potential_camera,
|
|
)
|
|
from utils.tscm.signal_classification import (
|
|
get_signal_strength_info,
|
|
)
|
|
|
|
logger = logging.getLogger("intercept.tscm.detector")
|
|
|
|
# Classification levels for TSCM devices
|
|
CLASSIFICATION_LEVELS = {
|
|
"informational": {
|
|
"color": "#00cc00", # Green
|
|
"label": "Informational",
|
|
"description": "Known device, expected infrastructure, or background noise",
|
|
},
|
|
"review": {
|
|
"color": "#ffcc00", # Yellow
|
|
"label": "Needs Review",
|
|
"description": "Unknown device requiring investigation",
|
|
},
|
|
"high_interest": {
|
|
"color": "#ff3333", # Red
|
|
"label": "High Interest",
|
|
"description": "Suspicious device requiring immediate attention",
|
|
},
|
|
}
|
|
|
|
# BLE device types that can transmit audio (potential bugs)
|
|
AUDIO_CAPABLE_BLE_NAMES = [
|
|
"headphone",
|
|
"headset",
|
|
"earphone",
|
|
"earbud",
|
|
"speaker",
|
|
"audio",
|
|
"mic",
|
|
"microphone",
|
|
"airpod",
|
|
"buds",
|
|
"jabra",
|
|
"bose",
|
|
"sony wf",
|
|
"sony wh",
|
|
"beats",
|
|
"jbl",
|
|
"soundcore",
|
|
"anker",
|
|
"skullcandy",
|
|
]
|
|
|
|
# Device history for tracking repeat detections across scans
|
|
_device_history: dict[str, list[datetime]] = {}
|
|
_history_window_hours = 24 # Consider detections within 24 hours
|
|
|
|
|
|
def _record_device_seen(identifier: str) -> int:
|
|
"""Record a device sighting and return count of times seen."""
|
|
now = datetime.now()
|
|
if identifier not in _device_history:
|
|
_device_history[identifier] = []
|
|
|
|
# Clean old entries
|
|
cutoff = now.timestamp() - (_history_window_hours * 3600)
|
|
_device_history[identifier] = [dt for dt in _device_history[identifier] if dt.timestamp() > cutoff]
|
|
|
|
_device_history[identifier].append(now)
|
|
return len(_device_history[identifier])
|
|
|
|
|
|
def _is_audio_capable_ble(name: str | None, device_type: str | None = None) -> bool:
|
|
"""Check if a BLE device might be audio-capable."""
|
|
if name:
|
|
name_lower = name.lower()
|
|
for pattern in AUDIO_CAPABLE_BLE_NAMES:
|
|
if pattern in name_lower:
|
|
return True
|
|
if device_type:
|
|
type_lower = device_type.lower()
|
|
if any(t in type_lower for t in ["audio", "headset", "headphone", "speaker"]):
|
|
return True
|
|
return False
|
|
|
|
|
|
class ThreatDetector:
|
|
"""
|
|
Analyzes scan results to detect potential surveillance threats.
|
|
"""
|
|
|
|
def __init__(self, baseline: dict | None = None):
|
|
"""
|
|
Initialize the threat detector.
|
|
|
|
Args:
|
|
baseline: Optional baseline dict containing expected devices
|
|
"""
|
|
self.baseline = baseline
|
|
self.baseline_wifi_macs = set()
|
|
self.baseline_bt_macs = set()
|
|
self.baseline_rf_freqs = set()
|
|
|
|
if baseline:
|
|
self._load_baseline(baseline)
|
|
|
|
def _load_baseline(self, baseline: dict) -> None:
|
|
"""Load baseline device identifiers for comparison."""
|
|
# WiFi networks and clients
|
|
for network in baseline.get("wifi_networks", []):
|
|
if "bssid" in network:
|
|
self.baseline_wifi_macs.add(network["bssid"].upper())
|
|
if "clients" in network:
|
|
for client in network["clients"]:
|
|
if "mac" in client:
|
|
self.baseline_wifi_macs.add(client["mac"].upper())
|
|
|
|
for client in baseline.get("wifi_clients", []):
|
|
if "mac" in client:
|
|
self.baseline_wifi_macs.add(client["mac"].upper())
|
|
|
|
# Bluetooth devices
|
|
for device in baseline.get("bt_devices", []):
|
|
if "mac" in device:
|
|
self.baseline_bt_macs.add(device["mac"].upper())
|
|
|
|
# RF frequencies (rounded to nearest 0.1 MHz)
|
|
for freq in baseline.get("rf_frequencies", []):
|
|
if isinstance(freq, dict):
|
|
self.baseline_rf_freqs.add(round(freq.get("frequency", 0), 1))
|
|
else:
|
|
self.baseline_rf_freqs.add(round(freq, 1))
|
|
|
|
logger.info(
|
|
f"Loaded baseline: {len(self.baseline_wifi_macs)} WiFi, "
|
|
f"{len(self.baseline_bt_macs)} BT, {len(self.baseline_rf_freqs)} RF"
|
|
)
|
|
|
|
def classify_wifi_device(self, device: dict) -> dict:
|
|
"""
|
|
Classify a WiFi device into informational/review/high_interest.
|
|
|
|
Returns:
|
|
Dict with 'classification', 'reasons', and metadata
|
|
"""
|
|
mac = device.get("bssid", device.get("mac", "")).upper()
|
|
ssid = device.get("essid", device.get("ssid", ""))
|
|
signal = device.get("power", device.get("signal", -100))
|
|
|
|
reasons = []
|
|
classification = "informational"
|
|
|
|
# Track repeat detections
|
|
times_seen = _record_device_seen(f"wifi:{mac}") if mac else 1
|
|
|
|
# Check if in baseline (known device)
|
|
in_baseline = mac in self.baseline_wifi_macs if self.baseline else False
|
|
|
|
if in_baseline:
|
|
reasons.append("Known device in baseline")
|
|
classification = "informational"
|
|
else:
|
|
# New/unknown device
|
|
reasons.append("New WiFi access point")
|
|
classification = "review"
|
|
|
|
# Check for suspicious patterns -> high interest
|
|
if is_potential_camera(ssid=ssid, mac=mac):
|
|
reasons.append("Matches camera device patterns")
|
|
classification = "high_interest"
|
|
|
|
try:
|
|
signal_val = int(signal) if signal else -100
|
|
except (ValueError, TypeError):
|
|
signal_val = -100
|
|
|
|
# Use standardized signal classification
|
|
signal_info = get_signal_strength_info(signal_val)
|
|
if not ssid and signal_info["strength"] in ("strong", "very_strong"):
|
|
reasons.append(f"Hidden SSID with {signal_info['label'].lower()} signal")
|
|
classification = "high_interest"
|
|
|
|
# Repeat detections across scans
|
|
if times_seen >= 3:
|
|
reasons.append(f"Repeat detection ({times_seen} times)")
|
|
if classification != "high_interest":
|
|
classification = "high_interest"
|
|
|
|
# Include standardized signal classification
|
|
signal_info = get_signal_strength_info(signal_val)
|
|
|
|
return {
|
|
"classification": classification,
|
|
"reasons": reasons,
|
|
"in_baseline": in_baseline,
|
|
"times_seen": times_seen,
|
|
"signal_strength": signal_info["strength"],
|
|
"signal_label": signal_info["label"],
|
|
"signal_confidence": signal_info["confidence"],
|
|
}
|
|
|
|
def classify_bt_device(self, device: dict) -> dict:
|
|
"""
|
|
Classify a Bluetooth device into informational/review/high_interest.
|
|
|
|
Now uses the v2 tracker detection data if available.
|
|
|
|
Returns:
|
|
Dict with 'classification', 'reasons', and metadata
|
|
"""
|
|
mac = device.get("mac", device.get("address", "")).upper()
|
|
name = device.get("name", "")
|
|
rssi = device.get("rssi", device.get("signal", -100))
|
|
device_type = device.get("type", "")
|
|
manufacturer_data = device.get("manufacturer_data")
|
|
|
|
reasons = []
|
|
classification = "informational"
|
|
|
|
# Track repeat detections
|
|
times_seen = _record_device_seen(f"bt:{mac}") if mac else 1
|
|
|
|
# Check if in baseline (known device)
|
|
in_baseline = mac in self.baseline_bt_macs if self.baseline else False
|
|
|
|
# Use v2 tracker detection data if available (from get_tscm_bluetooth_snapshot)
|
|
tracker_data = device.get("tracker", {})
|
|
is_tracker_v2 = tracker_data.get("is_tracker", False)
|
|
tracker_type_v2 = tracker_data.get("type")
|
|
tracker_name_v2 = tracker_data.get("name")
|
|
tracker_confidence_v2 = tracker_data.get("confidence")
|
|
tracker_evidence_v2 = tracker_data.get("evidence", [])
|
|
|
|
# Use v2 risk analysis if available
|
|
risk_data = device.get("risk_analysis", {})
|
|
risk_score = risk_data.get("risk_score", 0)
|
|
risk_factors = risk_data.get("risk_factors", [])
|
|
|
|
# Fall back to legacy detection if v2 not available
|
|
tracker_info_legacy = None
|
|
if not is_tracker_v2:
|
|
tracker_info_legacy = is_known_tracker(name, manufacturer_data)
|
|
|
|
is_tracker = is_tracker_v2 or (tracker_info_legacy is not None)
|
|
|
|
if in_baseline:
|
|
reasons.append("Known device in baseline")
|
|
classification = "informational"
|
|
else:
|
|
# New/unknown BLE device
|
|
if not name or name == "Unknown":
|
|
reasons.append("Unknown BLE device")
|
|
classification = "review"
|
|
else:
|
|
reasons.append("New Bluetooth device")
|
|
classification = "review"
|
|
|
|
# Check for trackers -> high interest
|
|
if is_tracker_v2:
|
|
tracker_label = tracker_name_v2 or tracker_type_v2 or "Unknown tracker"
|
|
conf_label = f" ({tracker_confidence_v2})" if tracker_confidence_v2 else ""
|
|
reasons.append(f"Tracker detected: {tracker_label}{conf_label}")
|
|
classification = "high_interest"
|
|
|
|
# Add evidence from v2 detection
|
|
for evidence_item in tracker_evidence_v2[:2]: # First 2 items
|
|
reasons.append(f"Evidence: {evidence_item}")
|
|
|
|
# Add risk factors if significant
|
|
if risk_score >= 0.3:
|
|
reasons.append(f"Risk score: {int(risk_score * 100)}%")
|
|
for factor in risk_factors[:2]: # First 2 factors
|
|
reasons.append(f"Risk: {factor}")
|
|
|
|
elif tracker_info_legacy:
|
|
reasons.append(f"Known tracker: {tracker_info_legacy.get('name', 'Unknown')}")
|
|
classification = "high_interest"
|
|
|
|
# Check for audio-capable devices -> high interest
|
|
if _is_audio_capable_ble(name, device_type):
|
|
reasons.append("Audio-capable BLE device")
|
|
classification = "high_interest"
|
|
|
|
# Strong signal from unknown device - use standardized classification
|
|
try:
|
|
rssi_val = int(rssi) if rssi else -100
|
|
except (ValueError, TypeError):
|
|
rssi_val = -100
|
|
|
|
signal_info = get_signal_strength_info(rssi_val)
|
|
if signal_info["strength"] in ("strong", "very_strong") and not name:
|
|
reasons.append(f"{signal_info['label']} signal from unnamed device")
|
|
classification = "high_interest"
|
|
|
|
# Repeat detections across scans
|
|
if times_seen >= 3:
|
|
reasons.append(f"Repeat detection ({times_seen} times)")
|
|
if classification != "high_interest":
|
|
classification = "high_interest"
|
|
|
|
# Include standardized signal classification
|
|
try:
|
|
rssi_val = int(rssi) if rssi else -100
|
|
except (ValueError, TypeError):
|
|
rssi_val = -100
|
|
signal_info = get_signal_strength_info(rssi_val)
|
|
|
|
return {
|
|
"classification": classification,
|
|
"reasons": reasons,
|
|
"in_baseline": in_baseline,
|
|
"times_seen": times_seen,
|
|
"is_tracker": is_tracker,
|
|
"tracker_type": tracker_type_v2,
|
|
"tracker_name": tracker_name_v2,
|
|
"tracker_confidence": tracker_confidence_v2,
|
|
"risk_score": risk_score,
|
|
"is_audio_capable": _is_audio_capable_ble(name, device_type),
|
|
"signal_strength": signal_info["strength"],
|
|
"signal_label": signal_info["label"],
|
|
"signal_confidence": signal_info["confidence"],
|
|
}
|
|
|
|
def classify_rf_signal(self, signal: dict) -> dict:
|
|
"""
|
|
Classify an RF signal into informational/review/high_interest.
|
|
|
|
Returns:
|
|
Dict with 'classification', 'reasons', and metadata
|
|
"""
|
|
frequency = signal.get("frequency", 0)
|
|
power = signal.get("power", signal.get("level", -100))
|
|
signal.get("band", "")
|
|
|
|
reasons = []
|
|
classification = "informational"
|
|
freq_rounded = round(frequency, 1)
|
|
|
|
# Track repeat detections
|
|
times_seen = _record_device_seen(f"rf:{freq_rounded}")
|
|
|
|
# Check if in baseline (known frequency)
|
|
in_baseline = freq_rounded in self.baseline_rf_freqs if self.baseline else False
|
|
|
|
# Get frequency risk info
|
|
risk, band_name = get_frequency_risk(frequency)
|
|
|
|
if in_baseline:
|
|
reasons.append("Known frequency in baseline")
|
|
classification = "informational"
|
|
else:
|
|
# New/unidentified RF carrier
|
|
reasons.append(f"Unidentified RF carrier in {band_name}")
|
|
|
|
if risk == "low":
|
|
reasons.append("Background RF noise band")
|
|
classification = "review"
|
|
elif risk == "medium":
|
|
reasons.append("ISM band signal")
|
|
classification = "review"
|
|
elif risk in ["high", "critical"]:
|
|
reasons.append(f"High-risk surveillance band: {band_name}")
|
|
classification = "high_interest"
|
|
|
|
# Strong persistent signal - use standardized classification
|
|
if power:
|
|
power_info = get_signal_strength_info(float(power))
|
|
if power_info["strength"] in ("strong", "very_strong"):
|
|
reasons.append(f"{power_info['label']} persistent transmitter")
|
|
classification = "high_interest"
|
|
|
|
# Repeat detections (persistent transmitter)
|
|
if times_seen >= 2:
|
|
reasons.append(f"Persistent transmitter ({times_seen} detections)")
|
|
classification = "high_interest"
|
|
|
|
# Include standardized signal classification
|
|
try:
|
|
power_val = float(power) if power else -100
|
|
except (ValueError, TypeError):
|
|
power_val = -100
|
|
signal_info = get_signal_strength_info(power_val)
|
|
|
|
return {
|
|
"classification": classification,
|
|
"reasons": reasons,
|
|
"in_baseline": in_baseline,
|
|
"times_seen": times_seen,
|
|
"risk_level": risk,
|
|
"band_name": band_name,
|
|
"signal_strength": signal_info["strength"],
|
|
"signal_label": signal_info["label"],
|
|
"signal_confidence": signal_info["confidence"],
|
|
}
|
|
|
|
def analyze_wifi_device(self, device: dict) -> dict | None:
|
|
"""
|
|
Analyze a WiFi device for threats.
|
|
|
|
Args:
|
|
device: WiFi device dict with bssid, essid, etc.
|
|
|
|
Returns:
|
|
Threat dict if threat detected, None otherwise
|
|
"""
|
|
mac = device.get("bssid", device.get("mac", "")).upper()
|
|
ssid = device.get("essid", device.get("ssid", ""))
|
|
vendor = device.get("vendor", "")
|
|
signal = device.get("power", device.get("signal", -100))
|
|
|
|
threats = []
|
|
|
|
# Check if new device (not in baseline)
|
|
if self.baseline and mac and mac not in self.baseline_wifi_macs:
|
|
threats.append(
|
|
{
|
|
"type": "new_device",
|
|
"severity": get_threat_severity("new_device", {"signal_strength": signal}),
|
|
"reason": "Device not present in baseline",
|
|
}
|
|
)
|
|
|
|
# Check for hidden camera patterns
|
|
if is_potential_camera(ssid=ssid, mac=mac, vendor=vendor):
|
|
threats.append(
|
|
{
|
|
"type": "hidden_camera",
|
|
"severity": get_threat_severity("hidden_camera", {"signal_strength": signal}),
|
|
"reason": "Device matches WiFi camera patterns",
|
|
}
|
|
)
|
|
|
|
# Check for hidden SSID with strong signal - use standardized classification
|
|
try:
|
|
signal_int = int(signal) if signal else -100
|
|
except (ValueError, TypeError):
|
|
signal_int = -100
|
|
|
|
signal_info = get_signal_strength_info(signal_int)
|
|
if not ssid and signal_info["strength"] in ("strong", "very_strong"):
|
|
threats.append(
|
|
{
|
|
"type": "anomaly",
|
|
"severity": "medium",
|
|
"reason": f"Hidden SSID with {signal_info['label'].lower()} signal",
|
|
}
|
|
)
|
|
|
|
if not threats:
|
|
return None
|
|
|
|
# Return highest severity threat
|
|
threats.sort(key=lambda t: ["low", "medium", "high", "critical"].index(t["severity"]), reverse=True)
|
|
|
|
return {
|
|
"threat_type": threats[0]["type"],
|
|
"severity": threats[0]["severity"],
|
|
"source": "wifi",
|
|
"identifier": mac,
|
|
"name": ssid or "Hidden Network",
|
|
"signal_strength": signal,
|
|
"details": {
|
|
"all_threats": threats,
|
|
"vendor": vendor,
|
|
"ssid": ssid,
|
|
},
|
|
}
|
|
|
|
def analyze_bt_device(self, device: dict) -> dict | None:
|
|
"""
|
|
Analyze a Bluetooth device for threats.
|
|
|
|
Args:
|
|
device: BT device dict with mac, name, rssi, etc.
|
|
|
|
Returns:
|
|
Threat dict if threat detected, None otherwise
|
|
"""
|
|
mac = device.get("mac", device.get("address", "")).upper()
|
|
name = device.get("name", "")
|
|
rssi = device.get("rssi", device.get("signal", -100))
|
|
manufacturer = device.get("manufacturer", "")
|
|
device_type = device.get("type", "")
|
|
manufacturer_data = device.get("manufacturer_data")
|
|
tracker_data = device.get("tracker", {}) or {}
|
|
|
|
threats = []
|
|
|
|
# Check if new device (not in baseline)
|
|
if self.baseline and mac and mac not in self.baseline_bt_macs:
|
|
threats.append(
|
|
{
|
|
"type": "new_device",
|
|
"severity": get_threat_severity("new_device", {"signal_strength": rssi}),
|
|
"reason": "Device not present in baseline",
|
|
}
|
|
)
|
|
|
|
# Check for known trackers (v2 tracker data if available)
|
|
if tracker_data.get("is_tracker"):
|
|
tracker_label = tracker_data.get("name") or tracker_data.get("type") or "Tracker"
|
|
confidence = str(tracker_data.get("confidence") or "").lower()
|
|
severity = "high" if confidence in ("high", "medium") else "medium"
|
|
threats.append(
|
|
{
|
|
"type": "tracker",
|
|
"severity": severity,
|
|
"reason": f"Tracker detected: {tracker_label}",
|
|
"tracker_type": tracker_label,
|
|
"details": tracker_data.get("evidence", []),
|
|
}
|
|
)
|
|
|
|
# Check for known trackers (legacy patterns)
|
|
tracker_info = is_known_tracker(name, manufacturer_data)
|
|
if tracker_info:
|
|
threats.append(
|
|
{
|
|
"type": "tracker",
|
|
"severity": tracker_info.get("risk", "high"),
|
|
"reason": f"Known tracker detected: {tracker_info.get('name', 'Unknown')}",
|
|
"tracker_type": tracker_info.get("name"),
|
|
}
|
|
)
|
|
|
|
# Check for suspicious BLE beacons (unnamed, persistent) - use standardized classification
|
|
try:
|
|
rssi_int = int(rssi) if rssi else -100
|
|
except (ValueError, TypeError):
|
|
rssi_int = -100
|
|
|
|
signal_info = get_signal_strength_info(rssi_int)
|
|
if not name and signal_info["strength"] in ("moderate", "strong", "very_strong"):
|
|
threats.append(
|
|
{
|
|
"type": "anomaly",
|
|
"severity": "medium",
|
|
"reason": f"Unnamed BLE device with {signal_info['label'].lower()} signal",
|
|
}
|
|
)
|
|
|
|
if not threats:
|
|
return None
|
|
|
|
# Return highest severity threat
|
|
threats.sort(key=lambda t: ["low", "medium", "high", "critical"].index(t["severity"]), reverse=True)
|
|
|
|
return {
|
|
"threat_type": threats[0]["type"],
|
|
"severity": threats[0]["severity"],
|
|
"source": "bluetooth",
|
|
"identifier": mac,
|
|
"name": name or "Unknown BLE Device",
|
|
"signal_strength": rssi,
|
|
"details": {
|
|
"all_threats": threats,
|
|
"manufacturer": manufacturer,
|
|
"device_type": device_type,
|
|
},
|
|
}
|
|
|
|
def analyze_rf_signal(self, signal: dict) -> dict | None:
|
|
"""
|
|
Analyze an RF signal for threats.
|
|
|
|
Args:
|
|
signal: RF signal dict with frequency, level, etc.
|
|
|
|
Returns:
|
|
Threat dict if threat detected, None otherwise
|
|
"""
|
|
frequency = signal.get("frequency", 0)
|
|
level = signal.get("level", signal.get("power", -100))
|
|
modulation = signal.get("modulation", "")
|
|
|
|
if not frequency:
|
|
return None
|
|
|
|
threats = []
|
|
freq_rounded = round(frequency, 1)
|
|
|
|
# Check if new frequency (not in baseline)
|
|
if self.baseline and freq_rounded not in self.baseline_rf_freqs:
|
|
risk, band_name = get_frequency_risk(frequency)
|
|
threats.append(
|
|
{
|
|
"type": "unknown_signal",
|
|
"severity": risk,
|
|
"reason": f"New signal in {band_name}",
|
|
}
|
|
)
|
|
|
|
# Check frequency risk even without baseline
|
|
risk, band_name = get_frequency_risk(frequency)
|
|
if risk in ["high", "critical"]:
|
|
threats.append(
|
|
{
|
|
"type": "unknown_signal",
|
|
"severity": risk,
|
|
"reason": f"Signal in high-risk band: {band_name}",
|
|
}
|
|
)
|
|
|
|
if not threats:
|
|
return None
|
|
|
|
# Return highest severity threat
|
|
threats.sort(key=lambda t: ["low", "medium", "high", "critical"].index(t["severity"]), reverse=True)
|
|
|
|
return {
|
|
"threat_type": threats[0]["type"],
|
|
"severity": threats[0]["severity"],
|
|
"source": "rf",
|
|
"identifier": f"{frequency:.3f} MHz",
|
|
"name": f"RF Signal @ {frequency:.3f} MHz",
|
|
"signal_strength": level,
|
|
"frequency": frequency,
|
|
"details": {
|
|
"all_threats": threats,
|
|
"modulation": modulation,
|
|
"band_name": band_name,
|
|
},
|
|
}
|
|
|
|
def analyze_all(
|
|
self,
|
|
wifi_devices: list[dict] | None = None,
|
|
bt_devices: list[dict] | None = None,
|
|
rf_signals: list[dict] | None = None,
|
|
) -> list[dict]:
|
|
"""
|
|
Analyze all provided devices and signals for threats.
|
|
|
|
Returns:
|
|
List of detected threats sorted by severity
|
|
"""
|
|
threats = []
|
|
|
|
if wifi_devices:
|
|
for device in wifi_devices:
|
|
threat = self.analyze_wifi_device(device)
|
|
if threat:
|
|
threats.append(threat)
|
|
|
|
if bt_devices:
|
|
for device in bt_devices:
|
|
threat = self.analyze_bt_device(device)
|
|
if threat:
|
|
threats.append(threat)
|
|
|
|
if rf_signals:
|
|
for signal in rf_signals:
|
|
threat = self.analyze_rf_signal(signal)
|
|
if threat:
|
|
threats.append(threat)
|
|
|
|
# Sort by severity (critical first)
|
|
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
threats.sort(key=lambda t: severity_order.get(t.get("severity", "low"), 3))
|
|
|
|
return threats
|
|
|
|
|
|
def classify_device_threat(source: str, device: dict, baseline: dict | None = None) -> dict | None:
|
|
"""
|
|
Convenience function to classify a single device.
|
|
|
|
Args:
|
|
source: Device source ('wifi', 'bluetooth', 'rf')
|
|
device: Device data dict
|
|
baseline: Optional baseline for comparison
|
|
|
|
Returns:
|
|
Threat dict if threat detected, None otherwise
|
|
"""
|
|
detector = ThreatDetector(baseline)
|
|
|
|
if source == "wifi":
|
|
return detector.analyze_wifi_device(device)
|
|
elif source == "bluetooth":
|
|
return detector.analyze_bt_device(device)
|
|
elif source == "rf":
|
|
return detector.analyze_rf_signal(device)
|
|
|
|
return None
|