0

New Open source SDR anomaly detection tool

link to my project: https://github.com/jacobsdr95/RF-SENTINEL-V2

Introduction RF Sentinel is a radio-frequency spectrum monitoring tool for blue team (defensive) use. It connects to a HackRF One (or equivalent SDR), performs continuous sweeps across a configurable channel watchlist, and detects anomalous behaviour using a three-layer AI pipeline.

Fully passive — the tool only receives signals (RX) and never transmits. A hard TX-Guard layer immediately aborts the program if any TX call bypasses the receive-only proxy.

The system is written in pure Python with no special drivers beyond pyhackrf / pyrtlsdr, and runs reliably on Linux, macOS, and WSL2.

Features Group Detail AI / ML Dual-AI: DBSCAN + IsolationForest (global) · HMM / LOF / OCSVM / IF (per-type) · RandomForest / LightGBM / XGBoost (supervised) DSP Savitzky-Golay spectrum pre-filter · DTW FHSS pattern tracker · CAF cyclostationary analysis · FFT / PSD / kurtosis / sample entropy / STFT entropy Confidence gating 4 layers: Persistence · Critical Z-score · EMA drift · RF downgrade-only Wideband survey 300–1000 MHz scan to discover channels outside the watchlist Agent plugins SOM · Teacher-Student · Cognitive Freq · Active Learning · Automated Response · Protocol Decoder Web UI Flask :1717 · Chart.js donut/line/bar · SSE real-time · CSV/JSON export · Calibration wizard Database SQLite with schema versioning · auto-label pipeline · spectrum history Safety ReceiveOnlySDRProxy TX-guard · SHA-256 hash integrity check · NO SILENT FAILURES policy Testing 20+ test classes, zero hardware required Requirements Hardware HackRF One (https://greatscottgadgets.com/hackrf/) — required for normal mode USB 2.0 or better HackRF does not need a calibrated LNA/PA to run, but a calibration pass is required for accurate dBm readings (see Configuration & Calibration) No HackRF? Run --web-only to start the UI and DB without hardware. Useful for replaying saved events or debugging routes.

Software (Python 3.8+) Python >= 3.8 numpy >= 1.21 scipy >= 1.7 scikit-learn >= 1.0 joblib >= 1.1 flask >= 2.0 pyhackrf >= 0.1.0 # or pyrtlsdr for RTL-SDR Full dependency list: requirements.txt.

Installation

  1. Clone the repo git clone https://github.com/your-org/rf-sentinel.git cd rf-sentinel
  2. Create a virtual environment (recommended) python3 -m venv .venv source .venv/bin/activate # Linux/macOS

.venv\Scripts\activate # Windows

  1. Install dependencies pip install -r requirements.txt Optional deps (LightGBM, XGBoost, PDF reports, etc.) are in the same file under the [OPTIONAL] section. Uncomment or install individually as needed.

  2. Install HackRF driver (Linux)

Ubuntu / Debian

sudo apt install hackrf libhackrf-dev

Verify connection

hackrf_info 5. Grant USB permissions (Linux — avoid sudo) sudo cp udev/53-hackrf.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules sudo udevadm trigger

Replug HackRF

  1. Verify the installation python test_sdr_sentinel.py # full test suite, no hardware required python check_no_swallow.py # verify no silently-swallowed errors Configuration & Calibration Environment variables Variable Default Description RF_SENTINEL_LOGS_DIR ./rf_logs/ Directory for DB, logs, and evidence RF_WEBHOOK_TELEGRAM — Telegram webhook URL for alerts RF_WEBHOOK_DISCORD — Discord webhook URL RF_WEBHOOK_SLACK — Slack webhook URL RF_RESPONSE_SCRIPT — Shell script to run on HIGH/CRITICAL alert Example .env:

export RF_SENTINEL_LOGS_DIR=/var/lib/rf-sentinel/logs export RF_WEBHOOK_TELEGRAM=https://api.telegram.org/bot<TOKEN>/sendMessage Power calibration (required for accurate dBm readings) Important: Without calibration, dBm values are relative only (default offset -50.0 dB). The system still detects anomalies via Z-score (relative), but absolute dBm thresholds will be wrong.

Each frequency band has an offset_dB:

offset_dB = true_power_dBm − sdr_reading_dBm Calibration procedure (signal generator)

Band Recommended test frequency 100–200 MHz 150 MHz 200–400 MHz 300 MHz 400–700 MHz 500 MHz 700–1000 MHz 868 MHz 1000–1500 MHz 1090 MHz 1500–2400 MHz 1575 MHz 2400–3000 MHz 2437 MHz 3000–6000 MHz 5800 MHz For each band:

Set signal generator to the test frequency at a known output level P_gen_dBm. Connect to HackRF via cable + appropriate attenuator. Open Channel monitor in the UI and select a channel in that band. Record P_sdr_dBm from the UI. Compute offset = P_gen_dBm - P_sdr_dBm. Enter offsets via UI → Calibration → Save, or via the API:

curl -X POST http://127.0.0.1:1717/api/calibration
-H "Content-Type: application/json"
-d '{ "bands": [ [100e6, 200e6, -3.5], [200e6, 400e6, -4.1], [400e6, 700e6, -5.0], [700e6, 1000e6, -5.8], [1000e6, 1500e6, -6.2], [1500e6, 2400e6, -7.0], [2400e6, 3000e6, -8.5], [3000e6, 6000e6, -12.0] ] }' After saving, the UI shows ✅ CALIBRATION_VERIFIED.

Typical offset reference (HackRF One)

Band Typical offset 100–400 MHz −2 to −5 dB 400–1000 MHz −4 to −7 dB 1000–2400 MHz −6 to −10 dB 2400–6000 MHz −8 to −15 dB Larger offsets at higher frequencies are normal due to cable loss and the internal LNA frequency response.

Recalibration schedule

Situation Action HackRF or antenna replaced Full recalibration Ambient temperature change > 15°C Recalibrate After 6 months of operation Recheck 2–3 primary bands Sudden drop in RF model accuracy Check calibration before retraining Usage Normal mode (HackRF required) python SDR-BLUE-TEAM.py The system will:

Initialise the SQLite DB and Random Forest. Start the Flask UI at http://127.0.0.1:1717/. Connect to HackRF. Run an 8-round baseline sweep. Enter the continuous monitoring loop (Ctrl+C to stop). Web-only mode (no hardware) python SDR-BLUE-TEAM.py --web-only Starts Flask + DB without opening HackRF. Useful for replaying saved events, debugging routes, or testing the UI.

Force model retrain python SDR-BLUE-TEAM.py --train-now

Combinable with --web-only:

python SDR-BLUE-TEAM.py --web-only --train-now Monitored Threat Types Threat type Channel Band Description GPS_JAM GPS L1, L2 1575 / 1227 MHz Deliberate GPS jamming FAKE_BTS GSM900, GSM1800 935 / 1842 MHz Fake BTS / IMSI catcher CELL_JAM TETRA, PMR446 392 / 446 MHz Mobile network / tactical jamming IOT_REPLAY ISM433 433 MHz IoT signal replay attack LORA_SKIM ISM868, ISM915 868 / 915 MHz LoRa network attack DRONE_FHSS DRONE 2.4G / 5.8G 2440 / 5800 MHz FHSS drone control (DJI/ELRS) WIFI_DEAUTH WiFi 2.4G CH6, BT 2437 / 2480 MHz Wi-Fi deauth / BT flooding ADSB_SPOOF ADS-B 1090 1090 MHz Spoofed ADS-B aircraft signal SATCOM Sat L-band 1545 MHz Satellite band anomaly EMERGENCY UHF/VHF Emerg 457 / 155 MHz Emergency channel jamming Each threat type has its own rule engine in analyze() and a dedicated AI model in RFAnomalyAI.

Web UI Access: http://127.0.0.1:1717/

Feature Description Dashboard Donut chart (threat types), line chart (power timeline), bar chart (hourly activity) Heatmap Activity by frequency band Real-time SSE /api/stream — pushed immediately on each new event Filter By severity, threat type, channel, time range Export /api/export?format=csv or ?format=json Calibration Per-band offset wizard, toggle CALIBRATION_VERIFIED RF Model Accuracy, classes, degenerate status Channels Baseline and last Z-score for each watchlist channel API endpoints GET / → Dashboard HTML GET /api/events → Recent events (JSON) GET /api/stream → SSE stream (text/event-stream) GET /api/export?format=csv|json → Full event history export GET /api/calibration → Read current offsets POST /api/calibration → Update offsets (body: {bands: [...]}) GET /api/channels → Status of each watchlist channel GET /vendor/chart.js → Chart.js local copy (offline fallback) Security: The UI binds to 127.0.0.1:1717 (localhost only) by default. Do not expose it to the internet without authentication and TLS (e.g., nginx reverse proxy with Basic Auth).

Agent Layer rf_sentinel_agents.py provides six agents orchestrated by AgentSuite:

  1. SimpleSOM — Self-Organizing Map An 8×8 neuron SOM with online learning. Each event is mapped to its best matching unit (BMU); large BMU distance signals a high anomaly score.

  2. TeacherStudentBridge — Semi-supervised Uses labeled events as a "teacher" to pseudo-label unlabeled events via kNN. Results feed into the RF classifier.

  3. CognitiveFrequencyAgent — Frequency tracking Tracks power history per frequency and time slot. Detects drift, burst patterns, and novel frequencies.

  4. ActiveLearningAgent — Active Learning Selects the events most in need of labeling (highest uncertainty + diversity) and writes them to active_learning_queue.json for operator review.

  5. AutomatedResponseAgent — Alert & Response On threat ≥ HIGH:

Sends webhooks (Telegram / Discord / Slack) Generates a PDF incident report (requires reportlab) Executes RF_RESPONSE_SCRIPT if configured 6. ProtocolDecoderAgent — Protocol decoding Demodulates FSK / OOK / BPSK from saved IQ snapshots. Outputs raw protocol frames to evidence/ and JSON.

BearingEstimator (helper) Estimates signal bearing (AoA / RSSI) from single-station measurements over time.

Architecture Module overview ┌─────────────────────────────────────────────────────────────────┐ │ SDR-BLUE-TEAM.py │ │ (Core: DSP + AI + DB + sweep loop + Flask glue) │ │ │ │ ┌────────────────┐ ┌───────────────────┐ ┌───────────────┐ │ │ │ Signal Process │ │ AI / Detection │ │ Database │ │ │ │ ───────────── │ │ ─────────────── │ │ ──────────── │ │ │ │ FFT / PSD │ │ HybridCognitiveAI │ │ SQLite CRUD │ │ │ │ Savitzky-Golay │ │ RFAnomalyAI │ │ auto-label │ │ │ │ DTW FHSS │ │ RFThreatClassifier│ │ spectrum hist │ │ │ │ CAF cylostat. │ │ PersistenceTracker│ │ export │ │ │ └────────────────┘ └───────────────────┘ └───────────────┘ │ │ │ │ ┌────────────────┐ ┌───────────────────┐ ┌───────────────┐ │ │ │ Hardware I/O │ │ Sweep / Monitor │ │ TX-Guard │ │ │ │ ──────────── │ │ ──────────────── │ │ ──────────── │ │ │ │ FastSweepEngine│ │ monitor_loop() │ │ RxOnlyProxy │ │ │ │ EngineHandle │ │ sweep_once() │ │ verify_hash │ │ │ │ connect_hackrf │ │ init_baseline() │ │ TransmitBlock │ │ │ └────────────────┘ └───────────────────┘ └───────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ DI (dependency injection) │ DI ▼ ▼ ┌──────────────────────┐ ┌────────────────────────────┐ │ rf_sentinel_ui.py │ │ rf_sentinel_agents.py │ │ ─────────────────── │ │ ───────────────────────── │ │ register(app, fns) │ │ AgentSuite.on_event() │ │ Flask routes │ │ SimpleSOM │ │ SSE broadcaster │ │ TeacherStudentBridge │ │ Dashboard HTML │ │ CognitiveFrequencyAgent │ │ │ │ ActiveLearningAgent │ │ │ │ AutomatedResponseAgent │ │ │ │ ProtocolDecoderAgent │ └──────────────────────┘ └────────────────────────────┘ Signal processing & detection pipeline HackRF One │ IQ samples (2 MSPS, FFT_SIZE=1024) ▼ ReceiveOnlySDRProxy ← TX-Guard: any TX call → immediate abort │ ▼ FastSweepEngine ← ~25 ms/channel, rolling through WATCHLIST │ ▼ Signal Processing Pipeline ├─ iq_bytes_to_complex() ├─ estimate_power_dbm() ← apply calibration offset ├─ smooth_psd_savgol() ← [C] Savitzky-Golay pre-filter ├─ compute_psd() ← FFT → Power Spectral Density ├─ spectral_entropy() ├─ kurtosis_of() ├─ sample_entropy_of() ├─ stft_entropy() ├─ cyclostationary_score() ← CAF basic ├─ cyclo_detect_advanced() ← [D] CAF advanced, multi symbol-rate └─ FHSSTracker.update() ← [B] DTW centroid history → FHSS score │ ▼ Dual-AI Detection ├─ HybridCognitiveAI ← AI [1] Global: DBSCAN + IsolationForest │ (all channels at once — detects anomalous clusters) └─ RFAnomalyAI ← AI [2] Per-type: HMM / LOF / OCSVM / IF (each threat_type has its own model) │ ▼ analyze() → ThreatResult ├─ Per-threat-type rule engine (GPS_JAM, FAKE_BTS, DRONE_FHSS…) ├─ EW_AnomalyResult (scores from both AIs) └─ fingerprint() ← store RF fingerprint per channel │ ▼ Confidence Gates ├─ [Gate 1] PersistenceTracker ← ≥3/5 rounds ├─ [Gate 2] Critical Z-score ← must exceed threshold ├─ [Gate 3] EMA drift ← must show trend └─ [Gate 4] RF downgrade-only ← RF may only lower threat level, not raise it │ ▼ RFThreatClassifier (AI [3] Supervised) ├─ RandomForest / LightGBM / XGBoost ├─ Activates after ≥ 200 labeled events ├─ Guards against label leakage (max class share ≤ 85%) └─ Auto-detects rule-table copy model → marks UNUSABLE │ ▼ Event Persistence (SQLite) ├─ db_log_event() ├─ save_evidence() ← save IQ snapshot (.sigmf) + PNG spectrogram └─ _auto_label() ← auto-assign LOW/MEDIUM+ when confidence is high │ ▼ Output ├─ Logger (sentinel.log, rotating 8 MB × 4) ├─ Flask Web UI :1717 ← SSE real-time push ├─ AgentSuite ← 6 agents running in parallel └─ Wideband survey ← 300–1000 MHz every 40 sweep rounds Key classes Channel (dataclass)

@dataclass class Channel: name: str # "GPS_L1" freq_hz: float # 1575.42e6 priority: int # 1=critical, 2=medium, 3=low threat_type: str # "GPS_JAM" baseline_mean: float # EMA mean power baseline_std: float # EMA std power z_score: float # Z-score from last scan fhss_score: float # DTW FHSS score last_power: float # power (dBm) from last scan scan_count: int # total scan count ThreatResult (dataclass)

@dataclass class ThreatResult: channel: Channel threat_level: ThreatLevel # OK/LOW/MEDIUM/HIGH/CRITICAL threat_type: str # "GPS_JAM", "FAKE_BTS"… confidence: float # 0.0–1.0 anomaly_score: float # composite anomaly score ew_result: EW_AnomalyResult # detailed AI scores rule_flags: dict # flags from the rule engine timestamp: str # ISO 8601 UTC HybridCognitiveAI — AI [1]

Features: [power_z, entropy, kurtosis, sample_entropy, stft_entropy, cyclo, fhss]

Online: ├─ EMA baseline update (every EMA_UPDATE_INTERVAL_S seconds) └─ Z-score on sliding window

Batch (every N events): ├─ DBSCAN → cluster outlier score └─ IsolationForest → anomaly score

Output: float 0–1 RFAnomalyAI — AI [2]

Per threat_type, different model: DRONE_FHSS / GPS_SPOOF → HMM (hmmlearn) IOT_REPLAY / LORA_SKIM / FAKE_BTS → LOF (LocalOutlierFactor) EMERGENCY → OCSVM (OneClassSVM)

  • (default) → IsolationForest

Online: buffer features, fit when MIN_SAMPLES reached Output: float 0–1 RFThreatClassifier — AI [3]

Input: labeled events from DB Features: 12 DSP + AI score features

Guards: ├─ < RF_MIN_TRAINING (200) rows → SKIP ├─ < 2 classes → SKIP ├─ any class > RF_MAX_CLASS_SHARE (85%) → SKIP └─ CV5 score > RF_RULE_TABLE_CEILING (0.97) → DEGENERATE / UNUSABLE

Backend (auto-select): LightGBM > XGBoost > RandomForest Retrain: every RF_RETRAIN_EVERY_N (500) new labeled events Database schema (SQLite) CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL, -- ISO 8601 UTC channel TEXT NOT NULL, -- "GPS_L1" threat_type TEXT NOT NULL, -- "GPS_JAM" level TEXT NOT NULL, -- "HIGH" confidence REAL, power_dbm REAL, zscore REAL, anomaly_scr REAL, global_scr REAL, type_scr REAL, entropy REAL, kurtosis REAL, cyclo_scr REAL, fhss_scr REAL, persist_ratio REAL, iq_path TEXT, -- path to .iq file in evidence/ confirmed INTEGER DEFAULT NULL -- NULL=unreviewed, 1=TP, 0=FP );

CREATE TABLE labeled_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT, channel TEXT, threat_type TEXT, label TEXT NOT NULL, -- label assigned by operator or auto-label power_dbm REAL, -- … same DSP features as events source TEXT DEFAULT 'auto' -- 'auto' / 'operator' );

CREATE TABLE spectrum_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT, channel TEXT, power_dbm REAL -- indexed on (channel, ts) for timeline queries ); Dependency injection pattern To avoid circular imports between the three main modules, RF Sentinel uses dependency injection:

SDR-BLUE-TEAM.py → rf_sentinel_ui.py register(app, fn1, fn2, …) # fn1, fn2 = functions from core # rf_sentinel_ui does NOT import core

SDR-BLUE-TEAM.py → rf_sentinel_agents.py AgentSuite(sdr_handle, db_fn, …) # rf_sentinel_agents does NOT import core Rule: child modules must not import the parent module.</div>


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí