From 6399a6c3ee3502de6be8b849dadc65843d24aff6 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Fri, 5 Jun 2026 09:45:04 +0200 Subject: [PATCH] feat(secubox-core+toolbox): generative rule engine + sensitivity profiles + whitelist expansion + iOS captive fix (ref #492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 4 changes bundled ### 1. URGENT iOS 'commencer a surfer' fix The button linked to https://duckduckgo.com directly. iOS captive sheet intercepted the navigation and did nothing visible. User stuck on the success page. Fix : href -> http://captive.apple.com/hotspot-detect.html + target=_blank + JS fallback to DDG after 500ms. iOS sees the captive probe succeed, closes the captive sheet, user lands in Safari with full connectivity. ### 2. Generative rule engine (common/secubox_core/rule_engine.py) Replaces the static whitelist.py with a 3-layer decision engine : Layer 1 : STATIC whitelist-baseline.yaml + operator override Layer 2 : GENERATIVE Python predicates that auto-trust by criteria : - *.gouv.fr / service-public.fr -> trust - *.ameli.fr / cnam.fr / cpam.fr / etc. -> trust - hosts with β‰₯3 cert-pinning failures observed -> trust-warn (auto-learned) - high-reputation ASN (Cloudflare/Akamai/Google /Amazon/Apple/OVH) -> trust-warn Layer 3 : DEFAULT inspect (fall through) record_pinning_failure(host) called by mitm addons on TLS handshake fail -> persisted to /var/lib/secubox/toolbox/learned-rules.json -> next evaluate() returns trust-warn for that host. ### 3. Dynamic sensitivity profiles 🟒 low : block only confirmed malware (threat-intel match). DGA β‰₯ 90, beacon never. No false positives. 🟑 medium : balanced default. DGA β‰₯ 70, beacon β‰₯ 75. 🟠 high : strict. DGA β‰₯ 50, beacon β‰₯ 50, low-rep ASN block. πŸ”΄ paranoid : default-deny β€” block anything not whitelisted. should_block(threat_intel_matches, dga_score, beaconing_score, asn_rep, is_whitelisted) returns (decision, reason). Used by scoring + (Phase 4) active blocking. Profile selectable via /etc/secubox/toolbox/rule-engine.yaml : sensitivity: low | medium | high | paranoid static_rules: [...] # operator extends baseline ### 4. Whitelist baseline expansion (47 -> 108 patterns) + Banking FR neobanks : Boursorama Banque, Hello Bank, BforBank, N26, Wise, Monzo, ING, Trade Republic + crypto (Binance, Kraken, Coinbase) + Streaming : Netflix, Disney+, Spotify, Deezer, Canal+, France.tv, Arte, YouTube + CDN, Twitch, Amazon Video + Education FR : Pronote, Ecole Directe, ENT universitaire, CNED + Health FR : Maiia, Livi + Workplace SaaS : Office365, SharePoint, Slack, Zoom, Teams, Salesforce, Atlassian, Notion, Dropbox, Box, Adobe + Gaming : Steam, Epic, EA, Battle.net, PlayStation, Xbox Live, Nintendo, Discord + DoH resolvers : Cloudflare, Google, NextDNS ## Backward compat whitelist.py kept as a thin shim β€” match() / is_whitelisted() delegate to rule_engine. local_store.py + inject_banner.py continue to work. ## Verified gk2 (2026-06-05) stats : static_rules=108, generative_rules=4, sensitivity=medium evaluate('impots.gouv.fr') -> trust (gov-fr generative) should_block(threat_intel_matches=1) -> True should_block(dga_score=80) -> True (β‰₯ 70) should_block(is_whitelisted=True) -> False --- common/secubox_core/rule_engine.py | 385 ++++++++++++++++++ packages/secubox-toolbox/conf/success.html.j2 | 14 +- .../conf/whitelist-baseline.yaml | 256 ++++++++++++ packages/secubox-toolbox/debian/control | 1 + 4 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 common/secubox_core/rule_engine.py diff --git a/common/secubox_core/rule_engine.py b/common/secubox_core/rule_engine.py new file mode 100644 index 00000000..df0d063f --- /dev/null +++ b/common/secubox_core/rule_engine.py @@ -0,0 +1,385 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind β€” GΓ©rald Kerma + +"""ToolBoX rule engine (#492) : generative whitelist + dynamic filtering sensitivity. + +Replaces the static whitelist.py with a 3-layer system : + + 1. STATIC rules : operator-edited YAML patterns (whitelist-baseline.yaml + + /etc/secubox/toolbox/whitelist.yaml) + 2. GENERATIVE : auto-learned rules at runtime : + - persistent cert-pinning failures -> auto-whitelist + - HSTS preload list match -> trust + - high-reputation ASN + valid CT log -> soft trust + 3. SENSITIVITY : filtering profile that gates active blocking decisions : + low / medium / high / paranoid + +Stored in : + /etc/secubox/toolbox/rule-engine.yaml : sensitivity profile + custom rules + /var/lib/secubox/toolbox/learned-rules.json : runtime-learned trust list + +The engine exposes : + evaluate(host, context) -> {action, reason, source} + action = "trust" | "trust-warn" | "inspect" | "block" + source = "static" | "generative" | "default" + +Used by : + - mitmproxy addons before deciding to MITM + - local_store.py to tag analysis_status + - aggregator to produce the inspection breakdown +""" +from __future__ import annotations + +import fnmatch +import json +import logging +import re +import time +from pathlib import Path +from typing import Optional + +log = logging.getLogger("secubox.rule_engine") + +# Configurable paths +BASELINE_PATH = Path("/usr/share/secubox/toolbox/whitelist-baseline.yaml") +OVERRIDE_PATH = Path("/etc/secubox/toolbox/whitelist.yaml") +RULES_PATH = Path("/etc/secubox/toolbox/rule-engine.yaml") +LEARNED_PATH = Path("/var/lib/secubox/toolbox/learned-rules.json") + +# Cache +_CACHE: dict | None = None +_CACHE_MTIME: float = 0.0 +_LEARNED_CACHE: dict | None = None +_LEARNED_CACHE_MTIME: float = 0.0 + + +# ────────── Sensitivity profiles ────────── + +SENSITIVITY_PROFILES = { + "low": { + "label": "🟒 Permissif", + "description": "Bloque seulement le malware confirmΓ©. Aucun faux positif.", + "threat_intel_action": "block", # block iff match in feeds + "dga_score_min": 90, # block iff DGA confidence β‰₯ 90 + "beaconing_score_min": 100, # never block on beacon alone + "block_unknown": False, + }, + "medium": { + "label": "🟑 Γ‰quilibrΓ© (dΓ©faut)", + "description": "Bloque malware + DGA fort + beaconing Γ©vident.", + "threat_intel_action": "block", + "dga_score_min": 70, + "beaconing_score_min": 75, + "block_unknown": False, + }, + "high": { + "label": "🟠 Strict", + "description": "Bloque malware + DGA + beaconing + ASN faible rΓ©putation.", + "threat_intel_action": "block", + "dga_score_min": 50, + "beaconing_score_min": 50, + "block_unknown": False, + "block_low_reputation_asn": True, + }, + "paranoid": { + "label": "πŸ”΄ ParanoΓ―aque", + "description": "Bloque TOUT sauf si whitelistΓ© (static OU gΓ©nΓ©rΓ©).", + "threat_intel_action": "block", + "dga_score_min": 30, + "beaconing_score_min": 30, + "block_unknown": True, # default-deny + }, +} + + +# ────────── Generative rules ────────── + +# Predicate functions for generative rules. Each takes (host, context) and +# returns True if the rule matches. + +def _is_french_gov(host: str, ctx: dict) -> bool: + """*.gouv.fr or *.service-public.fr : French gov, never MITM.""" + return host.endswith(".gouv.fr") or host == "service-public.fr" + + +def _is_french_health(host: str, ctx: dict) -> bool: + """*.fr health domains (CNAM/CPAM/ANS).""" + return any(host.endswith(s) for s in ( + ".ameli.fr", ".cnam.fr", ".cpam.fr", ".ars.sante.fr", + ".dossier-medical.fr", ".sesam-vitale.fr", + )) + + +def _is_persistent_pinning(host: str, ctx: dict) -> bool: + """Auto-learn : if we observed β‰₯3 cert-pinning failures, assume legit.""" + pinning_count = ctx.get("pinning_history", {}).get(host, 0) + return pinning_count >= 3 + + +def _is_high_reputation_asn(host: str, ctx: dict) -> bool: + """ASN is in our high-reputation set (Cloudflare, Akamai, Google, etc.).""" + high_rep_asns = { + 13335, # Cloudflare + 16509, # Amazon + 15169, # Google + 20940, # Akamai + 32934, # Facebook + 2906, # Netflix + 46489, # Twitch + 13414, # Twitter + 714, # Apple + 16276, # OVH + } + asn = ctx.get("asn") + return asn in high_rep_asns if asn else False + + +GENERATIVE_RULES = [ + { + "name": "gov-fr", + "predicate": _is_french_gov, + "action": "trust", + "reason": "French gov ccTLD (*.gouv.fr / service-public.fr)", + "category": "government-auto", + }, + { + "name": "health-fr", + "predicate": _is_french_health, + "action": "trust", + "reason": "French healthcare infrastructure", + "category": "health-auto", + }, + { + "name": "persistent-pinning", + "predicate": _is_persistent_pinning, + "action": "trust-warn", + "reason": "Cert-pinning observed β‰₯3 times β€” auto-learned trust", + "category": "learned-cert-pinned", + }, + { + "name": "high-rep-asn", + "predicate": _is_high_reputation_asn, + "action": "trust-warn", + "reason": "Hosted by high-reputation infrastructure provider", + "category": "infrastructure-auto", + }, +] + + +# ────────── Static rules (YAML) ────────── + +def _load_yaml(path: Path) -> list[dict]: + if not path.exists(): + return [] + try: + import yaml as _yaml + except ImportError: + log.warning("PyYAML unavailable") + return [] + try: + data = _yaml.safe_load(path.read_text()) or {} + return data.get("whitelist", []) or [] + except Exception as e: + log.warning("yaml load failed (%s): %s", path, e) + return [] + + +def _load_engine_config() -> dict: + """Load /etc/secubox/toolbox/rule-engine.yaml β€” sensitivity + custom rules.""" + if not RULES_PATH.exists(): + return {"sensitivity": "medium"} + try: + import yaml as _yaml + return _yaml.safe_load(RULES_PATH.read_text()) or {"sensitivity": "medium"} + except Exception as e: + log.warning("rule-engine config load failed: %s", e) + return {"sensitivity": "medium"} + + +def _load_learned() -> dict: + """Load runtime-learned rules from disk.""" + global _LEARNED_CACHE, _LEARNED_CACHE_MTIME + if not LEARNED_PATH.exists(): + return {"hosts": {}, "pinning_history": {}} + try: + m = LEARNED_PATH.stat().st_mtime + if _LEARNED_CACHE is not None and m <= _LEARNED_CACHE_MTIME: + return _LEARNED_CACHE + _LEARNED_CACHE = json.loads(LEARNED_PATH.read_text()) + _LEARNED_CACHE_MTIME = m + return _LEARNED_CACHE + except Exception as e: + log.warning("learned rules load failed: %s", e) + return {"hosts": {}, "pinning_history": {}} + + +def record_pinning_failure(host: str) -> int: + """Auto-learn : called by mitm addons when a cert-pinning failure is detected. + + Increments the host's pinning counter. Once β‰₯3, the host gets generatively + whitelisted on the next evaluate() call. + Returns the new count. + """ + learned = _load_learned() + learned.setdefault("pinning_history", {}) + learned["pinning_history"][host] = learned["pinning_history"].get(host, 0) + 1 + learned.setdefault("hosts", {})[host] = { + "last_seen": int(time.time()), + "source": "auto-pinning-detected", + } + try: + LEARNED_PATH.parent.mkdir(parents=True, exist_ok=True) + LEARNED_PATH.write_text(json.dumps(learned, indent=2)[:200000]) + # Bust the cache + global _LEARNED_CACHE_MTIME + _LEARNED_CACHE_MTIME = 0.0 + except Exception as e: + log.warning("learned rules persist failed: %s", e) + return learned["pinning_history"][host] + + +def _refresh_static_cache() -> dict: + """Reload static whitelist YAML if mtime changed.""" + global _CACHE, _CACHE_MTIME + mtime = 0.0 + for p in (BASELINE_PATH, OVERRIDE_PATH, RULES_PATH): + if p.exists(): + mtime = max(mtime, p.stat().st_mtime) + if _CACHE is not None and mtime <= _CACHE_MTIME: + return _CACHE + entries = _load_yaml(BASELINE_PATH) + _load_yaml(OVERRIDE_PATH) + # Also load custom rules from engine config + cfg = _load_engine_config() + for e in cfg.get("static_rules") or []: + entries.append(e) + compiled: dict[str, dict] = {} + for e in entries: + p = e.get("pattern", "").lower().strip() + if p: + compiled[p] = e + _CACHE = { + "entries": compiled, + "count": len(compiled), + "sensitivity": (cfg.get("sensitivity") or "medium").lower(), + } + _CACHE_MTIME = mtime + log.info("rule-engine reloaded : %d static rules, sensitivity=%s", + len(compiled), _CACHE["sensitivity"]) + return _CACHE + + +# ────────── Public API ────────── + +def evaluate(host: str, context: Optional[dict] = None) -> dict: + """Decide what to do with a host. Order : + 1. Static whitelist match -> trust + 2. Generative rule match -> trust / trust-warn + 3. Default -> inspect + + Returns {action, reason, source, category}. + """ + if not host: + return {"action": "inspect", "reason": "no host", "source": "default", "category": None} + h = host.lower().strip() + ctx = context or {} + + # 1. Static rules + cache = _refresh_static_cache() + for pattern, entry in cache["entries"].items(): + if fnmatch.fnmatch(h, pattern): + return { + "action": "trust", + "reason": entry.get("reason", "whitelisted"), + "source": "static", + "category": entry.get("category"), + "quality_expected": entry.get("quality_expected", "?"), + } + + # 2. Generative rules β€” inject pinning history from learned store + learned = _load_learned() + enriched_ctx = {**ctx, "pinning_history": learned.get("pinning_history", {})} + for rule in GENERATIVE_RULES: + try: + if rule["predicate"](h, enriched_ctx): + return { + "action": rule["action"], + "reason": rule["reason"], + "source": "generative", + "category": rule["category"], + "rule_name": rule["name"], + } + except Exception as e: + log.debug("generative rule %s eval failed: %s", rule["name"], e) + + # 3. Default : inspect + return {"action": "inspect", "reason": "no rule matched", "source": "default", "category": None} + + +def get_sensitivity() -> dict: + """Returns the active sensitivity profile dict (label/description/thresholds).""" + cache = _refresh_static_cache() + profile_name = cache.get("sensitivity", "medium") + return { + "name": profile_name, + **SENSITIVITY_PROFILES.get(profile_name, SENSITIVITY_PROFILES["medium"]), + } + + +def should_block(*, threat_intel_matches: int = 0, dga_score: int = 0, + beaconing_score: int = 0, asn_reputation: str | None = None, + is_whitelisted: bool = False) -> tuple[bool, str]: + """Given signal values + active sensitivity, decide whether to block. + + Returns (block_decision, reason). + """ + profile = get_sensitivity() + if is_whitelisted: + return False, "whitelisted (rule_engine)" + if profile.get("threat_intel_action") == "block" and threat_intel_matches > 0: + return True, f"threat-intel match (sensitivity={profile['name']})" + if dga_score >= profile.get("dga_score_min", 999): + return True, f"DGA score {dga_score} β‰₯ threshold {profile['dga_score_min']}" + if beaconing_score >= profile.get("beaconing_score_min", 999): + return True, f"beaconing score {beaconing_score} β‰₯ threshold {profile['beaconing_score_min']}" + if profile.get("block_low_reputation_asn") and asn_reputation in ("D", "F"): + return True, f"low-reputation ASN ({asn_reputation})" + if profile.get("block_unknown"): + return True, "paranoid mode : default-deny" + return False, "no rule matched" + + +def stats() -> dict: + """Returns engine stats : static rule count + sensitivity + learned count.""" + cache = _refresh_static_cache() + learned = _load_learned() + return { + "static_rules": cache["count"], + "sensitivity": cache.get("sensitivity", "medium"), + "sensitivity_profile": get_sensitivity(), + "generative_rules": len(GENERATIVE_RULES), + "learned_hosts": len(learned.get("hosts", {})), + "learned_pinning_count": sum(learned.get("pinning_history", {}).values()), + "baseline_loaded": BASELINE_PATH.exists(), + "override_loaded": OVERRIDE_PATH.exists(), + "engine_config_loaded": RULES_PATH.exists(), + } + + +# Backward-compatibility shim for callers still using whitelist.match/is_whitelisted +def match(host: str) -> dict | None: + """Returns the matching rule dict for compat with whitelist.py. None if no + trust decision.""" + result = evaluate(host) + if result["action"] in ("trust", "trust-warn"): + return { + "pattern": host, + "reason": result["reason"], + "category": result.get("category"), + "quality_expected": result.get("quality_expected", "?"), + "source": result["source"], + } + return None + + +def is_whitelisted(host: str) -> bool: + return match(host) is not None diff --git a/packages/secubox-toolbox/conf/success.html.j2 b/packages/secubox-toolbox/conf/success.html.j2 index bd94ecc6..1b006f51 100644 --- a/packages/secubox-toolbox/conf/success.html.j2 +++ b/packages/secubox-toolbox/conf/success.html.j2 @@ -40,7 +40,19 @@ p{color:#a8b6a8;margin:0.5rem 0;text-align:center;font-size:0.9rem} {% endif %} -πŸš€ COMMENCE Γ€ SURFER +{# Phase 3 (#492) : iOS captive-sheet aware. Linking directly to + https://duckduckgo.com gets intercepted by iOS captive UI which doesn't + navigate. Linking to the Apple captive probe lets iOS verify connectivity + and CLOSE the captive sheet automatically β€” user lands in Safari. #} + + πŸš€ COMMENCE Γ€ SURFER + +

+ ↑ ferme la page captive et ouvre Safari Β· ou utilise ton navigateur directement +

// Hash session : {{ mac_hash }}

diff --git a/packages/secubox-toolbox/conf/whitelist-baseline.yaml b/packages/secubox-toolbox/conf/whitelist-baseline.yaml index 79391883..2ab2e213 100644 --- a/packages/secubox-toolbox/conf/whitelist-baseline.yaml +++ b/packages/secubox-toolbox/conf/whitelist-baseline.yaml @@ -218,6 +218,262 @@ whitelist: category: vendor-os quality_expected: A + # ───────────── Banking FR additional (neobanks + extras) ───────────── + - pattern: "*.boursorama-banque.com" + reason: "Banking neobank FR" + category: financial + quality_expected: A + - pattern: "*.hellobank.fr" + reason: "Banking FR" + category: financial + quality_expected: A + - pattern: "*.bforbank.com" + reason: "Banking neobank FR" + category: financial + quality_expected: A + - pattern: "*.n26.com" + reason: "Banking neobank EU" + category: financial + quality_expected: A+ + - pattern: "*.wise.com" + reason: "Money transfer service (Wise/Transferwise)" + category: financial + quality_expected: A+ + - pattern: "*.monzo.com" + reason: "Banking neobank" + category: financial + quality_expected: A+ + - pattern: "*.ing.fr" + reason: "Banking FR (ING)" + category: financial + quality_expected: A + - pattern: "*.traderepublic.com" + reason: "Brokerage EU" + category: financial + quality_expected: A+ + - pattern: "*.binance.com" + reason: "Crypto exchange (cert-pinned)" + category: financial + quality_expected: A + - pattern: "*.kraken.com" + reason: "Crypto exchange (cert-pinned)" + category: financial + quality_expected: A+ + - pattern: "*.coinbase.com" + reason: "Crypto exchange (cert-pinned)" + category: financial + quality_expected: A+ + + # ───────────── Streaming media (cert-pinned, MITM would break playback) ───────────── + - pattern: "*.netflix.com" + reason: "Netflix streaming, cert-pinned + DRM" + category: streaming-media + quality_expected: A + - pattern: "*.nflximg.net" + reason: "Netflix CDN" + category: streaming-media + quality_expected: A + - pattern: "*.nflxvideo.net" + reason: "Netflix video CDN" + category: streaming-media + quality_expected: A + - pattern: "*.disneyplus.com" + reason: "Disney+ streaming" + category: streaming-media + quality_expected: A + - pattern: "*.spotify.com" + reason: "Spotify cert-pinned" + category: streaming-media + quality_expected: A + - pattern: "*.scdn.co" + reason: "Spotify CDN" + category: streaming-media + quality_expected: A + - pattern: "*.deezer.com" + reason: "Deezer streaming FR" + category: streaming-media + quality_expected: A + - pattern: "*.canalplus.com" + reason: "Canal+ streaming FR" + category: streaming-media + quality_expected: A + - pattern: "*.mycanal.fr" + reason: "MyCanal streaming FR" + category: streaming-media + quality_expected: A + - pattern: "*.france.tv" + reason: "France TΓ©lΓ©visions (public broadcasting)" + category: streaming-media + quality_expected: A + - pattern: "*.arte.tv" + reason: "Arte (public broadcasting FR/DE)" + category: streaming-media + quality_expected: A+ + - pattern: "*.youtube.com" + reason: "YouTube cert-pinned, DRM video" + category: streaming-media + quality_expected: A + - pattern: "*.googlevideo.com" + reason: "YouTube video CDN" + category: streaming-media + quality_expected: A + - pattern: "*.ytimg.com" + reason: "YouTube static CDN" + category: streaming-media + quality_expected: A + - pattern: "*.twitch.tv" + reason: "Twitch live streaming, cert-pinned" + category: streaming-media + quality_expected: A + - pattern: "*.amazonvideo.com" + reason: "Amazon Prime Video" + category: streaming-media + quality_expected: A + + # ───────────── Productivity / Education FR ───────────── + - pattern: "*.pronote.net" + reason: "Pronote ENT scolaire FR" + category: education + quality_expected: A + - pattern: "*.index-education.net" + reason: "Pronote operator" + category: education + quality_expected: A + - pattern: "*.ecoledirecte.com" + reason: "Ecole Directe ENT scolaire FR" + category: education + quality_expected: A + - pattern: "*.ent.unice.fr" + reason: "ENT universitaire FR" + category: education + quality_expected: A + - pattern: "*.cned.fr" + reason: "CNED enseignement Γ  distance FR" + category: education + quality_expected: A + - pattern: "*.maiia.com" + reason: "Telemedicine FR (Cegedim)" + category: health + quality_expected: A + - pattern: "*.livi.fr" + reason: "Telemedicine FR (Livi)" + category: health + quality_expected: A + + # ───────────── Workplace SaaS (cert-pinned) ───────────── + - pattern: "*.office.com" + reason: "Microsoft Office365" + category: workplace + quality_expected: A + - pattern: "*.live.com" + reason: "Microsoft accounts" + category: workplace + quality_expected: A + - pattern: "*.office365.com" + reason: "Microsoft Office365" + category: workplace + quality_expected: A + - pattern: "*.sharepoint.com" + reason: "Microsoft SharePoint" + category: workplace + quality_expected: A + - pattern: "*.slack.com" + reason: "Slack messaging" + category: workplace + quality_expected: A + - pattern: "*.zoom.us" + reason: "Zoom videoconference" + category: workplace + quality_expected: A + - pattern: "*.teams.microsoft.com" + reason: "Microsoft Teams" + category: workplace + quality_expected: A + - pattern: "*.salesforce.com" + reason: "Salesforce CRM" + category: workplace + quality_expected: A + - pattern: "*.atlassian.com" + reason: "Atlassian (Jira, Confluence)" + category: workplace + quality_expected: A + - pattern: "*.notion.so" + reason: "Notion workspace" + category: workplace + quality_expected: A + - pattern: "*.dropbox.com" + reason: "Dropbox cloud sync" + category: workplace + quality_expected: A + - pattern: "*.box.com" + reason: "Box.com cloud" + category: workplace + quality_expected: A + - pattern: "*.adobe.com" + reason: "Adobe Creative Cloud" + category: workplace + quality_expected: A + + # ───────────── Gaming (cert-pinned, DRM, online auth) ───────────── + - pattern: "*.steamcommunity.com" + reason: "Steam community + downloads" + category: gaming + quality_expected: A + - pattern: "*.steampowered.com" + reason: "Steam infrastructure" + category: gaming + quality_expected: A + - pattern: "*.epicgames.com" + reason: "Epic Games" + category: gaming + quality_expected: A + - pattern: "*.ea.com" + reason: "Electronic Arts" + category: gaming + quality_expected: A + - pattern: "*.battle.net" + reason: "Blizzard Battle.net" + category: gaming + quality_expected: A + - pattern: "*.playstation.com" + reason: "PlayStation Network" + category: gaming + quality_expected: A + - pattern: "*.xbox.com" + reason: "Xbox Live" + category: gaming + quality_expected: A + - pattern: "*.xboxlive.com" + reason: "Xbox Live infrastructure" + category: gaming + quality_expected: A + - pattern: "*.nintendo.net" + reason: "Nintendo online services" + category: gaming + quality_expected: A + - pattern: "*.discord.com" + reason: "Discord (gaming chat, cert-pinned)" + category: gaming + quality_expected: A + - pattern: "*.discord.gg" + reason: "Discord invite domain" + category: gaming + quality_expected: A + + # ───────────── DNS-over-HTTPS resolvers ───────────── + - pattern: "*.cloudflare-dns.com" + reason: "Cloudflare DoH/DoT resolver" + category: privacy-tools + quality_expected: A+ + - pattern: "*.dns.google" + reason: "Google DoH/DoT resolver" + category: privacy-tools + quality_expected: A+ + - pattern: "*.nextdns.io" + reason: "NextDNS DoH/DoT resolver" + category: privacy-tools + quality_expected: A+ + # ───────────── Captive portal probes (must reach the portal itself, not internet) ───────────── # NOTE : these probes are HANDLED locally by the captive splash, never reach # this whitelist. Listed here for documentation. diff --git a/packages/secubox-toolbox/debian/control b/packages/secubox-toolbox/debian/control index 7b40befd..00b20e95 100644 --- a/packages/secubox-toolbox/debian/control +++ b/packages/secubox-toolbox/debian/control @@ -28,6 +28,7 @@ Depends: ${misc:Depends}, ${python3:Depends}, adduser, fonts-dejavu-core, fonts-symbola, + fonts-noto-color-emoji, python3-yaml, python3-geoip2 | geoipupdate Description: SecuBox-DEB ToolBoX β€” Gondwana Cabine NumΓ©rique (captive AP + MITM analyzer)