Commit Graph

3230 Commits

Author SHA1 Message Date
github-actions[bot]
9cbad893ab ci: Update metrics [skip ci] 2026-07-29 09:05:24 +00:00
CyberMind
c3f6d82eb4
Merge pull request #925 from CyberMind-FR/feat/meshtastic-usage
meshtastic usage: local node + messaging + channel join link/QR (0.1.5)
2026-07-29 09:54:28 +02:00
github-actions[bot]
837ecbd802 ci: Update metrics [skip ci] 2026-07-29 06:20:59 +00:00
e9a4c604bd feat(meshtastic): channel join link + offline QR (the 'lien')
GET /api/v1/meshtastic/channel-url returns the device's sharable Meshtastic
channel URL (localNode.getURL — encodes name + PSK + LoRa config) plus an
offline inline-SVG QR (segno, Recommends). Open/scan it on a phone or second
node to JOIN the SecuBox mesh. The URL discloses the channel key → JWT-gated,
and the daemon logs each disclosure to journald. _SerialRadio.channel_url()
reads it from the live iface; MockRadio mirrors it. Panel: a 'Générer le lien
de jonction' button on the Channels tab renders the URL + QR (inline SVG,
CSP-safe, no data: URI).

+3 web tests (67 total). Live-verified on gk2: /channel-url returns the URL +
a 2.4KB QR SVG, panel loads 200.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-29 07:53:07 +02:00
421802dc26 feat(meshtastic): seed local node + peers, record sent messages, live node events
The panel was empty even with the radio present: mesh state was only populated
from received packets, never from the device's own node DB, and our own sent
texts were never recorded. Now:
- Engine.seed_from_radio() pulls the device node DB (self + known peers) at
  connect; radio.on('node') feeds live NODEINFO updates.
- send_cb records the outbound message (record_sent) so /messages shows it.
- Node gains is_self; apply_nodeinfo folds in position/battery/snr from the DB.

_SerialRadio exposes my_num()/node_db(); MockRadio mirrors them. +3 tests (64
total). Live-verified on gk2: /nodes shows the RAK (is_self, !0b8e4573), and a
POST /send appears in /messages (outbound) and TXs on the radio.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-29 07:43:29 +02:00
CyberMind
50d5512889
Merge pull request #924 from CyberMind-FR/fix/meshtastic-radio-present-918
meshtastic: fix 'radio absent' despite a working radio — leaf-dir ownership drift (#918)
2026-07-29 07:33:28 +02:00
cf4258f94e fix(meshtastic): radio 'absent' despite a working radio — leaf-dir ownership drift (ref #918)
The RAK4631 (Meshtastic fw 2.7.15, node 193873267) opens and handshakes fine —
proven manually and under the daemon's full systemd sandbox. The panel showed
'radio: absent' because the leaf dirs /var/cache/secubox/meshtastic and
/var/log/secubox/meshtastic had drifted to owner 'secubox' (not the daemon's
'secubox-meshtastic' user), so the daemon could not write state.json. The cache
refresh loop swallowed the PermissionError, freezing the panel on a 2-day-stale
cached snapshot.

- postinst: ENFORCE leaf-dir ownership on every configure (install -d only sets
  it on create; a pre-existing/mass-reinstalled dir keeps the wrong owner). Also
  chgrp+g+r /etc/secubox/meshtastic.toml every configure (it could be left
  root:root → ConfigError → crash-loop). Leaf dirs only, never shared parents.
- cache.py: refresh loop no longer swallows write errors silently — logs to
  stderr/journald so a frozen cache is diagnosable.
- radio.py: bounded open retry (gc between attempts frees the orphaned pyserial
  FD on a restart-race) + stderr open-lifecycle logs (the logging module is
  unusable — meshtastic reconfigures the root logger on import, which had made
  the real failure invisible).

Deploy-tested on gk2 from a deliberately-drifted state (dirs->secubox,
config->root:root): install repaired ownership, radio -> present, state.json
fresh, service NRestarts=0. 61 tests pass.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-29 07:32:11 +02:00
github-actions[bot]
9a87b406f5 ci: Update metrics [skip ci] 2026-07-28 20:39:14 +00:00
CyberMind
8d43caea95
Merge pull request #923 from CyberMind-FR/feat/torrent-exposure-durable
Some checks are pending
License Headers / check (push) Waiting to run
Declarative per-vhost waf_bypass + durable torrent exposure (#917)
2026-07-28 20:22:43 +02:00
CyberMind
b5c67c9151
Merge pull request #922 from CyberMind-FR/feat/wan-link-guard-913
Backport WAN link guard package + netplan static fallback (#913)
2026-07-28 20:22:30 +02:00
940aa64fcf feat(haproxy,torrent): declarative per-vhost waf_bypass + durable torrent exposure (ref #917)
haproxyctl: honour an opt-in `waf_bypass = true` field per vhost so the
generator emits `use_backend <backend>` (nginx_vhosts) instead of forcing
mitmproxy_inspector under waf_enabled. Makes the WAF-bypass of media/large-
upload services (nc/photoprism/matrix/torrent) declarative, auditable and
regen-durable instead of fragile hand-edits of the generated haproxy.cfg.
Normal vhosts are unaffected (verified in isolation).

secubox-torrent postinst: register torrent.<host>.<suffix> in haproxy.toml
(backend=nginx_vhosts, waf_bypass=true, ssl=true) idempotently and run a
drift-guarded `haproxyctl generate` — which refuses safely on a board that
still has un-migrated hand-edit vhosts (live cfg untouched) and succeeds on a
fresh board. Torrent bypasses sbxwaf by design, so no sbxwaf route is needed.

CLAUDE.md: WAF-bypass section updated — default stays 'never bypass'; the ONLY
sanctioned exception is the declarative waf_bypass=true in haproxy.toml, never
a hand-edit of the generated cfg.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 19:38:59 +02:00
4c17304aa2 feat(wan): package secubox-wan-link-guard + netplan static fallback (backport live gk2 fix) (ref #913)
Backports the live gk2 WAN link guard into source as a proper package
(mirrors the secubox-frigate-diskguard skeleton):
- sbin/secubox-wan-link-guard: mvpp2 eth2 pause-off (ethtool -A) + renegotiate
  (ethtool -r) + lan0 parasite route/addr cleanup + force default via eth2.
  NEVER ip link down/up (wedges the comphy). Tunable via /etc/default.
- secubox-wan-link-guard.service + boot/30s .timer
- 10-secubox-eth2-noflowctl.link: flow-control off at eth2 appearance (boot)
- secubox-eth2-flowctl-off.service backstop (installed, left disabled; .link
  supersedes it, matching live)
- postinst migrates the pre-dpkg hand-placed copies out of /etc/systemd +
  /usr/local/sbin so the packaged units win, then enables the timer.

netplan (board/mochabin): add static fallback 192.168.1.200/24 on eth2 at
route-metric 200 (higher than the DHCP route's 100) so the box is always
reachable even without a lease — closes #913 task 1. Source-only; not applied
live (avoids a remote WAN blip on the already-marginal link).

Verified on gk2: install migrated the old units, timer enabled+active, guard
ran clean (Result=success), WAN gateway stayed reachable throughout.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 19:26:28 +02:00
CyberMind
771b1da3fc
Merge pull request #921 from CyberMind-FR/fix/torrent-async-get
torrent v2.1.2: fix remove/files/stream 500 (webtorrent 2.x client.get is async)
2026-07-28 18:17:31 +02:00
ca56f1e3d3 fix(torrent): engine.get() sync via client.torrents (webtorrent 2.x client.get is async) — fixes remove/files/stream 500 (ref #917)
WebTorrent 2.x made client.get() async (returns a Promise). engine.get() used it
as if synchronous, so every caller operated on a Promise instead of the Torrent:
remove → promise.destroy() (500), /files → promise.files (500), stats →
promise.progress (garbage), and /stream never resolved its file. Match on the
synchronous client.torrents array instead. FakeWebTorrent.get() was sync and
masked this — made it async to model reality, added regression tests asserting
engine.get() returns a Torrent (not a Promise) and remove() drops it cleanly.

Verified on gk2: add/files/remove all 200 (files+remove were 500), service
restarts:0.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 18:17:09 +02:00
CyberMind
771523653d
Merge pull request #920 from CyberMind-FR/feat/torrent-add-sources
torrent v2.1.1: add by magnet/URL/.torrent file + fix add-torrent 502 (uint8-util pin)
2026-07-28 18:10:06 +02:00
31ace331e7 fix(torrent): pin uint8-util 2.2.6 — fixes add-torrent 502 (webtorrent arr2hex vs hex-string infoHash) (ref #917)
uint8-util 2.3.0 rewrote arr2hex() to require a TypedArray (reads data.buffer/
byteOffset/byteLength). webtorrent 2.8.5's _onTorrentId passes it parse-torrent's
infoHash, which is a hex STRING (bytes live in infoHashBuffer) — so arr2hex threw
'Received undefined', crashing the add in a microtask before the torrent resolved
its infoHash. The request then blocked past the proxy read timeout and surfaced as
a board-wide 502 on /add. webtorrent declares uint8-util ^2.2.5, so the committed
lockfile had silently drifted up to the breaking 2.3.x. Pin to 2.2.6 (last pre-2.3,
string-tolerant arr2hex) via npm overrides; regenerate lockfile.

Verified on gk2: add returns 200 in ~0.1s (was a >15s hang), torrent lands in the
library, service stays up with no unhandledRejection.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 18:07:52 +02:00
f713635a2a fix(torrent): resolve add on 'infoHash' event (infoHash is set a tick after add(), not sync) — add now returns fast (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 17:45:41 +02:00
73438d1194 fix(torrent): non-blocking add — resolve on infohash, fetch metadata in background (was: /add blocked up to 60s on metadata > WAF timeout → gateway error) (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 17:43:21 +02:00
github-actions[bot]
dec01bb952 ci: Update metrics [skip ci] 2026-07-28 15:41:59 +00:00
971e96889a fix(torrent): uncaughtException net + never store undefined magnet (malformed magnet no longer crashes service→502) (ref #917)
WebTorrent throws SYNCHRONOUSLY (arr2hex(undefined) in _onTorrentId) on some
malformed magnets — not an 'error' event, so engine listeners can't catch it,
crashing the process (user sees 502). Add process.on('uncaughtException') in
start() (kept alive), and store a canonical infohash-derived magnet when
magnetURI is absent so resumeLibrary never re-adds a bad value.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 17:35:51 +02:00
4c87101e43 fix(torrent): handle WebTorrent error events (bad magnet/torrent no longer crashes service) + install-lxc restarts on upgrade + npm-ci lock-hash guard (ref #917)
- engine: client.on('error') + torrent.on('error',reject) — invalid input rejects cleanly (was: unhandled 'error'→process crash, exposed by /add-file with bad bytes)
- install-lxc: 'systemctl enable --now' does NOT restart a running unit → in-place upgrade kept old code; add explicit restart
- install-lxc: skip the ~10min npm ci native rebuild when package-lock hash unchanged
- 28 node tests (+error-path)

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 17:27:08 +02:00
5ec54ab52b feat(torrent): add by .torrent URL + uploaded .torrent file (webui mode selector) — v2.1.0 (ref #917)
engine.add accepts magnet/URL/Buffer; POST /add-file (raw x-bittorrent, 5MiB cap)
+ torrentUrl on /add; library stores canonical magnetURI for resume; webui gets a
magnet/URL/file selector. 27 node tests.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 17:09:17 +02:00
CyberMind
c6f2742d96
Merge #919: secubox-torrent v2.0 WebTorrent streaming (Closes #917)
Some checks are pending
License Headers / check (push) Waiting to run
secubox-torrent v2.0 — WebTorrent streaming pivot (Closes #917)
2026-07-28 16:46:44 +02:00
de3e56d72f fix(torrent): seed /etc/resolv.conf in LXC (download rootfs has none, apt/npm can't resolve) (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 16:28:26 +02:00
8dfcad291e fix(torrent): only add lxc.idmap if absent (default.conf already provides it; duplicate aborts container) (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 16:25:26 +02:00
41b83eb5a2 fix(torrent): LXC create via download template + unprivileged idmap (gk2 rejects -t debian for unprivileged) (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 16:21:28 +02:00
252fb6825a fix(torrent): preserve kept flag on re-add, resume library at startup, honest paths (ref #917)
library.js add() used INSERT OR REPLACE, so re-adding an already-kept
magnet reset kept=0 and added_at — the ephemeral purge sweep would then
delete it 6h later as if it had never been kept. Switch to
INSERT ... ON CONFLICT DO UPDATE SET last_played_at only, preserving kept/
added_at/name/magnet/path across a re-add.

server.js never resynced the WebTorrent engine with the library DB at
startup: kept titles 404'd on /stream after any restart (engine.get()
returns null for a torrent the engine never re-learned about), and
runPurge's engine.remove() became a silent no-op for ephemeral rows,
leaking their downloaded bytes on disk while their DB rows vanished. Add
resumeLibrary(engine, library, { rmrf }), wired into start(): kept rows are
re-added to the engine (try/catch + promise .catch so one bad magnet can't
abort the loop); ephemeral rows are dropped from the library and their
download dir is reclaimed via an injectable rmrf.

api.js recorded a fictitious tmp/<infohash> (add) and library/<infohash>
(keep) path that no code ever moves files into — files download flat to
downloadDir. Record path.join(downloadDir, infohash) in both places so the
path resumeLibrary's rmrf acts on matches where files actually live.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 16:10:15 +02:00
fd4e507a40 fix(torrent): run install-lxc.sh via bash, add secubox-hub dep, build-essential fallback (ref #917)
postinst invoked the bash provisioner through /bin/sh (dash), which rejects
`set -o pipefail` on line 1 and silently aborted LXC provisioning behind the
`|| echo … deferred …` guard — apt install exited 0 with no LXC ever
created. Also add secubox-hub to Depends (owns the $lan_client nginx map
torrent.conf's auth gate references) and python3/build-essential to the
in-LXC apt install so npm can compile better-sqlite3/@roamhq/wrtc from
source on bookworm's Node 18 when an arm64 prebuild is missing.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 16:10:06 +02:00
6edd0dfe9d feat(torrent): debian packaging v2.0.0, remove Transmission (ref #917)
Pivots the secubox-torrent Debian package to the WebTorrent/LXC-native
design (Tasks 1-7): control/rules/postinst/postrm now ship the Node app
to /usr/lib/secubox/torrent/app, run install-lxc.sh at configure, load
the nft egress scope from /etc/nftables.d/ (not the brief's unused
/etc/secubox/nft.d/), and symlink the standalone vhost into
sites-available/sites-enabled instead of the location-snippet secubox.d/.

Deletes the old host-side Transmission FastAPI (api/main.py) and its
systemd unit; Depends drops python3-uvicorn/transmission in favour of
lxc, debootstrap, nftables. Structural packaging test added
(tests/test_packaging.py). Builds cleanly to
secubox-torrent_2.0.0-1~bookworm1_all.deb.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 15:51:10 +02:00
7890587a92 feat(torrent): host LXC provisioner + vhost + nft egress + config (ref #917)
Extends the Task 1 install-lxc.sh skeleton with the full app deploy
(tar-copy app/+www/ into the LXC, npm ci, torrent.env, in-LXC systemd
unit enable+start), plus the standalone torrent.gk2.secubox.in nginx
vhost, an nft egress visibility tap on the LXC veth, conf/torrent.toml,
and the menu.d entry (icon/category/description).

Picks 10.100.0.160 for the LXC (10.100.0.130 was already taken), pins
a predictable veth (veth-torrent0) so the nft rule can target it, and
bind-mounts /data/torrent from the host so downloaded data and the
disk-floor statfs() check in server.js see real storage instead of
LXC-overlay space.

The nginx vhost is a full standalone server{} block (own server_name),
so it targets sites-available/ (not secubox.d/, which is merged into
the shared hub server block and can't hold a nested server{}) -- Task 8
must update debian/rules accordingly. Its auth gate mirrors the current
LAN-only $lan_client stub (Authelia was decommissioned; there is no
aggregator JWT-verify endpoint to proxy to yet), not the brief's
aggregator/api/v1/auth/verify snippet.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 15:37:56 +02:00
405d287f06 feat(torrent): server wiring + purge sweep + player webui (ref #917)
Adds server.js (env-driven Engine/Library/buildApi wiring, ephemeral
torrent purge sweep on a timer, static webui serving) and the runPurge
unit tests (TTL + kept-flag + disk-floor sweep, in-memory Library only).
webtorrent and @fastify/static are imported dynamically inside start()
so runPurge stays testable without the native wrtc build.

Replaces the old Transmission-container panel with a single-page
WebTorrent player webui (magnet add, list with keep/remove, in-browser
playback via /stream, BitTorrent-only status badge) per the cyan
hybrid-dark self-contained module vhost guidelines.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 15:22:16 +02:00
github-actions[bot]
c96273b184 ci: Update metrics [skip ci] 2026-07-28 13:16:22 +00:00
fd6809b873 feat(torrent): api.js Fastify routes + stream mount (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 15:07:46 +02:00
7aaa8d5409 feat(torrent): library.js SQLite + purge selection (ref #917)
Adds SQLite-backed torrent library (add/list/get/touch/keep/remove) and
the expiredEphemeral() query the purge job uses to find stale
kept=0 torrents (last_played_at < now-ttl). Includes package-lock.json
and a lxc/app .gitignore for node_modules.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 15:00:32 +02:00
d45803db0c fix(torrent): stream.js Critical/Important review fixes (ref #917)
Critical: Change file.stream({start,end}) → file.createReadStream({start,end})
to match real webtorrent ^2.5.1 API (WHATWG ReadableStream vs Node Readable).

Important #1: RFC 7233 compliance — clamp end >= total to total-1 instead
of rejecting with 416. Only reject when start >= total or start > end.

Important #2: Attach stream error handlers to prevent unhandled crashes
on peer disconnect or missing pieces.

Minor: Add tests for range clamping (bytes=50-999999 on 100-byte file)
and out-of-range fileIdx. All 6 tests passing (green).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:51:18 +02:00
a6cc0df720 feat(torrent): stream.js HTTP Range handler (ref #917)
Implement handleStream(engine, req, res) for WebTorrent streaming with
HTTP Range support. Handles 200 full, 206 partial, 416 unsatisfiable,
404 not found responses. MIME type detection from file extension.
Tests via node --test with fake engine/request/response (all green).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:44:39 +02:00
f57ccfc035 fix(torrent): engine.remove() capacity-release + unused import (ref #917)
FakeTorrent now stores client reference and splices itself from torrents[]
on destroy(), matching real webtorrent semantics. New test verifies
remove→add within maxActive limit succeeds (capacity freed).

Also remove unused 'import path' from engine.js.

4/4 tests passing.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:40:49 +02:00
e7adb31cc8 feat(torrent): engine.js WebTorrent wrap + fakes (ref #917)
TDD complete: 3/3 tests passing. Engine constructor stores webrtc flag for
API /status route to report transport availability. Fakes enable unit tests
without real WebTorrent network or WebRTC dependencies.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:35:25 +02:00
30f89dcf6f feat(torrent): LXC skeleton + wrtc-arm64 spike (ref #917)
Node app skeleton (package.json, wrtc-probe.js) and idempotent
install-lxc.sh skeleton for the secubox-torrent v2.0 WebTorrent
streaming pivot.

Validated @roamhq/wrtc on arm64 via the existing peertube LXC on gk2
(Node v22.22.2, Debian 12 aarch64) since the dev workstation is x86-64
and the dedicated torrent LXC is not provisioned yet: npm install
resolved a linux-arm64 prebuild cleanly, and the shipped wrtc-probe.js
printed WEBRTC_AVAILABLE=true with a working RTCPeerConnection. Result
recorded in SPIKE-RESULT.md; @roamhq/wrtc stays in package.json.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:31:06 +02:00
98a03aa7eb docs(torrent): implementation plan v2.0 WebTorrent (8 tasks, ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 14:23:46 +02:00
CyberMind
49f4c68550
Merge #916: secubox-antirootkit host-IDS anti-escape + exec scanner (Closes #915)
Some checks are pending
License Headers / check (push) Waiting to run
secubox-antirootkit: host-IDS anti-rootkit — super anti-escape + auditd exec scanner (Closes #915)
2026-07-28 13:52:43 +02:00
ded619a55f docs(torrent): WebTorrent streaming pivot design spec (ref #917)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 13:51:41 +02:00
69c77bff70 docs: WIP/HISTORY — WAN guard 3-layer + 503 sbxwaf fix + antirootkit #915 deployed (ref #913 #915)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 12:18:07 +02:00
19bcd10b2d perf(antirootkit): preload dpkg path index (O(1) is_backed) instead of dpkg -S per exec (ref #915)
At ~150 execs/s a dpkg -S subprocess per event can't keep up; the scanner
fell permanently behind and never reached recent execs. DpkgIndex loads all
/var/lib/dpkg/info/*.list paths into a set once at startup. +2 tests.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 12:07:53 +02:00
dfaf164eba fix(antirootkit): dpkg_backing resolves merged-usr aliases (/usr/bin<->/bin) (ref #915)
On bookworm merged-usr, audit reports /usr/bin/grep but dpkg owns /bin/grep,
so dpkg -S failed and legit system binaries were flagged non-dpkg -> false
positive flood in the scanner. Try both aliased forms. +2 regression tests.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 12:04:42 +02:00
4d0edfdb3a fix(antirootkit): execscan must NOT use ausearch -i (parser needs raw numeric uid) (ref #915)
-i renders uid=root and decodes exe; parse_ausearch expects raw (uid=0,
hex/quoted exe) and matched ZERO events on interpreted output. Regression
guard test added. This was the last blocker: scanner now flags non-dpkg execs.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 11:58:47 +02:00
dba99765a9 fix(antirootkit): read audit via scoped-sudo ctl execscan (ausearch needs root for auditd.conf+rotated logs) (ref #915)
Non-root ausearch can't read /etc/audit/auditd.conf -> single-log fallback ->
--checkpoint continuity breaks. Route audit reads through the ctl (root) like
jail does. New execscan verb + sudoers entry.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 11:53:21 +02:00
946fd7faea fix(antirootkit): ausearch --checkpoint incremental polling + seed-on-first-start (ref #915)
-ts windowing was unworkable (epoch rejected, recent=30k events/poll); use
ausearch's native --checkpoint incremental mode + seed the checkpoint on first
start so pre-daemon history isn't replayed. Verified on gk2.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 11:47:54 +02:00
d069548a36 fix(antirootkit): ausearch needs --input-logs (plain ausearch reads nothing on gk2 auditd 3.0.9) (ref #915)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 11:42:27 +02:00
0475dc0880 fix(antirootkit): broad execve audit rule + alert-only enforce policy + deploy fixes (ref #915)
On-hardware validation (gk2, aarch64) revealed the v1 exec-detection didn't
fire and had an unsafe blast radius. Fixes:

- audit: replace targeted `-w <dir> -p x` watches (which watch the dir inode,
  NOT execs of files within it — 0 records on real execs) with a broad
  `-a always,exit -F arch=b64 -S execve -k sbx_exec` rule. `-F dir=` does not
  filter execve by target path either (both verified on the raw audit log).
  ~150 exec/s on a populated board, lost=0.
- api/policy.py (new): enforce flag (default FALSE = alert-only 'process
  scanner') + jail_dirs scope. Broad detection would otherwise auto-jail
  legitimate non-dpkg SecuBox binaries and cut their egress. Alert-only logs
  + alerts every flagged exec but never jails; enforce=true jails only flagged
  execs under a jail_dir. Fail-safe: corrupt/missing config => alert-only.
- daemon: ExecLog now persists only FLAGGED (non-dpkg) execs, not the ~150/s
  legitimate ones (would balloon to millions of rows/day, zero forensic value).
  jail_fn receives the whole ExecEvent so policy can gate on the exe path.
- unit: SupplementaryGroups=adm so the daemon can actually read
  /var/log/audit/audit.log (0640 root:adm) — without it the scanner is blind.
- ctl nft-load: mkdir the slice cgroup before `nft -f` (nft resolves the
  cgroupv2 path at load time; missing cgroup => load fails).
- packaging: nginx route -> /etc/nginx/secubox-routes.d (the dir the hub's
  webui.conf includes; secubox.d is inert on the admin vhost); postinst
  reloads nginx so the panel goes live.

89 tests (was 80): +test_policy.py, +make_jail_fn alert-only/enforce tests.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 11:38:47 +02:00