Commit Graph

3084 Commits

Author SHA1 Message Date
a5e5bd4dbe feat(release): package scaffold + reprepro-copy actuator (arm64-guard, op-gated argv) (ref release-rings)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-26 07:42:25 +02:00
0b1cff48a1 feat(annuaire): release_publish/promote/demote/ring_assign grant-gated verbs (ref release-rings)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-26 07:36:50 +02:00
5cb2b22d26 fix(annuaire): box_ring fail-closed on unverified author + regression test (ref release-rings)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-26 07:33:26 +02:00
498cbf663b feat(annuaire): releases.py — pure resolver (ring state, box ring, sovereignty) (ref release-rings)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-26 07:28:04 +02:00
5ea74bee75 feat(annuaire): release ops + Evolution/RingState/RingAssign models (ref release-rings)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-26 07:23:09 +02:00
56e592def2 Merge remote-tracking branch 'origin/master' into feat/release-rings 2026-07-26 07:07:12 +02:00
CyberMind
f6bc3d5115
Merge pull request #907 from CyberMind-FR/fix/aggregator-watchdog-grace-and-wpad-port
Some checks failed
License Headers / check (push) Has been cancelled
fix: aggregator-watchdog startup grace + wpad-vhost :9080 (board-stability, backport gk2)
2026-07-25 18:13:30 +02:00
github-actions[bot]
3e6e01b5b8 ci: Update metrics [skip ci] 2026-07-25 15:04:05 +00:00
c0c65fcb51 fix: aggregator-watchdog startup grace + wpad-vhost listen :9080 (backport live gk2 fixes)
Two board-stability fixes verified live on gk2 during the assist deploy:
- secubox-aggregator-watchdog.sh: 480s startup grace so the auto-heal watchdog
  stops restart-looping the aggregator during its slow (~5min) in-process cold
  mount (RuntimeDirectoryPreserve stale socket → probe fails → kill mid-start).
- secubox-proxypac wpad-vhost: listen :9080 not :80 (HAProxy owns :80; the bind
  conflict aborted every nginx reload, silently freezing config board-wide).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 16:20:22 +02:00
CyberMind
6b8d652a09
Merge pull request #906 from CyberMind-FR/feat/assist-request
Some checks are pending
License Headers / check (push) Waiting to run
feat(assist): support/assistance request — live consent-gated help sessions (sous-projet 2/3)
2026-07-25 16:18:47 +02:00
851a18fa8b docs(release): design spec — center-driven progressive release rings
Livraison progressive d'artifacts (deb/image/www) par rings draft→internal→
published sur apt.secubox.in (reprepro distributions). Promotion + assignation
de ring par-box pilotées par un centre via grant capability=release (Centres &
Grants), honorées souverainement (issued_by==self_did). Ring published = profil
base auto. Actuateur repo reprepro-copy op-gated + actuateur box 4R fail-safe
(drop-in sources, apt échoué ne brique pas). apt GPG signé bout-en-bout.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 16:16:56 +02:00
6dfc5a5ff0 fix(annuaire): standing-mode assist request now requires an active assist grant
can_open() accepted mode=="standing" unconditionally, letting a center open a
session on the strength of the request alone. Per spec, standing mode is a
persistent authorization and must be backed by a real, self-issued delegation:
can_open() now additionally requires an active, SELF-issued (issued_by ==
self_did) capability="assist" Grant naming that request's center_did, via
grants.active_grants() — the sovereignty filter is already enforced there.
Missing grant -> (False, "no-standing-grant"). Per-incident mode is unchanged:
the box-authored request remains its own authority.

ref sous-projet 2

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 14:40:35 +02:00
d08820d675 fix(assist): WS daemon self_did/session security + scoped-sudo NNP + console de-advertised (v0.1.1)
Consolidated fix wave from the whole-branch review of feat/assist-request:

- daemon.py: self_did is resolved from a PUBLIC source (SECUBOX_SELF_DID env,
  else world-readable /etc/secubox/annuaire/node.did), cached once at module
  load instead of re-derived per connection. The WS daemon (User=secubox-assist,
  not in group secubox) previously opened the sovereign private key on every
  connection and got PermissionError, silently authorizing zero sessions.
  postinst now derives node.did once from the key as root (guarded: a missing
  key does not fail the install), writing only the file world-readable —
  the shared /etc/secubox/annuaire parent is left untouched.
- daemon.py: the mid-session recheck now requires active_session().session_id
  to match the session this socket originally authorized, not merely "some
  session is active" — closing session s1 and opening s2 no longer lets a
  socket authorized under s1 keep running catalog actions under s2's window.
- secubox-assist.service: NoNewPrivileges=false — catalog.resolve() dispatches
  service/config actions as `sudo -n secubox-assistctl ...` run by this unit;
  NNP=true silently blocked sudo's setuid transition for every one of them.
  secubox-assist-api.service is unaffected (it never sudoes).
- www/assist/index.html + changelog: console-grant/revoke buttons are disabled
  and relabelled "(à venir)" — the control-plane (ops, guard, keystroke audit)
  is real and tested, but no pty-over-WS channel consumes it yet; ship that
  honestly instead of a dead-looking-live button.

ref sous-projet 2

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 14:40:30 +02:00
eb796a006c build(annuaire): ship assist control-plane, 0.6.0 (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 14:20:58 +02:00
f4cf62ea23 fix(assist): board-wide outage risk in nft drop-in, unshippable AppArmor profile, wrong sudoers principal, blocking WS handler
Four review findings on the packaging added in 857a1fe9:

- nft/secubox-assist.nft (was zz-secubox-assist.conf): the standalone table
  used `policy drop` on the input hook — any base chain's terminal drop at
  that hook blackholes the whole board (SSH, webui) the instant postinst
  loads it, and /etc/secubox/nft.d/ is never boot-included so it also
  vanished on reboot. Now `policy accept` (self-contained, own chains,
  mirrors secubox-toolbox's nftables.d/secubox-toolbox-wg.nft) and shipped
  to /etc/nftables.d/, which /etc/nftables.conf actually globs in at boot
  (see secubox-vortex-firewall's postinst).
- apparmor/secubox-assist: profiled the bare /usr/bin/python3 interpreter,
  which would confine every python invocation on the box, including this
  package's own units and every other secubox module. No module in the repo
  confines a python daemon this way (secubox-macro keys its profile to a
  dedicated launcher script, secubox-macroctl, not the interpreter) and
  assist has no such launcher yet, so the profile is removed; confinement is
  deferred until a dedicated launcher exists for the WS daemon.
- sudoers/secubox-assist: granted `secubox`, but catalog.py's privileged
  argv runs via wsserver.dispatch from assist.daemon.handler, which is
  invoked under secubox-assist.service's own user (secubox-assist), not the
  webui/API's secubox user — every service.*/config.* action was silently
  denied by sudo -n. Principal corrected, still scoped to
  /usr/sbin/secubox-assistctl only.
- assist/daemon.py: handler() called wsserver.dispatch (sync subprocess.run,
  up to 60s, plus diag.collect's chained sync subprocess calls) directly on
  the single asyncio loop, stalling every other session. Offloaded via
  asyncio.to_thread, along with the per-message journal re-reads.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 14:16:50 +02:00
857a1fe959 feat(assist): packaging — WS daemon, units (non-root), scoped sudoers, AppArmor, mesh-only nft, postinst (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:56:42 +02:00
e9d0ff5136 fix(assist): menu entry uses name/category schema, not label/group
Sidebar renderer reads item.name and hub API reads item.category —
the assist menu shipped with label/group so the sidebar showed
"undefined" and misfiled it under Other. Matches 570-centers.json
schema (federation/mesh module bucket). Test now asserts name is a
non-empty string and category is one of the valid CATEGORY_META
buckets.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:49:14 +02:00
11ce6207a1 feat(assist): /assist panel (request + live monitor + kill + console consent), menu, vhost (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:45:11 +02:00
16eae5fa26 feat(assist): /assist API — in-process reads, mutations delegate to assistctl, JWT-gated (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:40:58 +02:00
c185c56acc fix(assist): cmd_request wraps assist_request in try/except to avoid traceback leak
cmd_request called verbs.assist_request with no try/except, so a bad
--mode or --scope let pydantic's ValidationError escape as a raw
traceback on stderr instead of the {"error": ...} JSON contract every
other mutating subcommand honors. Wrapped it the same way cmd_open and
cmd_accept already do (ValidationError subclasses ValueError). Added
the same defensive wrap to cmd_close, cmd_console_grant and
cmd_console_revoke for consistency across all mutating subcommands.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:36:45 +02:00
8e7b6b0a32 feat(assist): secubox-assistctl — signed session control CLI (request/open/close/console, DRYRUN) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:29:55 +02:00
93f863b9bc feat(assist): double-consent pty console (non-root, refuse-root, keystroke audit) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:24:30 +02:00
d3b793521c fix(assist): authorize() converts AssistError to fail-closed AuthError
annuaire.assist.active_session() raises AssistError when the journal
holds more than one active session (invariant breach). That exception
propagated uncaught out of authorize(), which would reach the WebSocket
serve loop as an unexpected type instead of a clean, auditable
rejection. Catch it and re-raise as AuthError, preserving the cause.

Also corrects wsserver docstrings that implied a console gate in this
file — console escalation is handled by the separate console module.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:21:51 +02:00
10e9c96c83 feat(assist): WS data-plane core — mesh-only bind (fail-closed), token auth, catalog dispatch+audit (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:17:00 +02:00
f461b0e4fe feat(assist): append-only JSON audit writer (fsync, never truncate) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:12:41 +02:00
b16fcb0b9b fix(assist): redact crypto keys and URI-embedded credentials in diag
Adversarial re-review found two redaction leaks in assist/diag: WireGuard
private keys in journalctl output (interface/env/JSON key styles) and
DSN passwords embedded in connection URIs (user:pass@host) were passed
through unredacted. Extend _SECRET_KV's keyword alternation with
private-key/key and add a dedicated URI-credential rule that masks only
the password segment.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:09:38 +02:00
722e58ceb7 fix(assist): redact underscore-compound secret fields in diag.redact
_SECRET_KV used a leading \b before the keyword group, but \b treats
underscore as a word char, so access_token/refresh_token/client_secret
and other snake_case compound field names were never matched and their
values leaked in the clear. Replace the leading \b with a lookbehind
that also treats '_' as a separator.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:04:10 +02:00
465ee7961c fix(assist): redact() closes Bearer/Basic token and JSON-quoted secret leaks
Bearer/Basic credentials survived redaction because the value-match
stopped at the first space (consuming only the scheme word) and a
bare "Bearer <token>" line with no ":"/"=" separator wasn't matched
at all. JSON-quoted pairs like {"password": "..."} also slipped
through since the keyword's closing quote sat between the key and
the colon. _SECRET_KV now tolerates an optional closing quote after
the keyword, a colon/equals-or-whitespace separator, and an optional
Bearer/Basic prefix before consuming the credential.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 13:00:08 +02:00
CyberMind
aacbb39fb8
Merge pull request #903 from CyberMind-FR/fix/nextcloud-connection-urls
Some checks are pending
License Headers / check (push) Waiting to run
fix(nextcloud): connection URLs use real public domain not localhost:8080
2026-07-25 12:55:01 +02:00
CyberMind
031e2501f6
Add GitHub Actions workflow for static site deployment
This workflow automates the deployment of static content to GitHub Pages upon pushes to the master branch or manually via the Actions tab.
2026-07-25 12:54:45 +02:00
676151d281 feat(assist): redacted read-only diag bundle (never secrets/*.key) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:54:30 +02:00
CyberMind
594a1277e2
Merge pull request #905 from CyberMind-FR/docs/904-secubox-landing-page
Some checks are pending
License Headers / check (push) Waiting to run
docs: publish the SecuBox public landing page
2026-07-25 12:53:14 +02:00
CyberMind
7842abf57d ci: publish landing page with GitHub Pages (ref #904) 2026-07-25 12:51:30 +02:00
CyberMind
f789e2e010 docs: add public SecuBox landing page (ref #904) 2026-07-25 12:51:24 +02:00
73869cc295 feat(assist): bounded action catalog → scoped ctl argv (no arbitrary shell, allow-list) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:49:45 +02:00
29729ff711 feat(assist): package scaffold + single-use session token (hash-only journaling) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:45:58 +02:00
7a791a668f feat(annuaire): assist_* signed verbs (request/accept/open/close/console) (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:41:30 +02:00
9df415824f fix(annuaire): assist sovereignty — reject foreign per-incident requests
pending_requests() ignored self_did entirely, surfacing every not-yet-
accepted ASSIST_REQUEST regardless of author. can_open() only checked
existence/acceptance/no-active-session, letting a per-incident request
issued_by a federated peer be opened locally. Both now require
issued_by == self_did OR mode == "standing" (standing-grant check stays
the caller's job); can_open returns (False, "not-authorized") otherwise.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:37:47 +02:00
e07b3c3648 feat(annuaire): assist.py — session/console resolution, single-session, sovereignty, fail-closed expiry (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:32:40 +02:00
bcb78ecc7e feat(annuaire): assist ops + AssistRequest/AssistSession models (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:29:15 +02:00
1dfeeb052b docs(assist): implementation plan — support/assistance request 14 tâches TDD (ref sous-projet 2)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:22:44 +02:00
github-actions[bot]
7d89067306 ci: Update metrics [skip ci] 2026-07-25 10:12:36 +00:00
7fbc0cdaeb docs(assist): design spec — support/assistance request (sous-projet 2/3)
Session assistée temps réel via control-plane (journal annuaire, ops ASSIST_*
signées + mesh_sync) + data-plane (daemon secubox-assist, WebSocket par-session
bind wg-mesh only). Catalogue borné → ctl scopés + audit ; escalade console sur
double-consentement (pty non-root). Initiation bi-mode (per-incident pull /
standing grant). Invariants CSPN : consentement explicite, mesh-only, token
hashé, audit append-only, expiry fail-closed, session unique. Roadmap à prévoir :
transfert fichiers, partage écran, multi-centres, replay.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 12:12:11 +02:00
4baf6eb1b5 fix(nextcloud): connection URLs use real public domain not localhost:8080
/connections only consulted ssl_domain (unset in secubox.conf [nextcloud],
which carries `domain`) and fell back to http://localhost:8080 — unreachable
from a phone/desktop client. The panel independently hardcoded localhost,
ignoring the web_url the API already resolves.

Add a pure _public_base_url() helper (ssl_domain > public domain > localhost),
reuse it in both /connections and /status, and render status.web_url in the
panel. Regression tests cover the domain-set, ssl-precedence, placeholder,
and bare-host cases.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 11:26:30 +02:00
CyberMind
27b33aa5e6
Merge pull request #902 from CyberMind-FR/feat/centers-grants-remote-config
Some checks are pending
License Headers / check (push) Waiting to run
feat(annuaire): socle Centres & Grants + Remote Config (auto-centre/multi-centres)
2026-07-25 11:09:59 +02:00
8918ef5a03 fix(annuaire): grants honorés SEULEMENT si issued_by==self (souveraineté vs grants fédérés d'un pair) + valide scope centers_effective
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 11:06:34 +02:00
95f6d91017 build(annuaire): package centres/grants (ctl, panel, compose/grants/router) + changelog
Task 10: ship the Centres & Grants substrate built in tasks 1-9.

- debian/rules installs sbin/sbx-centersctl (0755), www/centers/ (panel),
  menu.d/570-centers.json, and a new nginx/centers.conf static route for
  /centers/ (reuses the existing /api/v1/annuaire/ API route, so no new
  backend listener). annuaire/grants.py, config_compose.py and
  config_router.py already ship via the existing `cp -r annuaire/*` install
  of the annuaire/ library.
- debian/control: add python3-tomli-w (annuaire.config_compose composes
  TOML layers with tomli_w.dumps).
- Reconcile the box signing key: sbx-centersctl previously defaulted to its
  own ANNUAIRE_KEY at /etc/secubox/annuaire/box.key, a second key file never
  provisioned anywhere. The box's actual sovereign identity is the node key
  annuairectl already establishes at /etc/secubox/secrets/annuaire/node.key
  (env ANNUAIRE_KEY_PATH), also used by api/main.py's own
  _publish_self_node/_self_did_best_effort. Unified sbx-centersctl and
  api/main.py on ANNUAIRE_KEY_PATH / node.key -- one box, one identity. The
  CLI still never generates a key (annuairectl init remains the sole
  provisioning path).
- No sudoers added: sbx-centersctl runs subprocess-direct (no sudo) under
  the same `secubox` user as secubox-annuaire.service, and only touches
  paths that user already owns (journal, node key, /etc/secubox) -- unlike
  secubox-proxypac's ctl, it never needs root.
- changelog bump 0.4.0 -> 0.5.0 (feature release).
- Full package suite (275 tests) passes; dpkg-buildpackage -us -uc -b
  produces a clean secubox-annuaire_0.5.0-1~bookworm1_all.deb with all new
  files present and no build cruft (__pycache__, debian/secubox-annuaire/)
  leaking into the tree.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 10:50:48 +02:00
08d9d985e8 feat(annuaire): panneau /centers (matrice propriété + diff + délégation événements)
Ajoute le panneau webui /centers : matrice de propriété scope×calque
(baseline/override/local), formulaire d'octroi, liste des centres
accordés, section propositions (pushs non-accordés, acceptation en un
clic) et diff effectif/local par scope. Consomme l'API /centers de la
tâche 8, avec Authorization: Bearer sbx_token sur chaque appel (les
endpoints mutants sont JWT-gated). Aucune donnée d'API n'est jamais
interpolée dans un attribut d'événement inline (leçon proxypac) : les
actions de ligne (révoquer, accepter) passent par data-* + un listener
délégué. Test garde XSS renforcé copié de
secubox-toolbox/tests/test_rlevel_panel.py (détecte la concaténation,
pas seulement ${}).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 10:41:00 +02:00
8a71fc4daa feat(annuaire): API /centers (ownership + grant/revoke + proposals, ctl delegation)
Task 8. GET /centers, /centers/ownership, /centers/proposals, and
/centers/effective/{scope} resolve in-process from the journal
(annuaire.grants.active_grants / annuaire.config_router.route_config) —
public reads, no privileged action. POST /centers/grant, /centers/revoke,
and /centers/proposal/accept never hold the box signing key in-process:
they delegate to sbx-centersctl via a fixed-argv subprocess call
(_centers_ctl, overridable via SBX_CENTERSCTL), JWT-gated.

route_config() gains an `apply` kwarg (default True, existing callers
unaffected) so the read-only proposals/effective endpoints can collect the
same composed candidates and proposals a real route would produce without
ever calling apply_composed — nothing on disk is touched by those GETs.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 10:31:04 +02:00
97356909ef fix(annuaire): valide scope (anti path-traversal) à la frontière FS + modèle + DRYRUN ctl
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-25 10:22:51 +02:00