mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 19:16:07 +00:00
Compare commits
No commits in common. "dc6505a2f2c5123bce3903af715280ac4ce8a2f3" and "d2cb735f092f3b092e34e06c31fc56e6e9255bc7" have entirely different histories.
dc6505a2f2
...
d2cb735f09
|
|
@ -12,13 +12,10 @@ import the WireGuard profile, verify the tunnel, then open the live
|
|||
4. **Verify** — polls `/wg/r3-check` → "Tunnel R3 actif ✓".
|
||||
5. **Live metrics** — opens `/social/me` (cartographie sociale).
|
||||
|
||||
## Root path — fully-automated silent onboarding (#538, #551)
|
||||
When the device is **rooted**, the app runs the whole onboarding with **zero
|
||||
taps**: on launch it auto-detects root and, if this cabine host hasn't been
|
||||
onboarded yet, starts the silent sequence automatically (`RootAuto` step,
|
||||
streaming log). The **⚡ Installation automatique (root)** button stays for
|
||||
re-runs. The "already onboarded" flag is persisted per host (SharedPreferences)
|
||||
so reopening the app doesn't redo it. Steps:
|
||||
## Root path — fully-automated silent onboarding (#538)
|
||||
When the device is **rooted**, the Discover step shows an extra
|
||||
**⚡ Installation automatique (root)** button. Tapping it runs the whole
|
||||
onboarding with zero further interaction (`RootAuto` step, streaming log):
|
||||
|
||||
1. **System CA install** — downloads `/wg/ca.pem`, computes the OpenSSL
|
||||
`subject_hash_old` in pure Kotlin, and bind-mounts a populated copy of
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ android {
|
|||
applicationId = "in.secubox.toolbox"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 2
|
||||
versionName = "0.2.0"
|
||||
versionCode = 1
|
||||
versionName = "0.1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
|
|||
|
|
@ -64,50 +64,8 @@ fun OnboardApp() {
|
|||
val api = remember(host) { ToolboxApi(host) }
|
||||
var rootAvail by remember { mutableStateOf(false) }
|
||||
val rootLog = remember { mutableStateListOf<String>() }
|
||||
val prefs = remember {
|
||||
ctx.getSharedPreferences("secubox-toolbox", android.content.Context.MODE_PRIVATE)
|
||||
}
|
||||
var autoTried by remember { mutableStateOf(false) }
|
||||
|
||||
// The whole root-mode silent run, reused by the ⚡ button AND the
|
||||
// zero-tap auto-launch (#551). Persists an onboarded flag per host on
|
||||
// success so reopening the app doesn't redo it.
|
||||
val runRootAuto: () -> Unit = {
|
||||
busy = true; status = ""; rootLog.clear()
|
||||
scope.launch {
|
||||
val ok = withContext(Dispatchers.IO) { api.reachable() }
|
||||
if (!ok) {
|
||||
busy = false; status = "Borne injoignable — vérifie le réseau."
|
||||
} else {
|
||||
step = Step.RootAuto
|
||||
val onb = RootOnboard(api, ctx.cacheDir)
|
||||
val out = withContext(Dispatchers.IO) {
|
||||
onb.runSilent { line -> scope.launch(Dispatchers.Main) { rootLog.add(line) } }
|
||||
}
|
||||
busy = false
|
||||
onTunnel = out.verified
|
||||
if (out.verified) prefs.edit().putBoolean("onboarded:$host", true).apply()
|
||||
when {
|
||||
out.verified -> step = Step.Done
|
||||
out.wgViaApp -> { step = Step.ImportProfile
|
||||
status = "CA installé en root ✓ — termine le tunnel via l'app WireGuard." }
|
||||
else -> { step = Step.Verify
|
||||
status = "Active le tunnel puis vérifie." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect root once, off the main thread.
|
||||
LaunchedEffect(Unit) { rootAvail = withContext(Dispatchers.IO) { RootShell.available() } }
|
||||
// Zero-tap (#551): on a rooted device, auto-run the silent onboarding
|
||||
// once on launch — unless this host was already onboarded.
|
||||
LaunchedEffect(rootAvail) {
|
||||
if (rootAvail && !autoTried && step == Step.Discover) {
|
||||
autoTried = true
|
||||
if (!prefs.getBoolean("onboarded:$host", false)) runRootAuto()
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(colorScheme = darkColorScheme(
|
||||
primary = Gold, secondary = Cyan, background = Cosmos, surface = Cosmos,
|
||||
|
|
@ -147,11 +105,32 @@ fun OnboardApp() {
|
|||
}
|
||||
if (rootAvail) {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text("🔓 Root détecté — l'installation se lance automatiquement. " +
|
||||
"Tu peux aussi la relancer ici.",
|
||||
Text("🔓 Root détecté — installation 100% automatique possible.",
|
||||
color = Matrix, fontSize = 12.sp)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
OutlinedButton(onClick = runRootAuto, modifier = Modifier.fillMaxWidth(),
|
||||
OutlinedButton(onClick = {
|
||||
busy = true; status = ""; rootLog.clear()
|
||||
scope.launch {
|
||||
val ok = withContext(Dispatchers.IO) { api.reachable() }
|
||||
if (!ok) { busy = false; status = "Borne injoignable."; return@launch }
|
||||
step = Step.RootAuto
|
||||
val onb = RootOnboard(api, ctx.cacheDir)
|
||||
val out = withContext(Dispatchers.IO) {
|
||||
onb.runSilent { line ->
|
||||
scope.launch(Dispatchers.Main) { rootLog.add(line) }
|
||||
}
|
||||
}
|
||||
busy = false
|
||||
onTunnel = out.verified
|
||||
when {
|
||||
out.verified -> step = Step.Done
|
||||
out.wgViaApp -> { step = Step.ImportProfile
|
||||
status = "CA installé en root ✓ — termine le tunnel via l'app WireGuard." }
|
||||
else -> { step = Step.Verify
|
||||
status = "Active le tunnel puis vérifie." }
|
||||
}
|
||||
}
|
||||
}, modifier = Modifier.fillMaxWidth(),
|
||||
border = BorderStroke(1.dp, Matrix),
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = Matrix)) {
|
||||
Text("⚡ Installation automatique (root)", fontWeight = FontWeight.Bold)
|
||||
|
|
|
|||
|
|
@ -53,15 +53,9 @@ release is cut.
|
|||
build (release CI step / `web-ext sign`); for development use
|
||||
*about:debugging → Load Temporary Add-on*, or an ESR/Dev build with
|
||||
`xpinstall.signatures.required=false`.
|
||||
- **Linux Firefox (fast)** — one call grabs the `.xpi` and launches Firefox
|
||||
with it loaded (via `web-ext run`, no signing needed):
|
||||
```bash
|
||||
clients/webext-toolbox/install-firefox-linux.sh # from kbin.gk2.secubox.in
|
||||
clients/webext-toolbox/install-firefox-linux.sh --release # from the GitHub release
|
||||
clients/webext-toolbox/install-firefox-linux.sh --local # from this checkout
|
||||
```
|
||||
- **Chromium** — load unpacked (`chrome://extensions` → Developer mode).
|
||||
Ships rasterised PNG icons (`icons/icon-48/128.png`), so it loads as-is.
|
||||
Note: Chromium action icons must be raster — rasterise `icons/icon.svg`
|
||||
to PNG before a Chrome Web Store build (Firefox accepts the SVG as-is).
|
||||
|
||||
## Build
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
#
|
||||
# SecuBox ToolBoX — Linux Firefox installer (#547)
|
||||
# One call: grab the ToolBoX cartographie extension and launch Firefox with
|
||||
# it loaded. Prefers `web-ext run` (temporary load, works unsigned — fastest)
|
||||
# and falls back to opening the .xpi for the install prompt.
|
||||
#
|
||||
# Usage:
|
||||
# ./install-firefox-linux.sh # from kbin.gk2.secubox.in
|
||||
# ./install-firefox-linux.sh kbin.my.box # from another cabine host
|
||||
# ./install-firefox-linux.sh --release # from the latest GitHub release
|
||||
# ./install-firefox-linux.sh --local # build from this checkout (web-ext)
|
||||
set -euo pipefail
|
||||
|
||||
DEFAULT_HOST="kbin.gk2.secubox.in"
|
||||
RELEASE_URL="https://github.com/CyberMind-FR/secubox-deb/releases/download/webext-v0.1.1/secubox-toolbox-webext.xpi"
|
||||
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
say(){ printf '\033[1;36m▸\033[0m %s\n' "$*"; }
|
||||
warn(){ printf '\033[1;33m!\033[0m %s\n' "$*" >&2; }
|
||||
die(){ printf '\033[1;31m✗\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# ── resolve source ──
|
||||
MODE="host"; HOST="$DEFAULT_HOST"; SRC_URL=""
|
||||
case "${1:-}" in
|
||||
--release) MODE="release"; SRC_URL="$RELEASE_URL" ;;
|
||||
--local) MODE="local" ;;
|
||||
"") SRC_URL="https://${HOST}/wg/toolbox.xpi" ;;
|
||||
-*) die "unknown flag: $1 (use --release | --local | <host>)" ;;
|
||||
*) HOST="$1"; SRC_URL="https://${HOST}/wg/toolbox.xpi" ;;
|
||||
esac
|
||||
|
||||
# ── find a Firefox binary ──
|
||||
FX=""
|
||||
for c in firefox firefox-esr firefox-bin firefox-developer-edition; do
|
||||
if command -v "$c" >/dev/null 2>&1; then FX="$c"; break; fi
|
||||
done
|
||||
if [ -z "$FX" ] && command -v flatpak >/dev/null 2>&1 \
|
||||
&& flatpak info org.mozilla.firefox >/dev/null 2>&1; then
|
||||
FX="flatpak run org.mozilla.firefox"
|
||||
fi
|
||||
[ -n "$FX" ] || die "no Firefox found (install firefox / firefox-esr, or flatpak org.mozilla.firefox)"
|
||||
say "Firefox: $FX"
|
||||
|
||||
have_webext(){ command -v web-ext >/dev/null 2>&1 || command -v npx >/dev/null 2>&1; }
|
||||
runwebext(){ if command -v web-ext >/dev/null 2>&1; then web-ext "$@"; else npx --yes web-ext "$@"; fi; }
|
||||
|
||||
# ── fastest path: web-ext run (temporary load, no signing needed) ──
|
||||
if have_webext; then
|
||||
SRCDIR=""
|
||||
if [ "$MODE" = "local" ]; then
|
||||
[ -f "$SELF_DIR/manifest.json" ] || die "--local: no manifest.json next to this script"
|
||||
SRCDIR="$SELF_DIR"
|
||||
else
|
||||
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||
say "Downloading extension from ${SRC_URL} …"
|
||||
curl -fsSL "$SRC_URL" -o "$TMP/sbx.xpi" || die "download failed: $SRC_URL"
|
||||
head -c2 "$TMP/sbx.xpi" | grep -q PK || die "not a valid .xpi (zip) — wrong host/URL?"
|
||||
mkdir -p "$TMP/ext" && ( cd "$TMP/ext" && unzip -q "$TMP/sbx.xpi" )
|
||||
SRCDIR="$TMP/ext"
|
||||
fi
|
||||
say "Launching Firefox with the ToolBoX extension loaded (temporary)…"
|
||||
FXBIN="${FX%% *}" # web-ext wants the binary, not a flatpak wrapper
|
||||
if [ "$FX" = "${FX# }" ] && command -v "$FXBIN" >/dev/null 2>&1; then
|
||||
exec runwebext run --source-dir "$SRCDIR" --firefox "$FXBIN" \
|
||||
--start-url "https://${HOST}/social/me"
|
||||
fi
|
||||
exec runwebext run --source-dir "$SRCDIR" --start-url "https://${HOST}/social/me"
|
||||
fi
|
||||
|
||||
# ── fallback: open the .xpi so Firefox shows the install prompt ──
|
||||
warn "web-ext not found (no npx) — falling back to the install prompt."
|
||||
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||
[ "$MODE" = "local" ] && die "--local needs web-ext/npx; install nodejs or use a host/--release"
|
||||
say "Downloading ${SRC_URL} …"
|
||||
curl -fsSL "$SRC_URL" -o "$TMP/secubox-toolbox-webext.xpi" || die "download failed"
|
||||
head -c2 "$TMP/secubox-toolbox-webext.xpi" | grep -q PK || die "not a valid .xpi"
|
||||
cat <<'NOTE'
|
||||
! The .xpi is unsigned. Stock Firefox release refuses a permanent install.
|
||||
Use Firefox ESR/Developer/Nightly, or set in about:config:
|
||||
xpinstall.signatures.required = false
|
||||
…then accept the install prompt that opens now.
|
||||
NOTE
|
||||
say "Opening Firefox on the extension…"
|
||||
exec $FX "$TMP/secubox-toolbox-webext.xpi"
|
||||
|
|
@ -1,57 +1,20 @@
|
|||
{# SPDX-License-Identifier: LicenseRef-CMSD-1.0 #}
|
||||
{# Public landing page — kbin.gk2.secubox.in #}
|
||||
{# Radical-simplify redesign (#543): animated hero + one CTA + install panel
|
||||
up top ; everything else folded behind "En savoir plus". #}
|
||||
<!DOCTYPE html>
|
||||
<html lang=fr><head>
|
||||
<meta charset=UTF-8>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||
<meta name=description content="VILLAGE3B — Cabine numérique Gondwana : diagnostic compromission iPhone/Android anonyme, gratuit, open source CMSD">
|
||||
<title>👁️ VILLAGE3B — Qui te piste ?</title>
|
||||
<title>📡 VILLAGE3B — Cabine Numérique Gondwana</title>
|
||||
<link rel=manifest href=/manifest.json>
|
||||
<style>
|
||||
:root{--bg:#0a0a0f;--bg2:#0e0e15;--phos:#00dd44;--phos-hot:#00ff55;--dim:#006622;--text:#e8e6d9;--purple:#9e76ff;--gold:#c9a84c;--amber:#ffb347;--red:#ff4466;--cyan:#00d4ff}
|
||||
:root{--bg:#0a0a0f;--bg2:#0e0e15;--phos:#00dd44;--phos-hot:#00ff55;--dim:#006622;--text:#e8e6d9;--purple:#9e76ff;--gold:#c9a84c;--amber:#ffb347;--red:#ff4466}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Courier New',Menlo,monospace;background:var(--bg);color:var(--text);line-height:1.55;padding-bottom:3rem}
|
||||
a{color:var(--phos);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
|
||||
/* ── HERO ── */
|
||||
.hero{position:relative;overflow:hidden;background:radial-gradient(120% 120% at 50% -10%,#221041 0%,#0a0a0f 60%);padding:3rem 1.5rem 2.4rem;text-align:center;border-bottom:2px solid var(--phos)}
|
||||
.eye{font-size:3.4rem;line-height:1;display:inline-block;animation:gaze 5s ease-in-out infinite;filter:drop-shadow(0 0 14px rgba(0,255,85,0.55))}
|
||||
@keyframes gaze{0%,100%{transform:translateX(0) scale(1)}25%{transform:translateX(-6px) scale(1.04)}60%{transform:translateX(7px) scale(1.04)}}
|
||||
.hero h1{font-size:2.6rem;color:var(--phos-hot);text-shadow:0 0 10px var(--phos);letter-spacing:0.08em;margin-top:0.3rem}
|
||||
.hero .punch{color:var(--text);font-size:1.25rem;margin-top:0.6rem;font-weight:bold}
|
||||
.hero .punch b{color:var(--gold)}
|
||||
.hero .sub{color:var(--dim);font-size:0.82rem;margin-top:0.5rem;max-width:560px;margin-left:auto;margin-right:auto}
|
||||
/* floating tracker dots = "who's watching" */
|
||||
.dots{position:absolute;inset:0;pointer-events:none;z-index:0}
|
||||
.dots i{position:absolute;width:7px;height:7px;border-radius:50%;opacity:0.0;animation:float 7s ease-in-out infinite}
|
||||
.dots i:nth-child(1){left:12%;top:30%;background:var(--cyan);animation-delay:.0s}
|
||||
.dots i:nth-child(2){left:82%;top:24%;background:var(--amber);animation-delay:1.1s}
|
||||
.dots i:nth-child(3){left:24%;top:68%;background:var(--red);animation-delay:2.3s}
|
||||
.dots i:nth-child(4){left:70%;top:64%;background:var(--purple);animation-delay:.7s}
|
||||
.dots i:nth-child(5){left:50%;top:14%;background:var(--cyan);animation-delay:3.0s}
|
||||
.dots i:nth-child(6){left:90%;top:54%;background:var(--red);animation-delay:1.8s}
|
||||
@keyframes float{0%{opacity:0;transform:translateY(8px) scale(.6)}30%{opacity:.85}70%{opacity:.7}100%{opacity:0;transform:translateY(-14px) scale(1.1)}}
|
||||
.hero>*{position:relative;z-index:1}
|
||||
|
||||
/* ── big CTA row ── */
|
||||
.ctas{margin-top:1.4rem;display:flex;gap:0.6rem;justify-content:center;flex-wrap:wrap}
|
||||
.cta{display:inline-block;padding:0.85rem 1.6rem;font-weight:bold;border-radius:8px;font-size:1.02rem;text-shadow:none;transition:transform .12s,box-shadow .12s}
|
||||
.cta:hover{text-decoration:none;transform:translateY(-2px)}
|
||||
.cta.go{background:var(--phos);color:#0a0a0f;box-shadow:0 4px 18px rgba(0,221,68,0.4)}
|
||||
.cta.go:hover{box-shadow:0 6px 24px rgba(0,221,68,0.6)}
|
||||
.cta.alt{background:transparent;color:var(--purple);border:1px solid var(--purple)}
|
||||
.cta.alt:hover{background:rgba(158,118,255,0.12)}
|
||||
|
||||
/* ── quicknav (trimmed) ── */
|
||||
.quicknav{display:flex;flex-wrap:wrap;justify-content:center;gap:0.6rem;margin-top:1.4rem;max-width:620px;margin-left:auto;margin-right:auto}
|
||||
.qi{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0.5rem 0.4rem;min-width:74px;background:rgba(110,64,201,0.08);border:1px solid var(--purple);border-radius:8px;text-decoration:none;color:var(--text);transition:all 0.12s;font-family:inherit}
|
||||
.qi:hover{background:rgba(110,64,201,0.22);transform:translateY(-2px);box-shadow:0 4px 14px rgba(158,118,255,0.35);text-decoration:none}
|
||||
.qi-emoji{font-size:1.5rem;line-height:1}
|
||||
.qi-label{font-size:0.62rem;letter-spacing:0.04em;color:var(--phos-hot);font-weight:bold;white-space:nowrap}
|
||||
|
||||
.hero{background:linear-gradient(135deg,#1a0a2e 0%,#0a0a0f 100%);padding:2.5rem 1.5rem;text-align:center;border-bottom:2px solid var(--phos)}
|
||||
.hero h1{font-size:2.4rem;color:var(--phos-hot);text-shadow:0 0 8px var(--phos);letter-spacing:0.08em}
|
||||
.hero p.tag{color:var(--gold);font-size:1rem;margin-top:0.4rem;letter-spacing:0.08em}
|
||||
.hero p.sub{color:var(--dim);font-size:0.85rem;margin-top:0.6rem;max-width:600px;margin-left:auto;margin-right:auto}
|
||||
.container{max-width:1080px;margin:auto;padding:2rem 1.5rem}
|
||||
.section{margin-bottom:2.5rem}
|
||||
h2{color:var(--phos-hot);text-shadow:0 0 4px var(--phos);font-size:1.3rem;margin-bottom:0.8rem;border-bottom:1px solid var(--dim);padding-bottom:0.4rem;letter-spacing:0.04em}
|
||||
|
|
@ -80,54 +43,30 @@ svg.chart{width:100%;max-width:400px;height:auto}
|
|||
.svg-bar{fill:var(--phos);transition:fill 0.3s}
|
||||
.svg-bar.medium{fill:var(--amber)}
|
||||
.svg-bar.high{fill:var(--red)}
|
||||
.steps{counter-reset:step}
|
||||
.steps li{counter-increment:step;list-style:none;padding:0.6rem 0 0.6rem 2.4rem;position:relative;font-size:0.9rem}
|
||||
.steps li::before{content:counter(step);position:absolute;left:0;top:0.5rem;width:1.8rem;height:1.8rem;border-radius:50%;background:var(--phos);color:#0a0a0f;text-align:center;line-height:1.8rem;font-weight:bold;text-shadow:none}
|
||||
code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;color:var(--phos-hot)}
|
||||
.cta-sm{display:inline-block;background:var(--phos);color:#0a0a0f;padding:0.7rem 1.4rem;text-decoration:none;font-weight:bold;border-radius:4px;margin:0.5rem 0.3rem 0.5rem 0;text-shadow:none}
|
||||
.cta-sm.outline{background:transparent;color:var(--phos);border:1px solid var(--phos)}
|
||||
a{color:var(--phos);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
.cta{display:inline-block;background:var(--phos);color:#0a0a0f;padding:0.7rem 1.4rem;text-decoration:none;font-weight:bold;border-radius:4px;margin:0.5rem 0.3rem 0.5rem 0;text-shadow:none}
|
||||
.cta.outline{background:transparent;color:var(--phos);border:1px solid var(--phos)}
|
||||
.cta.purple{background:var(--purple);color:#0a0a0f}
|
||||
.footer{text-align:center;font-size:0.78rem;color:var(--dim);padding:1.5rem;border-top:1px solid var(--dim);margin-top:2rem}
|
||||
.arch{font-family:monospace;font-size:0.75rem;color:var(--phos-hot);text-shadow:0 0 4px var(--phos);background:var(--bg2);padding:1rem;border:1px solid var(--dim);border-radius:4px;overflow-x:auto;white-space:pre;line-height:1.4}
|
||||
|
||||
/* ── install panel (kept up top) ── */
|
||||
.install-panel{background:rgba(0,255,65,0.04);border:1px solid rgba(0,255,65,0.25);border-radius:6px;padding:0.6rem 0.9rem;margin:0.45rem 0;text-align:left}
|
||||
.install-panel summary{cursor:pointer;font-size:0.95rem;color:var(--phos-hot);list-style:none;outline:none}
|
||||
.install-panel summary::-webkit-details-marker{display:none}
|
||||
.install-panel[open] summary{margin-bottom:0.6rem}
|
||||
.install-panel .emoji{font-size:1.1rem;margin-right:0.3rem}
|
||||
.install-panel ol{padding-left:1.1rem;line-height:1.5;font-size:0.85rem}
|
||||
.install-panel .btn{display:inline-block;padding:0.45rem 0.75rem;margin:0.25rem 0.2rem 0.25rem 0;background:var(--purple);color:#fff;text-decoration:none;border-radius:5px;font-weight:bold;font-size:0.82rem}
|
||||
.install-panel .btn.alt{background:transparent;border:1px solid var(--purple);color:var(--purple)}
|
||||
.install-panel code{background:rgba(0,0,0,0.4);padding:0.1rem 0.35rem;border-radius:3px;font-size:0.8rem;color:var(--phos-hot)}
|
||||
.install-panel .note{color:var(--dim);font-size:0.78rem;margin-top:0.6rem;border-left:2px solid var(--amber);padding-left:0.6rem}
|
||||
.install-panel img{max-width:100%;border-radius:5px;margin:0.4rem 0}
|
||||
.install-panel pre{background:rgba(0,0,0,0.4);padding:0.5rem 0.7rem;border-radius:4px;overflow-x:auto;font-size:0.78rem;margin:0.4rem 0}
|
||||
|
||||
/* ── "En savoir plus" fold ── */
|
||||
.more{max-width:1080px;margin:0 auto;padding:0 1.5rem}
|
||||
.more>summary{cursor:pointer;list-style:none;text-align:center;color:var(--purple);font-size:0.95rem;letter-spacing:0.05em;padding:0.9rem;border:1px dashed var(--purple);border-radius:8px;margin-bottom:1rem;transition:background .12s}
|
||||
.more>summary::-webkit-details-marker{display:none}
|
||||
.more>summary:hover{background:rgba(158,118,255,0.1)}
|
||||
.more[open]>summary{margin-bottom:1.6rem}
|
||||
.more>summary .chev{display:inline-block;transition:transform .2s}
|
||||
.more[open]>summary .chev{transform:rotate(90deg)}
|
||||
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
||||
.v.tick{animation:flash 0.6s}
|
||||
@keyframes flash{0%{color:var(--gold);transform:scale(1.15)}100%{color:var(--phos-hot);transform:scale(1)}}
|
||||
.quicknav{display:flex;flex-wrap:wrap;justify-content:center;gap:0.7rem;margin-top:1.2rem;max-width:780px;margin-left:auto;margin-right:auto}
|
||||
.qi{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0.6rem 0.4rem;min-width:78px;background:rgba(110,64,201,0.08);border:1px solid var(--purple);border-radius:8px;text-decoration:none;color:var(--text);transition:all 0.12s;font-family:inherit}
|
||||
.qi:hover{background:rgba(110,64,201,0.22);transform:translateY(-2px);box-shadow:0 4px 14px rgba(158,118,255,0.35);text-decoration:none}
|
||||
.qi-emoji{font-size:1.6rem;line-height:1}
|
||||
.qi-label{font-size:0.65rem;letter-spacing:0.04em;color:var(--phos-hot);font-weight:bold;white-space:nowrap}
|
||||
</style></head><body>
|
||||
|
||||
<div class=hero>
|
||||
<div class=dots><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
<span class=eye>👁️</span>
|
||||
<h1>VILLAGE3B</h1>
|
||||
<p class=punch>Qui te piste ? <b>La cabine te le montre.</b></p>
|
||||
<h1>📡 VILLAGE3B</h1>
|
||||
<p class=tag>// CABINE NUMÉRIQUE GONDWANA · TOOLBOX</p>
|
||||
<p class=sub>Diagnostic gratuit de compromission iPhone / Android · Anonyme · Open Source · CMSD-1.0</p>
|
||||
|
||||
<div class=ctas>
|
||||
<a href="/wg/r3-install" class="cta go">✨ Protège-moi (R3)</a>
|
||||
<a href="/social/me" class="cta alt">🕸️ Qui me piste ?</a>
|
||||
</div>
|
||||
|
||||
{# trimmed quick-nav — CA iPhone / CA Android / QR profil moved into the
|
||||
per-platform install panel below (#543) #}
|
||||
{# Phase 6.I : quick-access icon nav — one-tap to all key endpoints #}
|
||||
<div class=quicknav>
|
||||
<a href="/wg/r3-install" class=qi title="Installer R3 WireGuard">
|
||||
<span class=qi-emoji>🌐</span><span class=qi-label>R3 Install</span>
|
||||
|
|
@ -138,6 +77,15 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
<a href="/social/me" class=qi title="Cartographie sociale — qui me piste, où ?">
|
||||
<span class=qi-emoji>🕸️</span><span class=qi-label>Ma carto</span>
|
||||
</a>
|
||||
<a href="/wg/ca.mobileconfig" class=qi title="CA R3 iPhone (.mobileconfig)">
|
||||
<span class=qi-emoji>📲</span><span class=qi-label>CA iPhone</span>
|
||||
</a>
|
||||
<a href="/wg/ca.pem" class=qi title="CA R3 Android/PC (.pem)">
|
||||
<span class=qi-emoji>🤖</span><span class=qi-label>CA Android</span>
|
||||
</a>
|
||||
<a href="/wg/qr.png" class=qi title="QR profil WireGuard">
|
||||
<span class=qi-emoji>📱</span><span class=qi-label>QR profil</span>
|
||||
</a>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb/wiki/R3-WireGuard-install" class=qi title="Wiki R3 multi-OS">
|
||||
<span class=qi-emoji>📖</span><span class=qi-label>Wiki</span>
|
||||
</a>
|
||||
|
|
@ -147,25 +95,7 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Install : auto-detected platform panel, front and centre ── #}
|
||||
<div class=container>
|
||||
<div class=section style="margin-bottom:1.5rem">
|
||||
<h2>📥 Installe en 1 tap</h2>
|
||||
<p style="font-size:0.85rem;color:var(--dim);margin-bottom:0.8rem">
|
||||
On a détecté <code>{{ install_platform }}</code> — le panneau adapté est ouvert.
|
||||
Le CA, le QR et le profil sont dedans. Autre appareil ? Déplie le bon panneau.
|
||||
</p>
|
||||
{{ install_panels | safe }}
|
||||
<p style="margin-top:0.8rem;font-size:0.78rem;color:var(--dim)">
|
||||
R3 marche hors-cabine (4G/5G, autre WiFi), couvre tout le HTTPS, et se révoque
|
||||
à tout moment. Page standalone : <a href=/wg/onboard>/wg/onboard</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Everything else, folded ── #}
|
||||
<details class=more>
|
||||
<summary><span class=chev>▸</span> En savoir plus — la cabine en détail, en chiffres, en open source</summary>
|
||||
|
||||
{# ── KPI live (auto-refresh 5s via /cumulative-stats.json) ── #}
|
||||
<div class=section>
|
||||
|
|
@ -311,6 +241,43 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{# ── Install : auto-detected platform panels (Phase 8.2 #500) ── #}
|
||||
<div class=section>
|
||||
<h2>📥 Installer R3 sur ton appareil</h2>
|
||||
<p style="font-size:0.85rem;color:var(--dim);margin-bottom:0.8rem">
|
||||
On a détecté <code>{{ install_platform }}</code> via ton navigateur — le panneau adapté
|
||||
est ouvert en premier. Autre appareil ? Déplie le bon panneau ci-dessous.
|
||||
</p>
|
||||
<style>
|
||||
.install-panel{background:rgba(0,255,65,0.04);border:1px solid rgba(0,255,65,0.25);
|
||||
border-radius:6px;padding:0.6rem 0.9rem;margin:0.45rem 0}
|
||||
.install-panel summary{cursor:pointer;font-size:0.95rem;color:var(--phos-peak,#00dd44);
|
||||
list-style:none;outline:none}
|
||||
.install-panel summary::-webkit-details-marker{display:none}
|
||||
.install-panel[open] summary{margin-bottom:0.6rem}
|
||||
.install-panel .emoji{font-size:1.1rem;margin-right:0.3rem}
|
||||
.install-panel ol{padding-left:1.1rem;line-height:1.5;font-size:0.85rem}
|
||||
.install-panel .btn{display:inline-block;padding:0.45rem 0.75rem;margin:0.25rem 0.2rem 0.25rem 0;
|
||||
background:var(--purple,#6e40c9);color:#fff;text-decoration:none;border-radius:5px;
|
||||
font-weight:bold;font-size:0.82rem}
|
||||
.install-panel .btn.alt{background:transparent;border:1px solid var(--purple,#6e40c9);
|
||||
color:var(--purple,#6e40c9)}
|
||||
.install-panel code{background:rgba(0,0,0,0.4);padding:0.1rem 0.35rem;border-radius:3px;
|
||||
font-size:0.8rem;color:var(--phos-peak,#00dd44)}
|
||||
.install-panel .note{color:var(--dim,#888);font-size:0.78rem;margin-top:0.6rem;
|
||||
border-left:2px solid var(--phos-hot,#ffb347);padding-left:0.6rem}
|
||||
.install-panel img{max-width:100%;border-radius:5px;margin:0.4rem 0}
|
||||
.install-panel pre{background:rgba(0,0,0,0.4);padding:0.5rem 0.7rem;border-radius:4px;
|
||||
overflow-x:auto;font-size:0.78rem;margin:0.4rem 0}
|
||||
</style>
|
||||
{{ install_panels | safe }}
|
||||
<p style="margin-top:0.8rem;font-size:0.78rem;color:var(--dim)">
|
||||
Avantage R3 : marche hors-cabine (4G/5G, autre WiFi). Inclut tout le trafic (HTTPS).
|
||||
Profil + CA bundlés. Le tunnel est révoquable à tout moment depuis Réglages.
|
||||
Page équivalente standalone : <a href=/wg/onboard>/wg/onboard</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{# ── Open Source ── #}
|
||||
<div class=section>
|
||||
<h2>🔓 Open Source — CMSD-1.0</h2>
|
||||
|
|
@ -319,8 +286,8 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
(audit citoyen possible, droits d'usage régis par licence CMSD). Pas de boîte noire.
|
||||
</p>
|
||||
<div style="margin-top:0.6rem">
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb" class=cta-sm>📂 Code source GitHub</a>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb/blob/master/LICENCE-CMSD-1.0.md" class="cta-sm outline">📜 Licence CMSD-1.0</a>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb" class=cta>📂 Code source GitHub</a>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb/blob/master/LICENCE-CMSD-1.0.md" class="cta outline">📜 Licence CMSD-1.0</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -347,7 +314,7 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
</div>
|
||||
</div>
|
||||
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class=footer>
|
||||
📡 Gondwana ToolBox · CyberMind / Gérald Kerma · Notre-Dame-du-Cruet (73130) · Savoie · France<br>
|
||||
|
|
@ -355,8 +322,14 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
// DIY · Open Source · Open Audit
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
||||
.v.tick{animation:flash 0.6s}
|
||||
@keyframes flash{0%{color:var(--gold);transform:scale(1.15)}100%{color:var(--phos-hot);transform:scale(1)}}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ── Live KPI auto-refresh from /cumulative-stats.json (+ count-up on first paint) ──
|
||||
// ── Live KPI auto-refresh from /cumulative-stats.json ──
|
||||
(function(){
|
||||
function dig(o,path){
|
||||
var parts = path.split('.');
|
||||
|
|
@ -376,23 +349,6 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
var ss = String(d.getSeconds()).padStart(2,'0');
|
||||
return 'maj ' + hh+':'+mm+':'+ss;
|
||||
}
|
||||
// count-up: animate each KPI from 0 → its server-rendered value, once.
|
||||
function countUp(el, target){
|
||||
var start = 0, dur = 900, t0 = null;
|
||||
function step(ts){
|
||||
if (t0 === null) t0 = ts;
|
||||
var p = Math.min(1, (ts - t0) / dur);
|
||||
var eased = 1 - Math.pow(1 - p, 3);
|
||||
el.textContent = Math.round(start + (target - start) * eased);
|
||||
if (p < 1) requestAnimationFrame(step);
|
||||
else el.textContent = target;
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
document.querySelectorAll('.kpi .v[data-live]').forEach(function(el){
|
||||
var n = parseInt(el.textContent.trim(), 10);
|
||||
if (!isNaN(n) && n > 0) countUp(el, n);
|
||||
});
|
||||
function refresh(){
|
||||
fetch('/cumulative-stats.json', {cache:'no-store'}).then(function(r){return r.json();}).then(function(d){
|
||||
document.querySelectorAll('[data-live]').forEach(function(el){
|
||||
|
|
@ -423,10 +379,16 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
var fp = document.getElementById('cert-fp-r3');
|
||||
if (!btn) return;
|
||||
|
||||
// Display the WG CA fingerprint (use ?ca=wg flag if endpoint supports it,
|
||||
// else fallback to default ca fingerprint).
|
||||
fetch('/ca/fingerprint').then(function(r){return r.json();}).then(function(d){
|
||||
fp.textContent = d.sha1 || d.sha256 || '?';
|
||||
}).catch(function(){fp.textContent='?';});
|
||||
|
||||
// Phase 6.H : 3-step probe :
|
||||
// 1) Detect if user is in WG R3 tunnel (probe our internal-only endpoint)
|
||||
// 2) Probe an external HTTPS (verifies mitm decrypt + CA trust)
|
||||
// 3) Combine results into a clear verdict
|
||||
function runProbe(){
|
||||
emj.textContent = '⏳';
|
||||
txt.innerHTML = 'Test en cours… (1/2 détection tunnel)';
|
||||
|
|
@ -454,12 +416,31 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
}
|
||||
}
|
||||
|
||||
// Phase 7 (#498) — same-origin HTTPS R3 probe.
|
||||
// The previous probe loaded http://10.99.0.1:8088/qr/splash.png as
|
||||
// an Image. iOS Safari blocks mixed content (HTTP from an HTTPS
|
||||
// page) so the request never fired and inWG always stayed false.
|
||||
// /wg/r3-check returns { tunnel: bool } based on the X-R3-Peer /
|
||||
// XFF headers mitm-wg sets via the inject_xff addon.
|
||||
fetch('/wg/r3-check?t=' + Date.now(), {cache: 'no-store'})
|
||||
.then(function(r){ return r.ok ? r.json() : {tunnel:false}; })
|
||||
.then(function(d){
|
||||
inWG = !!(d && d.tunnel);
|
||||
if (!inWG) { finalize(); return; }
|
||||
txt.innerHTML = 'Tunnel R3 détecté ✓ — test 2/2 cert mitm…';
|
||||
// Step 2 : probe an external HTTPS host that mitm-wg DOES
|
||||
// intercept (i.e. not in ignore_hosts). gstatic / google /
|
||||
// apple / fbcdn are whitelisted so they pass through with
|
||||
// their real cert ; useless to test CA trust. duckduckgo
|
||||
// isn't whitelisted, so the TLS handshake is performed by
|
||||
// mitm-wg with the R3 CA — it succeeds only if the iPhone
|
||||
// trusts that CA.
|
||||
//
|
||||
// fetch(no-cors) resolves on any successful TLS handshake
|
||||
// regardless of HTTP status, and rejects on cert / network
|
||||
// error — exactly the signal we want. Image.onload was
|
||||
// ambiguous : a 204 No Content reply (no image data) also
|
||||
// triggered onerror, making CA-trusted look like CA-untrusted.
|
||||
var extDone = false;
|
||||
fetch('https://duckduckgo.com/favicon.ico?t=' + Date.now(),
|
||||
{mode: 'no-cors', cache: 'no-store'})
|
||||
|
|
|
|||
|
|
@ -1,49 +1,3 @@
|
|||
secubox-toolbox (2.6.17-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Social correlation: domain-rollup + history + target↔tracker (#549).
|
||||
- fetch_graph() now returns three additive top-level keys (read-time,
|
||||
no schema change, d3 contract untouched):
|
||||
· by_domain — trackers rolled up under their registrable parent
|
||||
(eTLD+1, e.g. all *.doubleclick.net → doubleclick.net) with
|
||||
tracker_count / hits / sites / vendors ;
|
||||
· targets — inverse map: per 1st-party site, the trackers +
|
||||
parent domains watching it ;
|
||||
· history — per-UTC-day timeline (hits / trackers / sites) from
|
||||
the raw social_edges log over the window.
|
||||
- stats gains total_domains. Added a local _registrable_domain eTLD+1
|
||||
helper (mirrors the addon, no publicsuffix dep).
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 13 Jun 2026 13:00:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.16-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Injected banner neon-tube redesign (#545) — inject_banner.py.
|
||||
- New _LEVEL_THEME map: R3 (and the planned R4) get a neon-tube look
|
||||
(dark glass bar, glowing tube border via layered box-shadow + neon
|
||||
text-shadow on the title) ; R2 keeps the original amber flat bar.
|
||||
- _banner_html_dynamic() takes the level and themes both the
|
||||
CSP-strict (JS-less) and JS (dismissible) variants ; all inline CSS,
|
||||
no injected <style>/@keyframes, ASCII/NCR-clean for legacy charsets.
|
||||
- R4 theme defined but inert until _client_level() returns 'r4'.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 13 Jun 2026 12:30:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.15-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* kbin landing radical-simplify redesign (#543) — conf/landing.html.j2.
|
||||
- Animated hero (gazing 👁️ + floating tracker dots) + one big
|
||||
"✨ Protège-moi (R3)" CTA + "🕸️ Qui me piste ?" secondary.
|
||||
- Auto-detected install panel pulled up front ("📥 Installe en 1 tap").
|
||||
- KPIs / cert-probe / pitch / R0-R3 levels / charts / architecture /
|
||||
open-source / contact moved behind an "En savoir plus" <details> fold.
|
||||
- Quick-nav trimmed: removed CA iPhone / CA Android / QR profil cards
|
||||
(they live inside the per-platform install panel now) — kept R3
|
||||
Install / Mon rapport / Ma carto / Wiki / Cabine.
|
||||
- Count-up animation on the live KPIs on first paint. All Jinja
|
||||
variables + the live-stats and cert-probe scripts preserved.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Sat, 13 Jun 2026 12:00:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.14-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Serve the browser ToolBoX extension .xpi from the toolbox (#532).
|
||||
|
|
|
|||
|
|
@ -404,40 +404,8 @@ def _detect_csp_strict(flow: http.HTTPFlow) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
# Per-level visual theme (#545). R3 — and the planned R4 — get the
|
||||
# neon-tube treatment (dark glass bar, glowing tube border + neon
|
||||
# text-shadow). R2 keeps the original amber flat bar. All values are inline
|
||||
# CSS only (no injected <style>/@keyframes) so it survives strict CSP and
|
||||
# arbitrary third-party pages.
|
||||
_LEVEL_THEME = {
|
||||
"r2": {
|
||||
"neon": False,
|
||||
"bg": "linear-gradient(90deg,#ffb347 60%,#0a0a0f 100%)",
|
||||
"fg": "#0a0a0f", "edge": "#C04E24", "accent": "#ffb347",
|
||||
"glow": "", "link": "#0a5840", "chip": "rgba(0,0,0,0.1)",
|
||||
},
|
||||
"r3": {
|
||||
"neon": True,
|
||||
"bg": "rgba(8,8,14,0.95)",
|
||||
"fg": "#e8e6d9", "edge": "#00d4ff", "accent": "#00d4ff",
|
||||
"glow": "rgba(0,212,255,0.45)", "link": "#00d4ff",
|
||||
"chip": "rgba(0,212,255,0.12)",
|
||||
},
|
||||
# planned (#545): R4 drops in with its own neon colour — inert until
|
||||
# _client_level() can return 'r4'.
|
||||
"r4": {
|
||||
"neon": True,
|
||||
"bg": "rgba(12,8,16,0.96)",
|
||||
"fg": "#e8e6d9", "edge": "#ff3df0", "accent": "#ff3df0",
|
||||
"glow": "rgba(255,61,240,0.45)", "link": "#ff3df0",
|
||||
"chip": "rgba(255,61,240,0.12)",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _banner_html_dynamic(sha1: str, ctx: dict, csp_strict: bool,
|
||||
report_url: str, level_label: str,
|
||||
level: str = "r2") -> bytes:
|
||||
report_url: str, level_label: str) -> bytes:
|
||||
"""Render the injection payload.
|
||||
|
||||
Two flavors depending on CSP strictness :
|
||||
|
|
@ -487,47 +455,22 @@ def _banner_html_dynamic(sha1: str, ctx: dict, csp_strict: bool,
|
|||
# Static emojis used in the left-side text
|
||||
SAT_EMOJI = "📡" # 📡 satellite dish
|
||||
|
||||
# ── theme resolution (#545) : R3/R4 neon tube, R2 amber flat ──
|
||||
th = _LEVEL_THEME.get(level, _LEVEL_THEME["r2"])
|
||||
_base = (
|
||||
"position:fixed!important;top:0!important;left:0!important;right:0!important;"
|
||||
"z-index:2147483647!important;font-family:Menlo,Consolas,monospace!important;"
|
||||
"padding:6px 12px!important;font-size:11px!important;line-height:1.4!important;"
|
||||
"text-align:left!important;display:flex!important;justify-content:space-between!important;"
|
||||
"align-items:center!important;gap:8px!important;"
|
||||
)
|
||||
if th["neon"]:
|
||||
# glowing glass tube : outer + inset accent glow, neon edge
|
||||
bar_css = (
|
||||
_base
|
||||
+ f"background:{th['bg']}!important;color:{th['fg']}!important;"
|
||||
+ f"border-bottom:2px solid {th['edge']}!important;"
|
||||
+ f"box-shadow:0 0 10px {th['accent']},0 3px 22px {th['glow']},"
|
||||
f"inset 0 -1px 6px {th['glow']}!important;"
|
||||
+ "backdrop-filter:blur(3px)!important;"
|
||||
)
|
||||
title_css = f"color:{th['accent']};text-shadow:0 0 6px {th['accent']},0 0 12px {th['accent']}"
|
||||
else:
|
||||
bar_css = (
|
||||
_base
|
||||
+ f"background:{th['bg']}!important;color:{th['fg']}!important;"
|
||||
+ f"border-bottom:2px solid {th['edge']}!important;"
|
||||
+ "box-shadow:0 2px 8px rgba(0,0,0,0.3)!important;"
|
||||
)
|
||||
title_css = ""
|
||||
code_css = f"background:{th['chip']};padding:1px 4px;border-radius:2px"
|
||||
link_css = f"color:{th['link']};text-decoration:underline;font-weight:bold"
|
||||
title_attr = f" style=\"{title_css}\"" if title_css else ""
|
||||
|
||||
if csp_strict:
|
||||
# JS-less HTML banner — visible only, no close button. !important
|
||||
# everywhere so page CSS can't override the fixed positioning.
|
||||
# NCRs work even when page charset is iso-8859-1.
|
||||
html = (
|
||||
f"<div id=\"gondwana-mitm-banner\" role=\"status\" style=\"{bar_css}\">"
|
||||
f"<span><b{title_attr}>{SAT_EMOJI} ToolBoX {level_label}</b> · CA SHA1: "
|
||||
f"<code style=\"{code_css}\">{sha1[:23]}</code>"
|
||||
f" · <a href=\"{report_url}\" style=\"{link_css}\">Mon rapport</a></span>"
|
||||
f"<div id=\"gondwana-mitm-banner\" role=\"status\" "
|
||||
f"style=\"position:fixed!important;top:0!important;left:0!important;right:0!important;"
|
||||
f"z-index:2147483647!important;"
|
||||
f"background:linear-gradient(90deg,#ffb347 60%,#0a0a0f 100%)!important;"
|
||||
f"color:#0a0a0f!important;font-family:Menlo,Consolas,monospace!important;"
|
||||
f"padding:6px 12px!important;font-size:11px!important;line-height:1.4!important;"
|
||||
f"border-bottom:2px solid #C04E24!important;text-align:left!important;"
|
||||
f"display:flex!important;justify-content:space-between!important;align-items:center!important;gap:8px!important\">"
|
||||
f"<span><b>{SAT_EMOJI} ToolBoX {level_label}</b> · CA SHA1: "
|
||||
f"<code style=\"background:rgba(0,0,0,0.1);padding:1px 4px;border-radius:2px\">{sha1[:23]}</code>"
|
||||
f" · <a href=\"{report_url}\" style=\"color:#0a5840;text-decoration:underline;font-weight:bold\">Mon rapport</a></span>"
|
||||
f"<span style=\"color:#e8e6d9;background:rgba(0,0,0,0.4);padding:3px 8px;border-radius:3px\">"
|
||||
f"{right_text}"
|
||||
f" · <b style=\"color:{grade_color};background:#0a0a0f;padding:1px 5px;border-radius:2px\">{grade}</b>"
|
||||
|
|
@ -546,12 +489,6 @@ def _banner_html_dynamic(sha1: str, ctx: dict, csp_strict: bool,
|
|||
level_js = _json.dumps(level_label)
|
||||
sat_js = _json.dumps(SAT_EMOJI)
|
||||
mid_js = _json.dumps(" · ")
|
||||
# theme (#545) — JS-encoded so the same neon/amber styling applies here
|
||||
bar_css_js = _json.dumps(bar_css)
|
||||
title_attr_js = _json.dumps(title_attr)
|
||||
code_css_js = _json.dumps(code_css)
|
||||
link_css_js = _json.dumps(link_css)
|
||||
close_col_js = _json.dumps(th["fg"])
|
||||
|
||||
js = f"""
|
||||
(function(){{
|
||||
|
|
@ -562,7 +499,14 @@ def _banner_html_dynamic(sha1: str, ctx: dict, csp_strict: bool,
|
|||
var b=document.createElement('div');
|
||||
b.id='gondwana-mitm-banner';
|
||||
b.setAttribute('role','status');
|
||||
b.style.cssText={bar_css_js};
|
||||
b.style.cssText='position:fixed!important;top:0!important;left:0!important;right:0!important;'+
|
||||
'z-index:2147483647!important;'+
|
||||
'background:linear-gradient(90deg,#ffb347 60%,#0a0a0f 100%)!important;'+
|
||||
'color:#0a0a0f!important;font-family:Menlo,Consolas,monospace!important;'+
|
||||
'padding:6px 12px!important;font-size:11px!important;line-height:1.4!important;'+
|
||||
'border-bottom:2px solid #C04E24!important;box-shadow:0 2px 8px rgba(0,0,0,0.3)!important;'+
|
||||
'text-align:left!important;display:flex!important;'+
|
||||
'justify-content:space-between!important;align-items:center!important;gap:8px!important';
|
||||
var rightText={right_js};
|
||||
var grade={grade_js};
|
||||
var gradeCol={grade_col_js};
|
||||
|
|
@ -571,18 +515,14 @@ def _banner_html_dynamic(sha1: str, ctx: dict, csp_strict: bool,
|
|||
var level={level_js};
|
||||
var SAT={sat_js};
|
||||
var MID={mid_js};
|
||||
var TITLE_ATTR={title_attr_js};
|
||||
var CODE_CSS={code_css_js};
|
||||
var LINK_CSS={link_css_js};
|
||||
var CLOSE_COL={close_col_js};
|
||||
b.innerHTML='<span><b'+TITLE_ATTR+'>'+SAT+' ToolBoX '+level+'</b>'+MID+'CA SHA1: '+
|
||||
'<code style=\"'+CODE_CSS+'\">'+sha1+'</code>'+
|
||||
MID+'<a href=\"'+reportUrl+'\" style=\"'+LINK_CSS+'\">Mon rapport</a></span>'+
|
||||
b.innerHTML='<span><b>'+SAT+' ToolBoX '+level+'</b>'+MID+'CA SHA1: '+
|
||||
'<code style=\"background:rgba(0,0,0,0.1);padding:1px 4px;border-radius:2px\">'+sha1+'</code>'+
|
||||
MID+'<a href=\"'+reportUrl+'\" style=\"color:#0a5840;text-decoration:underline;font-weight:bold\">Mon rapport</a></span>'+
|
||||
'<span style=\"display:flex;align-items:center;gap:8px\">'+
|
||||
'<span style=\"color:#e8e6d9;background:rgba(0,0,0,0.4);padding:3px 8px;border-radius:3px\">'+
|
||||
rightText+MID+'<b style=\"color:'+gradeCol+';background:#0a0a0f;padding:1px 5px;border-radius:2px\">'+grade+'</b>'+
|
||||
'</span>'+
|
||||
'<a href=\"javascript:void(0)\" onclick=\"document.getElementById(\\'gondwana-mitm-banner\\').style.display=\\'none\\';document.body.style.paddingTop=0\" style=\"color:'+CLOSE_COL+';text-decoration:none;font-weight:bold;cursor:pointer\">[×]</a>'+
|
||||
'<a href=\"javascript:void(0)\" onclick=\"document.getElementById(\\'gondwana-mitm-banner\\').style.display=\\'none\\';document.body.style.paddingTop=0\" style=\"color:#0a0a0f;text-decoration:none;font-weight:bold;cursor:pointer\">[×]</a>'+
|
||||
'</span>';
|
||||
if(document.body.firstChild){{document.body.insertBefore(b,document.body.firstChild)}}
|
||||
else{{document.body.appendChild(b)}}
|
||||
|
|
@ -670,9 +610,8 @@ class InjectBanner:
|
|||
csp_strict = _detect_csp_strict(flow)
|
||||
report_url = _report_url_for(flow)
|
||||
level_label = _level_label(flow)
|
||||
level = _client_level(flow)
|
||||
snippet = _banner_html_dynamic(_CA_SHA1, ctx, csp_strict,
|
||||
report_url, level_label, level)
|
||||
report_url, level_label)
|
||||
except Exception as e:
|
||||
log.warning("banner compute failed for %s: %s", flow.request.host, e)
|
||||
# Fail-open : skip injection rather than break the page
|
||||
|
|
|
|||
|
|
@ -682,39 +682,12 @@ def fold_recent(window_seconds: int = 300) -> Tuple[int, int]:
|
|||
return nodes_touched, links_touched
|
||||
|
||||
|
||||
# eTLD+1 rollup (#549). Mirror of the addon's _registrable_domain so the
|
||||
# graph can group trackers under their registrable parent (all
|
||||
# *.doubleclick.net → doubleclick.net) without a publicsuffix dependency.
|
||||
_MULTI_LABEL_TLDS = {
|
||||
"co.uk", "ac.uk", "gov.uk", "org.uk", "net.uk",
|
||||
"co.jp", "ne.jp", "ac.jp",
|
||||
"com.au", "net.au", "org.au",
|
||||
"com.br", "com.cn", "com.hk", "com.tw", "com.mx",
|
||||
}
|
||||
|
||||
|
||||
def _registrable_domain(host: str) -> str:
|
||||
"""Cheap eTLD+1 : www.lemonde.fr → lemonde.fr ; a.b.example.co.uk →
|
||||
example.co.uk. Raw IPs and single-label hosts pass through."""
|
||||
h = (host or "").lower().strip(".")
|
||||
if not h or h.replace(".", "").replace(":", "").isdigit():
|
||||
return h
|
||||
parts = h.split(".")
|
||||
if len(parts) < 2:
|
||||
return h
|
||||
last_two = ".".join(parts[-2:])
|
||||
if last_two in _MULTI_LABEL_TLDS and len(parts) >= 3:
|
||||
return ".".join(parts[-3:])
|
||||
return last_two
|
||||
|
||||
|
||||
def fetch_graph(mac_hash: str, since_seconds: int = 86400) -> Dict:
|
||||
"""Return the per-client graph JSON contract.
|
||||
|
||||
{nodes:[{id,domain,family,hits,sites_count}],
|
||||
edges:[{src,dst,reuse_count,shared_trackers[],ja4_match}],
|
||||
stats:{total_trackers,total_sites,first_seen,last_seen},
|
||||
by_domain:[...], targets:[...], history:[...]} # additive (#549)
|
||||
stats:{total_trackers,total_sites,first_seen,last_seen}}
|
||||
"""
|
||||
since = int(time.time()) - max(since_seconds, 3600)
|
||||
out: Dict = {"nodes": [], "edges": [], "stats": {}}
|
||||
|
|
@ -797,84 +770,9 @@ def fetch_graph(mac_hash: str, since_seconds: int = 86400) -> Dict:
|
|||
# Phase 12.C — operator-grade / state-adjacent surfaces.
|
||||
opgrade = opgrade_for_client(mac_hash, since_seconds=since_seconds)
|
||||
out["opgrade"] = opgrade
|
||||
# ── #549 additive aggregations (read-time, no schema change) ──
|
||||
# (a) by_domain : roll trackers up under registrable parent.
|
||||
_dom: Dict[str, dict] = {}
|
||||
for n in out["nodes"]:
|
||||
parent = _registrable_domain(n["domain"])
|
||||
d = _dom.setdefault(parent, {
|
||||
"domain": parent, "tracker_count": 0, "hits": 0,
|
||||
"_trackers": set(), "_sites": set(), "_vendors": set(),
|
||||
"last_seen": 0,
|
||||
})
|
||||
d["_trackers"].add(n["domain"])
|
||||
d["hits"] += n["hits"] or 0
|
||||
d["_sites"].update(n["sites"])
|
||||
d["last_seen"] = max(d["last_seen"], n["last_seen"] or 0)
|
||||
for v in (n.get("cdn_vendor"), n.get("antibot_vendor"),
|
||||
n.get("opgrade_vendor")):
|
||||
if v:
|
||||
d["_vendors"].add(v)
|
||||
by_domain = []
|
||||
for d in _dom.values():
|
||||
by_domain.append({
|
||||
"domain": d["domain"],
|
||||
"tracker_count": len(d["_trackers"]),
|
||||
"trackers": sorted(d["_trackers"])[:30],
|
||||
"hits": d["hits"],
|
||||
"sites_count": len(d["_sites"]),
|
||||
"sites": sorted(d["_sites"])[:20],
|
||||
"vendors": sorted(d["_vendors"]),
|
||||
"last_seen": d["last_seen"],
|
||||
})
|
||||
by_domain.sort(key=lambda x: (-x["hits"], -x["tracker_count"]))
|
||||
out["by_domain"] = by_domain
|
||||
|
||||
# (b) targets : invert sites→trackers (who watches each page).
|
||||
_tgt: Dict[str, dict] = {}
|
||||
for n in out["nodes"]:
|
||||
for s in n["sites"]:
|
||||
t = _tgt.setdefault(s, {
|
||||
"site": s, "hits": 0,
|
||||
"_trackers": set(), "_domains": set(),
|
||||
})
|
||||
t["_trackers"].add(n["domain"])
|
||||
t["_domains"].add(_registrable_domain(n["domain"]))
|
||||
t["hits"] += n["hits"] or 0
|
||||
targets = []
|
||||
for t in _tgt.values():
|
||||
targets.append({
|
||||
"site": t["site"],
|
||||
"tracker_count": len(t["_trackers"]),
|
||||
"trackers": sorted(t["_trackers"])[:30],
|
||||
"parent_domains": sorted(t["_domains"]),
|
||||
"hits": t["hits"],
|
||||
})
|
||||
targets.sort(key=lambda x: (-x["tracker_count"], -x["hits"]))
|
||||
out["targets"] = targets
|
||||
|
||||
# (c) history : per-(UTC)day timeline from the raw edge log.
|
||||
history = []
|
||||
for r in c.execute(
|
||||
"SELECT (ts/86400) AS day_epoch, COUNT(*) AS hits, "
|
||||
"COUNT(DISTINCT tracker_domain) AS trackers, "
|
||||
"COUNT(DISTINCT src_site) AS sites "
|
||||
"FROM social_edges WHERE client_mac_hash = ? AND ts >= ? "
|
||||
"GROUP BY day_epoch ORDER BY day_epoch",
|
||||
(mac_hash, since),
|
||||
).fetchall():
|
||||
history.append({
|
||||
"day": int(r["day_epoch"]) * 86400,
|
||||
"hits": r["hits"],
|
||||
"trackers": r["trackers"],
|
||||
"sites": r["sites"],
|
||||
})
|
||||
out["history"] = history
|
||||
|
||||
out["stats"] = {
|
||||
"total_trackers": (stats_row["total_trackers"] or 0) if stats_row else 0,
|
||||
"total_sites": sites_count,
|
||||
"total_domains": len(by_domain),
|
||||
"first_seen": stats_row["first_seen"] if stats_row else None,
|
||||
"last_seen": stats_row["last_seen"] if stats_row else None,
|
||||
"antibot_sites": len({a["src_site"] for a in antibot}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user