Compare commits

..

2 Commits

Author SHA1 Message Date
41dbdadaa2 fix(toolbox): landing report/carto links carry ?mh= → no 'identity unresolved' (ref #683)
Clicking "Ma carto" / "Mon rapport" / "Qui me piste ?" hit /social/me +
/report/me with no ?mh=, so identity was re-resolved at click-time and could
400 "client identity unresolved" (off-tunnel/captive, or when X-R3-Peer wasn't
present on that request). The landing already knows the caller — now it resolves
mac_hash (new _client_mac_hash: ?mh → R3 WG peer → captive ARP) and bakes ?mh=
into the links so they always open the right client's view.

Verified live: R3 peer 10.99.1.2 → links carry ?mh=1b0ec958…; captive caller →
?mh from ARP. toolbox 2.7.8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:32:35 +02:00
e1b2e6ccbb fix(toolbox): injected banner trackers/cookies stuck at 0 — count live (ref #683)
The bar counted trackers (Resource Timing) + cookies (document.cookie) ONCE at
render time, which fires early — before resources/cookies have loaded — so it
showed 0, and the 2s poll's ensure() early-returned once the banner existed, so
it never refreshed. Spans now carry ids (sbx-trk/sbx-ck) and updateCounts()
re-counts on the poll → values climb to real within ~2s. toolbox 2.7.7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:21:04 +02:00
4 changed files with 75 additions and 8 deletions

View File

@ -123,7 +123,7 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
<div class=ctas> <div class=ctas>
<a href="/wg/r3-install" class="cta go">✨ Protège-moi (R3)</a> <a href="/wg/r3-install" class="cta go">✨ Protège-moi (R3)</a>
<a href="/social/me" class="cta alt">🕸️ Qui me piste ?</a> <a href="/social/me{{ '?mh=' + mac_hash if mac_hash else '' }}" class="cta alt">🕸️ Qui me piste ?</a>
</div> </div>
{# trimmed quick-nav — CA iPhone / CA Android / QR profil moved into the {# trimmed quick-nav — CA iPhone / CA Android / QR profil moved into the
@ -132,10 +132,10 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
<a href="/wg/r3-install" class=qi title="Installer R3 WireGuard"> <a href="/wg/r3-install" class=qi title="Installer R3 WireGuard">
<span class=qi-emoji>🌐</span><span class=qi-label>R3 Install</span> <span class=qi-emoji>🌐</span><span class=qi-label>R3 Install</span>
</a> </a>
<a href="/report/me/html" class=qi title="Mon rapport live"> <a href="/report/me/html{{ '?mh=' + mac_hash if mac_hash else '' }}" class=qi title="Mon rapport live">
<span class=qi-emoji>📊</span><span class=qi-label>Mon rapport</span> <span class=qi-emoji>📊</span><span class=qi-label>Mon rapport</span>
</a> </a>
<a href="/social/me" class=qi title="Cartographie sociale — qui me piste, où ?"> <a href="/social/me{{ '?mh=' + mac_hash if mac_hash else '' }}" class=qi title="Cartographie sociale — qui me piste, où ?">
<span class=qi-emoji>🕸️</span><span class=qi-label>Ma carto</span> <span class=qi-emoji>🕸️</span><span class=qi-label>Ma carto</span>
</a> </a>
<a href="https://github.com/CyberMind-FR/secubox-deb/wiki/R3-WireGuard-install" class=qi title="Wiki R3 multi-OS"> <a href="https://github.com/CyberMind-FR/secubox-deb/wiki/R3-WireGuard-install" class=qi title="Wiki R3 multi-OS">

View File

@ -1,3 +1,25 @@
secubox-toolbox (2.7.8-1~bookworm1) bookworm; urgency=medium
* fix: landing "Ma carto" / "Mon rapport" / "Qui me piste ?" links hit
/social/me + /report/me with NO ?mh=, so clicking them re-resolved identity
at click-time and could 400 "client identity unresolved". The landing now
resolves the caller's mac_hash (new _client_mac_hash helper: ?mh → R3 WG peer
→ captive ARP) and bakes ?mh= into those links, so they always open the right
client's view. No change to identity precedence; resolution stays server-side.
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 18:40:00 +0200
secubox-toolbox (2.7.7-1~bookworm1) bookworm; urgency=medium
* fix: injected banner trackers/cookies counts were stuck at 0. They were
computed ONCE at render time — which fires early, before page resources +
cookies have loaded (Resource Timing empty) — and the 2s poll's ensure()
early-returned once the banner existed, so the counts never refreshed. Now
the trackers/cookies spans have ids and updateCounts() re-counts live on the
poll, so they climb to real values within ~2s.
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 18:10:00 +0200
secubox-toolbox (2.7.6-1~bookworm1) bookworm; urgency=medium secubox-toolbox (2.7.6-1~bookworm1) bookworm; urgency=medium
* fix(#683): the 🧅 Tor indicator now appears on the ACTUAL injected banner. * fix(#683): the 🧅 Tor indicator now appears on the ACTUAL injected banner.

View File

@ -328,6 +328,36 @@ def _client_ip(request: Request) -> str | None:
return request.client.host if request.client else None return request.client.host if request.client else None
def _client_mac_hash(request: Request, salt: str) -> str | None:
"""Resolve the caller's identity hash, same precedence as /social/me:
explicit ?mh= R3 WG peer (wg-peers.json) captive ARP. Returns None when
unresolvable. Used to bake ?mh= into the landing links so they never hit the
'identity unresolved' 400 (the page already knows who you are)."""
mh_qp = (request.query_params.get("mh") or "").strip().lower()
if mh_qp and all(c in "0123456789abcdef" for c in mh_qp) and 8 <= len(mh_qp) <= 64:
return mh_qp
ip = _client_ip(request)
if ip and ip.startswith("10.99.1."):
try:
import hashlib as _h
import json as _j
from pathlib import Path as _P
_db = _P("/var/lib/secubox/toolbox/wg-peers.json")
if _db.exists():
for pubkey, meta in _j.loads(_db.read_text()).get("peers", {}).items():
if meta.get("ip") == ip:
return _h.sha256(pubkey.encode()).hexdigest()[:16]
except Exception:
pass
try:
_ip, mac = _resolve(request)
if mac:
return macmod.hash_mac(mac, salt)
except Exception:
pass
return None
# ───────────────── Public routes ───────────────── # ───────────────── Public routes ─────────────────
@router.get("/", response_class=HTMLResponse) @router.get("/", response_class=HTMLResponse)
@ -701,11 +731,15 @@ async def landing(request: Request) -> HTMLResponse:
stats = _cumulative_stats() stats = _cumulative_stats()
platform = _ua_platform(request.headers.get("user-agent") or "") platform = _ua_platform(request.headers.get("user-agent") or "")
install_panels = _install_panels_html(platform) install_panels = _install_panels_html(platform)
# Resolve identity now (the page knows who you are) so the report/carto links
# carry ?mh= and never hit the /social/me "identity unresolved" 400.
mac_hash = _client_mac_hash(request, _get_salt()) or ""
return HTMLResponse( return HTMLResponse(
_env.get_template("landing.html.j2").render( _env.get_template("landing.html.j2").render(
stats=stats, stats=stats,
install_panels=install_panels, install_panels=install_panels,
install_platform=platform, install_platform=platform,
mac_hash=mac_hash,
), ),
headers={"Cache-Control": "private, max-age=60, no-transform"}, headers={"Cache-Control": "private, max-age=60, no-transform"},
) )

View File

@ -151,12 +151,23 @@ _BANNER_CORE = r"""
return Object.keys(seen).length; return Object.keys(seen).length;
} catch (_) { return 0; } } catch (_) { return 0; }
} }
function countCookies(){
try { return document.cookie ? document.cookie.split(";").filter(function(x){return x.indexOf("=")>=0;}).length : 0; } catch (_) { return 0; }
}
// #683 — counts are taken at render time, but resources + cookies keep loading
// AFTER the banner appears (early render stuck at 0). Re-count live on the
// 2s poll so trackers/cookies climb to their real values.
function updateCounts(b){
var t = document.getElementById("sbx-trk");
if (t) t.textContent = "🛰️ " + countTrackers((b || {}).tracker_patterns) + " trackers";
var c = document.getElementById("sbx-ck");
if (c) c.textContent = "🍪 " + countCookies() + " cookies";
}
function render(b){ function render(b){
if (dismissed) return; if (dismissed) return;
if (document.getElementById("sbx-banner")) return; if (document.getElementById("sbx-banner")) return;
var trk = countTrackers(b.tracker_patterns); var trk = countTrackers(b.tracker_patterns);
var ck = 0; var ck = countCookies();
try { ck = document.cookie ? document.cookie.split(";").filter(function(x){return x.indexOf("=")>=0;}).length : 0; } catch (_) {}
var bar = document.createElement("div"); var bar = document.createElement("div");
bar.id = "sbx-banner"; bar.id = "sbx-banner";
bar.setAttribute("style", "position:fixed;left:0;right:0;top:0;z-index:2147483647;" bar.setAttribute("style", "position:fixed;left:0;right:0;top:0;z-index:2147483647;"
@ -174,8 +185,8 @@ _BANNER_CORE = r"""
+ cspProof + cspProof
+ tor + tor
+ "<span>" + esc((b.level || "r1").toUpperCase()) + "</span>" + "<span>" + esc((b.level || "r1").toUpperCase()) + "</span>"
+ "<span>🛰️ " + trk + " trackers</span>" + "<span id=\"sbx-trk\">🛰️ " + trk + " trackers</span>"
+ "<span>🍪 " + ck + " cookies</span>" + "<span id=\"sbx-ck\">🍪 " + ck + " cookies</span>"
+ pin + pin
+ "<a href=\"" + esc(b.report_url || "#") + "\" style=\"margin-left:auto;color:#2C70C0;text-decoration:none\">report ▸</a>" + "<a href=\"" + esc(b.report_url || "#") + "\" style=\"margin-left:auto;color:#2C70C0;text-decoration:none\">report ▸</a>"
+ "<button aria-label=\"dismiss\" style=\"background:none;border:0;color:#8A9AA8;cursor:pointer;font-size:14px\">✕</button>"; + "<button aria-label=\"dismiss\" style=\"background:none;border:0;color:#8A9AA8;cursor:pointer;font-size:14px\">✕</button>";
@ -186,7 +197,7 @@ _BANNER_CORE = r"""
} }
// ensure(): (re)render the banner if it's absent and the bundle is loaded and // ensure(): (re)render the banner if it's absent and the bundle is loaded and
// the user hasn't dismissed it. Cheap (a getElementById guard inside render). // the user hasn't dismissed it. Cheap (a getElementById guard inside render).
function ensure(){ if (bundle && !dismissed) ready(function(){ render(bundle); }); } function ensure(){ if (bundle && !dismissed) ready(function(){ if (document.getElementById("sbx-banner")) updateCounts(bundle); else render(bundle); }); }
// SPA re-assert: wrap history nav + popstate (defer so the framework settles), // SPA re-assert: wrap history nav + popstate (defer so the framework settles),
// plus a light 2s poll as a catch-all for DOM re-renders that drop the banner. // plus a light 2s poll as a catch-all for DOM re-renders that drop the banner.
["pushState","replaceState"].forEach(function(m){ ["pushState","replaceState"].forEach(function(m){