Commit Graph

302 Commits

Author SHA1 Message Date
a70ca89fa0 fix(plan): tâche 8 — nginx ne doit pas écouter sur :80 (boucle sur lui-même)
Revue pré-vol : la config nginx faisait écouter nginx sur :80 tout en
proxifiant vers 127.0.0.1:80, où Flask écoute déjà. nginx se serait parlé à
lui-même. nginx ne prend que le 443 ; les appareils non-Z joignent Flask
directement en :80.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-23 18:06:14 +02:00
89025c9313 docs(plan): secubox-picobrew phase 1 — LXC + picobrew_pico + DNS + TLS + panel
Some checks are pending
License Headers / check (push) Waiting to run
Plan d'implémentation de la phase 1 de la spec : 9 tâches TDD, 46 étapes.

Le provisionnement réel (debootstrap, root, réseau) n'étant pas testable en
unitaire, les parties décidables sont isolées derrière des entrypoints cachés
testables : __guard (validateur de SHA), __emit-config (config LXC) et
__emit-nginx (terminaison TLS). Le reste est couvert par une recette de
vérification manuelle explicite plutôt que par de faux tests.

Auto-revue : écart trouvé et corrigé — la spec place le TLS série Z en phase 1
alors que le plan le renvoyait hors périmètre ; or la série Z n'accepte que
HTTPS, un appareil Z aurait donc été inutilisable. Tâche 8 ajoutée.

Le contrôleur de capteurs (992 lignes) est déplacé intact dans lib/stillwatch/
plutôt que supprimé : la phase 2 le reprendra sans archéologie git.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-23 18:03:47 +02:00
58166f02d0 docs(spec): secubox-picobrew — suite brassage/fermentation/distillation en LXC
Réaligne le paquet Debian (squelette hôte : 992 lignes de contrôleur de
fermentation à capteurs, aucun LXC) sur l'intention du portage OpenWrt, SANS
jeter l'existant : le contrôleur déménage dans le LXC et devient sbx-stillwatch,
étendu à la distillation.

Décisions : LXC Debian natif (pas Alpine, pas Docker) ; upstream picobrew_pico
(Pico S/C/Pro + Z + Zymatic) plutôt que le fork CyberMind limité au Zymatic ;
clone GitHub latest puis SHA figé, update explicite refusé pendant une session
(un changement upstream en plein brassage est inacceptable) ; drop-in DNS
picobrew.com actif par défaut, le cloud constructeur étant éteint depuis 2020 ;
héberger l'éprouvé (CraftBeerPi 4) et n'écrire que le manquant.

Limite de conception posée : stillwatch surveille et guide la distillation mais
n'actionne pas d'alambic — les coupes têtes/coeur/queues sont matérialisées et
tracées, la décision reste humaine. cuts.py est une fonction pure, testable sans
matériel.

Livraison en 3 phases utilisables seules, avec porte de réévaluation YAGNI sur
CraftBeerPi en phase 3. LXC alloué en 10.100.0.140 (motif appliances 1x0).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-23 16:55:11 +02:00
fc8e0f9dc2 docs(spec): détection des pixels de suivi (web + email) — design validé
Neutralisation des pixels traceurs dans les emails entrants, en deux couches :
réécriture à la réception (data: URI 1x1 inline, zéro requête émise) avec
archivage du brut DKIM-intact, plus le filet MITM existant à l'ouverture.

Décisions actées : heuristique conservatrice (invisible OU domaine/chemin
connu), aucune réconciliation des compteurs entre couches, intégrité du
courrier non négociable (toute exception => remise de l'original intact).

Deux trous comblés en auto-revue : emplacement/déploiement du code (LXC mail,
pas l'hôte) et synchronisation hôte->LXC de learned-trackers.txt, sans laquelle
le partage de la liste entre couches était illusoire.

Signale au passage une anomalie hors périmètre : rspamd tourne mais n'est
câblé dans aucun milter Postfix (smtpd_milters vide) => ne filtre rien.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-23 13:14:11 +02:00
dc145db9c3 fix(meshtastic): final review — panel webui→ctl wiring (NNP+sudoers user) + mosquitto Recommends + minors (ref #897)
Reconcile the three pieces the webui→ctl delegation needs to actually work:
NoNewPrivileges=yes on the daemon unit neutralized sudo, and sudoers.d
granted to the wrong user (secubox instead of secubox-meshtastic, the
daemon's actual User=). Verified _ctl_cb's systemd-run argv matches the
sudoers command lines for set-mode/set-grid token-for-token.

Also demote mosquitto from Depends to Recommends (module never installs/
runs it; README already documents opt-in setup) and add sudo to Depends
for the ctl delegation. Minor cleanups: unused `field` import in model.py,
dead gridpolicy re-export in daemon.py, and a malformed broker port
(e.g. host:abc) no longer crashes Bridge.start() at daemon startup.

Deferred: append a note to the multigrid design spec about the
gridpolicy/bridge default broker-port mismatch (8883 vs 1883) and the
nft hostname-resolved-once caveat for future on-grid containment work.
2026-07-22 09:02:55 +02:00
4cf0178887 docs(meshtastic): implementation plan — 13 tasks, mock-driven (ref #897)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-22 06:19:00 +02:00
4b986f5c23 docs(meshtastic): spec — multi-grid LoRa node + passive listener (ref #897)
secubox-meshtastic: USB Meshtastic node, native-host daemon, three composable
grids (off-grid RF / private shared-grid over MirrorNet / opt-in public MQTT,
host-side bridged) + optional passive CLIENT_MUTE listener feeding the SOC.
Hardware procurement in #897.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-22 05:19:48 +02:00
356f8293a8 fix(profiles): default lifecycle is always-on, not eager (ref #896)
On a 184-module fleet, DEFAULT_LIFECYCLE="eager" made every module with
no manifest opinion idle-sleep-eligible, including core services (admin,
gitea, nextcloud) that never opted into scale-to-zero. Flip the default
to always-on: sleep is now a strict opt-in via lifecycle="eager" or
"on-demand" declared explicitly in the manifest. scan()-derived
manifests already relied on this same default (no code change needed
there), so they inherit the safer behavior too.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-20 18:11:24 +02:00
9d4da5bf67 docs(scale-to-zero): pivot to sbxwaf-side wake trigger (supersede nginx @waker); +waf-sync +sbxwaf Go tasks (ref #896)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-20 13:05:45 +02:00
afc549ba14 docs(profiles): implementation plan — scale-to-zero public services (12 tasks, ref #896)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-20 11:43:27 +02:00
a2998c1a23 docs(profiles): spec — scale-to-zero for public services (sleep/wake + lifecycle policy)
Design brainstormed 2026-07-20: sleep=STOP(free RAM)+splash-on-wake; per-module
lifecycle (always-on|eager|on-demand|manual) + wake_class; secubox-waker
activator (nginx->waker->wakectl+splash+lock); secubox-sleeper hybrid idle
(front last-request+conns + optional module /idle hint); reuses the 0.7.0
observed-state actuator. Single spec covering wake-on-access + auto-sleep.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-20 11:43:27 +02:00
649a544669 docs(profiles): implementation plan — actuation robustness, 7 tasks (ref #893)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-20 03:59:49 +02:00
be901fa7b3 docs(profiles): spec — actuation robustness, observed-state arbiter (ref #893)
The lite mass-apply test spuriously aborted because the actuator judged a
STOP/START by the command return code, not the observed state (metrics'
90s TimeoutStopSec > _run's 15s timeout -> rc=None -> spurious rollback).

Design: non-blocking commands + wait_state as sole arbiter (LXC via
lxc-info as root), wait_timeout derived per-module from systemd, _run
distinguishes TimeoutExpired from OSError, /apply reverts active on
rolled_back.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-20 03:47:58 +02:00
26e3d9ab7f docs(plan): profiles API/panel apply+rollback (webui→ctl) — 4 tasks
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 09:25:56 +02:00
95eaf2e4a5 docs(profiles): spec — API/panel apply+rollback via webui→ctl (NNP=false + sudoers)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 09:24:13 +02:00
968c3325b9 docs: Phase 2 (Wants=secubox-core) — WIP + HISTORY + module guideline rationale
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 09:10:08 +02:00
201f879159 docs(plan): profiles Phase 3a apply actuator — 4 tasks TDD
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 07:24:36 +02:00
48649fa413 docs(profiles): spec Phase 3a — apply actuator (validated on lyrion)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 07:16:17 +02:00
30931bc534 docs: guideline — webui delegates privileged ops to a confined, audited ctl
The webui/API runs unprivileged (secubox); operations touching root-owned config
or driving systemd/LXC/apps MUST delegate to the root helper secubox-<module>ctl
via scoped exact-command sudoers (shipped by the package), which acts as root and
audits each change. Documented in MODULE-COMPLIANCE (authoritative), MODULE-GUIDELINES
§7 (CTL), WEBUI-PANEL-GUIDELINES, and wiki/Architecture. Ref: secubox-cvectl fix.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 07:01:14 +02:00
fb78d50040 docs(plan): sbxwaf static-asset detect/escalate — 2 tasks TDD
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 05:43:53 +02:00
a40138d068 docs(sbxwaf): spec — detect/escalate fingerprinting on static-asset paths (Option B)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-19 05:42:01 +02:00
4f78632d87 docs(plan): WAF generator cross-category dedup — 2 tasks TDD
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-18 11:57:25 +02:00
9581e0edd6 docs(plan): profiles tiers + installer export — 3 tasks TDD
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-18 11:07:29 +02:00
7803235ec7 docs(profiles): spec for functional tiers + installer export
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-18 11:04:08 +02:00
9325376034 docs(plan): générateur WAF product-absent — 6 tâches TDD
Some checks are pending
License Headers / check (push) Waiting to run
3 modules purs (extract, presence, inventory) + emit additif/atomique + CLI
dry-run + panel. Les 2 barrières (allowlist appliance ET union dpkg∪vhosts∪
modules) avec fail-safe présent. Sortie toujours detect (jamais armée), preuve
par mutation à chaque tâche. Ajoute python3-yaml. 28 tests.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-18 05:30:01 +02:00
ed9382b30b docs(spec): générateur de règles WAF "produit absent"
Fiche les scanners sondant des exploits de produits que la box ne fait PAS
tourner (F5/PAN-OS/Ivanti): 0 faux positif car le produit est absent. Principe:
honeypot=DPI, rendre le bruit pertinent. Vit dans cve-triage (a déjà l'inventaire
dpkg + KEV). Source = sous-ensemble Nuclei vendored (MIT, pas de git-pull
runtime). Sortie = catégorie product_absent_probes en mode DETECT, additive/
idempotente, jamais les 17 catégories existantes, jamais armée (promotion
escalate = humain).

Oracle de présence = DEUX barrières: (1) allowlist de familles d'appliances
qu'une box Debian n'est jamais; (2) absent de dpkg ∪ vhosts routés ∪ modules —
crucial car nextcloud/gitea sont en LXC, absents du dpkg hôte mais routés
(vérifié gk2). Règle de sûreté: dans le doute, PRÉSENT (rater une sonde est
sans conséquence; bannir un vrai user ne l'est pas). S'appuie sur detect
(PR#872) + escalate (PR#873) livrés.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-18 05:13:23 +02:00
3549e2f743 docs(plan): sbxwaf mode escalate — 3 tâches TDD
Some checks are pending
License Headers / check (push) Waiting to run
Task 1: reconnaître modeEscalate (fail-closed préservé). Task 2: champ
escalateBan (compteur SÉPARÉ, défauts 24h/3), branche self-contained qui observe
(action=detect, passe) sous le seuil et banne au seuil via crowdsec+writeBan —
gatée sur escalateBan, JAMAIS s.ban, sinon les signaux se mélangent. nil →
comme detect. Task 3: déploiement + preuve live (observation; le ban au seuil
est prouvé déterministe­ment par les tests unitaires).

Un seul site d'appel de Match. Mutation obligatoire par tâche.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 18:07:05 +02:00
6d0e18eab9 docs(spec): sbxwaf mode escalate — observer puis bannir un scanner récidiviste
Moitié "mécanisme" du besoin (l'autre moitié = générateur Nuclei, spec B).
escalate = detect jusqu'à N sondes, puis block+cscli. Réutilise entièrement
Ban.Record + CscliReporter + les chemins detect/block livrés (PR #872) — la
seule pièce neuve est une 2e instance Ban avec fenêtre LONGUE (défaut 24h/3),
car un scanner lent échappe à la fenêtre 5min du mode block.

Clarification (retour user): escalate n'affaiblit PAS l'autoban. block reste le
défaut et banne les agressions immédiatement; les 17 catégories restent en
block. escalate AJOUTE l'autoban de l'adversaire patient que le ban rapide rate.

Deux limites posées franchement: scanner distribué (1 IP/sonde) n'atteint jamais
N; le ban escalate = drop nft de tout le trafic → les règles générées (spec B)
naissent en detect pur et sont promues à la main après vérif 0 FP.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 18:02:34 +02:00
3de66fb05c docs(spec): CVE — distinguer les deux buts aux filtres opposés
Correction de l'utilisateur: un CVE sert à deux choses aux filtres INVERSES.
PROTÉGER (ce spec): KEV ∧ paquet installé ∧ exposé — suis-je vulnérable.
FICHER (générateur WAF): le motif vise un produit ABSENT ⟹ 0 faux positif —
qui me sonde. Personne n'a de raison légitime de demander /mgmt/tm/util/bash
sur une box sans F5; c'est un signal pur qu'on ne bloque pas (mode detect) mais
qu'on fiche. Ce spec ne traite QUE PROTÉGER; le filtre fingerprinting appartient
au générateur, qui s'appuie sur le mode detect livré (PR #872).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 17:37:03 +02:00
a1b9add575 docs(plan): sbxwaf mode detect — 3 tâches TDD
Some checks are pending
License Headers / check (push) Waiting to run
Task 1: parser `mode` par catégorie (défaut block, fail-closed sur valeur
inconnue). Task 2: Match remonte le mode; le chemin detect journalise
action=detect et laisse passer, sans jamais bannir. Task 3: déploiement gk2 +
preuve sur trafic réel (bascule cve_2024 en detect à chaud, puis restauration).

Un seul site d'appel de Match (main.go:376) — rayon de souffle contenu.
Chaque tâche exige une preuve par mutation: casser le comportement, constater
l'échec du test, restaurer.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 15:42:20 +02:00
572d6eece9 docs(spec): sbxwaf mode detect — essayer une règle avant de l'armer
Né de la question "peut-on dériver des règles WAF depuis les CVE ?", dont
l'exploration a montré que ce mode en est le prérequis bloquant. Mais il vaut
seul, et il est livré seul.

Aujourd'hui rules.go n'a que deux états: catégorie sautée, ou évaluée ET
bloquante. Toute nouvelle règle est donc un pari armé en production devant
Nextcloud/PeerTube/Gitea — et une régression WAF a déjà servi du JS vide en
silence (YaCy blanc).

Champ `mode: detect|block` par catégorie. Absent ⇒ block: les 17 catégories
existantes ne changent pas. Un défaut detect transformerait le WAF en
observateur muet — une panne de sécurité silencieuse.

Un match en detect est compté et journalisé (action: "detect") mais laisse
passer et ne déclenche AUCUN ban. Invariant: une catégorie en detect doit être
aussi inoffensive qu'un enabled:false, à la journalisation près.

Donne enfin l'instrument pour prouver le poids mort déjà diagnostiqué: 39/149
patterns voip/xmpp inutiles en HTTP, et une catégorie cve_2024 visant PAN-OS,
Ivanti et F5 — trois produits absents de cette box Debian.

Générateur CVE→règles explicitement hors périmètre (chantier suivant): source
Nuclei (MIT vérifié; les CVE ne contiennent aucun exploit — vérifié sur le KEV),
filtre KEV ∧ installé ∧ exposé, expiration à la correction.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 15:36:02 +02:00
a33e3c0092 docs(spec): alertes CVE récurrentes et réelles
Le module secubox-cve-triage existe déjà et fait plus que prévu (dpkg, NVD,
CVSS, EPSS réel, flux KEV CISA, triage, panel). Il lui manque une gâchette
(aucun .timer), une voix (zéro notify) et un filtre anti-bruit. Ce spec livre
l'appelant, pas un module.

Filtre: KEV ∧ installé ∧ (exposé public ∨ rançongiciel) — un ET, pas un OU.
L'exposition vient de l'inventaire secubox-profiles livré ce jour (16 modules
publics sur 187). Anti-bruit: alerte sur le delta, ré-alerte sur escalade,
acquittement, digest, plancher anti-tempête.

CrowdSec CTI écarté: clé obligatoire, 40 requêtes/mois en gratuit, dépendance
cloud pour une donnée que le KEV CISA fournit librement (1647 CVE, sans clé).

XMPP chiffré honnêtement: le module jabber ne sait pas envoyer: c'est un
gestionnaire Prosody. Le sink exige slixmpp (présent dans bookworm), un compte
bot, un secret et un client — une tâche isolée, pas un branchement.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 15:17:50 +02:00
722b5c1b03 docs(plan): profils & modules — Phase 1 (lecture seule)
6 tâches TDD : manifestes, résolution d'état, sondes, diff ordonné, profiler
scan, CLI + paquet. 55 tests.

Phase 1 ne peut rien allumer ni éteindre — un test verrouille l'absence de la
commande apply, et observe.py n'exécute que des commandes de lecture.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 11:36:40 +02:00
26eea724e1 docs(spec): conception du système de profils & activation de modules
Bascule de contexte au quotidien (média / sécurité / démo) : profils exhaustifs
+ pins persistants, toggle individuel, taxonomie des modules, dépendances
minimisées, priorités présentées.

Architecture retenue : manifestes plats + moteur de réconciliation. Les .target
systemd sont écartées : LXC, routes WAF et menus ne sont pas des units, et une
target n'exprime pas 'éteint'.

Le design est contraint par le réel mesuré sur gk2 : 187 units (118 actives),
load 5.4 sur 4 cœurs, ~2 Go libres, 24 LXC, 134 menu.d, et 80 units en
Requires=secubox-core (un oneshot mkdir) — d'où application séquentielle,
extinction avant allumage, et conversion Requires -> Wants en prérequis.

Noyau protégé non négociable : un profil ne doit pas pouvoir éteindre ce qui
permet de le rallumer.

Métriques mesh explicitement hors périmètre (spec séparé).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-17 11:29:04 +02:00
CyberMind
bb3c56c2cd
feat(#817): Device Guardian consolidation — merge 5 device modules into secubox-nac (Project A) (#818)
Some checks are pending
License Headers / check (push) Waiting to run
* docs(device-guardian): Project A consolidation design — merge 5 device modules into nac (ref #817)

* docs(device-guardian): Project A implementation plan — 9 tasks (ref #817)

* feat(nac): SQLite device store + idempotent legacy migration (ref #817)

Add packages/secubox-nac/api/store.py — pure-stdlib sqlite3 module with
canon_mac(), DeviceStore (WAL, best-value upsert, list/get/count,
history), and migrate_legacy() for idempotent, per-source best-effort
import of mac-guard/device-intel/iot-guard legacy shapes. Reserved
Project-B columns (plane/provenance/geo_cc/geo_asn) created but unused.

Task 1 of the Device Guardian consolidation plan (docs/superpowers/plans/
2026-07-05-device-guardian-consolidation.md).

* fix(nac): store — preserve first_seen, skip corrupt legacy sources, no stray db, SPDX (ref #817)

* feat(nac): unified dnsmasq+ISC+ARP discovery merge (ref #817)

* fix(nac): discovery — latest ISC lease wins the IP on renewal (ref #817)

The merge loop only updated ip/source on a strict rank increase
(incoming_rank > existing_rank), so equal-rank sightings for the same
MAC never touched ip/source. Since dnsmasq -> isc -> arp is processed
in non-increasing rank order, multiple ISC dhcpd.leases blocks for one
MAC (a fresh block per renewal) are equal-rank ties, and the first
(oldest) block's ip was frozen instead of the latest one winning, as
the original secubox-device-intel._get_dhcp_leases did.

Change the condition to incoming_rank >= existing_rank so the latest
same-or-higher-rank sighting updates source, and overwrites ip when
the incoming ip is non-empty. Hostname rule and the lower-rank branch
are unchanged, so a later ARP sighting still cannot clobber a
higher-rank ip/hostname/source.

Add test_isc_latest_lease_wins: two ISC lease blocks for one MAC with
ip/hostname renewal, asserting the latest block wins. Confirmed this
test fails against the pre-fix >-only logic.

* feat(nac): absorbed enrichers — OUI vendor + device-type/risk + OpenWrt fingerprint (ref #817)

* fix(nac): enrich — is_router strictly vendor-based; cover classify/oui edge cases (ref #817)

* feat(nac): background collector + double-cache; handlers def, no inline scan (ref #817)

Task 4 of the Device Guardian consolidation: a Collector background
loop now owns all discover -> enrich -> upsert work (replacing
_monitor_clients), publishing an in-memory snapshot(). The status,
clients, and client/{mac} handlers become plain def reading the SQLite
store / collector cache instead of calling _discover_clients() inline
on every request -- the #808 aggregator SPOF.

* fix(nac): finish #808 — def-convert alerts/list_quarantine/summary; first_seen + recent_events (ref #817)

alerts/list_quarantine were still `async def` calling blocking
_discover_clients() inline on the shared aggregator loop; summary
awaited alerts on top of that (double loop-block). Converted all three
to plain `def` reading the SQLite store/collector snapshot instead,
completing the #808 SPOF fix started for status/clients/client.

Also: collector.cycle_once() now stamps first_seen on newly discovered
devices (store.upsert keeps the earliest via COALESCE), and get_client's
recent_events merges the SQLite device_history (client_joined) with the
JSON event log so a device's join event shows up again.

* feat(nac): allow/deny actions + def-convert action handlers (finish #808) (ref #817)

Task 5: fold mac-guard's allow/deny into nac's zones/nft. New
POST /allow/{mac} and /deny/{mac} set allow_state in the SQLite store
and mirror it into nft's existing inet secubox_nac blocked/lan_allowed
sets via _nft_add_element/_nft_delete_element (mac-guard's separate
inet secubox_mac_guard table stays retired; its element migration is
Task 9).

Carried from Task 4 review: zones, add_to_zone, remove_from_zone,
approve_client, ban_client, update_client, quarantine_client and
unquarantine were still async def wrapping blocking subprocess/nft
calls on the shared aggregator loop (#808). Converted to plain def so
FastAPI threadpools them off the loop; add_to_zone/ban_client's webhook
notify now fires via a new _fire_webhook_sync helper
(asyncio.run_coroutine_threadsafe against the loop captured at
startup) since a plain def can no longer await _notify_webhooks
directly.

Tests: tests/test_actions.py mocks nft via monkeypatched
_nft_add_element/_nft_delete_element/_nft_list_set (+ subprocess.run,
since ban_client/unban_client build raw nft argv) into an in-memory
set-membership dict; covers deny/allow/allow-then-deny, zones,
add_to_zone+approve_client, and ban_client+unban_client.

* feat(nac): absorbed endpoints — vendors/scan/probe/mdns/groups/export (ref #817)

* fix(nac): /scan enriches + validates subnet; /probe IP + CSV injection guards (ref #817)

* feat(nac): retire mac-guard/device-intel/iot-guard via 308 redirects; auth-gate network-anomaly (ref #817)

secubox-mac-guard, secubox-device-intel and secubox-iot-guard are
superseded by secubox-nac's consolidated Device Guardian. Their
api/main.py are reduced to thin shims: every route 308-redirects to
its secubox-nac equivalent (/devices->/clients, /device/{mac}->
/client/{mac}, /whitelist|/allow->/allow, /blacklist|/deny->/deny,
/scan, /vendors, /groups, /export/* pass through unchanged, and a
catch-all forwards anything else), with an X-SecuBox-Deprecated
header. /health stays a plain 200 so liveness probes never 308-loop.
Package removal is a separate, gated follow-up.

secubox-network-anomaly stays (separate traffic-anomaly engine) but
its /status endpoint was unauthenticated while every sibling route
already required a JWT; it now matches.

* feat(nac): webui — vendor/type/risk columns, groups view, export; drop retired webuis (ref #817)

- clients.js: render enriched /clients fields — oui_vendor, device_type,
  risk_level (color-coded badge)/risk_score, and OpenWrt/SecuBox/Router
  fingerprint badges. All values are attacker-influenceable (DHCP
  hostname/vendor) and are rendered exclusively via E()/textContent,
  never innerHTML. Null/missing device_type/vendor/risk render as
  "unknown"/"—", never "null". Adds an Export CSV button (direct link to
  GET /export/csv — auth via the existing SSO session cookie, same as
  every other same-origin nav link in this app; no new auth scheme).
- groups.js (new): list/create/delete device groups and assign a device
  to a group, hitting GET/POST/DELETE /groups* — mirrors zones.js's
  structure and auth (sbxFetch).
- nav.js: register the new "Groupes" tab alongside Zones.
- Remove packages/secubox-{mac-guard,device-intel,iot-guard}/www — their
  functionality is now consolidated into nac; secubox-network-anomaly/www
  is untouched (stays standalone per plan).

Note for Task 9: device-intel/iot-guard's debian/rules unconditionally
`cp -r www/<name>/.` — now broken since www/ is gone; mac-guard's rule is
already guarded (`[ -d www ] && ... || true`). Needs a debian/rules fix
when Task 9 retires/redirects these packages.

* chore(nac): packaging — ieee-data dep, nft migration, retirement redirects, changelog 3.0.0; fix retired debian/rules www (ref #817)

* fix(nac): whole-branch — lazy init for in-aggregator mount, collector off-loop, sqlite lock, batched zone lookup, sentinel-preserve (ref #817)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(nac): risk_score split — known router low, unknown router high (rogue-AP signal) (ref #817)

Split the router risk axis out of the generic device-type/open-ports
scoring: a router/gateway already known (allow-listed or already in the
store) is trusted infrastructure -> low risk; a newly-seen/unknown
router is a rogue-AP signal -> high risk, regardless of open ports.
Removes the old flat -10 "trusted router" discount. Non-router devices
keep the existing scoring unchanged.

risk_score() gains an is_known=False parameter (backward compatible).
collector.cycle_once() and main.scan() now compute is_known from the
prior store row (existing row or allow_state == "allow") before
upserting, reusing the row they already fetch for is_new/enrichment.

* feat(nac): mesh-sync (p2p peers as devices) + fingerprinted-router classification for rogue-AP risk (ref #817)

Part A: absorb iot-guard's missed "mesh-sync" capability. GET /mesh/peers
and POST /mesh/sync now live in nac, reading live P2P peers over
/run/secubox/p2p.sock (HTTP-over-UDS GET /peers, fail-safe -> [] on any
error/timeout/absent socket) and upserting one sb:-prefixed synthetic
device per peer (iot-guard's exact md5-hash MAC scheme), device_type
"mesh_node", source "mesh". iot-guard's retired redirect-shim catch-all
already forwards /mesh/* here, so no shim change needed.

Part B: classify_device_type's "router" keyword list is much narrower
than ROUTER_VENDORS (misses tp-link/asus/linksys/d-link/gl.inet/xiaomi/
huawei), so a router-vendor MAC with a generic hostname fingerprinted
is_router=True still classified as device_type="unknown" and skipped
risk scoring entirely (the omit-unknown guard) -- the known-router-vs-
rogue-AP HIGH signal never fired for the most common vendor case. Both
enrichment call sites (Collector.cycle_once and /scan) now reclassify
device_type="unknown"+is_router to "router" before scoring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(nac): postinst ensures secubox_nac table + zone sets (incl lxc_zone)

Nothing shipped created nac's inet secubox_nac table or its zone sets — they
were assumed to pre-exist (they don't: the live board had no such table), so
operator 'move to zone' failed for every zone and the mac-guard element
migration silently no-op'd. postinst now creates the table + all ZONES sets
(lan_allowed, lxc_zone, iot_zone, guest_zone, quarantine_zone) + blocked,
idempotently and best-effort, before the migration. Makes operator moves to
the LXC Containers zone work on a fresh install. nft commands validated on
gk2 (create/idempotent re-add/add-element/list), no live drift left.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>

---------

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:58:01 +02:00
b800b624d7 docs: track metalogue OSINT suite (#845) — wiki + WIP + HISTORY + TODO
Some checks are pending
License Headers / check (push) Waiting to run
- wiki/Metalogue.md (use cases: Maigret identity collector, stylized PDF report,
  SpiderFoot automation, OpenCTI deferred) + sidebar entry.
- WIP: metalogue section + follow-ups (OpenCTI P3, ip_forward drift, aggregator
  self-register, SpiderFoot re-enable on fresh image).
- HISTORY: dated 2026-07-12 entry (both modules, install root-causes, PDF report).
- TODO: metalogue follow-ups section.
- Adds the implementation plan doc.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-13 05:44:28 +02:00
2f0106c9b9 docs: implementation plan for MetaBlogizer Publisher Wizard (11 tasks, TDD)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-11 12:44:58 +02:00
9ea93ae55a docs: design spec for MetaBlogizer Publisher Wizard
Guided publish wizard in secubox-metablogizer: upload zip/html -> gitea
versioning -> WAF routing (sbxwaf host file, via a new secubox-publishctl root
helper, fixing the mitmproxy-LXC route-target drift that 421'd zem) -> cert
(wildcard for *.gk2, certbot for custom) -> portable .sbxsite backup (git
bundle + manifest). Also retires secubox-publish's broken unprivileged
/etc/haproxy writes (delegates to the wizard) and the droplet.toml ownership bug.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-11 12:36:33 +02:00
5f654e2c2f docs(tor-plan): Task 7 webui must warn about IPv6 leak on routed VPN clients (Task 3 limitation) 2026-07-09 08:44:01 +02:00
691a5cbad1 docs(tor-plan): Task 9 must-fix — package secubox-exposure-tor-reconcile sbin+unit (Task 5 persist-on-boot) 2026-07-09 08:35:43 +02:00
cf548517b7 docs(tor-plan): Task 8 also adds reapply-when-armed (edits apply without disarm/arm cycle) 2026-07-09 08:24:58 +02:00
4ccfe4a331 docs(tor-plan): add Task 8 obfs4 bridges; fold bridge API/webui into Tasks 4/7; bump live-e2e to Task 9 2026-07-09 07:54:51 +02:00
e983520604 docs(tor-spec): add feature 6 — obfs4 bridges (Niveau-1 anti-censorship, entry-side) 2026-07-09 07:53:53 +02:00
e9f1c0706d docs(tor-plan): Tor UI lives in /toolbox/#tor tab; name toolbox api module (secubox_toolbox/api.py) 2026-07-09 07:41:50 +02:00
f2ed362015 docs: Tor enhancement Phase-1 implementation plan (8 tasks, SDD) 2026-07-09 07:34:27 +02:00
1d298b622b docs: Tor enhancement design (country-exit, Tor-VPN per-client, emancipate .onion, persist, auto .onion DNS) 2026-07-09 07:28:46 +02:00
fe10cf5dcd docs(openclaw): tighten target regex in plan (reject leading-dash flag-injection) 2026-07-09 05:55:37 +02:00
32a2907201 docs: openclaw LXC scanner implementation plan (8 tasks, SDD) 2026-07-09 05:16:55 +02:00
eb15e8d2ac docs: openclaw spec — correct LXC path (/data/lxc) + free IP (.41, .30 taken by matrix) 2026-07-09 05:11:56 +02:00
a670cda9e6 docs: openclaw dedicated-LXC OSINT+scanner design 2026-07-09 05:08:47 +02:00