mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Add TSCM support to distributed agent with local mode parity
- Agent TSCM uses same ThreatDetector and CorrelationEngine as local mode - Added baseline_id parameter support using get_tscm_baseline() - Fixed RF scan stop_check to allow agent-specific stop events - Fixed 'undefined MHz' display for WiFi devices (added essid fallback and null check) - Fixed signal strength type conversion (string to int) for correlation engine - Agent threat detection matches local mode behavior: - No baseline: detects anomaly/hidden_camera threats only - With baseline: also detects new_device threats
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -153,10 +153,16 @@ def get_agent_detail(agent_id: int):
|
||||
client = create_client_from_agent(agent)
|
||||
metadata = client.refresh_metadata()
|
||||
if metadata['healthy']:
|
||||
caps = metadata['capabilities'] or {}
|
||||
# Store full interfaces structure (wifi, bt, sdr)
|
||||
agent_interfaces = caps.get('interfaces', {})
|
||||
# Fallback: also include top-level devices for backwards compatibility
|
||||
if not agent_interfaces.get('sdr_devices') and caps.get('devices'):
|
||||
agent_interfaces['sdr_devices'] = caps.get('devices', [])
|
||||
update_agent(
|
||||
agent_id,
|
||||
capabilities=metadata['capabilities'].get('modes') if metadata['capabilities'] else None,
|
||||
interfaces={'devices': metadata['capabilities'].get('devices', [])} if metadata['capabilities'] else None,
|
||||
capabilities=caps.get('modes'),
|
||||
interfaces=agent_interfaces,
|
||||
update_last_seen=True
|
||||
)
|
||||
agent = get_agent(agent_id)
|
||||
@@ -215,10 +221,15 @@ def refresh_agent_metadata(agent_id: int):
|
||||
|
||||
if metadata['healthy']:
|
||||
caps = metadata['capabilities'] or {}
|
||||
# Store full interfaces structure (wifi, bt, sdr)
|
||||
agent_interfaces = caps.get('interfaces', {})
|
||||
# Fallback: also include top-level devices for backwards compatibility
|
||||
if not agent_interfaces.get('sdr_devices') and caps.get('devices'):
|
||||
agent_interfaces['sdr_devices'] = caps.get('devices', [])
|
||||
update_agent(
|
||||
agent_id,
|
||||
capabilities=caps.get('modes'),
|
||||
interfaces={'devices': caps.get('devices', [])},
|
||||
interfaces=agent_interfaces,
|
||||
update_last_seen=True
|
||||
)
|
||||
agent = get_agent(agent_id)
|
||||
|
||||
@@ -944,7 +944,7 @@ def _scan_bluetooth_devices(interface: str, duration: int = 10) -> list[dict]:
|
||||
return devices
|
||||
|
||||
|
||||
def _scan_rf_signals(sdr_device: int | None, duration: int = 30) -> list[dict]:
|
||||
def _scan_rf_signals(sdr_device: int | None, duration: int = 30, stop_check: callable | None = None) -> list[dict]:
|
||||
"""
|
||||
Scan for RF signals using SDR (rtl_power).
|
||||
|
||||
@@ -956,7 +956,16 @@ def _scan_rf_signals(sdr_device: int | None, duration: int = 30) -> list[dict]:
|
||||
- 915 MHz: US ISM band
|
||||
- 1.2 GHz: Video transmitters
|
||||
- 2.4 GHz: WiFi, Bluetooth, video transmitters
|
||||
|
||||
Args:
|
||||
sdr_device: SDR device index
|
||||
duration: Scan duration per band
|
||||
stop_check: Optional callable that returns True if scan should stop.
|
||||
Defaults to checking module-level _sweep_running.
|
||||
"""
|
||||
# Default stop check uses module-level _sweep_running
|
||||
if stop_check is None:
|
||||
stop_check = lambda: not _sweep_running
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -1021,7 +1030,7 @@ def _scan_rf_signals(sdr_device: int | None, duration: int = 30) -> list[dict]:
|
||||
|
||||
# Scan each band and look for strong signals
|
||||
for start_freq, end_freq, bin_size, band_name in scan_bands:
|
||||
if not _sweep_running:
|
||||
if stop_check():
|
||||
break
|
||||
|
||||
logger.info(f"Scanning {band_name} ({start_freq/1e6:.1f}-{end_freq/1e6:.1f} MHz)")
|
||||
|
||||
@@ -106,7 +106,7 @@ function updateAgentHealthUI() {
|
||||
const selector = document.getElementById('agentSelect');
|
||||
if (!selector) return;
|
||||
|
||||
// Update each option in selector
|
||||
// Update each option in selector with status and latency
|
||||
agents.forEach(agent => {
|
||||
const option = selector.querySelector(`option[value="${agent.id}"]`);
|
||||
if (option) {
|
||||
|
||||
@@ -2231,7 +2231,7 @@
|
||||
|
||||
// Show agent selector for modes that support remote agents
|
||||
const agentSection = document.getElementById('agentSection');
|
||||
const agentModes = ['pager', 'sensor', 'rtlamr', 'listening', 'aprs', 'wifi', 'bluetooth', 'aircraft'];
|
||||
const agentModes = ['pager', 'sensor', 'rtlamr', 'listening', 'aprs', 'wifi', 'bluetooth', 'aircraft', 'tscm', 'ais', 'acars', 'dsc'];
|
||||
if (agentSection) agentSection.style.display = agentModes.includes(mode) ? 'block' : 'none';
|
||||
|
||||
// Show RTL-SDR device section for modes that use it
|
||||
@@ -8943,6 +8943,15 @@
|
||||
const btIndicator = document.getElementById('tscmBtIndicator');
|
||||
const rfIndicator = document.getElementById('tscmRfIndicator');
|
||||
|
||||
// Safety check for agent mode which may not return devices
|
||||
if (!devices) {
|
||||
// Just mark all as active if we don't have device info
|
||||
if (wifiIndicator) wifiIndicator.classList.add('active');
|
||||
if (btIndicator) btIndicator.classList.add('active');
|
||||
if (rfIndicator) rfIndicator.classList.add('active');
|
||||
return;
|
||||
}
|
||||
|
||||
if (wifiIndicator) {
|
||||
wifiIndicator.classList.toggle('active', devices.wifi);
|
||||
wifiIndicator.classList.toggle('inactive', !devices.wifi);
|
||||
@@ -8975,6 +8984,10 @@
|
||||
tscmEventSource.close();
|
||||
tscmEventSource = null;
|
||||
}
|
||||
if (typeof tscmAgentPollInterval !== 'undefined' && tscmAgentPollInterval) {
|
||||
clearInterval(tscmAgentPollInterval);
|
||||
tscmAgentPollInterval = null;
|
||||
}
|
||||
|
||||
document.getElementById('startTscmBtn').style.display = 'block';
|
||||
document.getElementById('stopTscmBtn').style.display = 'none';
|
||||
@@ -9518,46 +9531,113 @@
|
||||
reportWindow.document.close();
|
||||
}
|
||||
|
||||
let tscmAgentPollInterval = null;
|
||||
|
||||
function startTscmStream() {
|
||||
if (tscmEventSource) {
|
||||
tscmEventSource.close();
|
||||
tscmEventSource = null;
|
||||
}
|
||||
if (tscmAgentPollInterval) {
|
||||
clearInterval(tscmAgentPollInterval);
|
||||
tscmAgentPollInterval = null;
|
||||
}
|
||||
|
||||
// Check if using agent - connect to multi-agent stream
|
||||
// Check if using agent
|
||||
const isAgentMode = typeof currentAgent !== 'undefined' && currentAgent !== 'local';
|
||||
const streamUrl = isAgentMode
|
||||
? '/controller/stream/all'
|
||||
: '/tscm/sweep/stream';
|
||||
|
||||
tscmEventSource = new EventSource(streamUrl);
|
||||
if (isAgentMode) {
|
||||
// For agent mode, poll the agent for TSCM data since push may not be enabled
|
||||
console.log('[TSCM] Starting agent polling mode');
|
||||
pollAgentTscmData(); // Initial poll
|
||||
tscmAgentPollInterval = setInterval(pollAgentTscmData, 2000); // Poll every 2 seconds
|
||||
} else {
|
||||
// For local mode, use SSE stream
|
||||
const streamUrl = '/tscm/sweep/stream';
|
||||
tscmEventSource = new EventSource(streamUrl);
|
||||
|
||||
tscmEventSource.onmessage = function (event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
// If using multi-agent stream, filter for TSCM data
|
||||
if (isAgentMode) {
|
||||
if (data.scan_type === 'tscm' || data.type?.startsWith('tscm') ||
|
||||
data.type === 'wifi_device' || data.type === 'bt_device' ||
|
||||
data.type === 'rf_signal' || data.type === 'threat' ||
|
||||
data.type === 'sweep_progress') {
|
||||
// Add agent info to data for display
|
||||
if (data.agent_name) {
|
||||
data._agent = data.agent_name;
|
||||
}
|
||||
handleTscmEvent(data.payload || data);
|
||||
}
|
||||
} else {
|
||||
tscmEventSource.onmessage = function (event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
handleTscmEvent(data);
|
||||
} catch (e) {
|
||||
console.error('TSCM SSE parse error:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('TSCM SSE parse error:', e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
tscmEventSource.onerror = function () {
|
||||
console.warn('TSCM SSE connection error');
|
||||
};
|
||||
tscmEventSource.onerror = function () {
|
||||
console.warn('TSCM SSE connection error');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function pollAgentTscmData() {
|
||||
if (!isTscmRunning) {
|
||||
if (tscmAgentPollInterval) {
|
||||
clearInterval(tscmAgentPollInterval);
|
||||
tscmAgentPollInterval = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/controller/agents/${currentAgent}/tscm/data`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success' && result.data) {
|
||||
// Agent data is nested: result.data.data (controller wraps agent response)
|
||||
const data = result.data.data || result.data;
|
||||
|
||||
// Process WiFi devices
|
||||
if (data.wifi_devices) {
|
||||
data.wifi_devices.forEach(device => {
|
||||
if (!tscmWifiDevices.find(d => d.bssid === device.bssid)) {
|
||||
handleTscmEvent({ type: 'wifi_device', ...device });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process Bluetooth devices
|
||||
if (data.bt_devices) {
|
||||
data.bt_devices.forEach(device => {
|
||||
if (!tscmBtDevices.find(d => d.address === device.address)) {
|
||||
handleTscmEvent({ type: 'bt_device', ...device });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process anomalies/threats
|
||||
// Agent now uses same ThreatDetector as local mode, so format matches:
|
||||
// threat_type, severity, source, identifier, name, signal_strength
|
||||
if (data.anomalies) {
|
||||
data.anomalies.forEach(threat => {
|
||||
handleTscmEvent({
|
||||
type: 'threat_detected',
|
||||
...threat
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Process RF signals
|
||||
if (data.rf_signals) {
|
||||
data.rf_signals.forEach(signal => {
|
||||
handleTscmEvent({ type: 'rf_signal', ...signal });
|
||||
});
|
||||
}
|
||||
|
||||
// Update progress (simple time-based estimate)
|
||||
if (tscmSweepStartTime) {
|
||||
const elapsed = (Date.now() - tscmSweepStartTime) / 1000;
|
||||
const sweepType = document.getElementById('tscmSweepType')?.value || 'standard';
|
||||
const durations = { quick: 120, standard: 300, full: 900 };
|
||||
const maxDuration = durations[sweepType] || 300;
|
||||
const progress = Math.min(95, (elapsed / maxDuration) * 100);
|
||||
updateTscmProgress({ progress: Math.round(progress), phase: 'Scanning' });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TSCM] Agent poll error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
let tscmCorrelations = [];
|
||||
@@ -9714,7 +9794,7 @@
|
||||
tscmHighInterestDevices.push({
|
||||
id: id,
|
||||
protocol: protocol,
|
||||
name: device.name || device.ssid || `${device.frequency} MHz`,
|
||||
name: device.name || device.essid || device.ssid || (device.frequency ? `${device.frequency.toFixed(3)} MHz` : 'Unknown Device'),
|
||||
score: device.score,
|
||||
classification: device.classification,
|
||||
indicators: device.indicators || [],
|
||||
@@ -9811,7 +9891,7 @@
|
||||
document.getElementById('tscmInformationalCard').classList.toggle('active', counts.informational > 0);
|
||||
document.getElementById('tscmCorrelationsCard').classList.toggle('active', tscmCorrelations.length > 0);
|
||||
|
||||
// Update threat panel count (now shows high interest items)
|
||||
// Update threat panel count (shows high interest items only)
|
||||
document.getElementById('tscmThreatCount').textContent = counts.high_interest;
|
||||
}
|
||||
|
||||
@@ -9873,7 +9953,7 @@
|
||||
// Build detailed view
|
||||
let html = `
|
||||
<div class="device-detail-header ${getClassificationClass(device.classification)}">
|
||||
<h3>${getClassificationIcon(device.classification)} ${escapeHtml(device.name || device.ssid || device.mac || device.bssid || device.frequency + ' MHz')}</h3>
|
||||
<h3>${getClassificationIcon(device.classification)} ${escapeHtml(device.name || device.essid || device.ssid || device.mac || device.bssid || (device.frequency ? device.frequency.toFixed(3) + ' MHz' : 'Unknown'))}</h3>
|
||||
<span class="device-detail-protocol">${protocol.toUpperCase()}</span>
|
||||
</div>
|
||||
|
||||
@@ -10109,7 +10189,7 @@
|
||||
<div class="category-device-header">
|
||||
<span class="category-device-name">
|
||||
${getClassificationIcon(d.classification)}
|
||||
${escapeHtml(d.name || d.ssid || d.mac || d.bssid || d.frequency + ' MHz')}
|
||||
${escapeHtml(d.name || d.ssid || d.mac || d.bssid || (d.frequency ? d.frequency.toFixed(3) + ' MHz' : 'Unknown'))}
|
||||
</span>
|
||||
<span class="category-device-score">${d.score || 0}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user