Commit Graph

2048 Commits

Author SHA1 Message Date
4ef6d3aa76 docs: record #662 R3 cutover to Go engine + banner port (PR #670)
Some checks are pending
License Headers / check (push) Waiting to run
2026-06-18 19:20:21 +02:00
af76e33b45 docs: record #662 P5-prep + P6-prep (PRs #668, #669) in HISTORY 2026-06-18 19:19:38 +02:00
CyberMind
8df8f4d181
Merge pull request #670 from CyberMind-FR/feat/662-cutover-fix
feat(#662): R3 cutover to the Go MITM engine — unit fix, R3-CA loadCA, banner port
2026-06-18 19:19:23 +02:00
70d35eb7f2 feat(toolbox-ng): port real banner inject + /__toolbox portal reverse-proxy (ref #662) 2026-06-18 19:16:27 +02:00
73795bb3c3 feat(toolbox-ng): port transparency-banner loader inject + /__toolbox/* portal proxy (ref #662)
The Go MITM engine now injects the REAL visible transparency-banner loader
(replacing the invisible `<!-- sbx-ng banner -->` marker regression), mirroring
the authoritative Python inject_banner.py with stream_inject ON.

- banner.go: injectLoader() builds the guarded loader <script src="/__toolbox/
  loader.js" data-mh=.. data-wg=.. async> exactly like Python _loader_script;
  placement mirrors _LoaderInjector (after <head>'s '>', else before <body>,
  else unchanged); bannerGuard idempotency matches _GUARD; data-mh ascii-stripped.
- /__toolbox/loader.js + /__toolbox/bundle short-circuited in BOTH the CONNECT
  mitmPipeline and the transparent path, reverse-proxied to the portal
  (--portal, default http://127.0.0.1:8088). Startswith match (query-aware),
  fail-open to 204 so a banner asset never 502s the navigation.
- mitmPipeline threads `wg bool`: transparent path derives it from the
  10.99.1.0/24 peer IP (R3 WG), CONNECT passes false. Injection tightened to
  2xx text/html (Python skips non-200). injectMarker/Policy.Inject kept for the
  existing PoC tests.
- banner_test.go: guard idempotency, <head>/<body>/neither placement, wg + mh
  attributes, non-ascii stripping, path-detection + portal URL construction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:12:28 +02:00
03fdc8fe14 fix(toolbox-ng): cutover-ready worker unit — live R3 CA, transparent 10.99.1.1 bind, combined-PEM loadCA (ref #662) 2026-06-18 18:54:41 +02:00
CyberMind
223f81ac63
Merge pull request #669 from CyberMind-FR/feat/662-transparent-machash
Some checks are pending
License Headers / check (push) Waiting to run
feat(#662 Phase 6-prep): transparent SO_ORIGINAL_DST accept + mac_hash persona (DARK)
2026-06-18 18:41:17 +02:00
bf022f618f fix(toolbox-ng): transparent upstream must verify cert against SNI, not bare IP (ref #662)
The transparent mitm/allow path set req.URL.Host = ip:port, so http.Client
TLS-dialed the captured original-dst and verified the cert against the bare IP
→ guaranteed SNI/cert-name mismatch. Add a per-request transparentTransport
whose DialContext pins the TCP dial to the captured ip:port for every
connection while TLSClientConfig.ServerName = the SNI host, so the upstream is
reached at the real IP yet the cert is verified by hostname. req.URL.Host now
carries the SNI host (correct Host header + SNI); verification stays ON (no
InsecureSkipVerify). The CONNECT path is unchanged (dialHost == "" → it still
dials by req.URL.Host exactly as before).
2026-06-18 18:37:35 +02:00
9df984c73f fix(toolbox-ng): transparent splice must not decrypt — peek+replay ClientHello (ref #662)
handleTransparent previously forged a cert and terminated TLS BEFORE Decide, so
a splice host was already MITM'd and the splice branch then io.Copy'd decrypted
plaintext into a cleartext dial — a broken relay of a host policy says to pass
through untouched (cert-pinned apps, own media infra).

Now: peek the ClientHello off the raw conn without consuming it (recordingReader
tees the bytes), parse SNI with a new pure stdlib sniFromClientHello (fully
bounds-checked, never panics), and Decide on the peeked SNI with NO decryption.
splice → dial the ORIGINAL dst, replay the buffered ClientHello upstream, pipe
raw TCP both ways, NEVER tls.Server. allow/mitm/block → re-present the buffered
ClientHello to tls.Server via a prefixConn (Read drains the prefix then
delegates) and run the shared pipeline as before.

Adds table tests for sniFromClientHello (hand-assembled ClientHello with/without
SNI, non-handshake, truncated, not-ClientHello → ("",false)), a no-panic
truncation sweep, and prefixConn replay tests.
2026-06-18 18:37:13 +02:00
5acfdb17c6 fix(toolbox-ng): non-linux build regression in transparent dispatch (ref #662)
main.go (untagged) referenced px.handleTransparent + the transparent accept
loop unconditionally, so the linux-only transparent.go made `GOOS=darwin
go build ./...` fail. Move the accept loop into a linux-tagged runTransparent
helper and add a non-linux transparent_stub.go that log.Fatals; main.go now
calls runTransparent only when --transparent. Verified GOOS=linux/arm64,
linux/amd64 and darwin all build.
2026-06-18 18:36:46 +02:00
364b8c4a30 feat(toolbox-ng): transparent SO_ORIGINAL_DST accept path (build only, DARK) (ref #662)
Add cmd/sbxmitm/transparent.go (//go:build linux): parseOrigDst decodes a raw
sockaddr_in/sockaddr_in6 blob (endianness-robust family, big-endian port) into
host:port — PURE, fully unit-tested. origDst recovers the pre-DNAT destination
via getsockopt(SO_ORIGINAL_DST=80) using syscall.Syscall6 on the raw fd
(stdlib-only). handleTransparent recovers origDst, terminates TLS by SNI,
splices raw TCP to the REAL captured dst or runs mitmPipeline dialling it.

transparent_test.go table-tests parseOrigDst (v4/v6, both family endiannesses,
BE port, short-blob errors). End-to-end getsockopt capture needs nft DNAT and
is validated at Phase 5 shadow on the board, not in unit tests (documented).
2026-06-18 18:24:57 +02:00
ba933a6ec3 refactor(toolbox-ng): extract shared post-TLS MITM pipeline + add --transparent flag (ref #662)
Factor handleConnect's post-handshake logic (read request, apply verdict,
anonymize, proxy upstream, poison, inject, write) into mitmPipeline so the
CONNECT and transparent accept paths can't drift. dialHost param lets the
transparent path dial the captured original-dst instead of the SNI. Add a
--transparent bool flag: when set, a raw net.Listen accept loop dispatches each
conn to handleTransparent; default keeps the CONNECT http.Server EXACTLY.
CONNECT path + its tests unchanged.
2026-06-18 18:24:57 +02:00
67e85ba4dd feat(toolbox-ng): wire mac_hash into clientHashFromConn + Python parity (ref #662)
clientHashFromConn now resolves the peer IP via macHashOf (WG persona hash,
byte-identical to Python for 10.99.1.0/24), falling back to the raw peer IP for
non-WG/test conns so poison stays deterministic. Updated the TODO block: WG
mac_hash wiring DONE; remaining gap is only the transparent original-dst
plumbing (Deliverable 2) and the intentionally-out-of-scope R0-R2 ARP path.

test_machash_parity.py drives _common.mac_hash_of on the SAME fixtures; both
engines agree. Anti-rig verified on the Python side too.
2026-06-18 18:21:45 +02:00
5fb67f5b88 feat(toolbox-ng): port WG persona mac_hash to Go with cross-engine parity (ref #662)
Port _common._wg_hash_of / mac_hash_of to cmd/sbxmitm/machash.go: WG peers on
10.99.1.0/24 resolve to sha256(peer_pubkey)[:16], mtime-cached behind a mutex
(Go is concurrent; Python relied on the GIL). Off-subnet / R0-R2 ARP path is
out of scope for the R3 transparent engine; any error fails open to "".

Parity fixtures (testdata/wg-peers-fixture.json + machash-fixtures.json) carry
Python-authored expected values; machash_test.go asserts macHashOf matches.
Anti-rig verified: [:16]->[:15] fails the test.
2026-06-18 18:21:39 +02:00
CyberMind
c870b6362b
Merge pull request #668 from CyberMind-FR/feat/662-phase5prep-pkg
Some checks are pending
License Headers / check (push) Waiting to run
feat(#662 Phase 5-prep): wire Decide+jar+anonymize+poison into handlers + DARK debian package
2026-06-18 18:06:31 +02:00
de15a18c30 feat(#662 Phase 5-prep B): debian packaging for sbxmitm (DISABLED, dark)
New packages/secubox-toolbox-ng/debian/ producing the secubox-toolbox-ng
binary package (Architecture: arm64):
  - control: Maintainer Gerald KERMA; B-D golang-go; Depends
    only (static CGO_ENABLED=0 binary → no shlib deps). Compat 13 via
    debhelper-compat build-dep (debhelper rejects compat both ways).
  - changelog: 0.1.0-1~bookworm1.
  - rules: dh; GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GOPROXY=off go build
    (pure stdlib, offline). dh_installsystemd --no-enable --no-start so the
    unit is shipped but NEVER enabled/started.
  - secubox-toolbox-ng-worker@.service: systemd template mirroring the Python
    mitm-wg worker@ but running sbxmitm on 127.0.0.1:809%i (distinct from the
    Python 808%i so both fleets coexist during cutover). Reads the ca-wg CA.
    DISABLED BY DESIGN — header documents Phase-6-cutover-only enablement.
  - postinst: daemon-reload only; explicitly NO enable/start; NO nft.

Built locally for arm64: dpkg-deb verified — ships /usr/sbin/sbxmitm (arm64
static ELF) + the disabled template; postinst contains ZERO deb-systemd-helper
enable lines. .gitignore extended for in-tree build artifacts. DARK: install
changes no runtime behaviour (no service start, no DNAT, no live-R3 wiring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:03:40 +02:00
f65af3355c feat(#662 Phase 5-prep A): wire ported policy+jar into proxy handlers
Replace the hardcoded action() short-circuit in handleConnect with the
ported Decide(host,sni) + always-on anonymize + Set-Cookie poison:

  - allow/own-infra  → clean MITM (anonymize only, NO block/poison)
  - splice           → raw passthrough (unchanged)
  - block            → 204 (unchanged)
  - mitm + tracker   → poison tracking-id Set-Cookies via the HMAC jar

New pure, unit-testable helpers (privacy.go):
  - anonymizeRequest(http.Header): drop operator/carrier + re-id headers
    (mirrors privacy_guard._STRIP), pin DNT:1 + Sec-GPC:1.
  - isTrackingCookieName / poisonSetCookies: replace tracking-id cookie
    values with fakeID(clientHash,host,name,jarKey); attrs preserved,
    benign cookies untouched, fail-closed-to-clean when no key/clientHash.
  - Policy.isTracker / Policy.shouldPoison: poison ONLY on MITM'd tracker
    flows, never on allow/own-infra (same dark safety as the block path).
  - clientHashFromConn: PoC peer-IP stub, TODO(#662 P6) mac_hash via
    SO_ORIGINAL_DST + WG-peer map.

writeResponse (util.go) preserves multi-valued Set-Cookie headers.
Poison gated behind --poison (default on) AND a loaded --jar-key.
DARK: nothing wired to live R3. +8 tests (14→22), all green; vet clean;
arm64 cross-build OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:00:26 +02:00
CyberMind
7355e606ca
Merge pull request #667 from CyberMind-FR/feat/662-phase4-jar
Some checks are pending
License Headers / check (push) Waiting to run
feat(#662 Phase 4): anti-track HMAC jar port (byte-exact) + sidecar emit
2026-06-18 17:52:12 +02:00
e594f681a4 doc(#662 Phase 4): fix stale fakeID comment — clarify it MUST use registrableJar (anti-consolidation footgun guard) 2026-06-18 17:52:03 +02:00
0db96a8beb fix(#662 Phase 4): jar uses privacy-flavored registrableJar (not ad_ghost) — byte-parity on gov.uk/IP trackers; + divergence-guard fixtures 2026-06-18 17:49:05 +02:00
667d8a09e0 feat(#662 Phase 4): sidecar emit helper (fire-and-forget unix-socket POST)
Add sidecar.go (package main, stdlib only): emit(socketPath, route, payload)
relays a signal to a SecuBox module's unix socket in a detached goroutine —
never blocks the proxy flow, never raises into the caller (mirrors
_common.fire_forget_post + queue_async). emitSync is the same-package,
test-observable synchronous form under a 2s timeout (mirrors httpx timeout=2).

Documents the addon→socket mapping the live engine will use
(cookies/dpi/avatar/ja4/soc_relay → /run/secubox/*.sock; social_graph is
in-process). NOT wired into the live path — transport only (Phase 5+ wiring).

sidecar_test.go: delivery over a throwaway unix socket, dead-socket
no-panic/no-block, empty-route defaulting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:45:52 +02:00
170619053f feat(#662 Phase 4): port anti-track HMAC fake-identity jar to Go, byte-exact
Port privacy.py's _jar_key / _shape / fake_id into jar.go (package main,
stdlib only): loadJarKey (read+TrimSpace, empty->nil), shape (GA1/fb uint64
big-endian modulo math, uuid via rune-length >=32 branch, hex[:32] default),
fakeID (HMAC-SHA256 over client|registrable(tracker)|cookie, reuses the
Phase-3 registrable()). Returns ("",false) where Python returns None.

Cross-engine parity proven: testdata/jar-fixtures.json (expect values
GENERATED by privacy.fake_id with a FIXED test key, not the real /etc key)
covers _ga, _ga_<prop> GA4, _fbp, uuid, _pk_id, name>=32, generic hex, and a
doubleclick.net subdomain-folding case. jar_test.go (Go) and
tests/test_jar_parity.py (Python) load the SAME fixtures+key and both pass ->
byte-exact. No int-math or rune-length divergence found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 17:44:14 +02:00
CyberMind
25f6c19586
Merge pull request #666 from CyberMind-FR/feat/662-phase3-parity
feat(#662 Phase 3): Go block/splice decision engine + cross-engine parity harness
2026-06-18 17:38:07 +02:00
6dcf978e66 test(#662 Phase 3): harden parity harness — own-infra false-prefix + comment-kept learned-trackers fixtures (lock the loadLinesRaw vs loadLines divergence) 2026-06-18 17:37:45 +02:00
df052796d9 test(#662 Phase 3): cross-engine parity harness — Python side (source of truth)
tests/test_engine_parity.py loads the SAME parity-fixtures.json + testdata
config from ../secubox-toolbox-ng/testdata, monkeypatches ad_ghost paths
(_ALLOW_PATH/_LEARNED_PATH/_SELF_REGS + cache resets) at the snapshot, and
drives the production decision logic — ad_ghost._allowed + _AD_HOST + the
learned-trackers check composed with splice.should_splice — under the SAME
precedence as Go's Decide. Asserts action == expect for every fixture.

Parity proven: this run caught a real Go↔Python divergence — Go had
comment-stripped learned-trackers, but ad_ghost._learned_set does not; Go was
fixed (loadLinesRaw) to match Python. Python is the source of truth.

test_fixtures_present guards that all four action classes are exercised.
2026-06-18 17:32:38 +02:00
5fc8785d68 test(#662 Phase 3): cross-engine parity harness — Go side + fixtures
testdata/parity-fixtures.json + testdata/config/ snapshot: a FIXED config both
engines load identically. Fixtures cover every action class — static ad host,
learned-tracker, pure-tracker that is also a splice candidate (never wins →
block), own-infra secubox.in subdomain (allow), allowlisted host (allow),
splice-seed + splice-learned hosts (splice), fortknox site in never-set (mitm),
no-false-suffix negative (notdoubleclick.net → mitm), plain site (mitm).

policy_test.go: TestParityDecide loads the fixtures+config and asserts
Decide == expect; TestPolicyActionVerbs checks the legacy action() surface;
TestRegistrable exercises the _registrable port incl. 2-level TLDs.
2026-06-18 17:32:31 +02:00
25a3afaff1 feat(#662 Phase 3): port toolbox BLOCK/SPLICE logic into Go core
Add cmd/sbxmitm/policy.go: a LoadPolicy() layer that reads the SAME on-disk
config the Python addons use (ad-allowlist.txt, learned-trackers.txt,
tls-splice-seed.conf, splice-learned.txt, pure-trackers.txt) with the same
env overrides, plus a unified Decide(host, sni) -> {allow,block,splice,mitm}.

Ports, byte-for-byte against the Python source of truth:
  - _AD_HOST regex (RE2-safe → Go (?i) inline flag, no fallback needed)
  - _registrable incl. the _2L two-level-TLD list
  - splice.host_matches / should_splice (never wins; then seed∪learned)
  - ad_ghost._allowed (own-infra + allowlist ALWAYS win first)

Loader nuance preserved: ad_ghost._learned_set does NOT comment-strip
(machine-generated file), unlike the splice/allowlist loaders — mirrored via
loadLinesRaw vs loadLines so a '#' in learned-trackers is kept verbatim.

Decide precedence: allow > splice (never-set excludes trackers) > block > mitm.

Wire the loaded policy into the PoC CONNECT proxy (replacing the hardcoded
AdHosts/SpliceHosts); action() keeps the legacy 3-verb surface (allow→mitm).
Old TestActionDecision removed (drove the removed hardcoded fields); coverage
moves to the parity harness.
2026-06-18 17:32:20 +02:00
CyberMind
84f0a37fdf
Merge pull request #665 from CyberMind-FR/feat/662-phase2b-bench
Some checks are pending
License Headers / check (push) Waiting to run
feat(#662 Phase 2b): multi-core throughput bench (3.4x at 4 cores)
2026-06-18 17:23:35 +02:00
ca9b38b175 feat(#662 Phase 2b): parallel handshake bench — Go core scales 3.4x at 4 cores (multi-core gate settled) 2026-06-18 17:23:23 +02:00
8a4996d14c docs(#662): Phase 2 bench results — Go PoC proven on arm64 (CA-compat/204/inject/JA4/12MB); throughput gate deferred to controlled bench 2026-06-18 17:19:01 +02:00
CyberMind
da71515d79
Merge pull request #664 from CyberMind-FR/fix/662-restore-ng-source
Some checks are pending
License Headers / check (push) Waiting to run
fix(#662): restore Go PoC source lost to .gitignore
2026-06-18 17:13:42 +02:00
73e79b85b4 fix(#662): restore Go PoC source — .gitignore 'sbxmitm' wrongly ignored cmd/sbxmitm/ dir (anchored to /sbxmitm) 2026-06-18 17:13:28 +02:00
CyberMind
56d1bee9fb
Merge pull request #663 from CyberMind-FR/feature/662-epic-migrate-toolbox-mitm-engine-off-pyt
epic: migrate toolbox MITM engine off Python mitmproxy (gomitmproxy/hudsucker/Squid analysis + phased switch)
2026-06-18 17:09:26 +02:00
6daacb1987 feat(#662 Phase 1): MITM-engine migration analysis + phased plan + compiled/tested Go forging-MITM PoC
Analysis: gomitmproxy (unmaintained, dropped) vs martian/goproxy (Go) vs hudsucker
(Rust) vs Squid+ICAP, mapped to the 18-addon capability set. Recommendation: Go
hot-path core + retained Python analysis sidecars. Phased plan with shadow-run +
nft-DNAT-flip rollback (no big-bang cutover). Phase-1 PoC (packages/secubox-toolbox-ng,
stdlib-only): forge from ca-wg CA, 204-block, body-inject, SNI-splice, ClientHello/JA4
capture — go vet clean, tests green, arm64 cross-compile OK. NOT wired to live R3.
2026-06-18 17:07:48 +02:00
CyberMind
69f19e72da
Merge pull request #661 from CyberMind-FR/fix/cap-lists-5
Some checks are pending
License Headers / check (push) Waiting to run
ui(toolbox): cap all dashboard lists to 5 shown
2026-06-18 14:49:59 +02:00
be7704bc1a ui(toolbox): cap all admin dashboard lists to top-5 shown (#filtres/#social/#ads) 2026-06-18 14:49:35 +02:00
CyberMind
051ca6d1d7
Merge pull request #660 from CyberMind-FR/feature/659-feat-toolbox-per-visitor-ad-block-breakd
Some checks are pending
License Headers / check (push) Waiting to run
feat(toolbox): per-visitor ad-block breakdown in #ads (top visitors + per-visitor drill-down)
2026-06-18 14:13:41 +02:00
69659f6a67 chore(toolbox): changelog 2.6.58 for per-visitor ad breakdown (ref #659) 2026-06-18 14:13:16 +02:00
1bd5108472 feat(toolbox #659): #ads tab — top visitors + per-visitor drill-down
renders d.top_visitors into #ads-visitors; clickable mac_hash calls
loadAdsClient → /admin/ad-stats/client/{mac_hash} into #ads-client-detail.
all values escaped.
2026-06-18 14:10:00 +02:00
6c96ba62e4 feat(toolbox #659): GET /admin/ad-stats/client/{mac_hash} drill-down
per-visitor ad-block detail endpoint, hours clamped 1..168.
2026-06-18 14:09:22 +02:00
4d0cbf8b7f feat(toolbox #659): ad_ghost accumulates blocked ads per visitor
guarded mac_hash_of import; _cli hot-path dict; per-visitor tally on the
204 block branch; drained + offloaded to record_ad_client_blocks in _flush.
hot path stays in-memory increments only.
2026-06-18 14:08:59 +02:00
4f96da87d7 feat(toolbox #659): store per-visitor ad-block breakdown
ad_block_client_host table + record_ad_client_blocks upsert +
ad_client_stats drill-down; ad_stats now returns top_visitors.
2026-06-18 14:07:02 +02:00
CyberMind
2b036db0d6
Merge pull request #658 from CyberMind-FR/fix/ad-learn-self-block-hardening
fix(toolbox #658): ad-learn never self-blocks own infra + exact-host promotion
2026-06-18 13:55:47 +02:00
376b4ecd2a fix(toolbox #658): ad-learn never self-blocks own infra + exact-host promotion (no registrable over-fold) 2026-06-18 13:53:42 +02:00
a3cd643da4 docs: HISTORY — #656 Ad Intelligence shipped + splice/banner reverted
Some checks are pending
License Headers / check (push) Waiting to run
2026-06-18 13:33:49 +02:00
CyberMind
f9e2032750
Merge pull request #657 from CyberMind-FR/feature/656-feat-toolbox-ad-intelligence-aggressive
feat(toolbox): Ad Intelligence — aggressive ad-URL learning + block/silent/drop + contextual #ads metrics
2026-06-18 13:21:01 +02:00
e39123dc7e fix(toolbox): allow #ads deep-link to auto-open the tab on load (ref #656) 2026-06-18 13:20:26 +02:00
d5408536b3 chore(toolbox): changelog 2.6.56 for Ad Intelligence (ref #656) 2026-06-18 13:17:46 +02:00
12277f3c08 feat(toolbox): #ads dashboard tab — contextual ad-block stats (ref #656) 2026-06-18 13:16:48 +02:00
976db154a0 feat(toolbox): autolearn _ad_feed promotes ad-candidates to blocklist (ref #656) 2026-06-18 13:15:45 +02:00