File size: 23,629 Bytes
5d97275 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | # DibaGuard β design (v0.1, 2026-10-30)
> **Open-release copy** ([DibaAi/DibaGuard-Open](https://huggingface.co/DibaAi/DibaGuard-Open)). This document was written for the private build of DibaGuard. Numbers that involve the HTTP request model (`http_v2`) were measured with that model, which is **not** part of the open release; the open release ships the licence-clean network models described in `CLEAN_MODELS.md`. Licence statements inside this document are superseded by `DATA_LICENSES.md`, `CLEAN_MODELS.md` and the repository's `LICENSE`, `LICENSE-WEIGHTS` and `NOTICE` files. Sections of the original that were written in Persian were removed from this copy; nothing else was edited.
> **Historical note.** This is the v0.1 design document. Its statement that a licence-clean retrain could use the "MIT-licensed merged NetFlow v3" copy was wrong: that label was set by an uploader who is not the data owner (`DATA_LICENSES.md`, row 3). The licence-clean models were trained as described in `CLEAN_MODELS.md`.
A CPU-only attack-intensity model for network traffic and web requests, built on the DibaOne decision architecture
(typed calibrated outputs, tiered cascade with a router, adaptive memory with feedback). Working name: DibaGuard.
**Answer it gives, for every event and continuously for every entity and site:**
| level | output |
|---|---|
| event (one flow, one HTTP request, one packet burst) | `p_attack` (calibrated), `family` (open set: sqli, xss, traversal, cmdi, ssrf, ssti, xxe, jndi, scan, bruteforce, dos, botnet, exfil, β¦), `severity` 0β1, `score` 0β100, `evidence` (decoded payload span / features that drove it), `tier` and Β΅s spent |
| entity (source IP, session, API key, JA3/UA fingerprint) | `risk` 0β100 with decay, `campaign` id when several entities share a fingerprint |
| site | `pressure` 0β100: how much of the current traffic is attack, by family |
## 1. Hard budgets (the product is the budget)
| budget | target, 1 vCPU, no GPU |
|---|---|
| model files, all tiers | < 15 MB (int8) |
| RAM, runtime + 1 M tracked entities | < 300 MB |
| HTTP throughput (tiers 0β1, batched) | β₯ 20 000 req/s; p99 < 1 ms incl. tier 2 |
| flow throughput (tier 1) | β₯ 200 000 flows/s |
| tier 2 share of traffic | β€ 5 % (router-controlled) |
| dependencies at inference | numpy + onnxruntime (no torch) |
Every report states the machine, threads, batch size and the measured numbers. A run with no numbers is not a run.
## 2. Architecture (DibaOne heritage β what is new here)
```
βββββββββ tier 0: stream state (O(1)/event) ββββββββββ
packets βββΊ β flow aggregator (NetFlow v2-compatible features) β
NetFlow βββΊ β per-entity counters: count-min sketch + EWMA ββββΊ features ββ
HTTP βββΊ β canonicaliser: multi-round decoding, normal form β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ βΌ
tier 1 (Β΅s): flows β gradient-boosted trees (ONNX/treelite) calibrated p, family
HTTP β hashed char/token n-grams + structure β linear (sparse)
router: confident? ββyesβββΊ decision layer
no (β2β5 %)
tier 2 (β0.3 ms): tiny byte transformer (β1β2 M params, int8), multi-task heads
βΌ
decision layer: per-family temperature, Mondrian conformal thresholds (FPR β€ Ξ±
per site/endpoint), abstain = "review", entity risk accumulation, evidence
βΌ
adaptive layer: site profile (template + parameter-type model), kNN memory,
feedback (/v1/feedback) β instant correction without retraining
```
### Innovations (each one has a benchmark that can prove or kill it)
1. **Guaranteed false-alarm budget per deployment.** Thresholds are set by Mondrian conformal prediction on the
site's own benign traffic, per endpoint group and per source type, so `FPR β€ Ξ±` holds with a finite-sample
guarantee instead of a vendor-chosen threshold. Conformal martingales over the same p-values detect drift and
trigger recalibration. *Benchmark:* realised FPR vs Ξ± on held-out benign traffic of unseen sites.
2. **Compute-budgeted cascade.** The router (as in DibaOne X1) sends each event to the cheapest tier that is
confident enough, under a per-request `max_us`; thresholds fitted on dev to minimise expected cost.
*Benchmark:* accuracy vs Β΅s/event curve; share of events per tier.
3. **Evasion-invariant training.** A deterministic canonicaliser (URL/HTML/unicode/hex/base64 decoding until a fixed
point, SQL/JS comment and whitespace folding, case folding) plus a mutation engine (encodings, case, comments,
synonyms, splitting) and a consistency loss: an attack and its obfuscated variants must score alike.
*Benchmark:* detection on obfuscated attacks never seen in training (evasion suite).
4. **Self-learned positive model per site (zero-day).** A compact profile of each site's normal requests: path
templates (Drain-style tree), parameter types (int / uuid / email / slug / free text / Persian text), length and
charset distributions, in sketches of a few MB. Violations add an anomaly term to the supervised score, so
attacks with no known signature still raise the score. *Benchmark:* leave-one-family-out recall.
5. **Attack intensity, not only labels.** Event probability Γ severity, accumulated per entity with decay and linked
across entities by shared fingerprints (low-and-slow scans, credential stuffing from many IPs).
*Benchmark:* time-to-detect and campaign recall on replayed multi-stage attacks.
6. **Persian-aware.** Benign hard negatives with Persian text in parameters (percent-encoded UTF-8, ZWNJ, Persian
digits), Iranian site traffic (zanbil.ir logs) β a common false-positive source for Western WAF rules.
7. **Learning from analysts in seconds** (DibaOne adaptive memory): a marked false positive changes the next
decision immediately (kNN override + gated adapter), and is kept for the next training round.
8. **Feeds anything:** raw packets (own aggregator), NetFlow/IPFIX exports from routers (no capture needed), access
logs (nginx/apache/IIS), reverse-proxy mirror, or an inline SDK call.
## 3. Data (private R&D first; licences tracked per row, `meta.licence`)
| modality | sources (Kaggle-attached; nothing downloaded through the office network) |
|---|---|
| flows, NetFlow v2 view | NF-UQ-NIDS-v2 (UNSW-NB15, BoT-IoT, ToN-IoT, CSE-CIC-IDS2018), NF v3 sample |
| flows, CICFlowMeter view | CIC-IDS2017 & CSE-CIC-IDS2018 (DistriNet label-corrected), CIC-DDoS2019, CIC-IoT2023 |
| real traffic | LUFlow (university network, honeypot-labelled, to 2026) |
| packets | UNSW + CIC-IDS2017 labelled PCAP subset |
| HTTP | CSIC 2010, SQLi/XSS sets (incl. obfuscated), labelled access logs, attack-simulation lab, payload repos (PayloadsAllTheThings, SecLists β MIT; OWASP CRS regression tests β Apache-2.0) |
| benign HTTP | CSIC normal, zanbil.ir logs (CC0), requests generated from public OpenAPI specs (APIs.guru, CC0), Persian/code/SQL-looking benign text as hard negatives |
Rules: no IPs, timestamps or flow ids as features (shortcuts); dedup by feature hash before splitting; splits by
capture day / site where possible; licences per row (many NIDS sets are CC BY-NC-SA β private R&D; a commercial
release trains on the permissive subset or needs permission).
## 4. Evaluation (frozen test sets; one scorer)
* in-distribution, and **leave-one-dataset-out** (train on the others, test on the held-out dataset) β the honest
generalisation number most papers omit;
* TPR at FPR = 1e-3 and 1e-4, AUROC, AUPRC, macro-F1 over families, ECE;
* evasion suite (obfuscated attacks), Persian benign hard-negative FPR, leave-one-family-out recall;
* Β΅s/event, events/s, RAM, model MB on a stated CPU.
## 5. Plan
| step | where | status |
|---|---|---|
| S0 survey of every dataset (schemas, labels, counts) | Kaggle CPU, both accounts | running |
| S1 build `flows_v1` and `http_v1` corpora β `DibaAi/dibaguard-data-private` | Kaggle CPU | next |
| S2 tier 1 baselines (trees for flows, n-gram linear for HTTP) + leave-one-dataset-out | Kaggle CPU | |
| S3 tier 2 byte model + evasion training | Kaggle GPU (T4) | |
| S4 decision layer (calibration, conformal, entity risk) + runtime (numpy/onnxruntime) | local | |
| S5 site profile, feedback, private test Space, report | local + HF Space (private) | |
## 6. Deployment: one engine, many hosts (added 2026-10-30)
Goal: the strongest detector we can train, packaged so it drops in next to any load balancer or becomes one.
**Model contract (language-agnostic).** A tier-1 model is a flat file:
`feat[S] int16, border[S] float32, tree_tests[T,D] int16, leaves[T,2^D,C] float32|int8, bias[C], info json`
(`dibaguard/flows/trees.py`). Evaluation = S compares, TΓD bit gathers, T leaf lookups, one sum. The HTTP tier-1
model adds `emb[B,E] int8 + scale` and the n-gram hash spec (`dibaguard/hashing.py`: FNV-64 rolling, splitmix mix,
orders 2β6). Both are ~100 lines in Go; a parity test (`tests/parity/*.jsonl` = inputs + expected raw scores,
bit-exact) is part of the release, so any port is proven, not trusted.
**Three independent layers, each replaceable:**
1. *Score* β stateless, pure function `record β raw score`. Library (Python/numpy, Go), or sidecar.
2. *Decide* β threshold (conformal, site- or shipped-calibrated), temperature, family head on alarms, entity risk,
site pressure, drift martingale. Small mutable state (β a few MB), exportable/importable as JSON β can be shared
across LB replicas or kept per replica.
3. *Adapt* β site profile (percentile grids; `siteprofile.py` compiles them into the tree borders, so the deployed
model stays raw-feature, same size, same speed), feedback memory, periodic recalibration. Runs offline or in a
control-plane process; ships new borders/thresholds to layer 1/2 atomically.
**Hosts.** (a) Sidecar: HAProxy SPOE / Envoy ext_proc / nginx `auth_request` β local UDS/HTTP, p50 < 100 Β΅s.
(b) Native Go load balancer with the engine embedded (flows from conntrack/NetFlow mirror, HTTP from the proxy
itself) β score every request, rate-limit / challenge / block per entity risk and site pressure. (c) Library.
Decision outputs are the same objects everywhere: `{p, alarm, family, severity, score 0-100, entity_risk,
site_pressure, threshold_source}`.
**Test ladder (every release):** unit parity (PythonβGo bit-exact) β LODO cross-network numbers β site-calibrated
FPR actually β€ Ξ± on held-out benign β evasion/obfuscation suites (HTTP) β load test (req/s, p99, RSS) β 24 h soak on
replayed real logs with drift alarms counted.
## 7. v2: the woven cascade (2026-10-30)
Why: v1 scores each flow / request alone. Cross-network evaluation with *source-capped* sampling (every source
contributes at most one 60k-flow run, so a few DoS bots cannot dominate) shows that single-record detectors - trees
or a GRU - transfer poorly; what a site can always have is its *own* normal traffic. v2 therefore weaves three
threads per source and lets the site's benign stream condition every one of them:
| layer | what | cost (Go, 1 core) | runs on |
|---|---|---|---|
| 0 entity state | decayed counters, novelty memory, gaps (36 features) | 275 ns | every flow / request |
| 1 trees | oblivious trees on layer-0 features (shipped prior: "what attacks look like") | 1.7 Β΅s | every flow |
| 1' site anomaly | quantile p-values / isolation forest on layer-0 features, fitted on this site's benign only | ~1 Β΅s | every flow |
| 2 sequence net | causal GRU per source over its event stream (+ optional site context FiLM) | 8.6 Β΅s at d=24 (keep d β€ 64) | warm sources only (tier-1 above the low-water mark) |
| HTTP | hashed n-gram bag + MLP conditioned on the site profile (HttpModel2); the request also enters layer 0 as an event | 8.9 Β΅s | every request |
Decision: every thread yields a conformal p-value against the site's benign reference; the verdict is the minimum
(Bonferroni over threads), so the realised false-alarm rate stays at Ξ± whatever the mix. Family head on alarms
only; EntityRisk / SitePressure / drift as before.
Evaluation protocol (guard_jobs/seq_train.py): temporal + leave-one-network-out on source-capped corpora, each
run reporting flow AUROC, site-calibrated FPR/TPR, entity-minute AUROC and the intensity correlation for the GRU,
the trees, the two anomaly baselines and the fused verdicts. Honest result: single-record transfer is weak for
everyone; the fused, site-conditioned verdict is what ships.
Modern threat landscape (2025-26 vendor reports): L7 floods and bots (half of web traffic), API abuse, credential
stuffing, scanners, AI crawlers - i.e. *behaviour over time per client*, which is exactly what layers 0/2 and the
HTTP session stream measure. Injection payloads still matter and are covered by the n-gram model; the 2025
ModSecurity thirty-day archive (CC BY 4.0) is the modern attack-payload source.
LLM bridge (dibaguard/llm.py): five tools (status, score flows, score http, calibrate, report) as MCP over stdio,
OpenAI / Anthropic tool schemas and a stdlib HTTP server; the incident report (dibaguard/report.py) is a fixed JSON
schema rendered to English and Persian from the engine's own state - no model narrates numbers it did not compute.
## 8. v2.1: the real-world layer (2026-10-30, evening)
What was weak after v2 and what fixed it - every item measured, none tuned by hand:
| weakness | root cause | fix (dibaguard/engine.py) |
|---|---|---|
| per-flow detection at Ξ± = 1e-3 β 0 on unseen networks | the flow is the wrong unit: an attacker sends hundreds of flows, a verdict per flow throws the sequence away | **per-source accumulated evidence** `E β EΒ·e^(-Ξt/Ο) + (βlog p)` (Ο = 120 s), judged per (source, minute) against the site's benign source-minute maxima (`decide.SourceEvidence`). LODO CIC-IDS2018: every attacking source caught, source-minute TPR 0.6 at FPR 2e-3 (was 0.0 per flow) |
| realised FPR above target when the traffic mix changes | one global reference; a new or bursty service moves every threshold | **Mondrian references by service Γ source age** (`ent.service_key`, `AGE_EDGES`): a client's first flows are judged only against other clients' first flows; a cell without reference **abstains** (p = 1) instead of guessing |
| false alarms on new clients (flash crowds) | births look extreme against steady-state sources | same age conditioning + abstention; destination-side features kept out of per-flow verdicts (they belong to site pressure) |
| FPR ratchet over hours (+1 %/h) | self-cleaning rolling references trimmed the benign tail; anomaly scores against a growing reference change scale | **epoch re-calibration**: every `recal_s` (30 min) all references are rebuilt at once from the last `recal_window_s` (2 h) of traffic, sources with evidence left out; the evidence reference is built out of sample (last quarter scored against the first three). Measured: 0.08 β 0.23 β 0.23 % per source-minute over three benign hours at Ξ± = 1e-3 (was 0.18 β 1.0 β 2.5 %) |
| wrong attack families | the GRU family head does not transfer | transparent **rules on the stream counters** (`ent.family_rules`): port walking β portscan, same (host, port) again and again β bruteforce, β₯ 10 flows/s at one service β dos/ddos, host walking with failures β recon |
| supervised prior scoring a SYN scan as benign | dataset artefacts in unconstrained trees | **monotone trees** on the 24 attack-direction features (more scanning can never look more benign) |
| unseen web application (WordPress) | vocabulary the model never saw | **site n-gram novelty** (`HttpModel2.novelty`): share of a request's n-grams absent from the site's own benign vocabulary, rarity-weighted - evaluated (WordPress AUROC 0.92 vs 0.61), **not yet part of the live verdict** |
False-alarm budget: Ξ±/2 to the evidence tier, Ξ±/(2Β·k) to each of the k per-flow tiers (Bonferroni), so the union stays at Ξ±.
What the engine learns on the server and what it does not: every reference, threshold, site profile and vocabulary is
re-learned from the site's own traffic every epoch (no labels, poisoning-resistant by excluding sources with evidence);
model weights (trees, GRU, HTTP MLP) are frozen - online weight updates without labels would be a poisoning surface.
The feedback loop (operator-confirmed alarms β periodic offline retraining) is the next step, not an online one.
## 9. v2.2: distributed floods, slow scans, feedback, rules, and the live lab (2026-10-31)
The static cross-dataset numbers hid live failures that only a running, multi-wave environment shows. A local
red-team/blue-team lab (scripts/redteam_lab.py: a self-owned localhost target, benign traffic, and the engine's own
playbook patterns replayed over several waves, scored against known ground truth) drove the next round of fixes:
| gap the live lab exposed | fix |
|---|---|
| distributed / volumetric floods (amplification, peer/sybil floods) have no single guilty source | **per-destination evidence tier**: a destination's (flows, no-reply, new-source) anomaly accumulated per destination-minute; a flow is blamed only if it is new to the destination *and* off-profile, so the server's established benign clients are spared. NTP amplification 1.0, peer flood 0.76-0.80 |
| slow scans (nmap -T1 over ~30 min) never accumulate at tau=120 s | **long-horizon source evidence** (tau=1800 s) in parallel, fed only by mildly-suspicious flows; once its reference is warm (~2 epochs) slow-scan detection is 0.95 |
| detection decayed wave-over-wave (partially-detected attacks seeped into the self-training) | **reference hygiene** (as of v2.5): a source whose alarms name an attack family, or whose alarms keep coming while it behaves like an attacker, is excluded from the reference entirely; a sporadic un-named alarm stays in (it is the benign tail - excluding it ratchets the FPR up); no source may write more than ~1% of a reference; plus an **immutable anchor** mixed into every epoch and a drift guard that asks who moved the tail. Destinations are not excluded. See docs/TECHNICAL.md section 4.3-4.4 |
| HTTP session attacks (credential stuffing, scrapers, API enumeration) under-detected | the request is also an event of the client's stream; with the burst preserved, stuffing/scraper/BOLA all reach 0.98-0.99 |
Live lab, 6 waves, shipped models, 3000-client synthetic site: HTTP attacks caught 100% (JNDI/WordPress via rules
1.0, credential stuffing 0.99, scraper 0.94, BOLA 0.98); flow attacks 77% averaged over cold+warm waves (NTP amp
1.0, rapid reset 0.95, SSH brute 0.75, peer flood 0.76, slow scan 0.16 cold -> 0.95 once warm); benign false-alarm
rate 2e-4 per (source, minute) for flows and 2e-4 for HTTP; blue-team feedback precision 0.95.
Operator layer:
- **Rules** (dibaguard/shield/rules.py): plain-language rules in Persian or English are interpreted (a built-in
bilingual parser, or an LLM returning the same JSON schema) into deterministic predicates and enforced from the
first second - so there is no "unprotected while it learns" window. The interpretation is shown back for
confirmation. An `allow` rule shields a source; the playbook's hard signatures (JNDI, CMS paths) also act at t=0.
- **Feedback** (dibaguard/shield/feedback.py): the operator confirms / rejects / allowlists alarms from a
dashboard, MCP or REST. Reject lifts the action and the source's traffic becomes benign reference immediately;
confirm takes (and escalates) the action; every verdict is logged and exported as labelled data for the
offline retraining job. So there is no online weight update (poisoning-safe), but the layer adapts at once to
operator intent and accumulates a retraining set.
- **Licence-clean retraining**: the network corpus is being rebuilt from the MIT-licensed merged NetFlow v3
(guard_jobs/entity_build.py nfv3_parquet, corrupt row groups skipped) so the shipped prior can be trained
without the CC BY-NC-SA sets.
## 10. v2.3: hard adversarial lab, zero-benign-block guarantee, graceful degradation (2026-10-31)
A hard-mode lab (scripts/redteam_hard.py) runs 8 waves in which the attacker *evolves* to evade (slower, more
source IPs, smaller payloads, UA mimicry) while the benign side is hostile (40% brand-new clients each wave, flash
crowds, Persian traffic, a newly-added service). It scores recall per family *and the benign BLOCK rate* (the real
harm), per wave, so the learning/degradation curve is visible.
What it forced, and the design principles that came out of it:
- **Evidence is a zero-mean CUSUM, not a sum.** Per-flow increment is `-log p - k` (k = the benign mean of the
suspicious-flow -log p), floored at 0. A benign source drifts around 0 at *any* request rate, so a chatty client
or a flash crowd no longer accumulates its way over the threshold; only a genuine excess builds up. Gated to
off-profile flows (p < 0.5) on both the fast (tau=120 s) and slow (tau=1800 s) horizons.
- **First contact is never auto-blocked.** A source's first flows carry no behaviour to judge; behavioural tiers
abstain for a newborn source unless the single flow is itself self-evident (p < alpha, e.g. a 1400-byte no-reply
UDP to a victim) or the supervised prior fires hard. A soft newborn alarm becomes `block_ok = False` -> the layer
issues a **challenge (HTTP) or monitor (flow)**, never a block. HTTP session alarms block only when the client's
requests are largely failing (4xx/5xx): a user browsing many pages trips the path-diversity signal but gets 200s,
so it is challenged; enumeration / credential stuffing / scanners fail requests and are blocked.
- **Result (8 waves, evolving attacker, 40% flash-crowd benign): zero benign auto-blocks on both flows and HTTP in
every wave; feedback precision 1.0.** Recall: rapid reset / NTP amplification / SSH brute force ~1.0, peer flood
0.94; the learning curve degrades *gracefully* only against the maximally-evading low-and-slow distributed cases
(evolving scan 1.0 -> 0.16, distributed credential stuffing 0.98 -> 0.23 as it spreads over many client IPs).
These are the known-hard, aggregate-only attacks; they are documented honestly and are the roadmap's global-rate
tier, not a silent failure. Benign alarms that are not blocks (challenges/monitors) are expected under a flash
crowd and are the standard "under attack" trade-off, tunable via policy.min_action_score.
- **Multilingual, by design.** Rule interpretation and the LLM bridge are language-agnostic: with an LLM wired in
(the normal deployment of an LLM-facing product) a rule in any language is interpreted to the same JSON schema
(rules.LLM_SCHEMA_PROMPT); the built-in parser is only the Persian/English offline fallback. The rendered
report and dashboard are English + Persian.
What is honestly *not* done (roadmap, not claimed): a global/aggregate rate tier for fully-distributed low-volume
attacks; automatic database- and network-topology profiling beyond the posture audit's service recognition; and
online weight learning (kept offline on purpose, fed by the feedback-exported labels) - see section 8.
|