mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
feat(secubox-core+toolbox): generative rule engine + sensitivity profiles + whitelist expansion + iOS captive fix (ref #492)
## 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
This commit is contained in:
parent
9690837177
commit
6399a6c3ee
385
common/secubox_core/rule_engine.py
Normal file
385
common/secubox_core/rule_engine.py
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
|
||||
"""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
|
||||
|
|
@ -40,7 +40,19 @@ p{color:#a8b6a8;margin:0.5rem 0;text-align:center;font-size:0.9rem}
|
|||
{% endif %}
|
||||
</div>
|
||||
|
||||
<a href="https://duckduckgo.com" class="go-surf">🚀 COMMENCE À SURFER</a>
|
||||
{# 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. #}
|
||||
<a href="http://captive.apple.com/hotspot-detect.html"
|
||||
target="_blank" rel="noopener"
|
||||
class="go-surf"
|
||||
onclick="setTimeout(function(){window.location.href='https://duckduckgo.com';}, 500);">
|
||||
🚀 COMMENCE À SURFER
|
||||
</a>
|
||||
<p style="font-size:0.7rem;color:#444;text-align:center;margin-top:-0.5rem">
|
||||
↑ ferme la page captive et ouvre Safari · ou utilise ton navigateur directement
|
||||
</p>
|
||||
|
||||
<p class="subtle">// Hash session : <span class="mac">{{ mac_hash }}</span></p>
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user