Project: The Sentinel — Network Intrusion Detection System (NIDS) Document: NIDS_AppFlow.md Status: ✅ Complete — Colab Edition (2026-08-28): flows now run inside 4 Colab notebooks on the T4 VM. Raw-packet flows (capture/simulators) are replaced by in-memory synthetic flows + CSV replay (Colab has no raw-socket access). Legend: ✅ IMPLEMENTED · 🟡 PARTIAL · 🔵 NOTEBOOK-ONLY (Colab) · 🟠 LOCAL-ONLY (removed) · 🔴 NOT IMPLEMENTED · ⚪ FUTURE
Notebook 01 ──► Notebook 02 ──► Notebook 03 ──► Notebook 04
EDA Training Inference+API Dashboard
───────── ───────── ──────────── ──────────
CSV (Drive) ──► stratify/clean ─► artifacts ──────► Gradio UI
class dist. encode/split FlowExtractor KPI + feed
correlations scalers synthetic flows leaderboard
data quality model arena FastAPI + WS SHAP explain
key numbers Optuna replay + tunnel traffic lab
ROC/AUC SHAP deep dive PNG export
artifacts ─────────────────────────► Drive sync
Input: open notebook 03 on Colab; run cells. Steps:
- Artifact discovery cell (
frag_discovery.py): finds/content/nids_artifacts/orMyDrive/nids_artifacts/(produced by notebook 02). - Inference stack cell (
frag_inference.py): loadsmodel.pkl,scaler.pkl,label_encoder.pkl; caches SHAP TreeExplainer; definespredict_flow(),get_severity(),is_benign(). - FastAPI app cell (
frag_api.py): creates SQLite engine, Alert model, validation helpers, and the FastAPI app with all routes. - Server cell (
frag_server.py): starts uvicorn in a background daemon thread, waits for/healthto return{"status": "ok"}. - Optional: public tunnel (cloudflared or ngrok) exposes the API.
Outputs: API listening on 127.0.0.1:8000; DB schema; model cached; WS manager ready.
Errors: missing artifacts → assertion error with clear instructions to run notebook 02 first.
Not portable to Colab (no raw-socket access on a VM). The Colab edition replaces live capture with synthetic packet-dict flows (notebook 03, Step 4) and CSV replay (Steps 7) that exercise the identical extraction → inference → persist → broadcast pipeline.
The original NetworkSniffer (Scapy) flow — 5-tuple assembly, FIN/RST/30 s/500-pkt close, ≥3-pkt submission, retry/backoff delivery — is preserved in git history and documented in the pre-Colab version of this file. Notebook 03's FlowExtractor (Flow 3) consumes the same packet-dict format the sniffer produced, so make_ddos_flow() / make_portscan_burst() / make_bruteforce_flow() / make_normal_flow() reproduce each attack pattern in memory.
Input: packets: List[dict] (same 5-tuple) + optional flow_key — produced by the synthetic-flow builders in Step 4 (previously: the sniffer).
Steps (FlowExtractor.extract_from_dicts):
- Empty list → all-zero dict.
- Split fwd/bwd:
p["src_ip"] == flow_key[0](else first packet's src_ip). - Collect timestamps/sizes, fwd/bwd sizes & payload sizes; totals.
- Duration (µs):
(max-min)*(1e6). - IATs: sorted timestamps → diffs, ×1e6; per-direction too.
- Rates: bytes/s, packets/s, fwd/bwd packets/s with
duration_safe = 1e-6guard. - Header lengths (default 20), FIN/PSH/ACK counts via substring in
tcp_flags. - Window sizes → Init_Win fwd/bwd;
act_data_pkt_fwd(payload_len>0);min_seg_size_forward. - Active/Idle: gap <
ACTIVE_TIMEOUT (5.0 s)= active, else idle; ×1e6. - Assemble 52-key dict; final NaN/Inf → 0.0 pass.
Outputs: exactly-52 floating features; guaranteed stable column order (CICIDS_FEATURES).
Input: flat JSON object (52 CICIDS keys + optional _source_ip/_destination_ip/_src_port/_dst_port).
Steps: (identical semantics to the original route — validation, threadpool inference, persist, broadcast)
_get_predict()→ lazy import wrapper (cached);predict_fn is None→ 503 "ML model not loaded."- Parse
raw = await request.json(); invalid JSON or a non-object root (array/string/number/null) → 400. - Metadata →
source_ip/destination_ipvalidated as real IPv4/IPv6 viaipaddress(absent/empty/"unknown" →"unknown"; oversized/control-char/non-IP values → 422 — blocks DB bloat, log forging, and data-borne chatbot prompt injection), ports (default 0, overflow-safe clamp to 0–65535). - Strict validation (
_validate_features): no feature keys → 400; non-numeric/non-finite/negative/>1e15 values → 422 (named); 1–8 missing → 0.0 (count exposed asmissing_features); >8 missing → 422 with count. predict_fn(features, feature_names=EXPECTED_FEATURES)runs in the threadpool (run_in_threadpool) so the event loop is never blocked:- array reshape (1, 52) →
scaler.transform→model.predict→ index predict_proba→ confidence;encoder.inverse_transform→ labelget_severity(label)→ NONE/CRITICAL/HIGH/MEDIUM/LOW (substring map, fallback LOW)- SHAP TreeExplainer → flatten (list/3D/2D) → sort by |v| → top-5
{feature, value}(non-finite → 0.0)
- array reshape (1, 52) →
- Create
Alert(timestamp=UTC now, IPs, ports, prediction, confidence, severity, shap_json=json.dumps(top5, allow_nan=False))→db.add → commit → refreshin the threadpool. not is_benign(prediction)→ws_manager.broadcast({id, timestamp, src_ip, source_ip, attack_type, prediction, severity, confidence, shap_top5, missing_features})+ warning log.- Return
PredictResponse(timestamp timezone-aware UTC…+00:00).
Errors: 400 invalid JSON / non-object root / empty payload · 422 malformed features or metadata · 500 generic "Inference failed" (raw exception only in server logs) · 503 model not loaded · WS broadcast failures swallowed (warning only).
Input: client connect (ws://127.0.0.1:8000/ws/live, or the tunnel URL).
Steps: (same contract: last-50 history batch, 10 s pings, bounded client cap 20, concurrent broadcast with 5 s per-client timeout, dead-client eviction; the NIDS_API_SECRET token gate is present in code but unset by default in the notebook)
- Auth gate (before any data): when
NIDS_API_SECRETis set the client must present?token=<secret>or anX-API-Keyheader (constant-time compare). Failure → accept + close 4401. OverNIDS_WS_MAX_CLIENTS(default 20) → accept + close 1013. ws_manager.connect→ accept, append, log count.- Initial batch:
SELECT * FROM alerts WHERE prediction NOT IN ('Normal Traffic','BENIGN') ORDER BY timestamp DESC, id DESC LIMIT 50(run in the threadpool so the event loop is never blocked); then reversed list → onesend_text(array)(timestamps…+00:00). - Loop:
asyncio.sleep(10)→ ping{"type": "ping"}→ drain (discard) any client-sent app messages. - New attack predictions → broadcast pushes to all clients concurrently with a 5 s per-client send timeout; stalled/dead clients are evicted so one slow dashboard cannot stall the rest.
- Client disconnect →
WebSocketDisconnect→ remove.
Outputs: dashboard receives history + live alerts. Errors: history query failure → warning "Could not send history", stream continues; unauthorized/over-cap clients closed with 4401/1013 before receiving any stream data.
Input: embedded API (notebook 03) + 3 s polling. Steps:
- Gradio Blocks UI launches (share=True → public
.gradio.liveURL). refresh_all()polls/api/stats+/api/alerts+/api/ip-leaderboardevery 3 s (the WebSocket-equivalent freshness) and updates: KPI strip, attack pie, severity bars, live alert feed, leaderboard, timeline.- 🧠 Explain tab: alert dropdown → SHAP top-5 bar plot per alert.
- 🧪 Traffic lab: injects balanced CSV flows through
POST /api/predict. - Step 4 exports static dashboard PNGs (report-ready).
Outputs: live-updating UI + static previews in nids_artifacts/.
Errors: backend offline → "Backend offline" KPI panel; empty data → explicit empty states on every surface.
| Query | Endpoint | Processing |
|---|---|---|
| Statistics | GET /api/stats |
total_flows; total_attacks (excludes BENIGN_LABELS); benign = diff; grouped by type & severity; uptime |
| Alert history | GET /api/alerts |
filters limit/offset/type/severity/exclude_benign; ORDER BY timestamp DESC, id DESC (deterministic); type substring with escaped wildcards |
| Top attackers | GET /api/ip-leaderboard |
limit bounded 1–100; GROUP BY source_ip COUNT MAX(ts) ORDER BY count DESC, last_seen DESC LIMIT n |
| Health | GET /health |
cached (10 s) db SELECT 1 → "ok"/"error"; model _model_loaded; sniffer is_running(); uptime; ws_clients |
| System status | GET /api/system |
health + manifest.json (model type/52 features/7 classes) + sniffer stats + rate-limit/API-key/NIDS_CAPTURE flags |
| Chat | POST /api/chat |
LangChain agent (Gemini) + 5 tools → reply, tool_used, freshness note; 2000-char cap, 60 s timeout (504), history bounded to ≤50 string entries with only user turns trusted |
Input: notebook cells.
| Scenario | Colab implementation (notebook 03, Step 4 / 7) | Output |
|---|---|---|
| DDoS flow | make_ddos_flow() — 300 tiny one-way SYN packets, 0.5 ms IAT |
52-feature vector → predict → CRITICAL |
| Port scan burst | make_portscan_burst() — 40 single-SYN flows to sequential ports; majority vote |
per-flow predictions → MEDIUM majority |
| Brute force | make_bruteforce_flow() — 40 PSH/ACK login attempts to :22 |
→ LOW |
| Normal browsing | make_normal_flow() — handshake + bidirectional payloads + teardown |
→ NONE |
| Dataset replay | Balanced per-class CSV sampling → POST /api/predict (notebook 03 Step 7, notebook 04 Traffic lab) |
DB alerts + dashboard events |
Verification: alerts appear in /api/stats, the WS feed, and the Gradio dashboard without a real adversary. The original raw-packet simulators (sim_*.py) are in git history.
Input: CSV from MyDrive/nids_data/ (or /content).
Steps:
- GPU check (
nvidia-smi+ torch) — T4 required for GPU cells. - Pass-1 count; Pass-2 stratified chunk sampling (target 400,000; float32 reads).
- Clean (inf → NaN, dropna, drop_duplicates).
- Label
Attack Type;LabelEncoder→label_encoder.pkl; stratified 80/20 split →X_test.npy,y_test.npy,feature_names.json. - Dual scalers fit →
scaler.pkl,robust_scaler.pkl(SMOTE optional flag, default off). - Model arena: LR, DT, RF, XGBoost (GPU), LightGBM — ranked by Macro F1.
- XGBoost CPU-vs-GPU timing bake-off (T4 speed-up).
- PyTorch MLP (256-128-64) on T4 — class-weighted loss, early stopping.
- Optuna Bayesian tuning of XGBoost (20 trials on T4) — tuned model enters the arena.
- ROC curves + per-class AUC, normalized confusion matrix, classification report, per-class F1, benign FPR (encoder-lookup benign index).
- SHAP TreeExplainer smoke test on the deployed model.
- Artifacts →
/content/nids_artifacts/+ Drive sync + zip download; Step 11 Training Summary table.
Errors: CSV missing → assertion with upload instructions; GPU missing → warning (CPU still works, slower).
Input: artifacts + X_test.npy/y_test.npy (same cell flow).
Steps: best model → confusion matrix (raw + normalized) → classification report → ROC curves + per-class AUC → per-class F1 + benign FPR (benign index looked up from encoder — the original evaluate.py's benign_label=0 assumption is fixed here) → SHAP smoke test. The original evaluate.py standalone script is 🟠 LOCAL-ONLY (removed).
| Layer | Failure | Behavior |
|---|---|---|
| API | bad JSON / non-object root / no feature keys / malformed features or metadata / missing model / inference error / oversized body | 400 / 400 / 400 / 422 / 503 / 500 (generic detail) / 413 |
| WS | unauthenticated client / over-cap client / dead or stalled client | close 4401 / close 1013 / evicted (5 s send timeout; concurrent fan-out) |
| Sniffer API | concurrent start / unknown interface / stop with no instance | one instance (RLock) / structured error + available list / not_running (no instantiation) |
| Sniffer | no Scapy / no Npcap / iface missing / permission | refuses to start OR L3 fallback OR logs & stops |
| API call from sniffer | connection refused/timeout | 3 attempts with 2 s/4 s backoff; 400/401/422/503 treated as permanent (warn + drop); total_retries/total_dropped counters |
| Chat | no LangChain / no API key / model error / timeout / oversize message or history | 503 / 503 / 502 (generic) / 504 / 400–422 |
| Frontend | backend down / WS auth rejected | offline status chips + WS reconnect attempts (stops on 4401); explicit error panels on data surfaces |
| Data | NaN/Inf/empty | extractor coerces to 0.0 |
| Training | no CSVs | exit(1) |
| Startup | corrupt artifacts / invalid NIDS_RATE_LIMIT |
model degrades to 503 mode / falls back to default with warning |