Integrate TSCM correlation engine with sweep and add comprehensive reporting

- Integrate correlation engine into sweep loop for real-time device profiling
- Add API endpoints for findings (/tscm/findings, /tscm/findings/high-interest,
  /tscm/findings/correlations, /tscm/findings/device/<id>)
- Add meeting window endpoints (/tscm/meeting/start, /tscm/meeting/end, /tscm/meeting/status)
- Add comprehensive report generation endpoint (/tscm/report)
- Update frontend to display scores, indicators, and recommended actions
- Add correlation findings display and cross-protocol analysis
- Show sweep summary with assessment on completion
- Add client-safe legal disclaimers throughout UI and API responses
- Sort devices by score (highest first) for prioritized review

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-01-14 14:04:22 +00:00
parent 94f4682f2f
commit ef6eec3cf8
2 changed files with 574 additions and 25 deletions
+300 -8
View File
@@ -37,6 +37,11 @@ from utils.database import (
update_tscm_sweep, update_tscm_sweep,
) )
from utils.tscm.baseline import BaselineComparator, BaselineRecorder from utils.tscm.baseline import BaselineComparator, BaselineRecorder
from utils.tscm.correlation import (
CorrelationEngine,
get_correlation_engine,
reset_correlation_engine,
)
from utils.tscm.detector import ThreatDetector from utils.tscm.detector import ThreatDetector
logger = logging.getLogger('intercept.tscm') logger = logging.getLogger('intercept.tscm')
@@ -910,7 +915,7 @@ def _run_sweep(
Run the TSCM sweep in a background thread. Run the TSCM sweep in a background thread.
This orchestrates data collection from WiFi, BT, and RF sources, This orchestrates data collection from WiFi, BT, and RF sources,
then analyzes results for threats. then analyzes results for threats using the correlation engine.
""" """
global _sweep_running, _current_sweep_id global _sweep_running, _current_sweep_id
@@ -933,8 +938,11 @@ def _run_sweep(
'rf': rf_enabled, 'rf': rf_enabled,
}) })
# Initialize detector # Initialize detector and correlation engine
detector = ThreatDetector(baseline) detector = ThreatDetector(baseline)
correlation = get_correlation_engine()
# Clear old profiles from previous sweeps (keep 24h history)
correlation.clear_old_profiles(24)
# Collect and analyze data # Collect and analyze data
threats_found = 0 threats_found = 0
@@ -973,8 +981,9 @@ def _run_sweep(
sev = threat.get('severity', 'low').lower() sev = threat.get('severity', 'low').lower()
if sev in severity_counts: if sev in severity_counts:
severity_counts[sev] += 1 severity_counts[sev] += 1
# Classify device # Classify device and get correlation profile
classification = detector.classify_wifi_device(network) classification = detector.classify_wifi_device(network)
profile = correlation.analyze_wifi_device(network)
# Send device to frontend # Send device to frontend
_emit_event('wifi_device', { _emit_event('wifi_device', {
'bssid': bssid, 'bssid': bssid,
@@ -984,8 +993,11 @@ def _run_sweep(
'security': network.get('privacy', ''), 'security': network.get('privacy', ''),
'is_threat': is_threat, 'is_threat': is_threat,
'is_new': not classification.get('in_baseline', False), 'is_new': not classification.get('in_baseline', False),
'classification': classification.get('classification', 'review'), 'classification': profile.risk_level.value,
'reasons': classification.get('reasons', []), 'reasons': classification.get('reasons', []),
'score': profile.total_score,
'indicators': [{'type': i.type.value, 'desc': i.description} for i in profile.indicators],
'recommended_action': profile.recommended_action,
}) })
last_wifi_scan = current_time last_wifi_scan = current_time
except Exception as e: except Exception as e:
@@ -1009,8 +1021,9 @@ def _run_sweep(
sev = threat.get('severity', 'low').lower() sev = threat.get('severity', 'low').lower()
if sev in severity_counts: if sev in severity_counts:
severity_counts[sev] += 1 severity_counts[sev] += 1
# Classify device # Classify device and get correlation profile
classification = detector.classify_bt_device(device) classification = detector.classify_bt_device(device)
profile = correlation.analyze_bluetooth_device(device)
# Send device to frontend # Send device to frontend
_emit_event('bt_device', { _emit_event('bt_device', {
'mac': mac, 'mac': mac,
@@ -1019,9 +1032,12 @@ def _run_sweep(
'rssi': device.get('rssi', ''), 'rssi': device.get('rssi', ''),
'is_threat': is_threat, 'is_threat': is_threat,
'is_new': not classification.get('in_baseline', False), 'is_new': not classification.get('in_baseline', False),
'classification': classification.get('classification', 'review'), 'classification': profile.risk_level.value,
'reasons': classification.get('reasons', []), 'reasons': classification.get('reasons', []),
'is_audio_capable': classification.get('is_audio_capable', False), 'is_audio_capable': classification.get('is_audio_capable', False),
'score': profile.total_score,
'indicators': [{'type': i.type.value, 'desc': i.description} for i in profile.indicators],
'recommended_action': profile.recommended_action,
}) })
last_bt_scan = current_time last_bt_scan = current_time
except Exception as e: except Exception as e:
@@ -1052,8 +1068,9 @@ def _run_sweep(
sev = threat.get('severity', 'low').lower() sev = threat.get('severity', 'low').lower()
if sev in severity_counts: if sev in severity_counts:
severity_counts[sev] += 1 severity_counts[sev] += 1
# Classify signal # Classify signal and get correlation profile
classification = detector.classify_rf_signal(signal) classification = detector.classify_rf_signal(signal)
profile = correlation.analyze_rf_signal(signal)
# Send signal to frontend # Send signal to frontend
_emit_event('rf_signal', { _emit_event('rf_signal', {
'frequency': signal['frequency'], 'frequency': signal['frequency'],
@@ -1062,8 +1079,11 @@ def _run_sweep(
'signal_strength': signal.get('signal_strength', 0), 'signal_strength': signal.get('signal_strength', 0),
'is_threat': is_threat, 'is_threat': is_threat,
'is_new': not classification.get('in_baseline', False), 'is_new': not classification.get('in_baseline', False),
'classification': classification.get('classification', 'review'), 'classification': profile.risk_level.value,
'reasons': classification.get('reasons', []), 'reasons': classification.get('reasons', []),
'score': profile.total_score,
'indicators': [{'type': i.type.value, 'desc': i.description} for i in profile.indicators],
'recommended_action': profile.recommended_action,
}) })
last_rf_scan = current_time last_rf_scan = current_time
except Exception as e: except Exception as e:
@@ -1088,6 +1108,10 @@ def _run_sweep(
# Complete sweep # Complete sweep
if _sweep_running and _current_sweep_id: if _sweep_running and _current_sweep_id:
# Run cross-protocol correlation analysis
correlations = correlation.correlate_devices()
findings = correlation.get_all_findings()
update_tscm_sweep( update_tscm_sweep(
_current_sweep_id, _current_sweep_id,
status='completed', status='completed',
@@ -1096,11 +1120,19 @@ def _run_sweep(
'bt_devices': len(all_bt), 'bt_devices': len(all_bt),
'rf_signals': len(all_rf), 'rf_signals': len(all_rf),
'severity_counts': severity_counts, 'severity_counts': severity_counts,
'correlation_summary': findings.get('summary', {}),
}, },
threats_found=threats_found, threats_found=threats_found,
completed=True completed=True
) )
# Emit correlation findings
_emit_event('correlation_findings', {
'correlations': correlations,
'high_interest_count': findings['summary'].get('high_interest', 0),
'needs_review_count': findings['summary'].get('needs_review', 0),
})
_emit_event('sweep_completed', { _emit_event('sweep_completed', {
'sweep_id': _current_sweep_id, 'sweep_id': _current_sweep_id,
'threats_found': threats_found, 'threats_found': threats_found,
@@ -1108,6 +1140,9 @@ def _run_sweep(
'bt_count': len(all_bt), 'bt_count': len(all_bt),
'rf_count': len(all_rf), 'rf_count': len(all_rf),
'severity_counts': severity_counts, 'severity_counts': severity_counts,
'high_interest_devices': findings['summary'].get('high_interest', 0),
'needs_review_devices': findings['summary'].get('needs_review', 0),
'correlations_found': len(correlations),
}) })
except Exception as e: except Exception as e:
@@ -1336,3 +1371,260 @@ def feed_rf():
if data: if data:
_baseline_recorder.add_rf_signal(data) _baseline_recorder.add_rf_signal(data)
return jsonify({'status': 'success'}) return jsonify({'status': 'success'})
# =============================================================================
# Correlation & Findings Endpoints
# =============================================================================
@tscm_bp.route('/findings')
def get_findings():
"""
Get comprehensive TSCM findings from the correlation engine.
Returns all device profiles organized by risk level, cross-protocol
correlations, and summary statistics with client-safe disclaimers.
"""
correlation = get_correlation_engine()
findings = correlation.get_all_findings()
# Add client-safe disclaimer
findings['legal_disclaimer'] = (
"DISCLAIMER: This TSCM screening system identifies wireless and RF anomalies "
"and indicators. Results represent potential items of interest, NOT confirmed "
"surveillance devices. No content has been intercepted or decoded. Findings "
"require professional analysis and verification. This tool does not prove "
"malicious intent or illegal activity."
)
return jsonify({
'status': 'success',
'findings': findings
})
@tscm_bp.route('/findings/high-interest')
def get_high_interest():
"""Get only high-interest devices (score >= 6)."""
correlation = get_correlation_engine()
high_interest = correlation.get_high_interest_devices()
return jsonify({
'status': 'success',
'count': len(high_interest),
'devices': [d.to_dict() for d in high_interest],
'disclaimer': (
"High-interest classification indicates multiple indicators warrant "
"investigation. This does NOT confirm surveillance activity."
)
})
@tscm_bp.route('/findings/correlations')
def get_correlations():
"""Get cross-protocol correlation analysis."""
correlation = get_correlation_engine()
correlations = correlation.correlate_devices()
return jsonify({
'status': 'success',
'count': len(correlations),
'correlations': correlations,
'explanation': (
"Correlations identify devices across different protocols (Bluetooth, "
"WiFi, RF) that exhibit related behavior patterns. Cross-protocol "
"activity is one indicator among many in TSCM analysis."
)
})
@tscm_bp.route('/findings/device/<identifier>')
def get_device_profile(identifier: str):
"""Get detailed profile for a specific device."""
correlation = get_correlation_engine()
# Search all protocols for the identifier
for protocol in ['bluetooth', 'wifi', 'rf']:
key = f"{protocol}:{identifier}"
if key in correlation.device_profiles:
profile = correlation.device_profiles[key]
return jsonify({
'status': 'success',
'profile': profile.to_dict()
})
return jsonify({
'status': 'error',
'message': 'Device not found'
}), 404
# =============================================================================
# Meeting Window Endpoints (for time correlation)
# =============================================================================
@tscm_bp.route('/meeting/start', methods=['POST'])
def start_meeting():
"""
Mark the start of a sensitive period (meeting, briefing, etc.).
Devices detected during this window will receive additional scoring
for meeting-correlated activity.
"""
correlation = get_correlation_engine()
correlation.start_meeting_window()
_emit_event('meeting_started', {
'timestamp': datetime.now().isoformat(),
'message': 'Sensitive period monitoring active'
})
return jsonify({
'status': 'success',
'message': 'Meeting window started - devices detected now will be flagged'
})
@tscm_bp.route('/meeting/end', methods=['POST'])
def end_meeting():
"""Mark the end of a sensitive period."""
correlation = get_correlation_engine()
correlation.end_meeting_window()
_emit_event('meeting_ended', {
'timestamp': datetime.now().isoformat()
})
return jsonify({
'status': 'success',
'message': 'Meeting window ended'
})
@tscm_bp.route('/meeting/status')
def meeting_status():
"""Check if currently in a meeting window."""
correlation = get_correlation_engine()
in_meeting = correlation.is_during_meeting()
return jsonify({
'status': 'success',
'in_meeting': in_meeting,
'windows': [
{
'start': start.isoformat(),
'end': end.isoformat() if end else None
}
for start, end in correlation.meeting_windows
]
})
# =============================================================================
# Report Generation Endpoints
# =============================================================================
@tscm_bp.route('/report')
def generate_report():
"""
Generate a comprehensive TSCM sweep report.
Includes all findings, correlations, indicators, and recommended actions
in a client-presentable format with appropriate disclaimers.
"""
correlation = get_correlation_engine()
findings = correlation.get_all_findings()
# Build the report structure
report = {
'generated_at': datetime.now().isoformat(),
'report_type': 'TSCM Wireless Surveillance Screening',
'executive_summary': {
'total_devices_analyzed': findings['summary']['total_devices'],
'high_interest_items': findings['summary']['high_interest'],
'items_requiring_review': findings['summary']['needs_review'],
'cross_protocol_correlations': findings['summary']['correlations_found'],
'assessment': _generate_assessment(findings['summary']),
},
'methodology': {
'protocols_scanned': ['Bluetooth Low Energy', 'WiFi 802.11', 'RF Spectrum'],
'analysis_techniques': [
'Device fingerprinting',
'Signal stability analysis',
'Cross-protocol correlation',
'Time-based pattern detection',
'Manufacturer identification',
],
'scoring_model': {
'informational': '0-2 points - Known or expected devices',
'needs_review': '3-5 points - Unusual devices requiring assessment',
'high_interest': '6+ points - Multiple indicators warrant investigation',
}
},
'findings': {
'high_interest': findings['devices']['high_interest'],
'needs_review': findings['devices']['needs_review'],
'informational': findings['devices']['informational'],
},
'correlations': findings['correlations'],
'disclaimers': {
'legal': (
"This report documents findings from a wireless and RF surveillance "
"screening. Results indicate anomalies and items of interest, NOT "
"confirmed surveillance devices. No communications content has been "
"intercepted, recorded, or decoded. This screening does not prove "
"malicious intent, illegal activity, or the presence of surveillance "
"equipment. All findings require professional verification."
),
'technical': (
"Detection capabilities are limited by equipment sensitivity, "
"environmental factors, and the technical sophistication of any "
"potential devices. Absence of findings does NOT guarantee absence "
"of surveillance equipment."
),
'recommendations': (
"High-interest items should be investigated by qualified TSCM "
"professionals using appropriate physical inspection techniques. "
"This electronic sweep is one component of comprehensive TSCM."
)
}
}
return jsonify({
'status': 'success',
'report': report
})
def _generate_assessment(summary: dict) -> str:
"""Generate an assessment summary based on findings."""
high = summary.get('high_interest', 0)
review = summary.get('needs_review', 0)
correlations = summary.get('correlations_found', 0)
if high > 0 or correlations > 0:
return (
f"ELEVATED CONCERN: {high} high-interest item(s) and "
f"{correlations} cross-protocol correlation(s) detected. "
"Professional TSCM inspection recommended."
)
elif review > 3:
return (
f"MODERATE CONCERN: {review} items requiring review. "
"Further analysis recommended to characterize unknown devices."
)
elif review > 0:
return (
f"LOW CONCERN: {review} item(s) flagged for review. "
"Likely benign but verification recommended."
)
else:
return (
"BASELINE ENVIRONMENT: No significant anomalies detected. "
"Environment appears consistent with expected wireless activity."
)
+274 -17
View File
@@ -2063,16 +2063,141 @@
margin-left: 6px; margin-left: 6px;
font-size: 10px; font-size: 10px;
} }
.tscm-device-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.tscm-device-name { .tscm-device-name {
font-weight: 600; font-weight: 600;
font-size: 12px; font-size: 12px;
margin-bottom: 2px;
} }
.tscm-device-meta { .tscm-device-meta {
font-size: 10px; font-size: 10px;
color: var(--text-muted); color: var(--text-muted);
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap;
}
.tscm-device-indicators {
margin-top: 6px;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.indicator-tag {
font-size: 9px;
padding: 2px 6px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.1);
color: var(--text-muted);
white-space: nowrap;
}
.score-badge {
font-size: 10px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 600;
}
.score-badge.score-low {
background: rgba(0, 204, 0, 0.2);
color: #00cc00;
}
.score-badge.score-medium {
background: rgba(255, 204, 0, 0.2);
color: #ffcc00;
}
.score-badge.score-high {
background: rgba(255, 51, 51, 0.2);
color: #ff3333;
}
.tscm-action {
margin-top: 4px;
font-size: 10px;
color: #ff9933;
font-weight: 600;
text-transform: uppercase;
}
.tscm-correlations {
margin-top: 16px;
padding: 12px;
background: rgba(255, 153, 51, 0.1);
border-radius: 6px;
border: 1px solid #ff9933;
}
.tscm-correlations h4 {
margin: 0 0 8px 0;
font-size: 12px;
color: #ff9933;
}
.correlation-item {
padding: 8px;
margin-bottom: 6px;
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
font-size: 11px;
}
.correlation-devices {
font-size: 10px;
color: var(--text-muted);
margin-top: 4px;
}
.tscm-summary-box {
display: flex;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.summary-stat {
flex: 1;
min-width: 100px;
padding: 12px;
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
text-align: center;
}
.summary-stat .count {
font-size: 24px;
font-weight: 700;
}
.summary-stat .label {
font-size: 10px;
color: var(--text-muted);
text-transform: uppercase;
}
.summary-stat.high-interest .count { color: #ff3333; }
.summary-stat.needs-review .count { color: #ffcc00; }
.summary-stat.informational .count { color: #00cc00; }
.tscm-assessment {
padding: 10px 14px;
margin: 12px 0;
border-radius: 6px;
font-size: 13px;
}
.tscm-assessment.high-interest {
background: rgba(255, 51, 51, 0.15);
border: 1px solid #ff3333;
color: #ff3333;
}
.tscm-assessment.needs-review {
background: rgba(255, 204, 0, 0.15);
border: 1px solid #ffcc00;
color: #ffcc00;
}
.tscm-assessment.informational {
background: rgba(0, 204, 0, 0.15);
border: 1px solid #00cc00;
color: #00cc00;
}
.tscm-disclaimer {
font-size: 10px;
color: var(--text-muted);
font-style: italic;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
margin-top: 8px;
} }
.tscm-threat-list { .tscm-threat-list {
display: flex; display: flex;
@@ -2236,6 +2361,13 @@
<!-- TSCM Dashboard --> <!-- TSCM Dashboard -->
<div id="tscmVisuals" class="tscm-dashboard" style="display: none; padding: 16px;"> <div id="tscmVisuals" class="tscm-dashboard" style="display: none; padding: 16px;">
<!-- Legal Disclaimer Banner -->
<div class="tscm-legal-banner" style="margin-bottom: 12px; padding: 8px 12px; background: rgba(74, 158, 255, 0.1); border: 1px solid rgba(74, 158, 255, 0.3); border-radius: 4px; font-size: 10px; color: var(--text-muted);">
<strong>TSCM Screening Tool:</strong> This system identifies wireless and RF anomalies.
Findings are indicators, NOT confirmed surveillance devices.
No content is intercepted or decoded. Professional verification required.
</div>
<!-- Threat Summary Banner --> <!-- Threat Summary Banner -->
<div class="tscm-threat-banner"> <div class="tscm-threat-banner">
<div class="threat-card critical" id="tscmCriticalCard"> <div class="threat-card critical" id="tscmCriticalCard">
@@ -2256,6 +2388,12 @@
</div> </div>
</div> </div>
<!-- Sweep Summary (shown after sweep completes) -->
<div id="tscmSweepSummary" style="display: none; margin-bottom: 16px;"></div>
<!-- Cross-Protocol Correlations (shown when correlations found) -->
<div id="tscmCorrelationsContainer" style="display: none;"></div>
<!-- Main Content Grid --> <!-- Main Content Grid -->
<div class="tscm-main-grid"> <div class="tscm-main-grid">
<!-- WiFi Panel --> <!-- WiFi Panel -->
@@ -9885,6 +10023,8 @@
}; };
} }
let tscmCorrelations = [];
function handleTscmEvent(data) { function handleTscmEvent(data) {
switch (data.type) { switch (data.type) {
case 'sweep_progress': case 'sweep_progress':
@@ -9902,6 +10042,9 @@
case 'threat_detected': case 'threat_detected':
addTscmThreat(data); addTscmThreat(data);
break; break;
case 'correlation_findings':
handleCorrelationFindings(data);
break;
case 'sweep_completed': case 'sweep_completed':
completeTscmSweep(data); completeTscmSweep(data);
break; break;
@@ -9912,6 +10055,11 @@
} }
} }
function handleCorrelationFindings(data) {
tscmCorrelations = data.correlations || [];
updateCorrelationsDisplay();
}
function addTscmWifiDevice(device) { function addTscmWifiDevice(device) {
// Check if already exists // Check if already exists
const exists = tscmWifiDevices.some(d => d.bssid === device.bssid); const exists = tscmWifiDevices.some(d => d.bssid === device.bssid);
@@ -10027,23 +10175,43 @@
} }
} }
function formatIndicators(indicators) {
if (!indicators || indicators.length === 0) return '';
return indicators.map(i => `<span class="indicator-tag">${escapeHtml(i.desc || i.type)}</span>`).join(' ');
}
function getScoreBadge(score) {
if (score === undefined || score === null) return '';
let scoreClass = 'score-low';
if (score >= 6) scoreClass = 'score-high';
else if (score >= 3) scoreClass = 'score-medium';
return `<span class="score-badge ${scoreClass}">Score: ${score}</span>`;
}
function updateTscmDisplays() { function updateTscmDisplays() {
// Update WiFi list // Update WiFi list
const wifiList = document.getElementById('tscmWifiList'); const wifiList = document.getElementById('tscmWifiList');
if (tscmWifiDevices.length === 0) { if (tscmWifiDevices.length === 0) {
wifiList.innerHTML = '<div class="tscm-empty">No WiFi networks detected</div>'; wifiList.innerHTML = '<div class="tscm-empty">No WiFi networks detected</div>';
} else { } else {
wifiList.innerHTML = tscmWifiDevices.map(d => ` // Sort by score (highest first)
const sorted = [...tscmWifiDevices].sort((a, b) => (b.score || 0) - (a.score || 0));
wifiList.innerHTML = sorted.map(d => `
<div class="tscm-device-item ${getClassificationClass(d.classification)}"> <div class="tscm-device-item ${getClassificationClass(d.classification)}">
<div class="tscm-device-name"> <div class="tscm-device-header">
<span class="classification-indicator">${getClassificationIcon(d.classification)}</span> <div class="tscm-device-name">
${escapeHtml(d.ssid || d.bssid || 'Hidden')} <span class="classification-indicator">${getClassificationIcon(d.classification)}</span>
${escapeHtml(d.ssid || d.bssid || 'Hidden')}
</div>
${getScoreBadge(d.score)}
</div> </div>
<div class="tscm-device-meta"> <div class="tscm-device-meta">
<span>${d.bssid}</span> <span>${d.bssid}</span>
<span>${d.signal || '--'} dBm</span> <span>${d.signal || '--'} dBm</span>
<span>${d.security || 'Open'}</span>
</div> </div>
${d.reasons && d.reasons.length > 0 ? `<div class="tscm-device-reasons">${d.reasons.join(' • ')}</div>` : ''} ${d.indicators && d.indicators.length > 0 ? `<div class="tscm-device-indicators">${formatIndicators(d.indicators)}</div>` : ''}
${d.recommended_action && d.recommended_action !== 'monitor' ? `<div class="tscm-action">Action: ${d.recommended_action}</div>` : ''}
</div> </div>
`).join(''); `).join('');
} }
@@ -10054,18 +10222,25 @@
if (tscmBtDevices.length === 0) { if (tscmBtDevices.length === 0) {
btList.innerHTML = '<div class="tscm-empty">No Bluetooth devices detected</div>'; btList.innerHTML = '<div class="tscm-empty">No Bluetooth devices detected</div>';
} else { } else {
btList.innerHTML = tscmBtDevices.map(d => ` // Sort by score (highest first)
const sorted = [...tscmBtDevices].sort((a, b) => (b.score || 0) - (a.score || 0));
btList.innerHTML = sorted.map(d => `
<div class="tscm-device-item ${getClassificationClass(d.classification)}"> <div class="tscm-device-item ${getClassificationClass(d.classification)}">
<div class="tscm-device-name"> <div class="tscm-device-header">
<span class="classification-indicator">${getClassificationIcon(d.classification)}</span> <div class="tscm-device-name">
${escapeHtml(d.name || 'Unknown')} <span class="classification-indicator">${getClassificationIcon(d.classification)}</span>
${d.is_audio_capable ? '<span class="audio-badge" title="Audio-capable device">🎤</span>' : ''} ${escapeHtml(d.name || 'Unknown')}
${d.is_audio_capable ? '<span class="audio-badge" title="Audio-capable device">🎤</span>' : ''}
</div>
${getScoreBadge(d.score)}
</div> </div>
<div class="tscm-device-meta"> <div class="tscm-device-meta">
<span>${d.mac}</span> <span>${d.mac}</span>
<span>${d.rssi || '--'} dBm</span> <span>${d.rssi || '--'} dBm</span>
<span>${d.type || 'Unknown'}</span>
</div> </div>
${d.reasons && d.reasons.length > 0 ? `<div class="tscm-device-reasons">${d.reasons.join(' • ')}</div>` : ''} ${d.indicators && d.indicators.length > 0 ? `<div class="tscm-device-indicators">${formatIndicators(d.indicators)}</div>` : ''}
${d.recommended_action && d.recommended_action !== 'monitor' ? `<div class="tscm-action">Action: ${d.recommended_action}</div>` : ''}
</div> </div>
`).join(''); `).join('');
} }
@@ -10076,17 +10251,24 @@
if (tscmRfSignals.length === 0) { if (tscmRfSignals.length === 0) {
rfList.innerHTML = '<div class="tscm-empty">No RF signals detected</div>'; rfList.innerHTML = '<div class="tscm-empty">No RF signals detected</div>';
} else { } else {
rfList.innerHTML = tscmRfSignals.map(s => ` // Sort by score (highest first)
const sorted = [...tscmRfSignals].sort((a, b) => (b.score || 0) - (a.score || 0));
rfList.innerHTML = sorted.map(s => `
<div class="tscm-device-item ${getClassificationClass(s.classification)}"> <div class="tscm-device-item ${getClassificationClass(s.classification)}">
<div class="tscm-device-name"> <div class="tscm-device-header">
<span class="classification-indicator">${getClassificationIcon(s.classification)}</span> <div class="tscm-device-name">
${s.frequency.toFixed(3)} MHz <span class="classification-indicator">${getClassificationIcon(s.classification)}</span>
${s.frequency.toFixed(3)} MHz
</div>
${getScoreBadge(s.score)}
</div> </div>
<div class="tscm-device-meta"> <div class="tscm-device-meta">
<span>${s.band}</span> <span>${s.band}</span>
<span>${s.power.toFixed(1)} dBm</span> <span>${s.power.toFixed(1)} dBm</span>
<span>+${(s.signal_strength || 0).toFixed(1)} dB above noise</span>
</div> </div>
${s.reasons && s.reasons.length > 0 ? `<div class="tscm-device-reasons">${s.reasons.join(' • ')}</div>` : ''} ${s.indicators && s.indicators.length > 0 ? `<div class="tscm-device-indicators">${formatIndicators(s.indicators)}</div>` : ''}
${s.recommended_action && s.recommended_action !== 'monitor' ? `<div class="tscm-action">Action: ${s.recommended_action}</div>` : ''}
</div> </div>
`).join(''); `).join('');
} }
@@ -10112,6 +10294,32 @@
} }
} }
function updateCorrelationsDisplay() {
const container = document.getElementById('tscmCorrelationsContainer');
if (!container) return;
if (tscmCorrelations.length === 0) {
container.innerHTML = '';
container.style.display = 'none';
return;
}
container.style.display = 'block';
container.innerHTML = `
<div class="tscm-correlations">
<h4>Cross-Protocol Correlations (${tscmCorrelations.length})</h4>
${tscmCorrelations.map(c => `
<div class="correlation-item">
<strong>${escapeHtml(c.description)}</strong>
<div class="correlation-devices">
Devices: ${c.devices.join(', ')} | Protocols: ${c.protocols.join(', ')}
</div>
</div>
`).join('')}
</div>
`;
}
function completeTscmSweep(data) { function completeTscmSweep(data) {
isTscmRunning = false; isTscmRunning = false;
if (tscmEventSource) { if (tscmEventSource) {
@@ -10128,6 +10336,55 @@
// Final update of counts // Final update of counts
updateTscmThreatCounts(); updateTscmThreatCounts();
// Display sweep summary with correlation results
const summaryContainer = document.getElementById('tscmSweepSummary');
if (summaryContainer && data) {
const highInterest = data.high_interest_devices || 0;
const needsReview = data.needs_review_devices || 0;
const correlations = data.correlations_found || 0;
let assessment = 'BASELINE ENVIRONMENT';
let assessmentClass = 'informational';
if (highInterest > 0 || correlations > 0) {
assessment = 'ELEVATED CONCERN';
assessmentClass = 'high-interest';
} else if (needsReview > 3) {
assessment = 'MODERATE CONCERN';
assessmentClass = 'needs-review';
} else if (needsReview > 0) {
assessment = 'LOW CONCERN';
assessmentClass = 'needs-review';
}
summaryContainer.innerHTML = `
<div class="tscm-summary-box">
<div class="summary-stat high-interest">
<div class="count">${highInterest}</div>
<div class="label">High Interest</div>
</div>
<div class="summary-stat needs-review">
<div class="count">${needsReview}</div>
<div class="label">Needs Review</div>
</div>
<div class="summary-stat">
<div class="count">${correlations}</div>
<div class="label">Correlations</div>
</div>
</div>
<div class="tscm-assessment ${assessmentClass}">
<strong>Assessment:</strong> ${assessment}
</div>
<div class="tscm-disclaimer">
This screening identifies wireless/RF anomalies, NOT confirmed surveillance devices.
Findings require professional verification.
</div>
`;
summaryContainer.style.display = 'block';
}
// Update correlations display
updateCorrelationsDisplay();
} }
async function tscmRecordBaseline() { async function tscmRecordBaseline() {