DibaGuard / docs /DESIGN.md
DibaAi's picture
DibaGuard: public licence-clean release (Dibachain)
5d97275 verified
|
Raw History Blame Contribute Delete
23.6 kB
# 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.