Download runtime/python/dibaguard/report.py from Dibachain/DibaGuard: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/Dibachain/DibaGuard/resolve/main/runtime/python/dibaguard/report.py
- Command line
-
hf download hf://Dibachain/DibaGuard/runtime/python/dibaguard/report.py
-
curl -L -o report.py https://huggingface.co/Dibachain/DibaGuard/resolve/main/runtime/python/dibaguard/report.py
10.3 kB
| """Incident reports: what the engine saw, in a fixed JSON shape (schema below) and in plain English / Persian. | |
| A report is the hand-over point to people and to language models: every number in it comes from the engine's | |
| own state (no model is asked to narrate), the text is rendered from the same dict, and the schema is what an LLM | |
| tool call receives. `REPORT_SCHEMA` is a JSON Schema so that any agent framework can validate it. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import time | |
| from collections import Counter, defaultdict | |
| from dibaguard.families import severity | |
| REPORT_SCHEMA = { | |
| "$schema": "https://json-schema.org/draft/2020-12/schema", | |
| "title": "DibaGuard incident report", | |
| "type": "object", | |
| "properties": { | |
| "generated_at": {"type": "string"}, | |
| "window": {"type": "object", "properties": {"since": {"type": "number"}, "until": {"type": "number"}}}, | |
| "level": {"type": "string", "enum": ["quiet", "elevated", "attack", "severe"]}, | |
| "site_pressure": {"type": "number", "description": "share of recent traffic judged attack, 0-1 (decayed)"}, | |
| "drift": {"type": "boolean", "description": "benign score distribution has drifted: recalibrate"}, | |
| "counts": {"type": "object", "additionalProperties": {"type": "integer"}}, | |
| "families": {"type": "object", "additionalProperties": {"type": "integer"}}, | |
| "entities": {"type": "array", "items": {"type": "object", "properties": { | |
| "entity": {"type": "string"}, "risk": {"type": "number"}, "alarms": {"type": "integer"}, | |
| "families": {"type": "array", "items": {"type": "string"}}, "targets": {"type": "array", "items": {"type": "string"}}, | |
| "ports": {"type": "array", "items": {"type": "integer"}}, "first": {"type": "number"}, "last": {"type": "number"}, | |
| "max_score": {"type": "integer"}, "evidence": {"type": "array", "items": {"type": "string"}}, | |
| "action": {"type": "string", "enum": ["monitor", "rate_limit", "challenge", "block"]}}}}, | |
| "thresholds": {"type": "object"}, | |
| "recommendations": {"type": "array", "items": {"type": "string"}}, | |
| "text": {"type": "object", "properties": {"en": {"type": "string"}, "fa": {"type": "string"}}}, | |
| }, | |
| "required": ["generated_at", "level", "site_pressure", "entities", "text"], | |
| } | |
| _ACTION = [(0.9, "block"), (0.6, "challenge"), (0.3, "rate_limit"), (0.0, "monitor")] | |
| _SEP_FA = "\u060c " # list separator of the Persian report text | |
| _FA_FAMILY = {"portscan": "\u0627\u0633\u06a9\u0646 \u067e\u0648\u0631\u062a", "dos": "DoS", "ddos": "DDoS", "bruteforce": "\u062d\u0645\u0644\u0647 brute force", "webattack": "\u062d\u0645\u0644\u0647 \u0648\u0628", | |
| "botnet": "\u0628\u0627\u062a\u200c\u0646\u062a", "infiltration": "\u0646\u0641\u0648\u0630", "exploit": "\u0627\u06a9\u0633\u067e\u0644\u0648\u06cc\u062a", "backdoor": "\u062f\u0631\u0628 \u067e\u0634\u062a\u06cc", "recon": "\u0634\u0646\u0627\u0633\u0627\u06cc\u06cc", | |
| "attack_generic": "\u062d\u0645\u0644\u0647 (\u062e\u0627\u0646\u0648\u0627\u062f\u0647 \u0646\u0627\u0645\u0634\u062e\u0635)", "sqli": "\u062a\u0632\u0631\u06cc\u0642 SQL", "xss": "XSS", "cmdi": "\u062a\u0632\u0631\u06cc\u0642 \u0641\u0631\u0645\u0627\u0646", | |
| "traversal": "\u067e\u06cc\u0645\u0627\u06cc\u0634 \u0645\u0633\u06cc\u0631", "scan": "\u0627\u0633\u06a9\u0646\u0631", "bot": "\u0628\u0627\u062a", "rce": "\u0627\u062c\u0631\u0627\u06cc \u06a9\u062f \u0627\u0632 \u0631\u0627\u0647 \u062f\u0648\u0631"} | |
| def _action(risk: float) -> str: | |
| return next(a for lo, a in _ACTION if risk >= lo) | |
| def build(guard, since: float | None = None, until: float | None = None, top: int = 10) -> dict: | |
| """Report over the engine's alarm log in [since, until] (engine time units, usually unix seconds).""" | |
| log = [e for e in guard.log if (since is None or e["t"] >= since) and (until is None or e["t"] <= until)] | |
| now = until if until is not None else (max((e["t"] for e in log), default=time.time())) | |
| per = defaultdict(lambda: {"alarms": 0, "families": Counter(), "targets": Counter(), "ports": Counter(), "first": None, | |
| "last": None, "max_score": 0, "evidence": []}) | |
| fam_all: Counter = Counter() | |
| for e in log: | |
| r = per[e["src"]] | |
| r["alarms"] += 1 | |
| r["families"][e["family"]] += 1 | |
| fam_all[e["family"]] += 1 | |
| r["targets"][e["dst"]] += 1 | |
| r["ports"][e["port"]] += 1 | |
| r["first"] = e["t"] if r["first"] is None else min(r["first"], e["t"]) | |
| r["last"] = e["t"] if r["last"] is None else max(r["last"], e["t"]) | |
| r["max_score"] = max(r["max_score"], e["score"]) | |
| if e.get("evidence") and len(r["evidence"]) < 3: | |
| r["evidence"].append(e["evidence"]) | |
| ents = [] | |
| for src, r in per.items(): | |
| risk = guard.entities.risk(src, now) | |
| # the recommended action weighs what the source did in the window (alarms x severity), not only its decayed risk | |
| acted = max(risk, (1.0 - math.exp(-r["alarms"] / 20.0)) * max((severity(f) for f in r["families"]), default=0.6)) | |
| ents.append({"entity": src, "risk": round(risk, 4), "alarms": r["alarms"], "families": [f for f, _ in r["families"].most_common(3)], | |
| "targets": [t for t, _ in r["targets"].most_common(3)], "ports": [int(p) for p, _ in r["ports"].most_common(5)], | |
| "first": r["first"], "last": r["last"], "max_score": int(r["max_score"]), "evidence": r["evidence"], | |
| "action": _action(acted)}) | |
| ents.sort(key=lambda e: (-(e["max_score"] * min(e["alarms"], 50)), -e["risk"])) | |
| pressure = float(guard.pressure.pressure) | |
| worst = max((severity(f) for f in fam_all), default=0.0) | |
| level = ("severe" if pressure > 0.5 or (ents and ents[0]["risk"] > 0.9 and worst >= 0.8) | |
| else "attack" if pressure > 0.1 or any(e["risk"] > 0.6 for e in ents) | |
| else "elevated" if ents else "quiet") | |
| rec = [] | |
| if guard.drift.drifted: | |
| rec.append("Benign score distribution drifted: re-run site calibration with fresh normal traffic.") | |
| st = guard.status() | |
| if any(v == 0 for k, v in st["site_scores"].items() if k in st["thresholds"]): | |
| rec.append("No site calibration yet: thresholds are the shipped ones; feed 30 minutes of normal traffic.") | |
| for e in ents[:top]: | |
| if e["action"] in ("block", "challenge"): | |
| rec.append(f"{e['action']} {e['entity']} ({', '.join(e['families'])}, risk {e['risk']:.2f})") | |
| rep = {"generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "window": {"since": since, "until": until}, | |
| "level": level, "site_pressure": round(pressure, 5), "drift": bool(guard.drift.drifted), | |
| "counts": {**guard.counts, "alarms_in_window": len(log), "entities_in_window": len(ents)}, | |
| "families": dict(fam_all.most_common()), "entities": ents[:top], | |
| "thresholds": {k: {"value": round(float(v[0]), 4), "source": v[1]} for k, v in st["thresholds"].items()}, | |
| "recommendations": rec} | |
| rep["text"] = {"en": render_en(rep), "fa": render_fa(rep)} | |
| return rep | |
| def render_en(r: dict) -> str: | |
| lines = [f"DibaGuard report - level {r['level'].upper()}, attack pressure {100 * r['site_pressure']:.1f}% of recent traffic" | |
| + (", DRIFT detected" if r["drift"] else "") + "."] | |
| c = r["counts"] | |
| lines.append(f"Seen: {c.get('flows', 0):,} flows, {c.get('http', 0):,} HTTP requests; {c.get('alarms_in_window', 0):,} alarms " | |
| f"from {c.get('entities_in_window', 0)} entities in this window.") | |
| if r["families"]: | |
| lines.append("Families: " + ", ".join(f"{f} x{n}" for f, n in r["families"].items()) + ".") | |
| for e in r["entities"]: | |
| lines.append(f"- {e['entity']}: risk {e['risk']:.2f}, {e['alarms']} alarms ({', '.join(e['families'])}), " | |
| f"targets {', '.join(e['targets'])}, ports {', '.join(map(str, e['ports']))}, max score {e['max_score']} -> {e['action']}.") | |
| for ev in e["evidence"]: | |
| lines.append(f" evidence: {ev}") | |
| for x in r["recommendations"]: | |
| lines.append(f"* {x}") | |
| return "\n".join(lines) | |
| def render_fa(r: dict) -> str: | |
| lv = {"quiet": "\u0622\u0631\u0627\u0645", "elevated": "\u0628\u0627\u0644\u0627", "attack": "\u062d\u0645\u0644\u0647", "severe": "\u0634\u062f\u06cc\u062f"}[r["level"]] | |
| lines = [f"\u06af\u0632\u0627\u0631\u0634 DibaGuard - \u0633\u0637\u062d {lv}\u060c \u0641\u0634\u0627\u0631 \u062d\u0645\u0644\u0647 {100 * r['site_pressure']:.1f}\u066a \u0627\u0632 \u062a\u0631\u0627\u0641\u06cc\u06a9 \u0627\u062e\u06cc\u0631" + ("\u060c \u0631\u0627\u0646\u0634 (drift) \u062f\u06cc\u062f\u0647 \u0634\u062f" if r["drift"] else "") + "."] | |
| c = r["counts"] | |
| lines.append(f"\u0645\u0634\u0627\u0647\u062f\u0647\u200c\u0634\u062f\u0647: {c.get('flows', 0):,} \u062c\u0631\u06cc\u0627\u0646\u060c {c.get('http', 0):,} \u062f\u0631\u062e\u0648\u0627\u0633\u062a HTTP\u061b {c.get('alarms_in_window', 0):,} \u0647\u0634\u062f\u0627\u0631 \u0627\u0632 " | |
| f"{c.get('entities_in_window', 0)} \u0645\u0648\u062c\u0648\u062f\u06cc\u062a \u062f\u0631 \u0627\u06cc\u0646 \u0628\u0627\u0632\u0647.") | |
| if r["families"]: | |
| lines.append("\u062e\u0627\u0646\u0648\u0627\u062f\u0647\u200c\u0647\u0627: " + "\u060c ".join(f"{_FA_FAMILY.get(f, f)} ×{n}" for f, n in r["families"].items()) + ".") | |
| act = {"block": "\u0645\u0633\u062f\u0648\u062f \u0634\u0648\u062f", "challenge": "\u0686\u0627\u0644\u0634 (captcha/JS)", "rate_limit": "\u0645\u062d\u062f\u0648\u062f\u06cc\u062a \u0646\u0631\u062e", "monitor": "\u067e\u0627\u06cc\u0634"} | |
| for e in r["entities"]: | |
| lines.append(f"- {e['entity']}: \u0631\u06cc\u0633\u06a9 {e['risk']:.2f}\u060c {e['alarms']} \u0647\u0634\u062f\u0627\u0631 ({_SEP_FA.join(_FA_FAMILY.get(f, f) for f in e['families'])})\u060c " | |
| f"\u0627\u0647\u062f\u0627\u0641 {_SEP_FA.join(e['targets'])}\u060c \u067e\u0648\u0631\u062a\u200c\u0647\u0627 {_SEP_FA.join(map(str, e['ports']))}\u060c \u0628\u06cc\u0634\u06cc\u0646\u0647 \u0627\u0645\u062a\u06cc\u0627\u0632 {e['max_score']} ← {act[e['action']]}.") | |
| for x in r["recommendations"]: | |
| lines.append(f"* {x}") | |
| return "\n".join(lines) | |