mirror of
https://github.com/smittix/intercept.git
synced 2026-07-20 07:18:11 -07:00
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:
+300
-8
@@ -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
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user