Commit Graph

1736 Commits

Author SHA1 Message Date
CyberMind
110133bee9
Merge pull request #539 from CyberMind-FR/feature/538-android-app-root-mode-fully-automated-si
Some checks are pending
License Headers / check (push) Waiting to run
Android root-mode fully-automated silent R3 onboarding (#538)
2026-06-13 07:47:00 +02:00
ac14e65353 docs: wiki/README/WIP/TODO/HISTORY for Android ToolBox app + root-mode (ref #538)
- new wiki page Android-ToolBox.md (install via /wg/toolbox.apk, manual +
  root onboarding flows, CI build, endpoints) + sidebar link
- README: document the root-mode silent path + revised constraints
- WIP/TODO/HISTORY: Android client section (#531/#536/#538)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 07:46:50 +02:00
1351054927 feat(android): root-mode fully-automated silent R3 onboarding (ref #538)
Add an optional one-tap, zero-interaction onboarding path for rooted
devices. When root is detected the app can:

- install the village3b CA into the SYSTEM trust store (bind-mount over
  /system/etc/security/cacerts + conscrypt APEX, SELinux ctx restored),
  so every app trusts the cabine CA — not just user-CA opt-in apps;
- bring the WireGuard tunnel up natively via the kernel module
  (ip link add … type wireguard + wg set), no WireGuard app needed;
- verify R3 reachability automatically.

Falls back to the existing manual handoff (KeyChain CA prompt + WG app)
when the kernel lacks WireGuard. All root actions are gated behind an
explicit ' Installation automatique (root)' tap — nothing runs as root
without the operator choosing it on their own device.

New: RootShell (su wrapper), RootOnboard (silent sequence + subject_hash_old
in pure Kotlin). MainActivity gains a RootAuto step with a streaming log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 07:40:58 +02:00
CyberMind
46dfd781d3
Merge pull request #537 from CyberMind-FR/feature/536-serve-the-android-toolbox-apk-from-the-t
Some checks are pending
License Headers / check (push) Waiting to run
feat(toolbox): serve Android APK from /wg/toolbox.apk + onboard button (#536)
2026-06-12 23:46:50 +02:00
cf3aef48c8 feat(toolbox): serve Android APK from /wg/toolbox.apk + onboard button (#536)
Follow-up to #531 — one-tap APK install from the cabine.
  - api.py GET /wg/toolbox.apk: serve local APK (android content-type),
    302 → latest GitHub release asset if absent (never dead-ends).
  - /wg/onboard Android panels (inline + _install_panels): 📱 Installer
    l'app ToolBoX (1-tap) button.
  - sbin/secubox-toolbox-fetch-apk: pull latest release asset into the
    serve path (best-effort + APK magic check); postinst creates the dir
    + first fetch.
  - build-android-apk.yml: publish APK as release asset
    secubox-toolbox-android.apk on android-v* tags (contents:write).
  - changelog 2.6.13.

Live on gk2: /wg/toolbox.apk 302 → release (fallback, no release yet),
serve dir created, onboard button rendered.
2026-06-12 23:46:29 +02:00
CyberMind
198fecea11
Merge pull request #535 from CyberMind-FR/feature/531-plan-android-apk-one-tap-toolbox-r3-clie
Some checks are pending
License Headers / check (push) Waiting to run
feat(android): one-tap toolbox R3 installer scaffold + CI (#531)
2026-06-12 23:27:34 +02:00
ab67c981dd feat(android): scaffold one-tap toolbox R3 installer + CI (ref #531)
Kotlin/Jetpack-Compose app under clients/android-toolbox/ replacing the
manual Android onboarding tutorial. 5-step flow:
  discover -> install CA (KeyChain intent) -> import WG profile (handed
  to the WireGuard app via FileProvider) -> verify (/wg/r3-check) ->
  live cartographie sociale (/social/me).

  - ToolboxApi: plain HttpURLConnection client for /wg/ca.crt,
    /wg/profile/new, /wg/r3-check, /social/me (no Retrofit/OkHttp —
    minimal deps + CI).
  - MainActivity: Compose stepper UI, palette-matched (cosmos/gold/cyan),
    FR copy. Intents for CA install + WireGuard handoff + Play fallback.
  - Gradle (AGP 8.5.2 / Kotlin 1.9.24 / Compose BOM 2024.06), minSdk 26,
    targetSdk 34, package in.secubox.toolbox. No wrapper jar committed.
  - res: adaptive launcher icon (vector eye glyph), theme, file_paths,
    strings — all text/XML, no binaries.
  - .github/workflows/build-android-apk.yml: setup-android + setup-gradle
    8.9 build assembleDebug -> APK artifact (sideloadable).
  - README documents the flow, build, and the Android user-CA-trust
    constraint (MVP guides manual confirm; release signing is follow-up).

Closes the scaffold half of #531; CI produces the debug APK.
2026-06-12 18:15:46 +02:00
CyberMind
92dee2d2d0
Merge pull request #534 from CyberMind-FR/feature/529-admin-wireguard-tunnel-wg-admin-for-secu
Some checks are pending
License Headers / check (push) Waiting to run
feat: admin WireGuard tunnel (wg-admin) + SSH hardening (#529)
2026-06-12 17:45:16 +02:00
CyberMind
a3666c4ba4
Merge pull request #533 from CyberMind-FR/fix/530-threatfox-feed-ingests-0-iocs-export-end
fix(threat_intel): ThreatFox ioc_value field — 0 → 5000 IOCs (#530)
2026-06-12 17:44:38 +02:00
e128505dfb fix(threat_intel): read ThreatFox ioc_value field — 0 IOCs → 5000 (closes #530)
The export/json/recent/ endpoint renamed the IOC field ioc → ioc_value;
the parser still read e.get('ioc') and skipped every entry, ingesting 0
for weeks and starving Phase 13.B DNS-guard of domain IOCs. Now reads
ioc_value or ioc. Verified live on gk2: threatfox 0 → 5000 ingested
(2935 domains + 1613 ips + 32 sha256). changelog 2.6.12.
2026-06-12 17:39:55 +02:00
725de94548 docs: WIP — admin WireGuard tunnel + SSH hardening (#529)
Some checks are pending
License Headers / check (push) Waiting to run
2026-06-12 15:09:46 +02:00
154872da9f feat(scripts): add idempotent 'harden' subcommand — key-only sshd + nft SSH-guard (ref #529)
After the wg-admin tunnel is confirmed working, 'harden' closes the
public SSH brute-force surface:
  - sshd drop-in: PasswordAuthentication no + PermitRootLogin
    prohibit-password (refuses if no root authorized_keys — no lockout).
  - nft SSH-guard: drop tcp/22 from sources outside the LAN (192.168.1.0/24)
    + 10.x tunnels, inserted BEFORE the blanket 'iif eth1 accept' so the
    router's port-forwarded public SSH (real public src IP, DNAT no-SNAT)
    is dropped while LAN + wg-admin stay reachable. Persisted in
    /etc/nftables.conf + applied live without flushing other tables.

Applied + verified on gk2: tunnel SSH (10.98.0.1) key-only works; public
admin.gk2.secubox.in:22 now times out. Tables (blacklist/wg) intact.
2026-06-12 15:09:00 +02:00
3c289c50b9 fix(scripts): local loop vars in write_server_conf so client $pip isn't clobbered (ref #529)
The while-read in write_server_conf reused the global pip/ppub names, so
after it ran the caller's $pip (the new peer IP for the client config)
was wiped to empty — the emitted client config showed 'Address = /32'.
The provisioned server state was always correct; only the printed config
was wrong. Loop now uses local _pn/_pip/_pub.
2026-06-12 14:49:38 +02:00
8f2a5c1608 feat(scripts): admin WireGuard tunnel setup script (wg-admin, ref #529)
Idempotent provisioner for a dedicated out-of-band admin tunnel,
distinct from wg-toolbox:
  - wg-admin UDP 51821, server 10.98.0.1/24, peers 10.98.0.2+/32.
  - generates server+peer keys once (0600, never in repo), writes
    /etc/wireguard/wg-admin.conf from a peers state file.
  - installs /etc/nftables.d/secubox-admin-wg.nft (allow udp/51821,
    persistent) — ADDITIVE, does not touch sshd or existing rules.
  - 'add <name>' mints a client .conf + QR (split-tunnel, endpoint
    admin.gk2.secubox.in:51821).
sshd hardening (key-only + restrict 22) is a deliberate separate step.
2026-06-12 14:47:21 +02:00
9a275e2355 docs: WIP repo-clean state, drop Round Eye item
Some checks failed
License Headers / check (push) Has been cancelled
2026-06-11 07:46:12 +02:00
2dab321e36 docs: Phase 12.C + Phase 13 (protection enforcement plane) complete — v2.13.16-19
- Phase 12.C operator-grade detection (#518, v2.13.16).
  - Phase 13 A/B/C/D enforcement plane complete (#519-#528, v2.13.17-19):
    nft blacklist spine + DNS-guard + per-device attribution/quarantine +
    detection→enforcement feedback loop (all escalation default OFF).
  - override_dh_strip drift bug fixed (#521).
  - Phase 14 deception-proxy idea captured (#525) for later.
2026-06-11 07:42:42 +02:00
CyberMind
982176209a
Merge pull request #528 from CyberMind-FR/feature/527-phase-13-d-feedback-loop-social-mapping
Phase 13.D — detection→enforcement feedback loop (#527)
2026-06-11 07:40:32 +02:00
de8e6de23c feat(toolbox): Phase 13.D — detection→enforcement feedback loop (ref #527)
Completes Phase 13 (protection enforcement plane). ALL SOURCES DEFAULT OFF.

  - escalate.py: evaluator reads social-mapping (by_opgrade/by_antibot)
    + device-blocks aggregates, applies operator-tunable thresholds:
      * operator-grade data-broker hosts over threshold → resolve IPs →
        blacklist_v4/v6 (+ optional cscli decision for audit/TTL),
      * anti-bot vendors over threshold → flag only (legit infra),
      * devices over DoH threshold → 13.C quarantine.
    Every action append-logged to /var/log/secubox/audit.log (CSPN),
    reversible (nft/cscli TTL + operator unban).
  - sbin/secubox-escalate + .service/.timer (10min); sources opted in
    via SECUBOX_ESCALATE_* drop-in.
  - api.py GET /admin/escalate: policy flags + last-cycle summary.
  - changelog 2.6.11.

Live on gk2: timer active, default-off cycle takes 0 actions; synthetic
test with SECUBOX_ESCALATE_DEVICE_DOH=1 quarantined a device over
threshold + wrote the audit entry. Detection stays factual; escalation
is separate, logged, reversible, opt-in (#519 doctrine).
2026-06-11 07:40:15 +02:00
CyberMind
ffa75bbe9d
Merge pull request #526 from CyberMind-FR/feature/524-phase-13-c-dhcp-wg-per-device-attributio
Phase 13.C — DHCP/WG per-device attribution + quarantine (#524)
2026-06-11 07:35:10 +02:00
9697ea05a9 feat(toolbox): Phase 13.C — DHCP/WG per-device attribution + quarantine (ref #524)
Turns the enforcement counters into per-device intelligence.

  - secubox-blacklist.nft: quarantine_v4/v6 sets + quarantine-first drop
    in the enforce chain (a quarantined device's saddr is fully cut off).
    Rate-limited log prefixes SBX-BL-DROP (enforce) + SBX-DOH (doh_watch,
    ct state new) for attribution.
  - device_blocks.py: device resolver (R3 WG peer hash / dnsmasq-lease
    MAC hashed with rotating salt / IP fallback) + device_blocks SQLite
    aggregate + retention. Anonymous, same mac_hash model as social.
  - sbin/secubox-blacklist-attrib: cursor-based journald kernel-log
    tailer buckets SBX-BL-DROP / SBX-DOH by source IP -> device ->
    device_blocks. 2-min timer.
  - api.py: GET /admin/device-blocks, POST /admin/quarantine/{ip} +
    /admin/unquarantine/{ip}.
  - app.py: device_blocks 7-day retention.
  - index.html operator tab: per-device blocked-attempts card + one-click
    quarantine.
  - changelog 2.6.10.

Live on gk2: enforce chain quarantine-first + drop logs, attrib timer
active, quarantine add/remove via API verified, device resolver maps
10.99.1.60 -> 9433ceb90895a075 (WG hash), record+aggregate green.
Auto-quarantine NOT wired (operator-manual this pass; threshold = #519 Q2).
2026-06-11 07:32:43 +02:00
CyberMind
f34dc633a8
Merge pull request #523 from CyberMind-FR/feature/522-phase-13-b-dns-guard-resolve-blocklisted
Some checks are pending
License Headers / check (push) Waiting to run
Phase 13.A + 13.B — protection enforcement spine + DNS-guard (#521 #522)
2026-06-11 07:25:58 +02:00
1063c91815 feat(toolbox): Phase 13.B — DNS-guard (resolve blocklisted domains + DoH/DoT detection) (ref #522)
Closes the DoH / hardcoded-IP bypass gap on top of 13.A.

  - secubox-blacklist-sync: Source 3 resolves threat_intel domain IOCs
    (ioc_type='domain') to A/AAAA via getent (2s per-lookup timeout,
    2000-domain cap) and pushes the IPs into blacklist_v4/v6 — a device
    that resolves a known-bad domain out-of-band (DoH / hardcoded IP) is
    still dropped at forward. Writes /run/secubox/blacklist-sync.json.
  - secubox-blacklist.nft: doh_detect_v4/v6 sets + count-only doh_watch
    chain (priority -11) countering device egress to known DoH/DoT
    providers (Cloudflare/Google/Quad9/AdGuard/OpenDNS/CleanBrowsing/
    Control-D/NextDNS) on tcp 443/853. Made the drop-in IDEMPOTENT via
    the create-or-replace idiom (table{} ; delete table ; table{...}) so
    nft -f reloads don't append duplicate rules.
  - secubox-blacklist-sync: populates DoH sets each cycle;
    SECUBOX_DOH_BLOCK=1 also enforce-drops them (forces devices back
    through Vortex DNS). Default off — intrusive, gated per #519 Q1.
  - api.py /admin/blacklist: doh_detect counts, doh_hits, resolved_domains,
    doh_block flag.
  - changelog 2.6.9.

Live on gk2: idempotency verified (enforce=4 / doh_watch=2 rules after
double reload), endpoint shows v4_count 20 + doh_detect 15 v4 / 6 v6,
doh_block false. resolved_domains 0 (threat_intel has no domain IOCs
yet — threatfox feed currently returns 0; path is correct).
2026-06-11 07:24:24 +02:00
a5c07c3d50 feat(toolbox): Phase 13.A — unified nft blacklist enforcement spine (ref #521)
First track of the protection enforcement plane (#519). Pure netfilter,
additive drops, DEFAULT-DROP doctrine preserved.

  - nftables.d/secubox-blacklist.nft: table inet secubox_blacklist with
    blacklist_v4/v6 sets (interval,timeout) + a single forward-hook
    chain (priority -10, policy accept) counter-dropping any routed
    packet whose saddr OR daddr is blacklisted. One rule set covers
    every device egress path (captive/WG/br-lxc/LAN), no per-iface logic.
  - sbin/secubox-blacklist-sync: unions CrowdSec ban decisions (cscli)
    + threat-intel C2 IPs (threat_intel ip IOCs) into the sets, 2h
    per-element timeout (auto-expire on stale feeds), 50k cap, idempotent
    add (no flush race with CrowdSec's own table).
  - systemd sync .service + .timer (5min + OnBootSec 90s).
  - postinst: install drop-in, reload nft, enable+start timer, first sync.
  - api.py GET /admin/blacklist: element counts + drop counters (nft -j).

  - debian/rules FIX: moved the whole override_dh_strip body into
    execute_after_dh_auto_install. dh_strip is NOT in the binary-indep
    sequence for an Architecture: all package, so override_dh_strip
    silently never ran — the nft drop-ins / unbound / nginx / perf
    drop-ins (and now the blacklist files) had stopped shipping in the
    .deb and gk2 was running stale on-disk copies (the live-config-drift
    we kept hitting). Now they all ship again.

  - changelog 2.6.8.

Live on gk2: table loaded, first sync populated 18 v4 C2 IPs with 2h
timeouts, enforce chain active (4 drop rules, counters wired), timer
enabled, /admin/blacklist returns active:true.
2026-06-11 07:16:35 +02:00
CyberMind
febf58fd27
Merge pull request #520 from CyberMind-FR/feature/518-phase-12-c-operator-grade-state-adjacent
Some checks are pending
License Headers / check (push) Waiting to run
Phase 12.C — operator-grade / state-adjacent identity detection (#518)
2026-06-10 14:51:58 +02:00
0d1d3475db feat(toolbox): Phase 12.C — operator-grade / state-adjacent identity detection (ref #518)
Extends Phase 8 Utiq (#500) into a general top-severity identity detector.
Passive detection only — no bypass.

  - social_graph.py detect_operator_grade(): telco header-enrichment
    (MSISDN/x-acr/WAP-bearer), operator-consortium IDs (Utiq/TrustPid),
    data-broker / state-adjacent hosts (LiveRamp/BlueKai/Acxiom/Neustar/
    Tapad/Experian/Palantir-class). Returns (vendor, category).
  - social.py: social_host_meta.opgrade_vendor+category; social_opgrade
    per-client table; record_host_opgrade + record_opgrade_event +
    opgrade_for_client; fetch_graph/aggregate/wipe wired.
  - social.js: top-tier void-purple lens (above anti-bot) + pulsing
    double ring +  banner; node-detail vendor+category.
  - index.html operator tab: opgrade breakdown card.
  - social_report.py: operator-grade evidence section in the PDF.
  - i18n FR/EN + cache-bust ?v=26c. changelog 2.6.7.

Live on gk2: social_opgrade table created, detect matrix green
(msisdn/x-acr/utiq/liveramp/bluekai/palantir/none), aggregate by_opgrade
+ opgrade_clients present, per-client stats populate, PDF 42777 valid.
Factual network-surface flagging only, no entity qualification.
2026-06-10 14:36:02 +02:00
66b608d760 docs: update WIP/HISTORY/TODO for Phase 11 complete + Phase 12.A/B + v2.13.15
Some checks are pending
License Headers / check (push) Waiting to run
- Phase 11 A/B/C + toolbox tabs (#513) + Phase 12.A CDN (#515) + 12.B
    anti-bot (#516) all merged via PR #517, secubox-toolbox 2.6.6, tag
    v2.13.15.
  - Round Eye: USB TX wedge diagnosed, needs physical Pi power-cycle.
  - Next: Phase 12.C operator-grade; 12.B bypass + 12.D noise gated
    behind doctrine.
2026-06-10 14:25:26 +02:00
CyberMind
3a0d60c588
Merge pull request #517 from CyberMind-FR/feature/516-phase-12-b-anti-bot-prove-you-re-human-c
Phase 11 (social mapping A/B/C) + Phase 12.A/B (CDN + anti-bot) + toolbox sub-tabs
2026-06-10 14:23:05 +02:00
8bf5fe4f9a fix(toolbox): Carto operator link redirects to absolute kbin URL (ref #516)
The Clients-tab 🕸️ Carto link 303'd to a relative /social/{token}, but
that route is only served on the kbin vhost — from admin.gk2 it hit the
aggregator's 'missing module' page. admin_client_social() now swaps the
leading admin. host label for kbin. and redirects to the absolute URL.

Live: 303 -> https://kbin.gk2.secubox.in/social/{token} -> 200. changelog 2.6.6.
2026-06-10 14:22:16 +02:00
9f850bef53 feat(toolbox): Phase 12.B — anti-bot detection + visible ring levels + per-client operator tools (ref #516)
Anti-bot / 'prove you're human' DETECTION (bypass stays gated behind doctrine):
  - social_graph.py detect_antibot(): reCAPTCHA/hCaptcha/Turnstile/Datadome/
    PerimeterX-HUMAN/Arkose/Kasada/Akamai-BotManager from URL+cookies+headers.
  - social.py: social_host_meta.antibot_vendor + social_antibot per-client
    challenge table; fetch_graph/aggregate/wipe_mac carry it.
  - social.js: cinnabar severe lens + spinning warning ring + 🤖 label +
    'challenged your humanity' alert banner; node-detail vendor.
  - operator social tab: anti-bot breakdown card.

Fix the ring-level visibility the user reported missing:
  - radial force now DOMINANT (strength 0.9) with weak charge/links, plus
    dashed ring guides (inner=sites, outer=trackers); assets cache-busted
    ?v=264b so the new layout actually loads.

Per-client operator tools in the Clients tab:
  - 🕸️ Carto link → GET /admin/clients/{mac}/social mints a token + opens
    that client's graph.
  - ↺ Reset (RAZ) → POST /admin/clients/{mac}/reset wipes social + events +
    consents + reports and zeroes score (store.reset_client + social.wipe_mac).

changelog 2.6.5. Live on gk2: both new tables created, detect_antibot matrix
green, aggregate by_antibot/antibot_clients present, reset endpoint 200, graph
nodes carry antibot_vendor, served assets contain the ring + cache-bust.
2026-06-10 14:22:16 +02:00
a93d949ede feat(toolbox): Phase 12.A — CDN detection + Round-Eye central-hotspot graph (ref #515)
First passive track of the #514 anti-human-detection platform.

  - social_graph.py: detect_cdn() classifies the edge network fronting
    each 3rd-party host from response headers (Cloudflare/Fastly/Akamai/
    CloudFront/Google/Vercel/Netlify/BunnyCDN/KeyCDN/Sucuri/Imperva +
    generic edge-cache). Host-stable, recorded off-thread. Also deduped
    a stray duplicate _ja4_hash left by the 11.C edit.
  - social.py: new social_host_meta table; fetch_graph LEFT JOINs
    cdn_vendor/cache_status onto nodes; aggregate adds by_cdn.
  - social.js: per-client graph re-centred on a Round-Eye hotspot —
    device = pulsing eye at centre, sites orbit on inner forceRadial
    ring, trackers push to outer ring (densest clusters = hot spots).
    Tracker nodes tinted by CDN vendor; node-detail shows CDN/cache.
  - social.css: eye halo/sclera/iris/pupil + breathing anim + cinnabar
    spokes.
  - index.html: operator social tab gains a CDN/edge breakdown card.
  - i18n: node_detail_cdn FR/EN. changelog 2.6.4.

Live on gk2: social_host_meta table created, addon loads detect_cdn,
detect_cdn matrix green (CF/Fastly/Akamai/CloudFront/none), end-to-end
read path proven (synthetic Cloudflare record -> graph node cdn_vendor
-> aggregate by_cdn). Populates from live traffic.
2026-06-10 14:22:16 +02:00
f945c393b8 feat(toolbox): Phase 11.C — consent probe + bilingual FR/EN evidence PDF (ref #508)
Completes Phase 11 (social mapping per device): A backend + B d3 view + C evidence/PDF.

  - social_graph.py: consent-platform probe (OneTrust/Didomi/Quantcast/
    Sourcepoint cookies + loader URLs) per (peer,site). Edges stamped
    consent_state none_seen|pre_consent|post_consent. pre_consent =
    tracker fired while site runs a CMP but no consent cookie yet
    (RGPD art. 6.1.a + 7).
  - social.py: consent_state column + GeoIP node columns + EU/EEA
    whitelist + GeoIP fold + evidence() (carried from 55626e51).
  - social_report.py: NEW bilingual FR/EN evidence PDF via fpdf2.
    Robust _mc() helper (set_x + epw) avoids fpdf 'not enough
    horizontal space'. Fact-only: cover + summary + pre-consent table
    + extra-EU table + RGPD article refs. Text fallback if no fpdf2.
  - api.py: GET /social/report/{token}.pdf (HMAC-token gated).
  - social_view.html.j2 + i18n + css: real PDF download button,
    active evidence-card summary.
  - changelog: 2.6.3-1~bookworm1.

Live on gk2: PDF 200 / 41870 bytes / valid PDF v1.3 via uvicorn + kbin.
Consent state machine verified (pre->post->none). Rebased on #513 so
the toolbox sub-tabs ship together.
2026-06-10 14:22:16 +02:00
d8bc4acd90 wip(toolbox): Phase 11.C backend schema + GeoIP fold + evidence helper (ref #508)
Checkpoint — backend evidence layer scaffolded.  Frontend wire + PDF
generator + addon consent probe land in follow-up commits.

  - social.py schema : social_edges.consent_state column ; social_nodes
    gains country_iso / asn_org / eu_inside / pre_consent_hits.
  - Idempotent _migrate_phase11c() ALTERs the pre-existing tables on
    2.6.0 → 2.6.x upgrades (no destructive recreate).
  - _EU_EEA_ISO whitelist (27 EU + 3 EFTA + UK adequacy) + is_eu_iso().
  - _geo_for() LRU-cached (4096) wrapper around the existing geo
    module ; fold time populates the GeoIP fields on every node row.
  - record_edge() accepts consent_state (default 'none_seen') ; fold
    accumulates pre_consent_hits into the per-node aggregate.
  - evidence(mac_hash) helper : returns the two legal-grade buckets
    (pre_consent + extra_eu) consumed by the PDF in the next commit.

Pivoting to admin tab routing per user request — Phase 11.C resumes
after that lands.
2026-06-10 14:22:16 +02:00
39ec84eaed feat(toolbox): WebUI sub-tab nav + remove redundant kbin /admin/ inline UI (ref #513)
Consolidates the two toolbox admin surfaces onto the canonical
admin.gk2.secubox.in/toolbox/ WebUI.

  - www/toolbox/index.html: 5-tab nav (Vue d'ensemble / Clients /
    Filtres MITM / Cartographie sociale / Config). Lazy-load per tab,
    live refresh only polls the visible tab, URL-hash deep-links.
    Clients tab folds in the R0-R3 level switcher from the old kbin
    admin UI. New Cartographie sociale tab surfaces the Phase 11
    operator aggregate (KPI + top trackers + anonymized clients).
  - api.py: DELETED the inline kbin /admin/ HTML route (admin_index,
    ~230 lines). All /admin/* JSON API routes preserved.
  - conf/landing.html.j2: removed the Admin quicknav icon.
  - debian/changelog: 2.6.1 -> 2.6.2.

Live on gk2: /toolbox/ 200 (19853 bytes, 5 tabs), kbin /admin/ now 404
by design, social-aggregate shows 225 trackers / 3 clients, all tab
endpoints (clients/rich, filter-control/list, social-aggregate) 200.
2026-06-10 14:22:16 +02:00
78ed66c7cd fix(toolbox): force layout pre-warm + data-based autoFit (ref #507)
Live test screenshot showed 86 trackers / 60 sites computed correctly,
but only one tracker node was visible — the force layout had spread
the rest off-screen and the on-tick autoFit caught the simulation
mid-flight using SVG getBBox (which is sensitive to label text far
outside the cluster).

  - Force tuning scales with node count (146 nodes → linkDist 40,
    chargeStr -60, collide 14, alphaDecay 0.05 for faster settle).
  - Pre-warm: 300 synchronous simulation.tick()s before first render so
    positions are already converged.
  - autoFit() uses node data positions (min/max x,y over nodes array)
    instead of content.getBBox() — labels no longer skew the fit.
  - First render: paint pre-warmed positions immediately, kick a gentle
    alpha(0.05) restart for polish, autoFit on the next animation frame
    with duration 0 (instant).
2026-06-10 14:22:16 +02:00
0aeedcf1f4 feat(toolbox): d3 pan + pinch-zoom + auto-fit initial render (ref #507)
- Wrap graph content (links + nodes) in a content <g> so d3.zoom's
    transform applies there without moving the SVG itself.
  - d3.zoom(): scale 0.2–6×, mouse wheel + touch pinch + drag-pan.
    Filter lets node drags pass through d3.drag (no collision with the
    pan gesture) and always accepts multi-touch + wheel.
  - autoFit(): after the simulation alpha drops below 0.1, compute the
    nodes' bounding box and transition to a zoom transform that fits
    all of them with 28 px padding (capped at 2.5× so a single-node
    graph doesn't blow up).  Same logic re-runs on dblclick (reset)
    and on viewport resize (debounced 150 ms).
  - Viewport-resize observer keeps the viewBox and zoom in sync when
    orientation changes.
2026-06-10 14:22:16 +02:00
8a4598e2b3 feat(toolbox): full-viewport graph + drop persistent overlay messages (ref #507)
User feedback : graph too small + 'Chargement…' / 'ZÉRO TRACKER DÉTECTÉ'
overlays kept stacking on top of the graph.

Layout reworked :
  - body flex column 100vh overflow hidden — header sticky, main owns
    all remaining vertical space, graph fills it.
  - stats tiles → small glassy pill floating top-left over the graph.
  - cards collapsed into a compact 3-cell horizontal strip at the
    bottom ; open-card popover slides up over the graph so the graph
    stays visible.
  - graph svg measured at render-time, viewBox set dynamically — d3
    force-center scales to actual viewport instead of the fixed
    600×600 box.

Persistent overlays removed :
  - .social-empty and .social-loading divs gone from template.
  - JS no longer references them.  Empty graph is its own indicator
    (stats tiles show 0/0).  Network errors log to console only.

Net effect : tap 'Ma carto' on splash → full-screen force-directed
graph, stats top-left, cards bottom strip, no overlay clutter.
2026-06-10 14:22:16 +02:00
720d61a9d3 fix(toolbox): move social-view i18n from data-* attr to <script> (ref #507)
JSON in a single-quoted HTML data-* attribute breaks on FR apostrophes
(l'effacement, L'analyse) — JSON.parse threw 'unterminated string at
line 1 column 475'.  <script>window.__SOCIAL_I18N__ = {…}</script>
keeps the JSON verbatim and is the standard idiom for shipping
server-rendered config to client JS.
2026-06-10 14:22:16 +02:00
d195408fd4 fix(toolbox): PYTHONPATH in mitm-wg launcher so addons can import secubox_toolbox (ref #507)
Root cause caught after Phase 11.B live deploy : the mitm-wg workers
run inside /opt/mitmproxy-toolbox/bin/python3 — a venv whose
site-packages does NOT include /usr/lib/secubox/toolbox/. Every addon
that does `from secubox_toolbox import ...` was therefore silently
degraded :

  - inject_banner.py: dpi_class / geo / store / utiq imports all
    failed → no host classification, no GeoIP, no ASN, no Utiq tile.
  - social_graph.py: _social = None → record_edge() no-op → zero
    social_edges rows even though the response hook fired correctly.

Adding `export PYTHONPATH=/usr/lib/secubox/toolbox` to the launcher
restores ALL helper imports for ALL addons.

Verified live on gk2 : after deploy + worker restart, 369 social_edges
recorded in 30s, fold produced cross-site links connecting 4 ad-tech
relay publishers (360yield, seedtag, smartadserver, smilewanted via
the same 35.214.136.108 endpoint).
2026-06-10 14:22:16 +02:00
8dc1449eab fix(toolbox): /social/me R3 transparent peer resolution via X-R3-Peer (ref #507)
iPhone hitting /social/me from the kbin splash on R3 tunnel was 400'ing
because the previous chain was only ?mh=<hash> + ARP _resolve(), and R3
peers are on 10.99.1.0/24 not the captive subnet.

New 3-step resolution chain (matches /report/me/html):
  1. ?mh=<hash>             — explicit hex hash in URL
  2. X-R3-Peer header       — sentinel set by mitm-wg's inject_xff
                              for transparent R3 flows ; we look up
                              the WG pubkey hash from
                              /var/lib/secubox/toolbox/wg-peers.json
                              keyed by peer IP.
  3. ARP _resolve()         — R2 captive subnet fallback

Verified live: GET /social/me with X-R3-Peer: 10.99.1.60 → 303 →
/social/<token-for-mac_hash 9433ceb90895a075>
2026-06-10 14:22:16 +02:00
fc22b64b99 fix(toolbox): Phase 11.B live-deploy hardening — addon import, static mount, kbin paths, splash link (ref #507)
Five fixes caught during the first live deploy on gk2 :

  1. mitmproxy_addons/social_graph.py: `from . import local_store` was
     never resolving because mitmproxy loads addons as top-level
     modules (not as package members).  Inlined the R3 path
     (10.99.1.0/24 → sha256(wg_pubkey)[:16]) directly.  Verified
     against the real iPhone IP (10.99.1.60 → 9433ceb90895a075).

  2. www/toolbox/social.js: the JSON fetch + wipe paths used the
     nginx-prefixed `/api/v1/toolbox/social/...`.  The kbin vhost
     routes via HAProxy straight to uvicorn (bypassing nginx) so
     that prefix never matches.  Rewrote to the FastAPI router
     paths `/social/graph/{token}` + `/social/wipe/{token}`.

  3. secubox_toolbox/app.py: FastAPI StaticFiles mount on /toolbox
     for the kbin HAProxy path (which doesn't go through nginx).
     The mount serves the same files nginx would alias from
     /usr/share/secubox/www/toolbox/.

  4. debian/postinst: chmod 0755 /usr/share/secubox/www so the
     `secubox-toolbox` service user (not in secubox group) can
     traverse to the WebUI directory.  Without this the StaticFiles
     mount crashed the service at startup with PermissionError.

  5. New /social/me endpoint + splash menu icon : self-resolving
     entry point that mints a 1 h HMAC token and 303-redirects to
     /social/{token}.  Mirrors the existing /report/me/html pattern.
     Splash page gets a 🕸️ "Ma carto" quick-nav icon.

End-to-end via gk2 live :
  /social/me?mh=<hash> → 303 → /social/{token} → 200 (4883 bytes FR)
  static assets : 200 / 7529 / 8359 / 279706 bytes
  addon smoke test : mac_hash for 10.99.1.60 → 9433ceb90895a075 ✓
2026-06-10 14:22:16 +02:00
693ee360e9 feat(toolbox): Phase 11.B social mapping per-client view + favicon proxy + i18n (ref #507)
Parent #502 (Phase 11 social mapping per device).  Builds on top of
the Phase 11.A backend (PR #506, branch feature/505).

  - secubox_toolbox/api.py: GET /social/{token} renders the HTML view
    (HMAC-token gated, FR/EN i18n via ?lang= or Accept-Language).
    GET /social/favicon/{domain} server-side cached proxy (7d TTL,
    strict charset, HTTPS only, hard 5s timeout, transparent gif
    fallback).

  - conf/social_view.html.j2: Jinja2 template implementing the
    locked design from the #502 round-2 lock — Cinzel header / IM
    Fell body / JetBrains Mono data, force-directed d3 graph, three
    collapsible cards, tracker-node bottom-sheet, wipe dialog with
    3 s countdown.

  - conf/i18n/social.{fr,en}.json: single dictionary per language.
    FR is the source-of-truth.

  - www/toolbox/social.css: palette-precise stylesheet.  Mobile-first;
    desktop bumps the node-detail panel to a right-rail at >=720 px.

  - www/toolbox/social.js: self-contained d3 driver.  Consumes the
    Phase A JSON contract, force layout (link distance 70, charge
    -180), tap-to-focus pulse on tracker nodes, wipe modal countdown
    timer, refresh after wipe.

  - www/toolbox/d3.v7.min.js: d3 v7.9.0 self-hosted (~280 KB),
    ISC-licensed, no CDN runtime dependency.

  - debian/changelog: bump to 2.6.1-1~bookworm1.

Smoke tests: FR + EN templates render (4771 / 4503 bytes), api.py
parses, social.js node-checks clean.

Phase C scope deferred — legal-evidence flag computation, bilingual
PDF report, GeoIP+ASN populated values on the node detail panel,
operator dashboard /admin/social/ HTML view (the JSON
/admin/social-aggregate endpoint already ships from 2.6.0).
2026-06-10 14:22:16 +02:00
bafd856d1e fix(toolbox): debian/rules ships sbin/* launchers consistently (ref #505)
Caught during 2.6.0 live deploy on gk2: `debian/rules` had install lines
for `sbin/secubox-toolbox-db-tune` (override_dh_strip) and a few wg-
phase scripts but NEVER for `sbin/secubox-toolbox-mitm-wg-launch`. The
existing launcher on gk2 was a leftover from an older deploy and dpkg
upgrades silently kept the file static — which masked the Phase 11.A
addon-chain edit (social_graph was added in source but never reached
the live launcher).

  - debian/rules: explicit install lines for the three sbin helpers
    (lxc-provision, mitm-wg-launch, wg-provision).  These were either
    ad-hoc-installed before or relied on the operator copying them
    by hand.
  - sbin/secubox-toolbox-mitm-wg-launch: chmod 0755 (was 0664).
  - sbin/secubox-toolbox-wg-restore: chmod 0755 (was 0664).

Closes a source-first regression caught by [feedback_source_first_always].
2026-06-10 14:22:16 +02:00
6b230d464d feat(toolbox): Phase 11.A social mapping backend — correlation engine + API (ref #505)
Parent #502 (Phase 11 social mapping per device).  Backend-only deliverable
unblocking Phase 11.B (d3 view) and 11.C (legal-evidence PDF).

  - secubox_toolbox/social.py: new module.  SQLite schema for
    social_edges (raw, 7d retention) + social_nodes (per-peer per-tracker
    aggregate) + social_links (per-peer per-site-pair with shared-tracker
    list and JA4 collision flag).  ThreadPoolExecutor fire-and-forget
    write path matches utiq.py.  fold_recent() runs every 5 min and
    derives the aggregate tables.  wipe_mac() backs the RGPD art. 17
    endpoint.  aggregate() backs the operator dashboard.

  - mitmproxy_addons/social_graph.py: new addon.  Loaded after
    local_store and before inject_banner in the mitm-wg chain.
    Parses Set-Cookie + Cookie headers, normalizes via
    cookie_id_hash = sha256(domain||name||value)[:16] — raw cookie
    values are NEVER persisted.  Deny-list strips
    session/CSRF/auth/locale cookie names.  Cheap eTLD+1
    approximation for 1st- vs 3rd-party classification.  Reuses
    local_store helpers for the rotating MAC hash.

  - secubox_toolbox/api.py: three new endpoints.
      GET  /social/graph/{token}       per-client graph (HMAC token)
      POST /social/wipe/{token}        RGPD art. 17 droit à l'effacement
      GET  /admin/social-aggregate     operator dashboard KPI view

  - secubox_toolbox/app.py: two new startup background tasks —
    social_fold_loop (5 min) and social_purge_loop (1 h).  Match the
    existing purge/threat_intel pattern.

  - sbin/secubox-toolbox-mitm-wg-launch: addon chain wired
    (… local_store → social_graph → inject_banner …).

Phase A scope locks: R3 mitm-wg path only.  R2 captive hookup, d3
graph view, bilingual FR/EN PDF report, and the consent-state /
extra-EU legal-evidence flags are deferred per the #502 design lock
(rounds 1 + 2).

Smoke-tested on the host : hash stability, deny-list, edge insertion,
fold, fetch_graph contract shape, aggregate, wipe_mac round-trip,
addon helpers (eTLD+1, Set-Cookie parser, Cookie parser, Domain attr).
All seven invariants green.

  - debian/changelog: bump to 2.6.0-1~bookworm1.
2026-06-10 14:22:16 +02:00
2b1d2173c0 docs: add Cartographie sociale 'You Have Been Tracked' README section + poster brief (ref #502)
Some checks are pending
License Headers / check (push) Waiting to run
- README: new VILLAGE3B subsection for the Phase 11 social mapping
    feature (per-client /social/me, d3 graph, RGPD wipe, operator tab).
  - docs/marketing/POSTER-you-have-been-tracked.md: poster brief with
    the real kbin metrics (218 trackers, 142 sites, 234 JA4, 83693
    connexions) + doctrine guardrails + declension variants.
2026-06-10 12:52:36 +02:00
CyberMind
4339590eb1
Merge pull request #512 from CyberMind-FR/fix/511-fix-var-log-secubox-traversal-converge-o
Some checks are pending
License Headers / check (push) Waiting to run
fix: /var/log/secubox traversal — converge postinsts to 0755 (#511)
2026-06-10 09:59:14 +02:00
13faf609f7 fix: converge /var/log/secubox postinsts to 0755 traversable (closes #511)
Shared-directory ownership race: secubox-toolbox + secubox-mesh created
/var/log/secubox at 0750 with their own module owner, secubox-admin at
750 root:secubox, while ~10 other packages use the correct 0755
secubox:secubox.  Whichever postinst ran last won — and when toolbox or
mesh won, the aggregator (user secubox) lost traversal and the /waf/ +
/soc/ dashboards went blank (regressed on gk2 2026-06-10).

  - secubox-toolbox/debian/postinst:47  0750 -> 0755 (+ guard comment)
  - secubox-mesh/debian/postinst:23     0750 -> 0755
  - secubox-admin/debian/postinst:11    750  -> 0755

Mode 0755 makes the shared parent world-traversable regardless of which
package's owner wins.  Per-module log files + subdirs inside keep their
own restricted perms.  Same class as the /etc/secubox traversal
constraint + the /usr/share/secubox/www chmod (#507).
2026-06-10 09:53:48 +02:00
8263bc7681 docs: update WIP/HISTORY/TODO for Phase 11 A+B + gk2 system triage + v2.13.14
Some checks are pending
License Headers / check (push) Waiting to run
- Phase 11 social mapping A (#506/2.6.0) + B (#507/2.6.1) deployed live;
    C (#508) checkpointed at 55626e51.
  - gk2 triage: CrowdSec bouncer tables, /var/log/secubox traversal,
    WAF double-buffer cache (#509/1.2.2), PeerTube/PhotoPrism restart.
  - CI: espressobin disable (#504), WAF perf (#510) merged, v2.13.14 tagged.
  - Carried: Round Eye link, toolbox tab decision, var/log postinst patch.
2026-06-10 08:44:55 +02:00
CyberMind
a6f44807c6
Merge pull request #510 from CyberMind-FR/fix/509-waf-soc-perf-double-buffered-cache-memor
perf(waf): double-buffered cache for /waf/stats + /soc (#509)
2026-06-10 08:42:12 +02:00
CyberMind
3ebb4477cc
Merge pull request #504 from CyberMind-FR/fix/503-ci-disable-espressobin-v7-ultra-image-bu
CI: drop espressobin-v7 + ultra from scheduled image matrix (#503)
2026-06-10 08:42:09 +02:00
b7666e5f57 perf(waf): double-buffered cache for /waf/stats (memory + disk) with incremental log reading (closes #509)
User reported /waf/ + /soc/ dashboards showing empty threats and tracked
attackers cards.  Live triage on gk2 found _get_threat_stats() iterating
all 332k JSONL entries in the 110 MB waf-threats.log on every request,
pinning the aggregator at 89% CPU with 8+ concurrent open handles.

  - api/main.py: _load_stats_disk_cache + _save_stats_disk_cache helpers
    persist counters + last-read byte position to
    /var/lib/secubox/waf/stats-disk-cache.json (atomic .tmp -> rename so
    a crash mid-write never corrupts).
  - api/main.py: _get_threat_stats reseats the file at the saved byte
    position, reads only the new tail since then, persists the new
    position before returning.
  - api/main.py: log rotation / truncation detected via size shrink;
    counters reset cleanly.
  - api/main.py: day rollover resets threats_today only; cumulative
    counters survive.
  - api/main.py: ip_countries dict capped at 1200 entries (most-active
    1000 retained on overflow) so the cache file stays small.

Live on gk2 after deploy: /waf/stats steady-state 30-37 ms (vs 30s+
timeout before).  /waf/alerts, /waf/bans/history, /soc/* all 200 in
under 200 ms.  Aggregator CPU drops from 89% to 52% under same load.

  - debian/changelog: 1.2.1 -> 1.2.2-1~bookworm1.
2026-06-10 08:39:57 +02:00