Commit Graph

3222 Commits

Author SHA1 Message Date
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
ffba634d72 fix(antirootkit): don't double-install <pkg>.service to /lib+/usr/lib (merged-usr dpkg collision) — let dh_installsystemd own it (ref #915)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 10:10:27 +02:00
388b390370 fix(antirootkit): make alertstore cross-process via SQLite (ref #915)
Re-review caught that api/alertstore.py was a module-level Python list,
which is a no-op across the real deployment topology: sbx-antirootkitd
.service (the daemon, python3 -m api.daemon) and secubox-antirootkit
.service (the API, uvicorn api.main:app) are two SEPARATE systemd
processes. The daemon's alertstore.append() mutated a list private to
its own process; GET /alerts, served by the other process, always read
a different, permanently-empty copy. The previous tests only passed
because they ran in a single pytest process.

Rework alertstore.py into an AlertStore class backed by SQLite at
/var/lib/secubox/antirootkit/alerts.db (WAL, check_same_thread=False),
exactly mirroring how api/execlog.py already correctly shares
execlog.db between both processes. The daemon and the API each
construct their own AlertStore pointed at the same db_path.

Added test_alertstore.py::test_cross_process_visibility: two
independent AlertStore instances (independent sqlite3 connections)
opened against the same db_path, standing in for the daemon and the
API, proving one instance's append() is visible from the other — the
assertion the previous in-memory store could never make.

README/changelog "Alerts (LIVE)" wording updated to describe the
SQLite-backed, cross-process store instead of the incorrect
"in-process" store it previously claimed.

80 tests passing (was 78; +2 new: full alert round-trip + the
cross-process proof).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 09:49:30 +02:00
1b52411ab7 fix(antirootkit): real poll cursor, live alerts, honest v1 scope (ref #915)
Whole-branch review fix wave on secubox-antirootkit:
- daemon.py: the ausearch checkpoint was reset to "recent" every loop,
  re-fetching/re-parsing/re-jailing the same ~10-min window forever
  (execlog rows duplicated ~120x, recycled PIDs falsely re-jailed). Now
  tracks a real (ts, serial) cursor via ExecEvent's own audit-record
  identity, so each event is processed exactly once regardless of how
  much successive ausearch windows overlap.
- New api/alertstore.py wires GET /alerts to real jail events (was a
  hardcoded [] stub): the daemon appends a minimal alert whenever it
  jails an unknown process.
- Trim over-claims in control/changelog/README: LIVE (anti-escape, exec
  scanner, execlog, webui, alert-only quarantine-prep) vs SCAFFOLDED
  v1.1 follow-up (debsums/aide/rkhunter integrity timer not shipped).
- Remove dead [procwatch] mode key; add /home to the auditd watch rules
  (heuristics.SUSPECT_DIRS already flags it, but nothing watched it).
- debian/control: add sudo + nftables to Depends (jail path + anti-escape
  silently no-op without them).
- nft/secubox-antiescape.nft: idempotent add+delete+redefine teardown so
  re-running nft-load never appends duplicate rules.

78 tests passing (was 66; +12 new covering the cursor and alert wiring).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 09:41:06 +02:00
71e194bf4c feat(antirootkit): Debian packaging + systemd wiring (Task 11, ref #915)
Turns the existing api/, sbin/, nft/, systemd/, sudoers/, www/, conf/,
menu.d/ trees (Tasks 1-10) into an installable secubox-antirootkit .deb,
mirroring secubox-p2p's debian/ conventions.

- debian/control: arch all, Depends auditd+debsums+python3+python3-fastapi,
  Recommends aide/rkhunter/chkrootkit (soft integrity-tool degradation).
- debian/rules installs the ctl to /usr/sbin, the nft table to
  /usr/share/secubox-antirootkit/ (matches the ctl's nft-load path), the
  sudoers drop-in, the audit rule, both systemd services + the
  sbx-untrusted.slice, the webui, menu entry and nginx route.
- debian/postinst creates the secubox-antirootkit system user, installs
  -d (never chowns) /var/lib/secubox/antirootkit under the shared
  /var/lib/secubox parent, visudo -cf validates the sudoers before
  trusting it, loads the nft table + augenrules, and starts
  sbx-untrusted.slice at the cgroup root (unnested — required for the nft
  `cgroupv2 level 1` match) plus both services.
- conf/99-sbx-procwatch.rules: targeted (not full) auditd exec watches on
  /usr/local/bin, /usr/local/sbin, /tmp, /dev/shm, /opt, /usr/lib/jvm.
- api/daemon.py: thin runner wiring ausearch polling around the existing
  execwatch.run_once()/cgroup.jail_pid()/ExecLog primitives for
  sbx-antirootkitd.service — no new decision logic.
- tests/test_packaging.py: 13 tests reading debian/ as text (control
  deps, no-chown-shared-parent, nft/audit wiring, DEBHELPER tokens,
  compat level, service units, slice nesting).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 09:21:20 +02:00
2c619fb8b3 fix(antirootkit): /status uses ExecLog.count() instead of len(recent(limit=100))
len(recent(limit=100)) caps the "Exec Rows" badge at 100 forever once a
live host records more than that — a monitoring blind spot. Add
ExecLog.count() (true SELECT COUNT(*) over the table, unbounded by any
recent() limit) and use it in the /status handler.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 09:14:12 +02:00
29ec46b338 feat(antirootkit): FastAPI app + webui panel for execlog/alerts/quarantine-prep (Task 10)
api/main.py exposes GET /status, /execlog, /alerts and POST
/quarantine-prep (delegates to api.quarantine.prepare, never executes)
via a create_app(execlog=None) factory; module-level `app` uses a
default ExecLog singleton. nginx/antirootkit.conf routes /antirootkit/
static + /api/v1/antirootkit/ to the dedicated socket, and
menu.d/595-antirootkit.json registers the sidebar entry.

api/execlog.py gains an optional check_same_thread kwarg (default True,
backward compatible) so the API's singleton can be opened with
check_same_thread=False — required because FastAPI's sync routes run
in a threadpool worker thread, not the thread that opened the sqlite3
connection.

www/antirootkit/index.html follows the WEBUI-PANEL-GUIDELINES
hybrid-dark look, builds every row via createElement/textContent (no
innerHTML, no inline onclick) with event-delegated alert-queue
quarantine buttons, and reads the bearer token from
localStorage['sbx_token'].

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 09:09:44 +02:00
b94ece7b68 fix(antirootkit): shlex.quote c2_ip and unit in quarantine plan (Task 9)
nft_block and disable_unit interpolated c2_ip/unit raw, unlike path,
letting a crafted unit or c2_ip smuggle shell metacharacters into the
plan strings an operator copies/pastes and runs. Quote both like path.
Also strengthens the no-side-effects test to monkeypatch os.chmod and
shutil.copy alongside os.system/subprocess.run, and adds a test
asserting metacharacters in c2_ip/unit come out shell-quoted.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 09:01:41 +02:00
0b6760da9d feat(antirootkit): quarantine-prep plan builder + ctl verb (Task 9)
api/quarantine.prepare() computes the neutralization plan for a
confirmed-malicious binary as data (chmod/copy/sha256/nft_block/
disable_unit strings) — it never executes anything, only builds
shlex-quoted command strings for operator confirmation. Adds the
matching quarantine-prep <path> verb to secubox-antirootkitctl,
which writes the plan to /root/quarantine/plan-<name>.txt and logs
it, without running any of the destructive steps itself.

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
2026-07-28 08:57:54 +02:00