v2.26.0: fix SSE fanout crash and branded logo FOUC

- Fix SSE fanout thread AttributeError when source queue is None during
  interpreter shutdown by snapshotting to local variable with null guard
- Fix branded "i" logo rendering oversized on first page load (FOUC) by
  adding inline width/height to SVG elements across 10 templates
- Bump version to 2.26.0 in config.py, pyproject.toml, and CHANGELOG.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-03-13 11:51:27 +00:00
parent 00362bcd57
commit e00fbfddc1
183 changed files with 2006 additions and 4243 deletions
+22 -22
View File
@@ -7,6 +7,7 @@ threat detection, and reporting.
from __future__ import annotations
import contextlib
import json
import logging
import queue
@@ -23,9 +24,9 @@ from data.tscm_frequencies import (
get_sweep_preset,
)
from utils.database import (
acknowledge_tscm_threat,
add_device_timeline_entry,
add_tscm_threat,
acknowledge_tscm_threat,
cleanup_old_timeline_entries,
create_tscm_schedule,
create_tscm_sweep,
@@ -43,6 +44,8 @@ from utils.database import (
update_tscm_schedule,
update_tscm_sweep,
)
from utils.event_pipeline import process_event
from utils.sse import sse_stream_fanout
from utils.tscm.baseline import (
BaselineComparator,
BaselineRecorder,
@@ -56,12 +59,10 @@ from utils.tscm.correlation import (
from utils.tscm.detector import ThreatDetector
from utils.tscm.device_identity import (
get_identity_engine,
reset_identity_engine,
ingest_ble_dict,
ingest_wifi_dict,
reset_identity_engine,
)
from utils.event_pipeline import process_event
from utils.sse import sse_stream_fanout
# Import unified Bluetooth scanner helper for TSCM integration
try:
@@ -659,8 +660,8 @@ def _scan_bluetooth_devices(interface: str, duration: int = 10) -> list[dict]:
Uses the BLE scanner module (bleak library) for proper manufacturer ID
detection, with fallback to system tools if bleak is unavailable.
"""
import platform
import os
import platform
import re
import shutil
import subprocess
@@ -874,10 +875,8 @@ def _scan_bluetooth_devices(interface: str, duration: int = 10) -> list[dict]:
except subprocess.TimeoutExpired:
process.kill()
try:
with contextlib.suppress(OSError):
os.close(master_fd)
except OSError:
pass
logger.info(f"bluetoothctl scan found {len(devices)} devices")
@@ -914,7 +913,8 @@ def _scan_rf_signals(
"""
# Default stop check uses module-level _sweep_running
if stop_check is None:
stop_check = lambda: not _sweep_running
def stop_check():
return not _sweep_running
import os
import shutil
import subprocess
@@ -954,11 +954,11 @@ def _scan_rf_signals(
# Tool exists but no device detected — try anyway (detection may have failed)
sdr_type = 'rtlsdr'
sweep_tool_path = rtl_power_path
logger.info(f"No SDR detected but rtl_power found, attempting RTL-SDR scan")
logger.info("No SDR detected but rtl_power found, attempting RTL-SDR scan")
elif hackrf_sweep_path:
sdr_type = 'hackrf'
sweep_tool_path = hackrf_sweep_path
logger.info(f"No SDR detected but hackrf_sweep found, attempting HackRF scan")
logger.info("No SDR detected but hackrf_sweep found, attempting HackRF scan")
if not sweep_tool_path:
logger.warning("No supported sweep tool found (rtl_power or hackrf_sweep)")
@@ -1059,14 +1059,14 @@ def _scan_rf_signals(
# Parse the CSV output (same format for both rtl_power and hackrf_sweep)
if os.path.exists(tmp_path) and os.path.getsize(tmp_path) > 0:
with open(tmp_path, 'r') as f:
with open(tmp_path) as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 7:
try:
# CSV format: date, time, hz_low, hz_high, hz_step, samples, db_values...
hz_low = int(parts[2].strip())
hz_high = int(parts[3].strip())
int(parts[3].strip())
hz_step = float(parts[4].strip())
db_values = [float(x) for x in parts[6:] if x.strip()]
@@ -1100,10 +1100,8 @@ def _scan_rf_signals(
finally:
# Cleanup temp file
try:
with contextlib.suppress(OSError):
os.unlink(tmp_path)
except OSError:
pass
# Deduplicate nearby frequencies (within 100kHz)
if signals:
@@ -1816,9 +1814,11 @@ def _generate_assessment(summary: dict) -> str:
# =============================================================================
# Import sub-modules to register routes on tscm_bp
# =============================================================================
from routes.tscm import sweep # noqa: E402, F401
from routes.tscm import baseline # noqa: E402, F401
from routes.tscm import cases # noqa: E402, F401
from routes.tscm import meeting # noqa: E402, F401
from routes.tscm import analysis # noqa: E402, F401
from routes.tscm import schedules # noqa: E402, F401
from routes.tscm import (
analysis, # noqa: E402, F401
baseline, # noqa: E402, F401
cases, # noqa: E402, F401
meeting, # noqa: E402, F401
schedules, # noqa: E402, F401
sweep, # noqa: E402, F401
)
+5 -6
View File
@@ -14,7 +14,6 @@ from datetime import datetime
from flask import Response, jsonify, request
from routes.tscm import (
_current_sweep_id,
_generate_assessment,
tscm_bp,
)
@@ -253,9 +252,9 @@ def get_pdf_report():
summary, and mandatory disclaimers.
"""
try:
from utils.tscm.reports import generate_report, get_pdf_report
from utils.tscm.advanced import detect_sweep_capabilities, get_timeline_manager
from routes.tscm import _current_sweep_id
from utils.tscm.advanced import detect_sweep_capabilities, get_timeline_manager
from utils.tscm.reports import generate_report, get_pdf_report
sweep_id = request.args.get('sweep_id', _current_sweep_id, type=int)
if not sweep_id:
@@ -306,9 +305,9 @@ def get_technical_annex():
for audit purposes. No packet data included.
"""
try:
from utils.tscm.reports import generate_report, get_json_annex, get_csv_annex
from utils.tscm.advanced import detect_sweep_capabilities, get_timeline_manager
from routes.tscm import _current_sweep_id
from utils.tscm.advanced import detect_sweep_capabilities, get_timeline_manager
from utils.tscm.reports import generate_report, get_csv_annex, get_json_annex
sweep_id = request.args.get('sweep_id', _current_sweep_id, type=int)
format_type = request.args.get('format', 'json')
@@ -900,8 +899,8 @@ def get_device_timeline_endpoint(identifier: str):
and meeting window correlation.
"""
try:
from utils.tscm.advanced import get_timeline_manager
from utils.database import get_device_timeline
from utils.tscm.advanced import get_timeline_manager
protocol = request.args.get('protocol', 'bluetooth')
since_hours = request.args.get('since_hours', 24, type=int)
-2
View File
@@ -25,7 +25,6 @@ from utils.database import (
set_active_tscm_baseline,
)
from utils.tscm.baseline import (
BaselineComparator,
get_comparison_for_active_baseline,
)
@@ -213,7 +212,6 @@ def get_baseline_diff(baseline_id: int, sweep_id: int):
def get_baseline_health(baseline_id: int):
"""Get health assessment for a baseline."""
try:
from utils.tscm.advanced import BaselineHealth
baseline = get_tscm_baseline(baseline_id)
if not baseline:
+1 -3
View File
@@ -91,7 +91,6 @@ def start_tracked_meeting():
"""
from utils.database import start_meeting_window
from utils.tscm.advanced import get_timeline_manager
from routes.tscm import _current_sweep_id
data = request.get_json() or {}
@@ -156,9 +155,9 @@ def end_tracked_meeting(meeting_id: int):
def get_meeting_summary_endpoint(meeting_id: int):
"""Get detailed summary of device activity during a meeting."""
try:
from routes.tscm import _current_sweep_id
from utils.database import get_meeting_windows
from utils.tscm.advanced import generate_meeting_summary, get_timeline_manager
from routes.tscm import _current_sweep_id
# Get meeting window
windows = get_meeting_windows(_current_sweep_id or 0)
@@ -194,7 +193,6 @@ def get_meeting_summary_endpoint(meeting_id: int):
def get_active_meeting():
"""Get currently active meeting window."""
from utils.database import get_active_meeting_window
from routes.tscm import _current_sweep_id
meeting = get_active_meeting_window(_current_sweep_id)
-1
View File
@@ -16,7 +16,6 @@ from routes.tscm import (
_get_schedule_timezone,
_next_run_from_cron,
_start_sweep_internal,
_sweep_running,
tscm_bp,
)
from utils.database import (
+3 -11
View File
@@ -7,27 +7,25 @@ Handles /sweep/*, /status, /devices, /presets/*, /feed/*,
from __future__ import annotations
import json
import logging
import os
import platform
import re
import shutil
import subprocess
from typing import Any
from flask import Response, jsonify, request
from data.tscm_frequencies import get_all_sweep_presets, get_sweep_preset
from routes.tscm import (
_baseline_recorder,
_current_sweep_id,
_emit_event,
_start_sweep_internal,
_sweep_running,
tscm_bp,
tscm_queue,
_baseline_recorder,
)
from data.tscm_frequencies import get_all_sweep_presets, get_sweep_preset
from utils.database import get_tscm_sweep, update_tscm_sweep
from utils.event_pipeline import process_event
from utils.sse import sse_stream_fanout
@@ -38,7 +36,6 @@ logger = logging.getLogger('intercept.tscm')
@tscm_bp.route('/status')
def tscm_status():
"""Check if any TSCM operation is currently running."""
from routes.tscm import _sweep_running
return jsonify({'running': _sweep_running})
@@ -98,7 +95,6 @@ def stop_sweep():
@tscm_bp.route('/sweep/status')
def sweep_status():
"""Get current sweep status."""
from routes.tscm import _sweep_running, _current_sweep_id
status = {
'running': _sweep_running,
@@ -116,7 +112,6 @@ def sweep_status():
@tscm_bp.route('/sweep/stream')
def sweep_stream():
"""SSE stream for real-time sweep updates."""
from routes.tscm import tscm_queue
def _on_msg(msg: dict[str, Any]) -> None:
process_event('tscm', msg, msg.get('type'))
@@ -218,7 +213,7 @@ def get_tscm_devices():
capture_output=True, text=True, timeout=5
)
blocks = re.split(r'(?=^hci\d+:)', result.stdout, flags=re.MULTILINE)
for idx, block in enumerate(blocks):
for _idx, block in enumerate(blocks):
if block.strip():
first_line = block.split('\n')[0]
match = re.match(r'(hci\d+):', first_line)
@@ -353,7 +348,6 @@ def get_preset(preset_name: str):
@tscm_bp.route('/feed/wifi', methods=['POST'])
def feed_wifi():
"""Feed WiFi device data for baseline recording."""
from routes.tscm import _baseline_recorder
data = request.get_json()
if data:
@@ -367,7 +361,6 @@ def feed_wifi():
@tscm_bp.route('/feed/bluetooth', methods=['POST'])
def feed_bluetooth():
"""Feed Bluetooth device data for baseline recording."""
from routes.tscm import _baseline_recorder
data = request.get_json()
if data:
@@ -378,7 +371,6 @@ def feed_bluetooth():
@tscm_bp.route('/feed/rf', methods=['POST'])
def feed_rf():
"""Feed RF signal data for baseline recording."""
from routes.tscm import _baseline_recorder
data = request.get_json()
if data: