mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-29 21:38:35 +00:00
Compare commits
2 Commits
20dfad720c
...
3fcdb8bd9a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fcdb8bd9a | ||
| cc478872ae |
|
|
@ -209,7 +209,10 @@ func injectInlineBanner(body []byte, scriptBody string) []byte {
|
|||
// match would never fire. Mirrors the Python request() p.startswith(...) checks.
|
||||
func isToolboxAssetPath(path string) bool {
|
||||
return strings.HasPrefix(path, "/__toolbox/loader.js") ||
|
||||
strings.HasPrefix(path, "/__toolbox/bundle")
|
||||
strings.HasPrefix(path, "/__toolbox/bundle") ||
|
||||
// #724 — banner R0..R3 level switch: same-origin GET from the page,
|
||||
// reverse-proxied to the portal /__toolbox/set-level.
|
||||
strings.HasPrefix(path, "/__toolbox/set-level")
|
||||
}
|
||||
|
||||
// portalTargetURL builds the absolute portal URL for an intercepted asset
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
secubox-toolbox-ng (0.1.15-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* #724 — intercept /__toolbox/set-level (banner level switch) and reverse-proxy
|
||||
it to the portal, same as /__toolbox/loader.js + /__toolbox/bundle.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Mon, 22 Jun 2026 15:10:00 +0000
|
||||
|
||||
secubox-toolbox-ng (0.1.14-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* quic/banner: strip Alt-Svc response header so browsers stop learning/preferring
|
||||
|
|
|
|||
|
|
@ -1,3 +1,13 @@
|
|||
secubox-toolbox (2.7.17-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* #724 banner: in-banner R0..R3 level switch — the injected transparency
|
||||
banner now shows the real client level AND lets the client change it inline
|
||||
(GET /__toolbox/set-level, reverse-proxied by sbxmitm like /__toolbox/bundle;
|
||||
store.set_client_level by hash; bundle cache invalidated so it reflects at
|
||||
once). r2 gated by config, r3 by wg server.pubkey.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Mon, 22 Jun 2026 15:10:00 +0000
|
||||
|
||||
secubox-toolbox (2.7.16-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix: restore banner on heavy sites (leparisien.fr). The #685 stream_large_bodies=1m
|
||||
|
|
|
|||
|
|
@ -78,6 +78,39 @@ async def toolbox_bundle(mh: str = Query(default=""), wg: int = Query(default=0)
|
|||
)
|
||||
|
||||
|
||||
@router.get("/__toolbox/set-level")
|
||||
async def toolbox_set_level(mh: str = Query(default=""), level: str = Query(default="")) -> JSONResponse:
|
||||
"""#724 — banner self-service level switch. Reverse-proxied to the portal by
|
||||
sbxmitm (same-origin from the page) like /__toolbox/bundle, so it carries no
|
||||
cookies/CSRF and is identified by the client's baked ``mh`` hash. Persists the
|
||||
analysis tier for that hash (R3 wg peers have no captive MAC, so this is the
|
||||
by-hash setter, not the nft-based /change-level)."""
|
||||
mh = (mh or "").strip().lower()
|
||||
level = (level or "").strip().lower()
|
||||
if not (mh and all(c in "0123456789abcdef" for c in mh) and 8 <= len(mh) <= 64):
|
||||
return JSONResponse({"ok": False, "error": "bad mh"}, status_code=400,
|
||||
headers={"Cache-Control": "no-store"})
|
||||
if level not in ("r0", "r1", "r2", "r3"):
|
||||
return JSONResponse({"ok": False, "error": "bad level"}, status_code=400,
|
||||
headers={"Cache-Control": "no-store"})
|
||||
# honour the same gates as /change-level
|
||||
try:
|
||||
cfg = _get_cfg()
|
||||
if level == "r2" and not cfg.r2.enabled:
|
||||
level = "r1"
|
||||
except Exception:
|
||||
pass
|
||||
if level == "r3" and not Path("/etc/secubox/toolbox/wg/server.pubkey").exists():
|
||||
level = "r1"
|
||||
try:
|
||||
store.set_client_level(mh, level)
|
||||
bundlemod.invalidate(mh) # drop cached bundle so the new level shows at once
|
||||
except Exception as e: # pragma: no cover
|
||||
return JSONResponse({"ok": False, "error": str(e)}, status_code=500,
|
||||
headers={"Cache-Control": "no-store"})
|
||||
return JSONResponse({"ok": True, "level": level}, headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
@router.get("/__toolbox/inline")
|
||||
async def toolbox_inline(
|
||||
mh: str = Query(default=""),
|
||||
|
|
|
|||
|
|
@ -95,6 +95,14 @@ def build_bundle(client_id: str, is_wg: bool = False) -> dict:
|
|||
}
|
||||
|
||||
|
||||
def invalidate(client_id: str) -> None:
|
||||
"""#724 — drop a client's cached bundle (both wg variants) so a level switch
|
||||
is reflected on the next banner render without waiting for the TTL."""
|
||||
cid = client_id or ""
|
||||
for k in ((cid, True), (cid, False)):
|
||||
_cache.pop(k, None)
|
||||
|
||||
|
||||
def get_bundle(client_id: str, is_wg: bool = False) -> dict:
|
||||
"""Return the cached bundle for a client, rebuilding past the TTL. Fail-open."""
|
||||
try:
|
||||
|
|
@ -163,6 +171,35 @@ _BANNER_CORE = r"""
|
|||
var c = document.getElementById("sbx-ck");
|
||||
if (c) c.textContent = "🍪 " + countCookies() + " cookies";
|
||||
}
|
||||
// #724 — inline R0..R3 level switch. Shows the real current level (highlighted)
|
||||
// and lets the client change it: GET /__toolbox/set-level (same-origin, the Go
|
||||
// engine reverse-proxies it to the portal), then reload so the new tier applies.
|
||||
function lvlSwitch(b){
|
||||
var cur = String(b.level || "r1").toLowerCase();
|
||||
var lv = ["r0","r1","r2","r3"], out = "<span id=\"sbx-lvl\" title=\"Niveau d'analyse — clique pour changer\">";
|
||||
for (var i=0;i<lv.length;i++){ var on = lv[i]===cur;
|
||||
out += "<button data-lvl=\"" + lv[i] + "\" class=\"sbx-lvl\" style=\"background:"
|
||||
+ (on?"#148C66":"transparent") + ";color:" + (on?"#0A0E14":"#8A9AA8")
|
||||
+ ";border:1px solid #148C66;border-radius:3px;padding:0 5px;margin:0 1px;"
|
||||
+ "font:inherit;font-size:11px;cursor:pointer\">" + lv[i].toUpperCase() + "</button>";
|
||||
}
|
||||
return out + "</span>";
|
||||
}
|
||||
function wireLevels(bar, b){
|
||||
var els = bar.querySelectorAll(".sbx-lvl");
|
||||
for (var i=0;i<els.length;i++){ (function(el){
|
||||
el.onclick = function(){
|
||||
var lvl = el.getAttribute("data-lvl");
|
||||
var who = (typeof mh !== "undefined" && mh) ? mh : (b.client_id || "");
|
||||
if (!who) return;
|
||||
el.textContent = "…";
|
||||
fetch("/__toolbox/set-level?mh=" + encodeURIComponent(who) + "&level=" + lvl,
|
||||
{credentials:"omit", cache:"no-store"})
|
||||
.then(function(r){ if (r && r.ok) { location.reload(); } else { el.textContent = lvl.toUpperCase(); } })
|
||||
.catch(function(){ el.textContent = lvl.toUpperCase(); });
|
||||
};
|
||||
})(els[i]); }
|
||||
}
|
||||
function render(b){
|
||||
if (dismissed) return;
|
||||
if (document.getElementById("sbx-banner")) return;
|
||||
|
|
@ -184,7 +221,7 @@ _BANNER_CORE = r"""
|
|||
bar.innerHTML = "<b style=\"color:#148C66\">SecuBox</b>"
|
||||
+ cspProof
|
||||
+ tor
|
||||
+ "<span>" + esc((b.level || "r1").toUpperCase()) + "</span>"
|
||||
+ lvlSwitch(b)
|
||||
+ "<span id=\"sbx-trk\">🛰️ " + trk + " trackers</span>"
|
||||
+ "<span id=\"sbx-ck\">🍪 " + ck + " cookies</span>"
|
||||
+ pin
|
||||
|
|
@ -192,7 +229,8 @@ _BANNER_CORE = r"""
|
|||
+ "<button aria-label=\"dismiss\" style=\"background:none;border:0;color:#8A9AA8;cursor:pointer;font-size:14px\">✕</button>";
|
||||
document.body.appendChild(bar);
|
||||
try { document.body.style.paddingTop = (bar.offsetHeight || 34) + "px"; } catch (_) {}
|
||||
var btn = bar.querySelector("button");
|
||||
wireLevels(bar, b);
|
||||
var btn = bar.querySelector("button[aria-label=\"dismiss\"]");
|
||||
if (btn) btn.onclick = function(){ dismissed = true; try { document.body.style.paddingTop = ""; } catch (_) {} bar.remove(); };
|
||||
}
|
||||
// ensure(): (re)render the banner if it's absent and the bundle is loaded and
|
||||
|
|
|
|||
|
|
@ -251,6 +251,27 @@ def upsert_client(mac_hash: str, ip: str, level: str = "r1") -> None:
|
|||
)
|
||||
|
||||
|
||||
def set_client_level(mac_hash: str, level: str) -> None:
|
||||
"""#724 — set a client's level by hash only (no ip needed), for the banner
|
||||
self-service switch on R3 wg peers (which have no captive MAC/ip). Updates the
|
||||
existing row; inserts a minimal row if the client is unknown."""
|
||||
now = int(time.time())
|
||||
with _conn() as c:
|
||||
try:
|
||||
c.execute("ALTER TABLE clients ADD COLUMN level TEXT NOT NULL DEFAULT 'r1'")
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
cur = c.execute("UPDATE clients SET level=?, last_seen=? WHERE mac_hash=?",
|
||||
(level, now, mac_hash))
|
||||
if cur.rowcount == 0:
|
||||
c.execute(
|
||||
"INSERT INTO clients(mac_hash, ip, level, first_seen, last_seen) "
|
||||
"VALUES (?,?,?,?,?) ON CONFLICT(mac_hash) DO UPDATE SET "
|
||||
"level=excluded.level, last_seen=excluded.last_seen",
|
||||
(mac_hash, "?", level, now, now),
|
||||
)
|
||||
|
||||
|
||||
def get_client_level(mac_hash: str) -> str:
|
||||
"""Returns 'r0' | 'r1' | 'r2'. Default 'r1' if not found."""
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user