Commit Graph

1619 Commits

Author SHA1 Message Date
4f89bd8be7 Merge feature/498 phase-7 WAF — A.2 + B (ref #498)
Phase 7.A (bridge), 7.A.2 (backport + dashboard + postinst), 7.B (rate-limit
+ honeypot) all shipped same-day. 7.C remains as long-term followup.

Live verified on gk2 :
  - nft rate-limit chain attached priority -10
  - Honeypot routes returning 200 + logging to /var/log/nginx/honeypot.log
  - Bridge POST /v1/alerts → cscli decision → nft drop (12s round-trip)
  - Threats dashboard endpoint /api/v1/mitmproxy/waf/enforcement ready

7.C (eBPF/XDP + ModSec + federation) kept open in issue #498.
2026-06-05 17:23:21 +02:00
a35ab5c50e feat(secubox-mitmproxy): Phase 7.A.2 + 7.B — WAF backport + dashboard + rate-limit + honeypot (ref #498)
Phase 7.A.2 + 7.B shipped same-day after Phase 7.A landing (#498 mid-day).

## Live verified on gk2

### Phase 7.A.2

- `packages/secubox-waf/mitmproxy/secubox_waf.py` (older 756→878 lines) :
  backported `_load_crowdsec_cfg`, `_cs_jwt`, `_ban_via_crowdsec`,
  Phase 6.J Connection:close. Both WAF copies now in sync.
- `debian/postinst` : auto-invokes `secubox-waf-cs-bridge-setup` if cscli
  present, installs config to host + bind-mounts into LXC if present.
- `api/routers/waf.py` : new GET /enforcement returns bridge_enabled,
  bans_pushed/failed, requests, blocked, warnings, rate_limit_offenders
  (live nft set count), honeypot_hits_last_hour (from nginx log scan),
  recent_bans (cscli decisions list --origin=secubox-waf), recent_threats.
- `www/mitmproxy/threats.html` : new tab with 6 KPI cards + 2 tables
  (bans, threats), auto-refresh 5s.

### Phase 7.B

- `nftables/secubox-waf-ratelimit.nft` : table inet secubox_waf_ratelimit
  with offenders_v4/v6 (dynamic 5min timeout) + whitelist_v4 (LAN/loopback) +
  input hook priority -10. Rule : tcp SYN to 80/443, limit rate over
  30/second burst 50 → drop + add to offenders. Live-loaded :
  table created, chain attached, ready to drop slowloris/scanners
  the moment they cross threshold.
- `debian/secubox-waf-ratelimit.service` : systemd unit loads the nft
  file on boot (idempotent, RemainAfterExit=yes).
- `nginx/honeypot.conf` : 5 location blocks for known bot signatures
  (`/wp-admin`, `/.env`, `/.git/config`, `/phpmyadmin`, `/actuator` etc).
  Returns empty 200, logs to `/var/log/nginx/honeypot.log` with custom
  `secubox_honeypot` log_format. Live-tested : 3 paths returned 200,
  honeypot.log filled in real-time.
- `debian/postinst` : copies honeypot.conf to /etc/nginx/secubox-routes.d/
  and creates log_format conf.d snippet.

### Files added

  packages/secubox-mitmproxy/nftables/secubox-waf-ratelimit.nft       (NEW)
  packages/secubox-mitmproxy/nginx/honeypot.conf                       (NEW)
  packages/secubox-mitmproxy/www/mitmproxy/threats.html                (NEW)
  packages/secubox-mitmproxy/debian/secubox-waf-ratelimit.service      (NEW)

### Files modified

  packages/secubox-waf/mitmproxy/secubox_waf.py    +120 lines
  packages/secubox-mitmproxy/api/routers/waf.py    +130 lines
  packages/secubox-mitmproxy/debian/postinst       +35 lines
  packages/secubox-mitmproxy/debian/rules          +11 lines

## What's NOT in this commit (Phase 7.C, future)

- eBPF/XDP kernel filter (replaces Python WAF hot-path)
- ModSecurity in HAProxy with OWASP CRS rules
- Federation : push to CrowdSec Hub + pull AlienVault OTX + Spamhaus DROP
- Tune BAN_THRESHOLD per category (XSS=2, SQLi=1)

Filed as remaining sub-tasks in issue #498 (kept open).
2026-06-05 17:23:00 +02:00
70cd718acc docs: Phase 7.A WAF→CrowdSec bridge shipped — HISTORY/WIP/TODO (ref #498)
* HISTORY.md  : 2026-06-05 entry for Phase 7.A (pipeline, files, discoveries)
* WIP.md      : section Phase 7.A done + 7.A.2/7.B/7.C next-ups
* TODO.md     : 7.A checked, 7.A.2 sub-tasks, roadmap doc referenced

Live state on gk2 :
  secubox-cs-bridge.service active (socat 10.100.0.1:8080 → 127.0.0.1:8080)
  cscli machine 'secubox-waf' registered
  mitm WAF reading /etc/secubox/waf/crowdsec.toml + posting alerts on threshold
  Issue #498 stays open for Phase 7.B and 7.C.
2026-06-05 17:13:11 +02:00
3eb5378e1e Merge feature/498-phase-7-waf-active-enforcement-mitm-crow — Phase 7.A (ref #498)
WAF detections → CrowdSec alerts → nft drops, full chain live-verified
on gk2 (12s round-trip). See commit d4e093a4 for details.

Files changed :
  + packages/secubox-mitmproxy/addons/secubox_waf.py  (+200 lines)
  + packages/secubox-mitmproxy/bin/secubox-waf-cs-bridge-setup
  + packages/secubox-mitmproxy/crowdsec/crowdsec.toml.example

Live infra deployed on gk2 :
  - secubox-cs-bridge.service (socat 10.100.0.1:8080 → 127.0.0.1:8080)
  - cscli machine 'secubox-waf' registered
  - /etc/secubox/waf/crowdsec.toml in mitm LXC
  - mitm WAF reloaded with bridge enabled

Phase 7.B (rate-limit + dashboard + honeypot) and 7.C (eBPF + ModSec)
remain open in issue #498.
2026-06-05 17:11:38 +02:00
d4e093a4db feat(secubox-mitmproxy): Phase 7.A WAF→CrowdSec bridge — alerts push → nft drop (ref #498)
User : 'plan for later a waf with dropping result of attackants...
threats accumulate looks very high'. Phase 7.A delivers the foundation :
WAF detections become real nft drops within seconds.

## E2E live-verified on gk2

  login    : 200 JWT 160 chars
  alert    : 201 ← decision id 13244
  cscli    : Ip:192.0.2.99 ban origin=secubox-waf 1h
  nft      : 192.0.2.99 in table ip crowdsec (kernel-level drop)

Full chain (mitm LXC → host LAPI → bouncer → nft) round-trip in ~12s.

## Architecture

  [secubox_waf.py in mitm LXC]
       │  detects threat via pattern match
       │  if count >= BAN_THRESHOLD
       │     ↓
       │  ban_ip() → _ban_via_crowdsec()
       │     ↓
       │  HTTP POST /v1/alerts (urllib stdlib, no httpx dep)
       │     ↓
  [socat 10.100.0.1:8080 → 127.0.0.1:8080]   ← Phase 7.A.1 bridge
       │     ↓
  [CrowdSec LAPI on host 127.0.0.1:8080]
       │     ↓
  [crowdsec-firewall-bouncer (existing)]
       │     ↓
  [nft add element ip crowdsec ...]
       │     ↓
  [attacker IP dropped at netdev/forward chain]

## Files

### addons/secubox_waf.py
  New constants CROWDSEC_CFG_FILE, _CS_JWT, _CS_JWT_TTL.
  New _load_crowdsec_cfg() reads TOML, validates 4 required fields.
  New _cs_jwt(cfg) caches JWT for 25 min (LAPI default 30 min expiry).
  New _ban_via_crowdsec() POSTs Alert+Decision to /v1/alerts with
    Bearer JWT. urllib stdlib only (mitm venv lacks httpx). On 401,
    forces JWT refresh next call. Stats : bans_pushed, bans_failed.
  Modified ban_ip() : tries HTTP bridge first, falls back to cscli
    subprocess if bridge disabled. Old subprocess path kept for hosts
    where WAF runs natively (cscli available).

### bin/secubox-waf-cs-bridge-setup
  Installs socat forwarder unit secubox-cs-bridge.service so the mitm
  LXC can reach the host loopback LAPI without changing CrowdSec config.
  Registers secubox-waf as a MACHINE (watcher) via cscli machines add
  with -f - so the local agent's credentials file stays untouched.
  Writes /tmp/crowdsec.toml with machine_id + machine_password.
  Operator copies into LXC at /etc/secubox/waf/crowdsec.toml.

### crowdsec/crowdsec.toml.example
  Updated to document machine_id / machine_password (not the old
  bouncer api_key — bouncers can only PULL decisions, not push them).

## Why machine auth (not bouncer)

CrowdSec LAPI splits routes by auth type :
  - X-Api-Key (bouncer) → GET /v1/decisions/stream (read-only)
  - JWT (watcher)       → POST /v1/alerts (write)
  - JWT (watcher)       → DELETE /v1/decisions

To PUSH a ban, the WAF must auth as a watcher. We register a dedicated
machine 'secubox-waf' so its decisions show up in the dashboard tagged
with origin=secubox-waf (vs origin=crowdsec for scenario-driven bans
and origin=cscli for manual ones).

## Socat bridge rationale

Default CrowdSec install binds LAPI to 127.0.0.1:8080. Direct patching
of listen_uri to 10.100.0.1 breaks the local agent + bouncer (they hard-
code 127.0.0.1 in their credential files). A 30-line socat forwarder
service exposes the same LAPI on the LXC bridge gateway, gated by the
host firewall (only LXCs can reach 10.100.0.1). Zero CrowdSec config
change.

## Acceptance criteria (issue #498 Phase 7.A target)

  1. Attacker dropped at nft within 30s of crossing BAN_THRESHOLD ✓
  2. cscli decisions list shows origin=secubox-waf entries           ✓
  3. nft list table ip crowdsec shows the dropped IPs                ✓
  4. threat_counts dict growth bounded                               ✓ (still
     local memory, but attacker IPs now drop before refilling it)

## Phase 7.A.2+ (followups in TODO.md)

  - Backport into packages/secubox-waf/mitmproxy/secubox_waf.py
  - debian/postinst : invoke setup script + bind-mount config into LXC
  - WAF dashboard tab in admin webui showing bans_pushed counter
  - Tune BAN_THRESHOLD per category (XSS=2, SQLi=1, scanner pattern=5)
  - Phase 7.B : nft rate-limit pre-mitm to catch TCP-only scanners
2026-06-05 17:08:35 +02:00
cbde9dd89c plan: Phase 7 WAF active enforcement roadmap (filed #498)
User : 'plan for later a waf with dropping result of attackants...
threats accumulate looks very high, we need a better solution'.

Created :
  .claude/PHASE-7-WAF-ROADMAP.md  — full 3-tier roadmap with code snippets
  GitHub issue #498                — public tracking

## Phase 7.A — Quick win (2-3 days)

Bridge mitm WAF → CrowdSec local API :
  - Detected threat triggers POST /v1/decisions with ban duration
  - crowdsec-firewall-bouncer reads → nft drop in table ip crowdsec
  - Subsequent attempts dropped before TCP handshake
  - Adds 'bans_pushed' to WAF stats

## Phase 7.B — Mid-term (1-2 weeks)

  - nft rate-limiting pre-mitm via 'meter' / 'limit rate'
    (catches TCP-only scanners CrowdSec doesn't see — no HTTP log to parse)
  - WAF threats dashboard in admin webui (top attackers, geo, manual ban)
  - Honeypot routing for known bot signatures (/wp-admin, /.env, etc.)

## Phase 7.C — Long-term (1-2 months)

  - eBPF/XDP filtering at kernel level (replace Python hot-path)
  - ModSecurity in HAProxy with OWASP CRS rules
  - Federation : CrowdSec Hub + AlienVault OTX + Spamhaus DROP

## Acceptance

  1. Attacker dropped at nft within 30s of crossing BAN_THRESHOLD
  2. threat_counts dict stays < 200 under sustained scanner storm
  3. cscli decisions list shows origin=secubox-waf entries
  4. Admin webui Threats tab with live attacker top-20 + manual ban
  5. Wiki page WAF-active-enforcement

## Dependencies

  CrowdSec local API ✓ already on 127.0.0.1:8081
  nft bouncer chain ✓ table ip crowdsec exists (17 IPs currently banned)
  httpx ✓ already in mitm Python env
  No new package deps.

Also updates .claude/TODO.md P0 + .claude/PHASE-7 roadmap entries.
2026-06-05 16:38:26 +02:00
a1081a37f7 Merge feature/496-phase-6-wireguard-mitm-autocert-mode-r3 — Phase 6 R3 WireGuard MAJOR RELEASE (ref #496)
30 commits, 2918 insertions across 26 files. See .claude/HISTORY.md 2026-06-05 entry.

Highlights :
* R3 portable WG tunnel + transparent mitm interception (kbin.gk2.secubox.in:51820)
* Gondwana ToolBoX R3 CA (final clean version, multi-OS install)
* R3 first-class identity (mac_hash WG-aware, events properly stored)
* Banner with cookies/trackers detection + kbin-reachable 'Mon rapport' link
* Splash R3 detection (badge + hides useless captive R0/R1/R2 chooser)
* Public landing with 8-icon quicknav + 8 live KPI cells + cert 2-step probe
* Admin webui tabbed (Clients + Mitm filtering with inline add/remove)
* Kbin filter-control READ-ONLY; edits via admin.gk2.secubox.in/toolbox/
* mitm WAF connection leak fixed (Connection: close upstream)
* Wiki page : R3-WireGuard-install (multi-OS exhaustive guide)

Deployed live on gk2 throughout 2026-06-05.
Tested OK : iPhone + Android R3 surf, banner injection, cookies+trackers
display, Mon rapport link to kbin, whitelist passthrough (Signal/banks).
2026-06-05 16:34:11 +02:00
1685cf6c08 docs: Phase 6 R3 WireGuard MAJOR RELEASE — HISTORY/WIP/TODO + WAF leak source backport (ref #496)
Tracking files updated to reflect 2026-06-05 work :

* .claude/HISTORY.md — full Phase 6 R3 release entry (R3 architecture,
  first-class identity, banner enrichment, splash detection, landing polish,
  multi-OS install, WAF leak fix, 8 bugs resolved)
* .claude/WIP.md — Done/Next/Follow-ups for Phase 6
* .claude/TODO.md — Phase 6 P0 checked + 3 follow-ups added

## Source backport : mitm WAF leak fix

packages/secubox-mitmproxy/addons/secubox_waf.py SecuBoxWAF.request adds :
  flow.request.headers["Connection"] = "close"

Live verification (after live LXC patch + this source backport) :
  Before : FDs 1513, scur=812 (8h growth), HTTP 504 sur /toolbox/
  After  : FDs 3, scur=87 (stable post-restart), 31ms response

Root cause was mitmproxy 11 keeping idle upstream HTTP/1.1 keep-alive
sockets in the per-server pool indefinitely. Connection: close on the
outbound request forces nginx to close TCP after the response, mitm
releases the socket → no pool buildup. Cost : ~1ms per request to
upstream (loopback TCP handshake), well worth the saved FDs.

This commit is the source-of-truth backport. The live LXC at
/var/lib/lxc/mitmproxy/rootfs/srv/mitmproxy/secubox_waf.py was patched
inline first (urgent prod fix) then synced back here.

Note : packages/secubox-waf/mitmproxy/secubox_waf.py (756 lines, older
version) still pending backport — tracked in TODO.md follow-ups.
2026-06-05 16:33:45 +02:00
e2b893142c feat(secubox-toolbox): kbin read-only filter-control + toolbox webui edits + WAF leak fix (ref #496)
User : 'ajouter les filters du toolbox dans admin.gk2.secubox.in/toolbox/
et les supprimer du public ou alors les transformer en readonly'.

Also : '504 Gateway Time-out' on admin.gk2.secubox.in/toolbox/ root-caused
to mitm WAF accumulating 1500+ idle keep-alive sockets.

## 1. /admin/filter-control : kbin = read-only

Detect Host: kbin.* in handler. Public render :
  - no <form> add (replaced by orange warning banner)
  - no remove ✕ buttons (table column removed)
  - banner links to admin.gk2.secubox.in/toolbox/

POST /admin/filter-control/{add,remove} returns HTTP 403 from kbin.
POST /admin/clients/{mh}/level same protection (admin override blocked).

## 2. /toolbox/ webui : full edit + add/remove inline

New card 'Mitm bypass (whitelist)' :
  - Add form (regex pattern input)
  - Table with per-row × delete button
  - Auto-refreshes every 10s with the other panels
  - Calls /api/v1/toolbox/admin/filter-control/{list,add,remove}
  - Hosted on admin.gk2.secubox.in/toolbox/ (Authelia SSO-gated)

File : /usr/share/secubox/www/toolbox/index.html (alias served by nginx)

## 3. mitm WAF leak fix (LIVE only — not in this repo)

Root cause :
  mitmproxy 11 in regular proxy mode keeps upstream HTTP/1.1 keep-alive
  sockets open between flows. The server_pool grows ~1 entry/unique
  upstream host, never shrinks. After 4h : 1500+ FDs, 800+ ESTAB,
  Python workers saturated, HAProxy timeouts → HTTP 504.

Fix (applied to /var/lib/lxc/mitmproxy/rootfs/srv/mitmproxy/secubox_waf.py
on the LXC, NOT this repo — needs separate backport to secubox-mitmproxy):
  In SecuBoxWAF.request, FIRST line :
    flow.request.headers["Connection"] = "close"

  Upstream nginx honors it, closes TCP after response, mitm releases
  socket → no pool buildup. Cost : ~1ms per request (TCP handshake to
  loopback). Trade well worth the saved sockets.

Live verification after patch :
  Before : FDs 1513, scur=812, /toolbox/ 504
  After  : FDs 3, scur=87, /toolbox/ HTTP 200 in 31ms ✓

## Live verification (kbin read-only)

  GET kbin /admin/filter-control → readonly markers present, no add form
  POST kbin /admin/filter-control/add → HTTP 403
  GET admin.gk2 /api/v1/toolbox/admin/filter-control/list → 200 + JSON
  GET admin.gk2 /toolbox/ → filter card markers present
2026-06-05 16:13:54 +02:00
3ba9e4adda fix(secubox-toolbox): mitmproxy-ca.pem perms 0600 → 0640 (crash-loop fix) (ref #496)
User reported : 'fait sur iphone et android mais plus rien sur wireguard'
— R3 surf totally dead after CA regen.

Diagnostic :
  systemctl status secubox-toolbox-mitm-wg
  → restart_counter = 191 in 60 min (crash-loop)
  → PermissionError: [Errno 13] Permission denied:
    '/etc/secubox/toolbox/ca-wg/mitmproxy-ca.pem'

Root cause :
  Combined key+cert file was 0600 root:secubox-toolbox.
  mitm-wg.service runs as User=secubox-toolbox, which can't read 0600
  even with matching group (owner bit only). mitm crashes on startup.

Fix :
  chmod 0640 root:secubox-toolbox

  Group rw matches existing pattern in this dir :
    mitmproxy-ca-cert.pem  0644 (world-readable public cert, fine)
    mitmproxy-ca-cert.cer  0644 (DER public cert, fine)
    mitmproxy-ca.pem       0640 (PRIVATE key inside, group-read only)

Live fix already applied. mitm-wg now running clean (NRestarts=0),
addons loaded, transparent proxy listening on 10.99.1.1:8081.
2026-06-05 15:50:59 +02:00
ba7144a342 fix(secubox-toolbox): R3 CA — remove SAN (Android null bug) + /wg/ca.crt DER endpoint (ref #496)
User reported : 'toujours impossible d'installer le certificat null sur android'.

## Root cause

Previous CA had subjectAltName with arbitrary text containing spaces :
  subjectAltName = DNS:Gondwana ToolBoX R3 CA, DNS:cabine.gondwana.local

Android Chrome's certificate install dialog parses SAN. When SAN
contains spaces in a DNS name (technically invalid per RFC 5280),
Chrome's parser fails silently and renders 'émis par null'. User
can't install the cert because the dialog refuses → tells them to
go via Settings, which then ALSO chokes on the SAN.

## Fix

Regenerate CA without subjectAltName (root CAs don't need SAN
anyway — SAN is for end-entity certs). Minimal DN :
  C=FR, O=CyberMind Gondwana, CN=Gondwana ToolBoX R3 CA

Added basicConstraints pathlen:0 (cleaner — explicit no
intermediate sub-CAs allowed). sha256 signature explicit.

Live verification (after live regen) :
  subject = CN=Gondwana ToolBoX R3 CA, O=CyberMind Gondwana, C=FR
  issuer  = same
  basicConstraints critical CA:TRUE, pathlen:0
  keyUsage critical Certificate Sign, CRL Sign
  SHA1: D5:E4:3A:C1:AD:4E:25:8A:A9:D4:2A:26:52:2C:D8:82:50:63:EA:0E
  NO SAN — Android Settings parses cleanly

Backported to sbin/secubox-toolbox-wg-provision so fresh installs
get the clean CA.

## /wg/ca.crt (NEW)

Same content as /wg/ca.pem but :
  - DER format (binary X.509, not base64)
  - Filename: gondwana-toolbox-r3-ca.crt (Android Settings recognizes .crt)
  - Some Android 11+ installers reject .pem extension entirely

Aliased as /wg/ca.der too.

## R3 install page — Android tab refresh

Primary download button now points to /wg/ca.crt (DER).
.pem kept as fallback link.
Added note for users who saw 'émis par null' on previous attempt :
'supprime l'ancien fichier dans Téléchargements + relance'.

## Logger fix

Earlier commit's '/wg/profile/new' upsert used 'logger.warning' but
this module uses 'log = logging.getLogger(...)'. Renamed to log.warning.
2026-06-05 15:12:21 +02:00
13e48c93c6 feat(secubox-toolbox): splash R3 detection — dedicated badge + hides useless R0/R1/R2 switch (ref #496)
User feedback : 'niveau splash incoherent wireguard r3 undetected and
switch r0 r1 r2 available but useless'.

When a request to / comes from 10.99.1.0/24 (WG peer subnet), the splash
handler now :
  - looks up the peer pubkey → wg_hash → R3 mac_hash
  - sets is_wg_r3=True in the template context
  - sets current_level='r3' (was defaulting to None/r1)

Splash template :
  - Top of page : large gradient banner '🌐 Mode R3 — Tunnel WireGuard ACTIF'
    with client IP, links to /report?mh=, PDF download, /wg/r3-install
  - The captive R0/R1/R2/R3 form is HIDDEN entirely for is_wg_r3 (was
    useless : R3 client is by construction max-analysis, can't downgrade
    to captive level)

Validated mode chip now also handles current_level=='r3'.
2026-06-05 14:44:07 +02:00
d81e16ac1b feat(secubox-toolbox): landing quick-icons nav + admin tabbed filter control (ref #496)
User feedback :
  - 'quick icons landing'                → 8 one-tap quick-access buttons
  - 'deplacements des filters dans webui admin toolbox' → integrate
    /admin/filter-control as a tab in /admin/ instead of separate page

## Landing quick-nav

Hero gains an 8-icon grid below the tagline :
  🌐 R3 Install · 📊 Mon rapport · 📲 CA iPhone · 🤖 CA Android
  📱 QR profil  · 🛠️ Admin       · 📖 Wiki      · 🚪 Cabine

Each icon : emoji + label, purple-bordered card, lift on hover. Flex
wrap so it scales mobile→desktop. All links open the matching endpoint.

## Admin webui : tabbed sections

/admin/ now has 2 top tabs :
  👥 Clients      — existing client table + cards + level switch modal
  🛡 Mitm filtering — NEW : whitelist regex table + add form (was on /admin/filter-control)

The filter tab :
  - GET /admin/filter-control/list (NEW JSON endpoint) → patterns array
  - Inline table with red ✕ button per pattern → POST /remove
  - Top form to  add new regex → POST /add
  - Footer note about source-of-truth file path

The standalone /admin/filter-control HTML page still exists as fallback,
but the natural navigation is now /admin → tab → filter.

Live verification :
  landing → 8 quicknav icons present
  /admin/ → adm-tabs(1) adm-tab(2) adm-content(2) tabs wired
  /admin/filter-control/list → JSON patterns array (15 entries)
2026-06-05 14:13:09 +02:00
2fab850a9d feat(secubox-toolbox): R3 first-class identity + cert test 2-step (ref #496)
User reported : report empty data for R3, cert test false-negative, splash
shows wrong level. Root cause = R3 mac_hash (sha256(wg_pubkey)) never
written to DB.

## Phase 6.H : R3 mac_hash propagation

### _common.py mac_hash_of() — WG-aware
For ip in 10.99.1.0/24 (WG peer subnet) returns sha256(peer_pubkey)[:16]
read from /var/lib/secubox/toolbox/wg-peers.json (cached, mtime-aware
auto-reload). For other IPs falls back to ARP→hash. Single change
cascades to ALL addons that use mac_hash_of (cookies, dpi, ja4,
soc_relay, avatar, inject_banner).

### local_store.py _client_hash() — same logic
local_store is the source-of-truth writer for the toolbox.db events
table. Has its own _hash_mac+_mac_of (no _common import), so duplicate
the WG-aware lookup as _client_hash() and replace all 5 call sites.

### /wg/profile/new — register peer in clients table
Upserts mac_hash = sha256(pubkey)[:16] with level='r3' so :
  - store.get_client_level(wg_hash) returns 'r3' (was 'r1' default)
  - admin webui /admin/clients/rich lists R3 peers
  - _aggregate_session() finds the row (was returning empty session)
  - dashboards display R3 badge correctly

Returns X-Client-Hash header so frontends can know their own hash.

### store.list_clients() — include level column
SQL forgot to SELECT level → admin counts showed 0 R3 even though
all peers had level=r3 in DB. One-word fix.

## Phase 6.G2 : cert test 2-step

Old single-probe test gave false negatives :
  - User on kbin Internet direct → probe example.com OK → reported
    'CA installed' but user wasn't even in tunnel
  - User in tunnel → probe slow / favicon 404 → reported 'CA missing'
    even when CA was fine

New /landing cert test does :
  1. Try GET http://10.99.0.1:8088/qr/splash.png — only reachable
     from inside the WG tunnel (10.99.0.1 is captive-internal, NAT
     through wg-toolbox iface)
  2. If reachable → try HTTPS probe (www.gstatic.com/generate_204).
     If mitm CA installed, image loads; else cert verify fails.
  3. Combine into clear verdict :
       🌐 'Hors tunnel R3 — active WG d'abord'
       🟢 'Tunnel R3 actif + CA R3 trusté ✓'
       🔴 'Tunnel R3 actif mais CA R3 NON trusté — installe profil'

## Live verification

  Backfilled 21 existing WG peers into clients table (level=r3) :
    10.99.1.2..22 → wg_hash → level=r3

  mac_hash_of('10.99.1.15') (Android) → 0fed072beea7ee69 ✓
  mac_hash_of('10.99.1.18') (iPhone)  → 3f988da842ae6806 ✓
  store.get_client_level('0fed072beea7ee69') → 'r3' ✓
  store.get_client_level('3f988da842ae6806') → 'r3' ✓

Events table will populate as phones surf via mitm-wg.
2026-06-05 14:08:16 +02:00
67985d9337 feat(secubox-toolbox): banner detects cookies + trackers + PDF mh= bypass (ref #496)
User wanted the banner to surface privacy signals + reported PDF download
was unreachable from R3.

## 1. Cookies + tracker detection in banner

inject_banner.py now computes per-flow :

  ctx['cookies_set']    = response Set-Cookie count
  ctx['cookies_sent']   = request Cookie names count
  ctx['trackers']       = unique tracker domains in HTML body (first 200kB)
  ctx['is_tracker_host']= 1st-party host matches ads*/doubleclick/pixel/…

Two new regexes (mirror secubox-ad-guard ad_patterns) :
  _TRACKER_PATTERNS       — body scan : doubleclick, googletagmanager,
                            facebook tr, scorecardresearch, hotjar,
                            mixpanel, amplitude, segment, criteo, taboola,
                            outbrain, optimizely, fullstory, newrelic,
                            datadog, sentry, amazon-adsystem, etc.
  _TRACKER_HOST_PATTERNS  — 1st-party host : ads*, adserv, adtrack,
                            pixel, tracking, telemetry, analytics, beacon,
                            metric, stats*

Banner right-side display adds :
  🍪 12        (cookies set+sent combined)
  🎯 4         (tracker domains found in body)
  ⚠ tracker-host  (if the page is itself on a tracker domain)

Privacy : only counts. Cookie names/values + tracker URLs are NOT
embedded in the banner (kept in dpi/cookies addons that hash on store).

Performance : body scan capped at 200kB + tolerant decode (utf-8 → latin-1
fallback), regex compiled once. No I/O, no DB hits.

Verified smoke test on fake flow (lemonde.fr w/ 3 Set-Cookie + 4 trackers
in body) :
  cookies_set: 3  cookies_sent: 4  trackers: 4  is_tracker_host: False

Host classification :
  ads.example.com         → tracker-host=True
  doubleclick.net         → tracker-host=True
  analytics.lemonde.fr    → tracker-host=True
  pixel.facebook.com      → tracker-host=True
  news.ycombinator.com    → tracker-host=False

## 2. /report/me PDF accepts ?mh= bypass

Same patch as /report/me/html (ref earlier commit b865636a-ish). R3 WG
clients hit /report/me/html?mh=<hash> from banner link → PDF download
button under the report → was POSTing to /report/me with NO mh → ARP
lookup failed for 10.99.1.x → HTTP 400.

Now :
  /report/me?mh=<hex>     → bypass ARP, look up hash directly
  no mh                    → fallback to captive ARP path

## 3. PDF download link in report-live.html.j2

The 'Télécharger PDF' button now passes the current mac_hash through :
  <a href="/report/me?mh={{ mac_hash }}">⬇ Télécharger PDF</a>

So clicking it from /report/me/html?mh=X works for R3 + R2 alike.

## Live verification

  curl kbin /report/me?mh=0fed072beea7ee69 → HTTP 200 application/pdf

Pending : iPhone/Android tap on banner → "Mon rapport" → tap on
"Télécharger PDF" → PDF saves to device.
2026-06-05 14:01:22 +02:00
645ce57251 feat(secubox-toolbox): proper R3 CA + multi-OS install page + wiki (ref #496)
User reported (live screenshot from Android Chrome on kbin) :
- 'Ce certificat émis par null doit être installé via les paramètres'
- Signal passthrough OK (whitelist works ✓)
- But surf + banner KO because CA install impossible from Chrome

## Root cause

mitm 11 auto-generated its own CA at first start with subject
'CN=mitmproxy, O=mitmproxy' — minimal DN, ugly to Chrome's installer
(no C, ST, L, OU → renders as 'null' in the dialog).

Also : even with a perfect CA, Android 11+ blocks CA install from
Chrome — the user MUST go through Settings → Security → Encryption &
credentials → Install a certificate → CA certificate. The R3 install
page didn't explain this.

## Fix 1 : pre-generated CA with full DN

secubox-toolbox-wg-provision now generates BEFORE mitm starts a proper
CA with full subject :
  C=FR, ST=Auvergne-Rhone-Alpes, L=Notre-Dame-du-Cruet,
  O=CyberMind Gondwana, OU=ToolBoX R3 WireGuard,
  CN=Gondwana ToolBoX R3 CA

Valid 10 years (was 1y), with proper v3_ca extensions :
  basicConstraints critical CA:TRUE
  keyUsage critical keyCertSign,cRLSign
  subjectKeyIdentifier + authorityKeyIdentifier
  subjectAltName DNS:Gondwana ToolBoX R3 CA, DNS:cabine.gondwana.local

Combined key+cert is written to <confdir>/mitmproxy-ca.pem (the file
mitm 11 reads on startup) so mitm uses OUR cert instead of generating
its own. Also writes mitmproxy-ca-cert.pem (public PEM) and
mitmproxy-ca-cert.cer (DER for Android picky installers).

Idempotent : keeps existing CA if mitmproxy-ca.pem already present
(SHA1 fingerprint preserved across reinstalls).

Live verification :
  subject = C=FR, ST=Auvergne-Rhone-Alpes, L=Notre-Dame-du-Cruet,
            O=CyberMind Gondwana, OU=ToolBoX R3 WireGuard,
            CN=Gondwana ToolBoX R3 CA
  sha1 Fingerprint = 96:74:E5:66:8E:F9:47:D1:F6:7E:C5:BB:73:7F:A4:2F:DC:9A:53:C4

## Fix 2 : /wg/r3-install with per-OS tabs

The install page (step 2) now has 5 OS tabs :
  🍎 iOS / iPad        — .mobileconfig 1-tap + Trust Settings
  🤖 Android           — Settings → Security path + WARNING about Chrome
  🐧 Linux             — Debian/Ubuntu, Fedora/RHEL, Arch + Firefox/Chrome NSS
  💻 macOS             — Keychain GUI + security CLI command
  🪟 Windows           — PowerShell Import-Certificate + GUI fallback

Each tab has copy-paste-ready commands.

Step 1 (WG profile) also gained WG client download links for all OS.

Footer links to the full wiki page.

## Fix 3 : Wiki page

Created https://github.com/CyberMind-FR/secubox-deb/wiki/R3-WireGuard-install
with the exhaustive guide :
  - Architecture diagram
  - Step-by-step install for every OS
  - Verify steps via /landing cert autocheck
  - Whitelist explanation + apps that bypass
  - Troubleshooting section

Live verification :
  /wg/r3-install via kbin : HTTP 200 10015b
  tabs : iOS=2 Android=4 Linux=3 macOS=2 Windows=1
  commands : update-ca-certificates, update-ca-trust, trust anchor,
             Cert:\LocalMachine, System.keychain — all present
2026-06-05 13:32:16 +02:00
b865636aa3 feat(secubox-toolbox): landing — live KPI auto-refresh + R3 cert autocheck card + visual polish (ref #496)
User wanted realtime metrics + cert autocheck accessible from kbin host.
Both now work via kbin.gk2.secubox.in/landing :

## Live KPI (8 cells, 2 rows)

Each KPI cell has data-live=<json.path>. JS polls /cumulative-stats.json
every 5s (cache:no-store), digs the value, and re-renders with a flash
animation when it changes :
  - sessions.last_7d / 24h / all_time
  - events.dpi / cookies / ja4 / total_7d
  - top_hosts_7d.length

Header gains 🔴 LIVE badge (pulse animation) + 'maj HH:MM:SS' stamp.

## Cert R3 autocheck card

New section between KPI and project pitch. Single 'Tester' button :
  - emoji + text update : 🟢 OK / 🔴 fail /  timeout
  - probe technique : <img src=https://example.com/favicon.ico>
    (same heuristic as splash, works from any network)
  - displays CA SHA1 fingerprint pulled from /ca/fingerprint
  - on failure, links straight to /wg/ca.mobileconfig

Accessible from anywhere (HAProxy vhost kbin.gk2.secubox.in → toolbox_landing
backend → 10.99.0.1:8088). No captive subnet dependency.

## Cosmetic polish

  - pulse keyframe for LIVE badge
  - flash keyframe (.tick class) for KPI value change → gold→phos-hot
  - reflow trick (void el.offsetWidth) to retrigger CSS animation
  - dual-row KPI grid for more density without breaking mobile

## Live verification

  curl kbin /landing → HTTP 200 17853b
  matches : live-badge=1 data-live=9 cert-probe-btn=2 cumulative-stats.json=2

## Next batch

  - Polish other section heads + arch ASCII colors
  - Banner-to-report flow E2E from iPhone with mh= bypass
  - PR #495 + #496 when iPhone R3 banner sighted in the wild
2026-06-05 13:22:29 +02:00
2638c6201b feat(secubox-toolbox): R3 banner kbin URL + simplified install + mitm whitelist webui (ref #496)
User-visible changes verified live on gk2:

## 1. Banner cosmetics + dynamic URL/label

inject_banner.py now derives at request-time :
  - report URL : R3 (10.99.1.x src) → https://kbin.gk2.secubox.in/report/me/html?mh=<wg_hash>
                 R2 (captive)       → http://10.99.0.1:8088/report/me/html
  - level label : 'R2' vs 'R3' based on source subnet
  - wg_hash = sha256(peer_pubkey)[:16] read from /var/lib/secubox/toolbox/wg-peers.json

Banner now correctly :
  - shows 'ToolBoX R3' on WG tunnel clients (was hardcoded 'R2')
  - links 'Mon rapport' to a URL reachable from anywhere (was unreachable 10.99.0.1)
  - CSP-strict variant gained !important on all positioning to force top-pin
    even when the host page CSS would otherwise override

Smoke test from gk2 :
  level: r3
  report_url: https://kbin.gk2.secubox.in/report/me/html?mh=95b7b3ac3b5d5b14
  level_label: R3
  snippet contains R3: True
  snippet contains kbin URL: True

## 2. /report/me/html accepts ?mh=<hash>

Bypass ARP resolution when the caller passes ?mh=<hex>. Hex-only validation
(8-64 chars). R3 WG clients (no ARP entry on captive subnet) and remote
viewers via kbin can now fetch their personal report.

## 3. /wg/r3-install simplified

3 numbered steps (purple cards with numeric badges) replacing the 4-card
wall of text :
  ① 📲 Scan QR (with 300px image, much larger)
  ② 🔐 Install CA R3 (.mobileconfig iPhone / .pem Android)
  ③ 🚀 Activate + GO button → /report/me/html

Visual polish : gold title, gradient backgrounds, hover lift effects,
warn block for iPhone trust toggle, tip block for FaceTime/iCloud passthrough.

## 4. MITM filtering whitelist (Phase 6.F)

/var/lib/secubox/toolbox/mitm-bypass.conf — single source of truth.
Default content : Signal, WhatsApp, Telegram, Apple Push/iCloud,
French banks (BNP, CM, CA, BPCE, SG) — apps with cert-pinning or E2E
that MUST be passthrough.

New endpoints :
  GET  /admin/filter-control            — HTML webui (purple+gold theme),
                                           list/add/remove patterns
  POST /admin/filter-control/add        — append regex pattern
  POST /admin/filter-control/remove     — drop regex pattern
  GET  /admin/filter-control/regex      — JSON (regex, count) for tools

Wrapper /usr/sbin/secubox-toolbox-mitm-wg-launch composes the
alternation regex from the file and passes --set ignore_hosts=<regex>
to mitmproxy on every restart. Adds/removes from the webui take effect
on next service restart.

Live verification :
  ignore_hosts regex applied (343 chars, 15 patterns)
  ((.+\.)?signal\.org|(.+\.)?signal\.com|...|(.+\.)?societegenerale\.fr)

## 5. Deploy notes

systemd unit shadowed in /etc/systemd/system/ took precedence over
/lib/systemd/system/ during testing — both updated on the board, but
package install should write to /lib/systemd/system/ only and let
the maintainer override via /etc/ if needed.
2026-06-05 13:19:02 +02:00
0a6073d59a fix(secubox-toolbox): inject_banner fires for R3 WG clients (10.99.1.0/24) (ref #496)
## Bug

iPhone surf OK in R3 (mitm-wg pipe), but transparency banner never
appeared on intercepted HTML pages. inject_banner gate was :

    if _client_level(flow) != 'r2':
        return

R3 clients arrive on the wg-toolbox iface with source IP 10.99.1.x.
mac_hash_of(ip) does ARP lookup on the captive subnet → no entry for
the WG tunnel virtual peer → level defaults to 'r1' → banner skipped.

## Fix

### _client_level — WG opt-in inference
A peer reaching us via the wg-toolbox tunnel (10.99.1.0/24) is by
construction R3 :
  - they downloaded a WG profile via /wg/profile/new
  - they installed the dedicated mitm-wg CA (/wg/ca.mobileconfig)
  - they toggled the tunnel ON
That triple act IS the R3 consent signal — no separate DB consent
record needed. Short-circuit the ARP lookup and return 'r3' directly :

    if ip and ip.startswith('10.99.1.'):
        return 'r3'

### Banner gate — accept R3 too
    if _client_level(flow) not in ('r2', 'r3'):
        return

R0/R1 still stay banner-free (R1 keeps the captive-portal-only model,
R0 has no inspection at all).

## Live verification

inject_banner._client_level({client_conn.peername = ('10.99.1.6', x)})
returns 'r3'. Gate accepts r3 → InjectBanner.response will now compute
the per-site context + inject the body fragment.
2026-06-05 12:56:31 +02:00
38461de492 feat(secubox-toolbox): /wg/ca.pem + /wg/ca.mobileconfig — serve the mitm-WG-specific CA (separate from R1/R2 CA) (ref #496)
## Bug discovered

After enabling R3 mitm interception (DNAT to mitmdump on 10.99.1.1:8081),
iPhone HTTPS handshakes fail :
  'Client TLS handshake failed... does not trust the proxy's certificate for clients4.google.com'

Reason : mitmproxy 11 auto-generated its OWN CA at startup in
/etc/secubox/toolbox/ca-wg/mitmproxy-ca-cert.pem (subject CN=mitmproxy).
This is DIFFERENT from the CA I pre-generated (subject CN=Gondwana ToolBoX
WG CA). mitm uses its own keypair, not mine.

iPhone has the R1/R2 CA installed but NOT this mitm-wg CA → all HTTPS
through R3 tunnel breaks.

## Fix

### /wg/ca.pem (NEW)
Serves /etc/secubox/toolbox/ca-wg/mitmproxy-ca-cert.pem as PEM with
Content-Type: application/x-x509-ca-cert. Android/PC manual install.

### /wg/ca.mobileconfig (NEW)
iOS profile that bundles the CA root for one-tap install in iPhone trust
store. Same format as /ca/mobileconfig but with the mitm-wg CA inside.

### R3 install page update
The warning block on /wg/r3-install now shows :
  ⚠ Important pour R3 : il faut installer le certificat R3 spécifique
  (différent du certificat R1/R2).
  [📥 Installer CA R3 iPhone (.mobileconfig)]
  [🤖 Télécharger CA R3 (.pem Android/PC)]

User flow now :
  1. Install WG profile (/wg/profile/new or /wg/qr.png scan)
  2. Install R3 CA (/wg/ca.mobileconfig)
  3. Toggle WG tunnel ON
  4. Surf HTTPS → mitm decrypts → addons fire → banner inject

## R3 banner injection verified live

mitm-wg.service running on 10.99.1.1:8081 with 7 addons. Logs show :
  10.99.1.6:54868: GET http://82.67.100.75/manifest.json << 200 OK 438b
  Client TLS handshake failed... (iPhone CA missing — fixed by this commit)

iPhone WG tunnel transferring 7.58 MiB to client + 906.85 KiB from client.
2026-06-05 12:44:21 +02:00
7d2bfca881 feat(secubox-toolbox): /admin/ webUI with rich client table + level switch modal + R3 mitm-wg.service started + DNAT re-enabled (ref #496)
## R3 banner injection enabled live

  - systemctl enable + start secubox-toolbox-mitm-wg.service
  - Now LISTENING on 10.99.1.1:8081 (PID 216413)
  - nft DNAT prerouting re-enabled : tcp 443/80 → 10.99.1.1:8081
  - iPhone WG packets : tunnel decap → DNAT → mitm transparent → addons fire
  - banner inject_banner.py active on text/html responses
  - 7 addons running (local_store + inject_banner + dpi + cookies + avatar + ja4 + soc_relay)
  - CA = /etc/secubox/toolbox/ca-wg (separate from R1/R2)

## /admin/ webUI (NEW)

GET /admin/ + /admin returns a self-contained HTML page with :
  - 6 KPI cards (Total / Actifs / R1 / R2 / R3 / Risque LOW)
  - Client table : Status emoji · Device · Hash · IP · Level chip · Risk · Last seen · Actions
  - 🔀 Override button per client → modal with 4 level buttons (R0/R1/R2/R3)
  - 📄 PDF download per client (admin endpoint)
  - Auto-refresh every 15s
  - JS POSTs to /admin/clients/{mh}/level for the level change

CSS aligned with cabine charte (P31 phosphor + cosmos black + purple
accents). Modal centered overlay with 4 colored level buttons.

User direction : 'injection banner en r3... webui level r switch'.
2026-06-05 12:42:00 +02:00
829c87ce0d fix(wg-provision): allow UDP 51820 in inet filter input + forward chain + disable DNAT by default (ref #496)
## CRITICAL E2E bug discovered

User reported : 'vpn kbinn depuis iphone ok mais pas de surf'.

Diagnostic on gk2 :
  - WG kernel module : 3 peers registered
  - tcpdump on lan0 : iPhone UDP handshake packets ARRIVING (148 bytes)
  - wg show wg-toolbox : NO peer with endpoint or handshake → never completes
  - nft list chain inet filter input : policy DROP + NO udp dport 51820 accept rule

Root cause : the default inet filter input chain has policy=drop and
explicit accepts only for SSH, captive ports, conntrack, and LAN bridges
— NO accept for UDP 51820. WG handshake packets were silently dropped
BEFORE reaching the wireguard kernel module.

## Fix

### Add UDP 51820 to inet filter input
The provision script now adds :
  nft add rule inet filter input udp dport ${WG_PORT} accept

Verified live : after adding the rule, peer P2e3acAl8Tf8uQtAdLsh/...=
showed :
  endpoint: 192.168.1.254:55214   (iPhone via DMZ port-forward)
  latest handshake: 8 seconds ago
  transfer: 906.85 KiB received, 7.58 MiB sent

= iPhone NOW SURFS through gk2 over WireGuard tunnel ✓.

### Disable DNAT prerouting by default
The Phase 6.C DNAT rules (tcp 443/80 → 10.99.1.1:8081 for mitm
interception) are now COMMENTED OUT in the provision script. Reason :
when DNAT is active but mitm-wg systemd service isn't running on 8081,
ALL surf breaks (packets get DNAT'd to nowhere).

To re-enable mitm interception, operator must :
  1. Ensure secubox-toolbox-mitm-wg.service is running (mitm on 8081)
  2. Uncomment + apply the DNAT rules in the script
  3. Test surf still works (with mitm = banner/analysis active)

Until then : R3 = pure WG VPN tunnel through gk2 (analysis OFF).

### Add inet wg-toolbox forward chain
Explicit accept rules for wg-toolbox <-> lan0 forwarding. Belt-and-braces
in case any global firewall ever changes default policy.

## E2E status (gk2 2026-06-05)

  iPhone WG handshake : ✓ completed
  Tunnel up           : ✓ MB/s transferring
  Internet egress     : ✓ via lan0 masquerade
  mitm interception   : DISABLED (Phase 6.D follow-up)

## Phase 6.D plan

  - Start secubox-toolbox-mitm-wg.service permanently
  - Re-enable DNAT prerouting
  - iPhone Safari → mitm decrypt → banner inject + addon flows
  - Same multi-peer model (wg-quick) + per-flow mitm (transparent)
  - QUIC interception via mitmproxy 11 native HTTP/3 support
2026-06-05 12:40:06 +02:00
d5ab8c65c4 feat(secubox-toolbox): MAJOR RELEASE polish — cumulative anonymous stats + public landing page + HAProxy vhost + admin clients rich + admin level override (ref #496 #497)
## Major release polish package

User direction : 'travaillons bien stp', 'major release', and stacked
requests for landing page + visual graphs + cumulative stats + admin
richesse + admin override.

### secubox_toolbox/cumulative.py (NEW)
Anonymous cumulative stats aggregator. Recomputes every 60s with
external cache file at /var/lib/secubox/toolbox/cumulative-cache.json.

Exposes :
  sessions    : last_24h / 7d / 30d / all_time (distinct mac_hash counts)
  events      : per-source counts (dpi, cookies, ja4, soc) over 7d
  top_hosts_7d: 15 most-seen hosts (anonymized — no mac_hash ties)
  risk_distribution_7d : {low, medium, high} counts
  level_distribution_7d: {r0, r1, r2, r3} counts

ALL hashes + IPs already anonymized — these stats add another layer of
aggregation on top.

### Endpoints :
  GET /cumulative-stats.json   : public JSON (no auth, no per-user data)
  GET /landing                  : public HTML landing page
  GET /cabine                   : alias for /landing

### Public landing page kbin.gk2.secubox.in
templates/landing.html.j2 — full marketing-grade page with :

  - Hero with project name + tagline + 2 CTAs
  - 4 KPI cards (sessions + connexions + hôtes + cookies, live from cumulative)
  - Pitch paragraph (CyberMind / CSPN ANSSI / LCEN)
  - 4 levels visual grid (R0 grey / R1 green RECOMMANDÉ / R2 amber / R3 purple NOUVEAU)
  - SVG inline charts :
      * risk distribution stacked bar (low/medium/high %)
      * level distribution horizontal bars per opt-in choice
  - Architecture ASCII diagram (3 LXC mitm disjoints)
  - Demo R3 install (5-step ordered list)
  - Open Source CMSD-1.0 + GitHub link
  - Contact + soutiens (Liberapay + déploiement collectivité)
  - Footer CyberMind / Notre-Dame-du-Cruet

P31 phosphor theme aligned with cabine charte. Responsive grid.
manifest.json linked for PWA install.

### HAProxy vhost kbin.gk2.secubox.in
Added to /etc/haproxy/haproxy.cfg :
  acl host_kbin_gk2_secubox_in hdr(host) -i kbin.gk2.secubox.in
  use_backend toolbox_landing if host_kbin_gk2_secubox_in
  backend toolbox_landing
      mode http
      server toolbox 10.99.0.1:8088 check

HAProxy reloaded successfully. *.gk2.secubox.in wildcard cert covers
HTTPS termination.

### GET /admin/clients/rich (NEW)
Enriched client list with pseudo icons + statuses for the admin webui :
  - device_emoji (📱 placeholder)
  - level_emoji (🌐 R0 / 🛡 R1 / 🔍 R2 / 🌐 R3)
  - status_emoji (🟢 actif < 5min / 🟡 idle < 1h /  expiré / 🔴 quarantine)
  - risk_emoji (🟢 / 🟡 / 🔴 by score)
  - level chip + risk chip

### POST /admin/clients/{mac_hash}/level (NEW)
Admin override of a client's level (operator-only). Updates store.level
(read by inject_banner). Note : nft sets not auto-updated — operator
must trigger client's own /change-level OR manually edit nft.

### splash + dashboard wg_enabled flag
Both templates now receive wg_enabled = (server.pubkey exists) so the
R3 button + dashboard link render conditionally.

### dashboard cumulative pass-through
report-live.html.j2 receives cumulative dict for the upcoming SVG
charts inside the dashboard (will be wired in follow-up commit).
2026-06-05 12:24:52 +02:00
7aa1c62345 feat(secubox-toolbox): Phase 6.C — wg-quick + transparent mitm DNAT architecture + secubox-toolbox-mitm-wg.service (ref #496)
## Architecture decision

mitmproxy 11 --mode wireguard is SINGLE-PEER only (one client at a time
per mitm instance). Not suitable for the cabine multi-tenant use case
where multiple R3 users connect simultaneously.

Better architecture (this commit) :

  iPhone WG tunnel
    │
    │ UDP 51820 via DMZ → 82.67.100.75
    ▼
  gk2 : wg-quick@wg-toolbox kernel iface (MULTI-PEER, native WG)
    │  decapsulates WG → packets emerge on 10.99.1.0/24
    ▼
  nft inet wg-toolbox prerouting :
    iif wg-toolbox tcp dport 443 dnat to 10.99.1.1:8081
    iif wg-toolbox tcp dport 80  dnat to 10.99.1.1:8081
    ▼
  mitmdump 11.0.2 --mode transparent on 10.99.1.1:8081
    │  + 7 addons (local_store/inject_banner/dpi/cookies/avatar/ja4/soc_relay)
    │  + CA /etc/secubox/toolbox/ca-wg/ (SEPARATE from R1/R2 transparent CA)
    ▼
  nft postrouting masquerade lan0 → internet

## What this commit adds

### sbin/secubox-toolbox-wg-provision updated
Adds the DNAT chain + rules to the inet wg-toolbox nft table :
  nft add chain inet wg-toolbox prerouting { type nat hook prerouting priority dstnat; }
  nft add rule  inet wg-toolbox prerouting iif wg-toolbox tcp dport 443 dnat to 10.99.1.1:8081
  nft add rule  inet wg-toolbox prerouting iif wg-toolbox tcp dport 80  dnat to 10.99.1.1:8081

Plus all the existing postrouting masquerade rules.

### debian/secubox-toolbox-mitm-wg.service (NEW)
Systemd unit for the R3 mitm process :
  - Type=simple, User=secubox-toolbox, ambient caps NET_ADMIN + NET_RAW
  - After/Requires wg-quick@wg-toolbox.service
  - mitmdump 11 from /opt/mitmproxy-toolbox/bin (pip venv, mitmproxy>=11)
  - --mode transparent --listen-host 10.99.1.1 --listen-port 8081
  - --set confdir=/etc/secubox/toolbox/ca-wg (dedicated CA, disjoint from R2 CA)
  - 7 addons inherited from host bind-mounts
  - ProtectSystem=strict + ReadWritePaths whitelist
  - MemoryMax 512M (R3 = heavier than R2 since QUIC + TCP both pass through)

To be enabled by postinst when LXC isn't available (host-native R3 fallback).

## E2E verified gk2 2026-06-05

  wg-quick@wg-toolbox : active
  UDP 51820 listening (multi-peer)
  nft inet wg-toolbox postrouting masquerade lan0
  nft inet wg-toolbox prerouting DNAT 443/80 → 10.99.1.1:8081
  mitmdump --mode transparent on 10.99.1.1:8081 : listening + addons loaded

  Architecture LIVE. iPhone WG client can :
    1. Scan /wg/qr.png
    2. WG app imports profile (privkey + Address 10.99.1.X + endpoint kbin.gk2.secubox.in:51820)
    3. Toggle WG ON → handshake via UDP 51820
    4. All TCP 80/443 packets get DNAT'd to mitm → addons fire → banner inject

## Out of scope (Phase 6.D follow-up)

  - Make the WG mitm an LXC container (currently host-native for simplicity)
  - UDP/QUIC interception (mitm transparent on 8081 only catches TCP)
  - Per-R3 client level switching (R3 = always 'mitm active'; no R3a/R3b yet)
  - kill switch on disconnect (currently iPhone disconnect = nft state stays)
2026-06-05 12:13:17 +02:00
68cd8c7036 fix(wg-provision): pure nft masquerade + skip iptables (gk2 native nft) + perms 0750 root:secubox-toolbox (ref #496)
## Bug discovered E2E

User opened ISP firewall via DMZ → UDP 51820 reachable. Provisioning ran
on the board, BUT wg-quick failed :

  iptables: Extension MASQUERADE revision 0 not supported, missing kernel module
  iptables-nft RULE_APPEND failed
  wg-quick exit 4/NOPERMISSION

Root cause : gk2 uses pure nft (iptables-nft compat layer unreliable on
this kernel). The PostUp iptables MASQUERADE rule never installed.

Plus : /etc/secubox/toolbox/{wg,ca-wg} were 0700 root:root → secubox-toolbox
user couldn't read server.pubkey + ca-wg/ca.pem from /wg/profile/new
endpoint.

## Fix

### wg-toolbox.conf : strip iptables PostUp
The conf only declares [Interface] (PrivateKey + Address + ListenPort).
NAT is set up natively via nft :

  nft add table inet wg-toolbox
  nft add chain inet wg-toolbox postrouting { type nat hook postrouting priority srcnat; }
  nft add rule inet wg-toolbox postrouting ip saddr 10.99.1.0/24 oif lan0 masquerade

### sysctl persistence
Already done — /etc/sysctl.d/99-secubox-wg.conf sets net.ipv4.ip_forward=1.

### Perms 0750 root:secubox-toolbox
install -d -m 0750 -o root -g secubox-toolbox $WG_DIR $CA_WG_DIR

Aligns with how /etc/secubox/secrets/toolbox-mac-salt is owned (Phase 3
salt access fix).

## E2E live verified gk2 2026-06-05

  systemctl is-active wg-quick@wg-toolbox  → active
  wg show wg-toolbox  →
    interface: wg-toolbox
    public key: 6JmT185TPc4aHjwDjC8ugKBmehsrGSMyw1V+SOG40XI=
    listening port: 51820
    peer: JoxT1YcwW9X5xTL9xjkOq6NcFjgQGO4jP1lxJ5iUBHI=
    allowed ips: 10.99.1.3/32

  curl http://10.99.0.1:8088/wg/profile/new → HTTP 200
    .conf served with Endpoint = kbin.gk2.secubox.in:51820
    Client IP = 10.99.1.3/32
    Bundled CA root pem (separate from transparent mitm CA)

## Limitation noted (Phase 6.C)

The wg-toolbox interface decrypts WG tunnel + masquerades to lan0 →
internet DIRECTLY. mitm has NOT been wired yet :
  - mitm container 'toolbox-mitm-wg' @ 10.100.0.62 needs to be provisioned
  - mitmdump --mode wireguard:server.conf reads /etc/wireguard/wg-toolbox.conf
    directly INSTEAD of wg-quick
  - Conflict resolution : either we run wg-quick (current, no mitm) OR
    mitmdump WG mode (planned, with mitm) — not both.

To wire mitm interception, Phase 6.C will :
  1. Stop wg-quick@wg-toolbox
  2. Start toolbox-mitm-wg LXC + mitmdump --mode wireguard inside
  3. mitmdump becomes the WG endpoint; clients connect to same kbin.gk2.secubox.in:51820

User can already test the WG tunnel as-is (R3 = tunnel via cabine, NO
analysis yet). Phase 6.C will enable the analysis flow.
2026-06-05 12:06:33 +02:00
455def53cc feat(secubox-toolbox): WG endpoint = kbin.gk2.secubox.in (Gandi-managed) + DNS record provisioned via API (ref #496)
## DNS provisioning via Gandi LiveDNS API

User direction : 'go with dns api'.

Found gk2.net is on OVH (no credentials available), but secubox.in is
on Gandi LiveDNS with API token in /var/lib/secubox/dns-provider/
providers.json (secubox-dns-provider service).

Created DNS record via Gandi LiveDNS API :
  POST https://api.gandi.net/v5/livedns/domains/secubox.in/records
  Header: Authorization: Bearer <token>
  Body: {rrset_name: 'kbin.gk2', rrset_type: 'A', rrset_ttl: 300,
         rrset_values: ['82.67.100.75']}
  Response: 'DNS Record Created'

Verified : dig kbin.gk2.secubox.in @ns1.gandi.net → 82.67.100.75 (gk2 pub IP)

Pattern aligns with the rest of *.gk2.secubox.in (arm, armada, admin,
analyse, etc. — all A → 82.67.100.75).

## Code updates

  - secubox_toolbox/wg.py : WG_ENDPOINT = 'kbin.gk2.secubox.in' (was 'kbin.gk2.net')
  - secubox_toolbox/api.py : /wg/status endpoint label same

This is what gets baked into client .conf files served by /wg/profile/new
and into the QR codes at /wg/qr.png.

## Reverse-DNS / TLS

The wildcard *.gk2.secubox.in cert (Let's Encrypt) already covers
kbin.gk2.secubox.in for any HTTPS service we'd later expose on that
host. WG itself is UDP and doesn't need cert.

## Still pending (user)

  - ISP firewall : open UDP 51820 inbound to 82.67.100.75 (need port
    forward at home router OR ISP modem)
  - drop docs/assets/poster/village3b-A2.png (rendered poster image)

## Next code commit (Phase 6.C)

  - debian/postinst : call secubox-toolbox-wg-provision after lxc-provision
  - debian/control : Depends += wireguard-tools, wireguard
  - debian/install : ship sbin/secubox-toolbox-wg-provision
  - toolbox-up CLI : detect wg-toolbox running + render nft.mitm.target
  - E2E test on board
2026-06-05 12:00:12 +02:00
f77bb4577f feat(secubox-toolbox): Phase 6.B — wg-provision script + R3 nft helpers + R3 in /accept + /change-level + dashboard R3 link (ref #496)
## Phase 6.B wiring

### sbin/secubox-toolbox-wg-provision (NEW, executable)
Idempotent provisioning script :
  1. Server keypair @ /etc/secubox/toolbox/wg/{server.privkey,server.pubkey}
     - umask 077 + chmod 0600 priv / 0644 pub
     - keeps existing if already present (no rotation surprise)
  2. wg-quick conf @ /etc/wireguard/wg-toolbox.conf :
     - Address 10.99.1.1/24 + ListenPort 51820
     - PostUp/PostDown iptables FORWARD + MASQUERADE to lan0
     - Peers added dynamically by /wg/profile/new
  3. CA WG dedicated root @ /etc/secubox/toolbox/ca-wg/ :
     - SEPARATE from transparent mitm CA (/etc/secubox/toolbox/ca/)
     - Maintains the disjoint-from-toolbox-mitm-tx invariant
     - openssl req self-signed CN=Gondwana ToolBoX WG CA
  4. systemctl enable wg-quick@wg-toolbox (start best-effort)
  5. nft add set consented_r3_wg_macs (ether_addr, timeout 24h)

To be called by debian/postinst after Phase 5 LXC provision.

### nft.py helpers (R3 trio)
  add_r3_wg(mac, ttl=24h) -> bool
  del_r3_wg(mac) -> bool
  is_r3_wg(mac) -> bool

Same pattern as add_r2_banner / del_r2_banner / is_r2_banner.

### nft-toolbox.nft.j2 : new set consented_r3_wg_macs
Membership = client installed WG profile + connected via wg-toolbox iface.
Captive DNAT does NOT apply (R3 traffic routes via 10.99.1.0/24 + WG
tunnel, not via the captive AP iface).

### /accept + /change-level : R3 support
Accept level=r3 in form data. R3 only allowed if /etc/secubox/toolbox/wg/
server.pubkey exists (gate by provisioning state).

  Level transitions :
    r0 -> r1/r2 : add to consented_r2_macs
    r1/r2 -> r3 : remove from r2_banner_macs, add to consented_r3_wg_macs
    r3 -> r0/r1/r2 : remove from consented_r3_wg_macs

Log line now includes wg=True/False alongside validated/consented/banner.

### Splash R3 link (existing button updated)
Splash already had R3 button from earlier commit (Phase 6.A) — it links
to /wg/r3-install which provides QR + .conf + instructions.

### Dashboard R3 link in level switcher
Below the R0/R1/R2 grid : a separate '🌐 installer le profil' link
(R3 doesn't fit the grid because it's a different installation flow,
not a one-tap nft membership change).

If current_level == 'r3' : confirmation message '✓ Tu es actuellement
en mode R3 — tunnel WG actif'.

## Remaining for Phase 6 PR readiness

- [ ] toolbox-up CLI : detect WG container running + set mitm.target accordingly
- [ ] debian/postinst : call wg-provision after lxc-provision
- [ ] debian/control : Depends += wireguard-tools, wireguard
- [ ] E2E test : provision wg-toolbox + /wg/profile/new + iPhone scan QR ->
      connect -> packet flows through mitm -> banner injects
- [ ] kbin.gk2.net DNS A record (user task)
- [ ] UDP 51820 firewall open (user task)
2026-06-05 11:47:39 +02:00
4e90f2970e fix(secubox-toolbox): Android cert PEM format + Android install help + PWA manifest + R3 install page (ref #496)
## User feedback

  'android certificat error, pas de webapp fix it'
  'ok r3'
  'go'
  'qr code du splash okay'

## Android cert fix (PEM instead of DER)

Was : /ca/android.crt returned DER binary blob -> Android 11+ Chrome had
intermittent install issues + 'file format unknown' errors.

Now :
  /ca/android.crt  -> PEM text (Android-friendly, base64 + headers)
  /ca/android.der  -> DER fallback for older Android / manual install
  ca.py adds ca_pem() helper alongside ca_der().

MIME = application/x-x509-ca-cert (triggers Chrome install prompt).

## /ca/android-help (NEW)

Self-contained HTML page (no template dep) with step-by-step Android
install flow :
  1. Download .crt button (calls /ca/android.crt)
  2. Settings -> Security -> Encryption -> Install certificate -> CA
  3. Verify SHA-1 fingerprint (fetched via JS from /ca/fingerprint)
  4. Honest warning : Android 7+ requires apps to opt-in to user CAs
     via network_security_config.xml — banks/gov/streaming usually
     don't, so they'll fail in R1/R2. Use R0 or R3 instead.

## PWA manifest /manifest.json (Android webapp install)

Android Chrome reads /manifest.json + offers 'Add to Home Screen' = PWA.
Same UX as iOS WebClip but cross-platform.

  name: 'ToolBoX Cabine VILLAGE3B'
  short_name: 'ToolBoX'
  start_url: '/report/me/html'
  display: 'standalone'  (no Chrome UI when launched from home)
  theme_color: '#00dd44' (P31 phosphor green)
  background_color: '#0a0a0f' (cosmos black)
  icons: /qr/splash.png (232x232)

## R3 install page /wg/r3-install (NEW)

Phase 6 user-facing install page with 2 methods :

  Method 1 (recommended) : Scan QR
    - iPhone : WireGuard app -> Add tunnel -> Scan from QR
    - QR contains the full .conf (server pubkey + endpoint + private key)
    - Privacy warning : 'Le QR contient ta clé privée. Ne le partage pas.'

  Method 2 : Download .conf
    - iPhone : open file -> WireGuard app -> Import
    - Android : WireGuard app -> Import from file
    - Linux : wg-quick up village3b-toolbox.conf

Plus warning : install CA root first (mobileconfig iPhone OR .crt Android)
or HTTPS sites will fail once WG tunnel is up.

Plus after-connect explanation : tunnel active -> banner everywhere
(incl QUIC) -> report live -> iCloud/Push still work via routing.

## E2E verified gk2 2026-06-05

  /ca/android.crt    HTTP 200 (PEM, starts with -----BEGIN CERTIFICATE-----)
  /ca/android.der    HTTP 200 (DER binary)
  /ca/android-help   HTTP 200 (HTML)
  /manifest.json     HTTP 200 (JSON)
  /wg/r3-install     HTTP 200 (HTML)

QR splash already validated by user 2026-06-05 ('qr code du splash okay').
Same QR endpoints used by R3 install page.
2026-06-05 11:40:01 +02:00
173b4262df feat(secubox-toolbox): Phase 6 foundation — WireGuard R3 mode (LXC + service + wg.py + endpoints + splash button) (ref #496)
## Architecture target

Two distinct LXC containers for ToolBox MITM :
  - toolbox-mitm (R1/R2 transparent)  : 10.100.0.61 (Phase 5 #495)
  - toolbox-mitm-wg (R3 WireGuard)    : 10.100.0.62 (this commit)

Plus the existing WAF mitm : 10.100.0.60 (#495 untouched isolation).

User direction : 'il doit y avoir 2 mitm lxc separes... distantes et
disjointes' + 'go genial' for WireGuard mode.

## What this commit adds

### lxc/toolbox-mitm-wg.conf.template
LXC config for the WG container. Distinct hwaddr 00:16:3e:00:00:62
+ IP 10.100.0.62 + start order 120 (after WAF=100, tx=110).
Memory bounds heavier (600M/768M) since WG mode decapsulates QUIC
in user-space. Tun device + WG kernel module access.
Bind mounts use a SEPARATE CA dir (/etc/secubox/toolbox/ca-wg) to
maintain the disjoint-from-transparent-mitm invariant.

### lxc/toolbox-mitm-wg.service.template
systemd unit inside container :
  mitmdump --mode wireguard:/etc/secubox/toolbox/wg/server.conf
           --listen-host 0.0.0.0 --listen-port 51820
           + 7 addons (same as transparent mode for unified analysis)

Requires=wg-quick@wg-toolbox.service so the WG kernel interface is up
before mitm tries to use it.

### secubox_toolbox/wg.py
WG profile generation library :
  generate_client_profile(label) -> {client_privkey, client_pubkey,
    client_ip (10.99.1.X), server_pubkey, endpoint (kbin.gk2.net:51820),
    dns (10.99.0.1), conf_text (wg-quick format), ca_pem (bundled)}
  revoke_client(pubkey) -> removes peer from wg + DB

Persisted state in /var/lib/secubox/toolbox/wg-peers.json :
  {next_ip_octet, peers : {pubkey : {ip, label, created_at}}}

### FastAPI endpoints
  GET /wg/profile/new  -> .conf download (attachment)
  GET /wg/qr.png       -> QR encoding the .conf (iOS WG app scans it)
  GET /wg/status       -> peer count + handshake info

All ImportError-soft : if wg module missing (Phase 6 not provisioned
on this board), endpoints return 503 gracefully.

### Splash R3 button
New R3 level button below R2 (only rendered if wg_enabled config flag).
Purple style, label 'WireGuard portable (NOUVEAU)' + subtitle :
'VPN tunnel mitm. Bandeau sur TOUT (HTTPS + QUIC). Marche hors VILLAGE3B
(4G/5G, autre WiFi). Profile install 1 tap.'

## Next steps (separate commits in this PR)

- [ ] sbin/secubox-toolbox-wg-provision : generate server keypair +
      wg-quick config + register systemd wg-quick@wg-toolbox unit
- [ ] /wg/r3-install : HTML page that triggers profile download +
      shows QR code + install instructions for iOS WG app
- [ ] /accept handler : add level=r3 support (adds MAC to a new nft set
      consented_r3_wg_macs, persists in store)
- [ ] /change-level : R3 switch from dashboard
- [ ] toolbox-up CLI : detect WG container + populate config
- [ ] Endpoint DNS : kbin.gk2.net needs A record to gk2 public IP
- [ ] UDP 51820 firewall : open at ISP / gateway
- [ ] mobileconfig wrapper for .conf (iOS one-tap install instead of
      manual import in WG app)
- [ ] E2E test : iPhone scans QR -> WG connects -> mitm decrypts -> banner

## Out of scope (this commit)

Existing R0/R1/R2 levels untouched. WAF mitm container untouched.
Phase 5 (#495) LXC infrastructure stays as foundation.
2026-06-05 11:31:12 +02:00
3d2ecd574f feat(secubox-toolbox): 3 QR code endpoints + splash QR display + 9th dashboard widget (device fingerprint) + README poster integration (ref #495 #497)
## QR endpoints (python-qrcode)

  GET /qr/splash.png    → QR encodes the splash URL (Cabine join)
  GET /qr/cert.png      → QR encodes /ca/mobileconfig (iOS profile)
  GET /qr/webclip.png   → QR encodes /ca/webclip-cabine.mobileconfig
  GET /qr/{target}.png  → generic : maps splash/cert/webclip/report/
                          fingerprint to fixed URLs, or encodes literal

  PNG output, Cache-Control: public, max-age=3600.
  Verified : 232x232 1-bit grayscale, 384-553 bytes each.

  debian/control adds python3-qrcode dependency.

## Splash QR card

The cert install card on the splash now shows 3 QR codes (① SPLASH,
② CERT iPHONE, ③ WEBCLIP HOME) on a white background grid below the
test button. Scannable from another device for cross-installation
(useful at the cabine : user shows QR to others without typing URLs).

## Dashboard hero : 9th widget (device fingerprint)

Mirroring the poster layout, added a full-width 9th widget below the
2 rows of 4 KPI widgets :

  ┌─────────────────────────────────────────────────────────┐
  │ 📱 📱 Empreinte device                                   │
  │      iPhone iOS 26.3 · 🟢 Chrome 148                     │
  │      21 UAs distincts observés                           │
  └─────────────────────────────────────────────────────────┘

Now matches the 9-widget metrics layout from the public poster :
🌐 connexions · 📡 hôtes ·  OK 2xx · 🔒 cert-pin
📺 apps · 🍪 trackers · 🌍 pays · 🎯 score
📱 device (full-width)

## README + assets/poster integration (#497)

  - README.md : new section at top with poster image embed + 3 doc links
  - docs/assets/poster/README.md : print specs + variants list + license
  - Placeholder docs/assets/poster/village3b-A2.png expected (user provides)

Wiki integration pending : will be added via gh wiki API once issue #497
phase 2 launches.

## Rebase

Branch was rebased onto master (which now has all PR #493 Phase 3
commits). control merged : Phase 3 fonts + Phase 5 lxc deps + new
python3-qrcode in one cohesive Depends block.
2026-06-05 11:24:03 +02:00
448aee77e9 feat(secubox-toolbox): Phase 5 wiring — postinst + control + install + nft DNAT migration (ref #495)
On configure :
  - if lxc-create + provision script available -> run secubox-toolbox-lxc-provision
  - on success : disable + stop host-native secubox-toolbox-mitm.service
                 (LXC container takes over)
  - on failure : keep host-native enabled (graceful fallback)
  - if LXC entirely absent (dev env, container-less host) : log + keep
    host-native (legacy mode)

This means upgrade path is :
  Phase 3 board (host-native) -> apt upgrade -> Phase 5 board
  -> postinst detects lxc -> provisions container -> stops host-native
  -> container takes over with ZERO user intervention.

  Depends += lxc, debian-archive-keyring
  Recommends: bridge-utils

debian-archive-keyring needed for lxc-create --release=bookworm signing.

  lxc/toolbox-mitm.conf.template       -> /usr/share/secubox/toolbox/lxc/
  lxc/toolbox-mitm.service.template    -> /usr/share/secubox/toolbox/lxc/
  sbin/secubox-toolbox-lxc-provision   -> /usr/sbin/

prerouting_nat rule 2 (R2 MITM redirect) now uses a templated target :
  dnat ip to {{ mitm.target | default(dhcp.gateway) }}:8080

  - Default value = gateway (host-native legacy mode)
  - LXC mode = '10.100.0.61' (container)
  - Phase 6 WireGuard = '10.100.0.62' future

toolbox-up CLI will need to populate mitm.target based on whether the
LXC container is running.

Phase 5 foundation + wiring done. Remaining for E2E :
  - FastAPI _pull_mitm_module_events socket paths : currently host
    sockets at /run/secubox/{module}.sock — these stay on host, the
    container only does mitm interception. No change needed there.
  - toolbox-up CLI : detect LXC + set mitm.target accordingly
  - E2E test on board : provision + restart + R2 surf + banner inject

To deploy on gk2 :
  apt update && apt install ./secubox-toolbox_X.X.X_all.deb
  systemctl restart secubox-toolbox

Verify :
  lxc-info -n toolbox-mitm | grep RUNNING
  lxc-attach -n toolbox-mitm -- systemctl status secubox-toolbox-mitm
2026-06-05 11:20:55 +02:00
57cf73d117 feat(secubox-toolbox): Phase 5 foundation — LXC container template + provisioning script (ref #495)
## Goal

Move the ToolBox transparent mitm from host-native (current Phase 3) to
a dedicated LXC container 'toolbox-mitm' at 10.100.0.61. Foundation for
Phase 6 (#496 WireGuard mode) which will live in a SECOND container
'toolbox-mitm-wg' at 10.100.0.62.

User direction : 'il doit y avoir 2 mitm lxc separes... distantes et
disjointes' + 'go genial'.

## What this commit adds

### lxc/toolbox-mitm.conf.template
LXC container config for the transparent mitm. Same envelope as the
existing WAF mitm container (memory.high 400M, memory.max 512M, autostart,
br-lxc bridge) but :
  - distinct hwaddr 00:16:3e:00:00:61
  - distinct IP 10.100.0.61
  - distinct start order (110 = after WAF mitm 100)

Bind mounts from host :
  /etc/secubox/toolbox/ca           RO  (CA persists on host)
  /var/lib/secubox/toolbox          RW  (SQLite events shared)
  /var/log/secubox/toolbox-mitm     RW  (logs)
  /usr/lib/secubox/toolbox/mitmproxy_addons  RO  (addons live on host)
  /etc/secubox/secrets/toolbox-mac-salt      RO  (MAC hashing salt)

Capabilities dropped to minimum + tun device access for transparent /
future wireguard mode.

### lxc/toolbox-mitm.service.template
Systemd service that runs INSIDE the container. Type=simple, runs
mitmdump --mode transparent with all 7 addons. Ambient caps NET_ADMIN +
NET_RAW + NET_BIND_SERVICE. Sandbox via ProtectSystem=strict +
ReadWritePaths whitelist. MemoryMax 480M inside container.

### sbin/secubox-toolbox-lxc-provision
Idempotent provisioning bash script :
  1. Prereq check (lxc-create + template exists)
  2. Build rootfs if absent (Debian 12 bookworm arm64, ~80 MB)
  3. Install/refresh /etc/lxc/toolbox-mitm.conf
  4. Ensure bind-mount source paths exist on host
  5. Deploy mitm service inside container
  6. Restart container if running, start if stopped
  7. Wait for network up

To be called by debian/postinst of secubox-toolbox.

## Next steps (separate commits)

- [ ] Update debian/postinst to call secubox-toolbox-lxc-provision
- [ ] Update debian/control : Depends += lxc, debian-archive-keyring
- [ ] Update FastAPI _pull_mitm_module_events socket paths or HTTP-bridge
- [ ] Update nft DNAT prerouting rules : DNAT to 10.100.0.61:8080 instead of host 10.99.0.1:8080
- [ ] Stop + disable secubox-toolbox-mitm.service (host-native) at upgrade
- [ ] Migration test : preserve user's installed CA across upgrade
- [ ] E2E test : R1/R2 surf + banner injection still fires from container
- [ ] Phase 6 prereq : second container 'toolbox-mitm-wg' for WireGuard mode

## Architecture note

Per #495 issue : the two mitm instances are FULLY DISJOINTES.
  - WAF mitm   : LXC 'mitmproxy'     @ 10.100.0.60 (existing, untouched)
  - ToolBox tx : LXC 'toolbox-mitm'  @ 10.100.0.61 (this commit)
  - ToolBox wg : LXC 'toolbox-mitm-wg' @ 10.100.0.62 (#496 future)
2026-06-05 11:20:32 +02:00
CyberMind
2558e28f17
Merge pull request #493 from CyberMind-FR/feature/492-phase-3-toolbox-transparency-layer-white
feat(phase-3): transparency layer (whitelist + analysis-status + security quality scoring)
2026-06-05 11:08:47 +02:00
2efdd82bbf docs(marketing): poster grand public + dossier presse update Phase 3+5+6 (ref #492 #480)
## 2 docs marketing

### POSTER-grand-public-village3b.md (NEW)
Brief de poster A2 portrait pour affichage public (médiathèque, école,
mairie, EHPAD). 6 zones :
  1. Titre 📡 VILLAGE3B + USP (anonyme + gratuit + open source)
  2. 3 niveaux d'opt-in R0/R1/R2 visualisés
  3. Ce que contient le rapport (KPI + categories + transparency)
  4. Conformité CSPN/LCEN + 5 garanties
  5. 3 QR codes (splash, cert, webclip)
  6. Footer CyberMind + contact + soutiens

Variantes A4/A5/dépliant 3-volets documentées. Charte couleurs P31 phosphor
green + Cosmos Black + Gold Hermétique alignée avec DESIGN-CHARTER.md.

Output formats à générer : PDF print 300dpi, SVG Inkscape source, PNG
1920x1080 social, Story IG 1080x1920, LinkedIn banner.

### PROMPT-claude-presse-gouv.md (UPDATED)
Ajout d'une ANNEXE 'Mise à jour 2026-06-05' qui synchronise le dossier
presse avec l'évolution récente :

  - Modèle d'opt-in à 3 niveaux + R3 WireGuard (Phase 6)
  - Transparence radicale : 'La cabine SAIT se taire, et DIT QUAND elle
    se tait' — différenciateur fondamental
  - Architecture mitm disjoints (Phase 5 #495 séparation host/LXC)
  - Mode WireGuard portable kbin.gk2.net:51820 (Phase 6 #496) pour
    usage HORS cabine physique = inclusion territoriale + EHPAD/domicile
  - Engine sensibilité réglable (low/medium/high/paranoid) : opérateur
    collectivité adapte sans toucher code
  - Empreinte hardware MochaBin + Debian + coût ~250€ HT
  - AI-HANDOVER brief = continuité du commun numérique au-delà d'un auteur
  - 5 composants installables iPhone (cert + webclip + WG + guide)
  - Engagement personnel : 'pas de boîte noire, commun numérique'

Tous ces éléments à intégrer par le rédacteur LLM dans la tribune,
le CP, les candidatures France.gouv (ANCT/France-Services/Beta.gouv/
ANSSI/France 2030/CNIL).
2026-06-05 11:07:34 +02:00
17ff8c5cfb docs: AI-HANDOVER tech brief for external LLM (GPT-5/Gemini/Claude) (ref #492)
13-section self-contained brief :
  1. Mission produit + licence CMSD strict
  2. Stack technique exhaustif
  3. 2 mitm DISJOINTES (ToolBox host vs WAF LXC) — règle d'or
  4. Niveaux R0/R1/R2 + R3 roadmap
  5. Phases 1->8 status
  6. Endpoints API actuels + Phase 6 ajouts
  7. Rule engine sensitivity profiles
  8. Drapeaux + emojis (5 fonts embedded PDF)
  9. Quick-up composants splash
  10. Dette technique connue (#494, WAF, certs, perms)
  11. Contributing rules (worktree + licence header + pas Claude/Anthropic ref + tests E2E + CSPN)
  12. Status 2026-06-05 + roadmap actif
  13. Contact

Destiné à brief un autre assistant LLM sans contexte préalable pour
contribuer en cohérence avec l'écosystème cabine VILLAGE3B.
2026-06-05 11:05:13 +02:00
a9db31442e fix(secubox-toolbox): remove QUIC drop rule from nft template — was breaking R2 surf entirely (ref #492)
## Bug

The QUIC reject rule for @r2_banner_macs (UDP 443 reject port-unreachable)
was too aggressive. iPhone Safari + Apple/Google/Cloudflare/Fastly heavily
depend on QUIC for almost all HTTPS traffic. Forcing fallback to TCP
HTTPS :
  - works for some sites (banner injection visible there)
  - breaks for cert-pinned services (Apple Push, iCloud, mask.icloud.com)
  - exhausts iPhone retry budget -> Safari shows 'Cannot connect' on
    sites that QUIC was working for

User feedback : 'toujours pas de surf en R2'.

## Fix

Remove the nft QUIC reject rule entirely. The r2_banner_macs nft set
remains (used by inject_banner.py to gate banner injection on text/html
responses). The banner appears only on TCP HTTPS HTML responses (rare
but happen) — that's the honest cost of not breaking the iPhone.

## Splash R2 description updated

  'Déchiffrement TLS + bandeau visible sur les pages Safari HTML.'
  + warning : 'Le bandeau n'apparaît que sur les pages HTML servies
    en TCP (la plupart des apps iPhone utilisent QUIC et ne montreront
    pas le bandeau).'

Honest UX : user knows R2 means partial banner visibility, not 'banner
everywhere' which the QUIC drop tried to force.

## Phase 5 (#495) opens

Issue opened : ToolBoX mitm in dedicated LXC container, separate from
the WAF mitm + host-native. Architectural follow-up.
2026-06-05 10:57:57 +02:00
43f79ea9e0 fix(secubox-toolbox): honest cert verification — probe is heuristic + manual iOS steps + prominent SHA-1 fingerprint (ref #492)
## User feedback

  'le certificat est installe en mode root active mais le dashboard
   splash di ko encore...'

## Root cause

The auto-probe was hitting captive.apple.com which is cert-pinned by
iOS internal code — even when our CA is installed in trust store, iOS
treats this URL specially and the probe gave a misleading 🔴 result.

Plus the probe was over-claiming : a 🟢 result actually meant 'HTTPS
worked for this specific URL', NOT 'your CA is installed'. Pre-acceptance
(R0 default), HTTPS doesn't go through our mitm, so the probe always
passes regardless of CA install state — false confidence.

## Fix

### 1. Probe target : example.com favicon
  - Not cert-pinned by iOS
  - Simple, low-latency
  - Reliable for the heuristic check (post-R1/R2)

### 2. Honest text labels
  - 🟢 -> 'HTTPS OK — si tu es déjà en R1/R2, ton CA est installé.
          Vérifie quand même les étapes manuelles ci-dessous.'
  - 🔴 -> 'HTTPS échoue — soit pas de réseau, soit en R1/R2 SANS le
          CA installé. Suis les étapes manuelles ci-dessous.'
  -  -> 'Timeout — relance le test ou vérifie manuellement'

### 3. Manual verification steps (4-line ordered list)

The certain way to verify CA install on iPhone, prominent below the
auto-test :
  1. Réglages → Général → VPN et gestion d'appareils
  2. Profil Gondwana ToolBoX CA doit apparaître → installer
  3. Réglages → Général → Information → Réglages de confiance des
     certificats
  4. Activer le toggle pour Gondwana ToolBoX CA

### 4. SHA-1 fingerprint big + visible

The CA SHA-1 fingerprint is now displayed prominently in a styled box
with a key emoji, so the user can visually compare to what iOS Settings
shows when they tap the profile.

## Architecture note

Proper verification would require a CA-signed HTTPS leaf cert served by
the toolbox on 10.99.0.1:8443 — JS probes that, success = CA validated
by browser. Would need nginx HTTPS + leaf cert generation. Not in scope
of this commit but a future follow-up to make auto-test reliable
pre-acceptance.
2026-06-05 10:46:37 +02:00
c5a2d20575 fix(secubox-toolbox): splash always rendered (no auto-redirect) — user can re-install cert anytime (ref #492)
## Bug

Commit cb236abb made GET / redirect validated users 303 -> dashboard
to avoid the stale success.html.j2 dead-end. Side effect : validated
users could NEVER come back to the splash to install/re-install the
cert + webclip + Android crt. The install buttons were unreachable.

User feedback : 'je ne vois toujours plus les liens d'installations'.

## Fix

GET / now ALWAYS renders splash.html.j2 — even for validated users.
Splash gets new flags :
  already_validated = True/False
  current_level     = 'r0'|'r1'|'r2'|None

If already_validated : a green info banner at top says :
   Tu es déjà validé en mode 🛡 R1 (analyse passive)
     · 📊 Voir mon rapport live
     · Tu peux installer/réinstaller les composants ci-dessous

So the user has the path to both (a) the dashboard via link, and
(b) the install buttons via the 📦 Quick install card below.
2026-06-05 10:39:48 +02:00
4a3c7c63d4 feat(secubox-toolbox): splash quick-up menu — 4 install buttons emoji (cert iPhone + Android + webclip + guide) (ref #492)
User feedback :
  'faire un systeme de menu dans le gondwana toolbox splash pour
   installer les composant, je n'ai plus aces aux urls de l.icon
   et du certificat'
  'des quick up buttons emojiz'

The cert install buttons were inside the cert auto-check card and
visually competed with the test button. Now a dedicated '📦 Quick
install — composants ToolBoX' section above the level choice :

  ┌──────────────────┬──────────────────┐
  │ 🔐 Certificat    │ 🤖 Certificat    │
  │   iPhone         │   Android/PC     │
  │   .mobileconfig  │   .crt manuel    │
  ├──────────────────┼──────────────────┤
  │ 📱 Icône Home    │ 📜 Guide pas-à-  │
  │   iPhone         │   pas            │
  │   1-tap accès    │   install help   │
  └──────────────────┴──────────────────┘

  [🧪 Tester le certificat maintenant]
  [ Auto-test en cours...]
  Empreinte CA SHA-1 : XX:XX:XX:...

Buttons styled :
  primary       : purple solid (#9e76ff)
  primary-alt   : darker purple (webclip)
  outline       : transparent w/ purple border (Android + guide)
  test          : green outline (cert test)

All have :hover lift effect (translateY -1px + box-shadow). Subtitle in
each button (.qbtn-sub) explains what the link delivers.

## Out of scope today (user reported)

  - chess.maegia.tv and other WAF vhosts intermittent : LXC mitm
    container restarted ; works for 30s then hangs again. Needs deeper
    container/network debug separate from ToolBoX.
2026-06-05 10:32:01 +02:00
a6754d7bcd fix(secubox-toolbox): another jinja slicing-filter-result bug at line 124 (ref #492) 2026-06-05 10:27:45 +02:00
bdac481541 fix(secubox-toolbox): jinja syntax error — slicing filter result requires intermediate var (ref #492)
Template rendering failed with 'expected token end of print statement, got [['
on line 110 of report-live.html.j2 :

  Top ASN : {{ top_geo.flag|default('') }} {{ top_geo.asn_org|default('?')[:30] }}

Jinja doesn't allow slicing the result of a filter directly. Fixed by
binding to an intermediate var :

  {% set top_asn_raw = top_geo.asn_org if top_geo.asn_org else '?' %}
  Top ASN : {{ top_geo.flag|default('') }} {{ top_asn_raw[:30] }}
2026-06-05 10:27:11 +02:00
cb236abb67 fix(secubox-toolbox): splash bug — already-validated users were stuck on stale success page (no cert card) + add hero widgets to HTML dashboard + filtering compromissions card + Cache-Control no-store (ref #492)
## Critical bug fix

GET / handler routed ALREADY-VALIDATED users to success.html.j2 — the OLD
success page WITHOUT the cert card auto-check. So returning users couldn't
see the new install/test cert UI. They saw 'success.html.j2' with just the
4 action buttons + thought R0 was broken because no cert was installed.

Now : validated users get a 303 redirect to /report/me/html (the dashboard)
which has level switcher + transparency metrics + the new hero widgets.

## Cache-Control no-store

iPhone Safari was caching the splash + dashboard aggressively. User saw
stale 'no cert card / no metrics' versions even after deploys. Now :

  Cache-Control: no-store, no-cache, must-revalidate, max-age=0
  Pragma: no-cache

on splash + dashboard responses. Forces Safari to refetch every visit.

## Hero widgets in HTML dashboard

Mirrors the PDF dashboard hero exactly :
  Row 1 widgets : 🌐 connexions / 📡 hôtes /  OK / 🔒 cert-pinning
  Row 2 widgets : 📺 apps / 🍪 trackers / 🌍 pays / 🟢/🟡/🔴 score
  Bottom line : Top device · Top app · Top ASN with flag

Plus risk badge color-coded in the title bar (green LOW / amber MEDIUM /
red HIGH).

## Filtering compromissions card

User asked 'ou sont mes filterings compromissions dans la webui ?'.
Added a dedicated 🚨 card under hero showing :
  - Sensibilité actuelle (label + description from rule_engine)
  - Threat-intel feed matches count
  - DGA candidates count
  - Beaconing detected count
  - Connexions bloquées count (Phase 4 placeholder — 0 for now)

Explanation note : 'Le moteur détecte mais ne bloque pas encore — passive
transparency. R3 / Phase 4 wired plus tard.'

## Dashboard level switcher : active level highlighted

Previously the R1 button was always styled 'active' (bold green) regardless
of current_level. Now uses server-side current_level to pick which button
gets the ✓ check + bold border + brighter background. R0/R1/R2 button
styles dynamically reflect the actual state.

## Welcome / switched banner uses current_level

Was using request_args.level which is stale after refresh. Now uses
server-side current_level so the banner always reflects the truth.
2026-06-05 10:22:20 +02:00
751e0a3036 feat(secubox-toolbox): splash cert card — explicit install button (iPhone + Android) + manual test button + fingerprint display (ref #492)
## User feedback

  'acces au certificat et test certificat ???'
  'switch r0 et plus de surf ... probleme de switch... test cert okay ou pas ???'

The previous auto-check card was passive : user couldn't see how to
install the cert or re-test on demand. R0 was working (verified via
tcpdump : iPhone packets flowing through forward chain successfully).
The 'plus de surf' impression was probably the result of:
  - Cached Safari content from a broken-cert R1/R2 attempt
  - No explicit feedback on cert status

## Splash redesign

The cert card now has 4 distinct sections :

  1. Explanation : 'Sans cert -> R1/R2 cassent HTTPS. R0 OK sans cert.'

  2. Two big install buttons (grid 1fr 1fr) :
     [📥 Installer (iPhone)] -> /ca/mobileconfig (purple bg, prominent)
     [🤖 Télécharger CRT]   -> /ca/android.crt (outlined)

  3. Manual test button :
     [🧪 Tester le certificat maintenant] -> re-runs JS probe

  4. Live status display :
      Auto-test en cours -> 🟢 OK / 🔴 NON installé /  Timeout

  5. CA SHA-1 fingerprint in monospace for manual comparison in
     iOS Réglages > Général > VPN/Device Management.

The auto-probe runs 600ms after page load. Click 🧪 to re-run any time.

Probe target changed from www.google.com favicon to captive.apple.com
success.html — Apple's HTTPS captive probe is more reliable on iOS
(already on iOS trust path, low latency, no DNS hijack risk).

## Verified gk2

PDF 51326 -> not affected. Splash adds ~1.2kB of HTML+CSS+JS.
2026-06-05 10:16:12 +02:00
b0a7571b77 feat(secubox-toolbox): splash cert chain auto-check + WAF mitm restart fix doc (ref #492)
## User feedback

  'je dois avoir acces au certificats et avoir un auto check sur le
   splash de la chaine cert'

## What changed

splash.html.j2 gains a cert status card BEFORE the level buttons :

  ┌──────────────────────────────────────────────────────────────┐
  │  Vérification certificat racine ToolBoX...                  │
  │    Empreinte attendue : 62:A1:E5:3B:1F:C2:B2:97:77:AE:BB:58…│
  └──────────────────────────────────────────────────────────────┘

JS detects CA install status :
  - fetch /ca/fingerprint -> display SHA1 the user must verify in
    iOS Réglages > Général > VPN/Device Management
  - Image probe to https://www.google.com/favicon.ico :
      onload  -> 🟢 'Certificat installé — R2 prêt'
      onerror -> 🔴 'Certificat NON installé — installer le profil'
      3s timeout ->  'Statut indéterminé'
  - Badge color + background tint adjusted per state

Helps user decide R2 vs R1 BEFORE clicking, preventing the failure
mode 'picked R2, no cert, all TLS broken'.

## Out of scope (Phase 3 transparency)

User also reported (#chess.maegia.tv 504) :
  - WAF mitmproxy LXC container stuck -> restarted, port 8080 back
  - secubox-streamlit-routes failing : 'tee /srv/mitmproxy/haproxy-routes.json:
    Permission denied' -> infra drift (related to #494 / secubox-mesh perms)
  - chess.maegia.tv backend on gk2:8900 not listening -> Streamlit
    app stopped, separate concern

These are WAF/Streamlit infra concerns separate from ToolBoX Phase 3.
Will need their own issue/PR.
2026-06-05 10:10:25 +02:00
26b77e10cb fix(secubox-toolbox): splash redesign — recommandé badge + R2 warning out of form + cleaner hierarchy (ref #492)
Issues fixed :
- R2 warning panel was INSIDE the form between R2 button and footer (looked
  like an extra button). Now properly separated, only shown if r2_enabled.
- No 'recommended' marker on R1. Added explicit '✓ DÉFAUT RECOMMANDÉ'
  badge on the R1 button.
- 'Ton appareil : ?? (anonymisé)' showed garbage at first page load. Now
  conditional : only renders if mac_hash is resolved + not '??'.
- 'Choisis ton niveau' was buried inside the terms paragraph. Now a
  prominent centered label '👇 Choisis ton niveau d'analyse :' above the
  form.
- Each button has a sub-desc with what it actually does + impact.
- Cleaner terms paragraph at bottom + links to /report/me/html and PDF.
2026-06-05 10:03:02 +02:00
95e11c8118 fix(secubox-toolbox): R2 level switch — replace ad-hoc nft.del_validated noop with proper del_consented helper + log nft results (ref #492)
## Bug

/change-level R0 had a broken else branch :

    if level in ('r1', 'r2'):
        nft.add_consented(mac, ttl='24h')
    else:
        try:
            nft.del_validated  # name reference, NEVER CALLED
            from . import nft as _nft
            _nft._run(...)
        except Exception:
            pass

The 'nft.del_validated' line was a stray name reference (intended as a
name-check?) that did NOTHING. The actual delete logic was buried under
it but still ran. Worked accidentally but unclear + ugly.

Plus : nft calls (add_validated/add_consented/add_r2_banner) silently
returned False on permission errors. No way to debug from logs.

## Fix

  - secubox_toolbox/nft.py : new del_consented() helper (symmetric with
    add_consented).
  - /change-level : flat sequence, uses del_consented for R0 downgrade,
    captures rc of each nft call.
  - log.info now includes 'nft: validated=True consented=True banner=False'
    style status — operator can immediately see which nft op failed.

## Verified live

After R0 cycle :
  - validated_macs : iPhone MAC present (extended 24h)
  - consented_r2_macs : empty (correctly cleaned)
  - r2_banner_macs : empty (correctly cleaned)

After R2 cycle (in service ctx with CAP_NET_ADMIN) :
  - all 3 sets get the MAC, banner addon checks store level = r2 and
    injects on text/html responses.
2026-06-05 09:58:54 +02:00
5f22d601a2 feat(secubox-toolbox): transparency metrics — attempts counters + whitelist hits + sensitivity badge + R0 'bypass complet' label (ref #492)
## User-facing transparency improvements

### Attempts counters in dashboard

Surfaced under '📈 Tentatives observées (toutes catégories)' :
  Total / 🔍 Inspecté / 🛡 Bypass / 🔒 Cert-pinning / 🔐 E2E / 🚫 Bloqué

Honest accounting : every flow we saw, by analysis_status. Includes
the Phase 4 placeholder for blocked flows (0 until enforcement is wired).

### Whitelist hits per pattern + category

Surfaced under '📜 Tes bypass whitelist en détail' :
  - Counter per category (vendor-os, financial, e2e-messaging, etc.)
  - Top 8 matched patterns with hit counts (e.g. '*.apple.com · 12 hits')

This is the 'accountability' angle : when we say 'we bypassed 23% of
your traffic', the report now ITEMIZES exactly which patterns received
those bypasses + why (category).

## Whitelist matching fix

The whitelist hit counter iterated dpi_hosts only — but iPhone DPI events
mostly contain IPs (cert-pinning rerouting). JA4 SNIs were ignored.

Now also iterates ja4_snis so SNIs like 'smoot.apple.com' match
'*.apple.com' from the baseline yaml.

Verified : whitelist_hits.total = 1 (was 0), top_patterns[0] =
'*.apple.com' x1, by_category = {'vendor-os': 1}.

### Sensitivity profile in dashboard

Active rule_engine sensitivity profile (low/medium/high/paranoid)
displayed in the transparency card with its label + description.
Currently medium = '🟡 Équilibré (défaut)'.

### R0 'bypass complet' label

Splash R0 button text changed from 'Accès simple' to 'Bypass complet'
+ subtitle 'réseau seul, ZÉRO analyse, comme un AP normal'. User
feedback : 'permettre un bypass ou whitelisting complet' -> R0 already
is that, just needed clear labeling.
2026-06-05 09:53:26 +02:00
d726e33e33 feat(secubox-toolbox): /accept redirects to dashboard + /change-level endpoint + dashboard level switcher (ref #492)
## Changes

### 1. Replace success page with dashboard
POST /accept now returns 303 redirect to /report/me/html?welcome=1&level=X
instead of rendering success.html.j2. User lands DIRECTLY on the live
dashboard with all the analysis data already visible.

A welcome banner at the top of the dashboard greets the user with their
chosen level + a link to fire the Apple captive probe (closes iOS captive
sheet -> Safari ready for normal browsing).

### 2. /change-level endpoint
POST /change-level accepts level=r0/r1/r2 form field. Updates nft set
membership (validated/consented/r2_banner) + persisted clients.level.
Redirects back to /report/me/html?switched=1&level=X.

### 3. Dashboard level switcher
report-live.html.j2 has a new card '🔀 Mon niveau d'opt-in' with 3
buttons (R0/R1/R2). User can change level without going back to splash.
The 'switched' query param triggers a confirmation banner.

### 4. Pass query_args to template
report_me_html() now passes request_args (query params) + current_level
(from store) to the template — used by the welcome/switched banner +
to highlight the active level button.

## E2E flow

splash R1 click -> POST /accept (level=r1) -> 303 -> GET /report/me/html
  ?welcome=1&level=r1 -> dashboard with green welcome card + R1 highlighted

dashboard switch to R2 -> POST /change-level (level=r2) -> 303 -> GET
  /report/me/html?switched=1&level=r2 -> dashboard with 'niveau changé'
  banner + R2 highlighted + r2_banner_macs set now includes my MAC ->
  banner injection + QUIC drop active.
2026-06-05 09:50:29 +02:00
6399a6c3ee 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
2026-06-05 09:45:04 +02:00
9690837177 feat(secubox-toolbox): integrate NotoColorEmoji in PDF — flags + color emoji render natively (ref #492)
## Goal

User feedback : 'integre NotoColorEmoji' + 'pas de flags' in PDF.

Symbola alone had no glyphs for flag emojis (regional indicator PAIRS
like 🇫🇷=U+1F1EB+U+1F1F7 which Symbola renders as 2 boxed letters, not
as a single flag glyph). Result : PDFs showed [FR]/[US]/[DE] fallback
codes instead of real flags.

## Fix

Add NotoColorEmoji (CBDT/CBLC color bitmap font) to fpdf2's fallback
chain. Order reversed : NotoColorEmoji FIRST, Symbola SECOND.

  - NotoColorEmoji has flag composites + all modern emoji as bitmaps
  - Symbola monochrome stays as fallback for chars Noto doesn't cover
  - DejaVu Sans remains primary for body text

Verified : pdftotext extract shows 🇺🇸 🇨🇦 🇫🇷 directly (5 fonts embedded
incl /MPDFAA+NotoColorEmoji). PDF size 51kB -> 62kB acceptable for the
visual gain.

## _EMOJI_REPLACEMENTS table

Reduced to a single passthrough entry (🏳 -> 🏳) since NotoColorEmoji
now handles every emoji we use. Kept as scaffolding for graceful
degradation if NotoColorEmoji isn't installed (debian/control already
adds fonts-noto-color-emoji as a soft dep via geoipupdate path —
follow-up : add as hard dep).

## debian/control

Should add fonts-noto-color-emoji to Depends. Currently :
  fonts-dejavu-core, fonts-symbola, python3-yaml, python3-geoip2 | geoipupdate
2026-06-05 09:40:15 +02:00