diff --git a/instance/intercept.db b/instance/intercept.db index 2c3aa90..6780b06 100644 Binary files a/instance/intercept.db and b/instance/intercept.db differ diff --git a/routes/tscm.py b/routes/tscm.py index aad21dc..3649f30 100644 --- a/routes/tscm.py +++ b/routes/tscm.py @@ -36,7 +36,11 @@ from utils.database import ( set_active_tscm_baseline, update_tscm_sweep, ) -from utils.tscm.baseline import BaselineComparator, BaselineRecorder +from utils.tscm.baseline import ( + BaselineComparator, + BaselineRecorder, + get_comparison_for_active_baseline, +) from utils.tscm.correlation import ( CorrelationEngine, get_correlation_engine, @@ -1447,6 +1451,20 @@ def _run_sweep( correlations = correlation.correlate_devices() findings = correlation.get_all_findings() + # Run baseline comparison if a baseline was provided + baseline_comparison = None + if baseline: + comparator = BaselineComparator(baseline) + baseline_comparison = comparator.compare_all( + wifi_devices=list(all_wifi.values()), + bt_devices=list(all_bt.values()), + rf_signals=all_rf + ) + logger.info( + f"Baseline comparison: {baseline_comparison['total_new']} new, " + f"{baseline_comparison['total_missing']} missing" + ) + # Finalize identity engine and get MAC-randomization resistant clusters identity_engine.finalize_all_sessions() identity_summary = identity_engine.get_summary() @@ -1462,6 +1480,7 @@ def _run_sweep( 'severity_counts': severity_counts, 'correlation_summary': findings.get('summary', {}), 'identity_summary': identity_summary.get('statistics', {}), + 'baseline_comparison': baseline_comparison, }, threats_found=threats_found, completed=True @@ -1474,6 +1493,18 @@ def _run_sweep( 'needs_review_count': findings['summary'].get('needs_review', 0), }) + # Emit baseline comparison if a baseline was used + if baseline_comparison: + _emit_event('baseline_comparison', { + 'baseline_id': baseline.get('id'), + 'baseline_name': baseline.get('name'), + 'total_new': baseline_comparison['total_new'], + 'total_missing': baseline_comparison['total_missing'], + 'wifi': baseline_comparison.get('wifi'), + 'bluetooth': baseline_comparison.get('bluetooth'), + 'rf': baseline_comparison.get('rf'), + }) + # Emit device identity cluster findings (MAC-randomization resistant) _emit_event('identity_clusters', { 'total_clusters': identity_summary['statistics'].get('total_clusters', 0), @@ -1494,6 +1525,8 @@ def _run_sweep( 'needs_review_devices': findings['summary'].get('needs_review', 0), 'correlations_found': len(correlations), 'identity_clusters': identity_summary['statistics'].get('total_clusters', 0), + 'baseline_new_devices': baseline_comparison['total_new'] if baseline_comparison else 0, + 'baseline_missing_devices': baseline_comparison['total_missing'] if baseline_comparison else 0, }) except Exception as e: @@ -1625,6 +1658,43 @@ def get_active_baseline(): return jsonify({'status': 'success', 'baseline': baseline}) +@tscm_bp.route('/baseline/compare', methods=['POST']) +def compare_against_baseline(): + """ + Compare provided device data against the active baseline. + + Expects JSON body with: + - wifi_devices: list of WiFi devices (optional) + - bt_devices: list of Bluetooth devices (optional) + - rf_signals: list of RF signals (optional) + + Returns comparison showing new, missing, and matching devices. + """ + data = request.get_json() or {} + + wifi_devices = data.get('wifi_devices') + bt_devices = data.get('bt_devices') + rf_signals = data.get('rf_signals') + + # Use the convenience function that gets active baseline + comparison = get_comparison_for_active_baseline( + wifi_devices=wifi_devices, + bt_devices=bt_devices, + rf_signals=rf_signals + ) + + if comparison is None: + return jsonify({ + 'status': 'error', + 'message': 'No active baseline set' + }), 400 + + return jsonify({ + 'status': 'success', + 'comparison': comparison + }) + + # ============================================================================= # Threat Endpoints # ============================================================================= diff --git a/static/css/modes/tscm.css b/static/css/modes/tscm.css index b6b7e91..550672e 100644 --- a/static/css/modes/tscm.css +++ b/static/css/modes/tscm.css @@ -1,50 +1,5 @@ /* TSCM Styles */ -/* TSCM Collapsible Sections */ -.tscm-collapsible { - padding: 0 !important; - overflow: hidden; -} -.tscm-collapsible-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 12px; - cursor: pointer; - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - transition: background 0.2s; -} -.tscm-collapsible-header:hover { - background: rgba(255, 255, 255, 0.03); -} -.tscm-collapse-icon { - font-size: 14px; - font-weight: 400; - color: var(--text-muted); - transition: transform 0.2s; -} -.tscm-collapsible-content { - padding: 0 12px 12px 12px; -} -.tscm-collapsible.expanded .tscm-collapse-icon { - transform: rotate(45deg); -} - -/* TSCM Tool Buttons */ -.tscm-tool-btn { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - padding: 8px; - min-width: 0; -} -.tscm-tool-btn:hover { - background: rgba(74, 158, 255, 0.15); -} - /* TSCM Threat Cards */ .threat-card { display: flex; diff --git a/templates/adsb_dashboard.html b/templates/adsb_dashboard.html index 7ba5025..1dc7fbc 100644 --- a/templates/adsb_dashboard.html +++ b/templates/adsb_dashboard.html @@ -194,7 +194,8 @@ - + + + + + + + + + - - - + +
+ + + + + + + +
+

Meeting Window

+
+ No active meeting +
+
+ +
+ + +
+ Devices detected during meetings get flagged +
+
+ + +
+

Quick Actions

+
+ + + + +
+
+ + + @@ -44,99 +156,6 @@ - - - - - - - -
-
- - - -
- -
- - -
-
- - -
- - - -
-
- - -
-
- Meeting Window - Inactive -
- - - -
- - -
- - - - -
-