mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-29 19:43:27 +00:00
Compare commits
2 Commits
f69384f1e0
...
6160ec9aaa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6160ec9aaa | ||
| f30b3b39f8 |
|
|
@ -1,3 +1,18 @@
|
|||
secubox-toolbox (2.6.56-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(#653): banner reliability — the injected loader now carries the
|
||||
per-client bundle inline (`data-bundle` attr) and renders WITHOUT a second
|
||||
`fetch('/__toolbox/bundle')`: faster (one less round-trip) and no
|
||||
`connect-src` CSP dependency. Fetch retained as back-compat fallback.
|
||||
* fix(#653): loader re-asserts the banner across SPA navigations
|
||||
(pushState/replaceState/popstate) and if the DOM drops it — render() stays
|
||||
idempotent (guards on #sbx-banner). NOTE: genuinely strict-CSP sites
|
||||
(script-src 'none' / strict style-src) still can't be served from the page
|
||||
without weakening their CSP — that's the browser-extension's job (separate),
|
||||
we deliberately do NOT rewrite site CSP headers.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Thu, 18 Jun 2026 16:30:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.55-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* perf(#651): broaden the media SNI-splice seed (11 → 35 video/image/audio
|
||||
|
|
|
|||
|
|
@ -702,8 +702,20 @@ def _stream_enabled() -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _attr_escape(s: str) -> str:
|
||||
"""Escape a string for an HTML double-quoted attribute value."""
|
||||
return (s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
.replace('"', """).replace("'", "'"))
|
||||
|
||||
|
||||
def _loader_script(flow) -> bytes:
|
||||
"""Tiny loader <script> tag carrying the client identity + WG flag."""
|
||||
"""Tiny loader <script> tag carrying the client identity + WG flag.
|
||||
|
||||
#653 — inline the per-client bundle as a data- attribute so the loader
|
||||
renders the banner WITHOUT a second `fetch('/__toolbox/bundle')`: faster
|
||||
(one less round-trip) and no `connect-src` CSP dependency. Best-effort; the
|
||||
loader falls back to the fetch path when the attribute is absent.
|
||||
"""
|
||||
mh, wg = "", "0"
|
||||
try:
|
||||
ip = flow.client_conn.peername[0] if flow.client_conn.peername else None
|
||||
|
|
@ -713,8 +725,17 @@ def _loader_script(flow) -> bytes:
|
|||
mh = mac_hash_of(ip) or ""
|
||||
except Exception:
|
||||
pass
|
||||
tag = ('<script src="/__toolbox/loader.js" data-mh="%s" data-wg="%s" async></script>'
|
||||
% (mh, wg))
|
||||
bundle_attr = ""
|
||||
if _HAS_LEVEL:
|
||||
try:
|
||||
import json as _json
|
||||
from secubox_toolbox import bundle as _b
|
||||
j = _json.dumps(_b.get_bundle(mh, wg == "1"), separators=(",", ":"))
|
||||
bundle_attr = ' data-bundle="%s"' % _attr_escape(j)
|
||||
except Exception:
|
||||
bundle_attr = ""
|
||||
tag = ('<script src="/__toolbox/loader.js" data-mh="%s" data-wg="%s"%s async></script>'
|
||||
% (mh, wg, bundle_attr))
|
||||
return b"<!-- " + _GUARD + b" -->" + tag.encode("ascii", "ignore")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -150,9 +150,31 @@ LOADER_JS = r"""(function(){
|
|||
var btn = bar.querySelector("button");
|
||||
if (btn) btn.onclick = function(){ try { document.body.style.paddingTop = ""; } catch (_) {} bar.remove(); };
|
||||
}
|
||||
fetch("/__toolbox/bundle?mh=" + encodeURIComponent(mh) + "&wg=" + encodeURIComponent(wg), {credentials:"omit"})
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(b){ ready(function(){ render(b); }); })
|
||||
.catch(function(){});
|
||||
// #653 — re-assert the banner across SPA navigations / DOM wipes. render()
|
||||
// is idempotent (guards on #sbx-banner), so re-calling is safe.
|
||||
function setupReassert(b){
|
||||
if (window.__SBX_REASSERT__) return; window.__SBX_REASSERT__ = 1;
|
||||
function reassert(){ ready(function(){ if (!document.getElementById("sbx-banner")) render(b); }); }
|
||||
["pushState","replaceState"].forEach(function(m){
|
||||
var orig = history[m];
|
||||
if (typeof orig === "function") {
|
||||
history[m] = function(){ var r = orig.apply(this, arguments); setTimeout(reassert, 60); return r; };
|
||||
}
|
||||
});
|
||||
window.addEventListener("popstate", function(){ setTimeout(reassert, 60); });
|
||||
}
|
||||
function go(b){ if (!b) return; ready(function(){ render(b); }); setupReassert(b); }
|
||||
// #653 — prefer the inlined bundle (no fetch → faster, no connect-src dep);
|
||||
// fall back to the fetch when the data- attribute is absent (back-compat).
|
||||
var inb = null;
|
||||
try { if (ds.bundle) inb = JSON.parse(ds.bundle); } catch (_) { inb = null; }
|
||||
if (inb) {
|
||||
go(inb);
|
||||
} else {
|
||||
fetch("/__toolbox/bundle?mh=" + encodeURIComponent(mh) + "&wg=" + encodeURIComponent(wg), {credentials:"omit"})
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(go)
|
||||
.catch(function(){});
|
||||
}
|
||||
})();
|
||||
"""
|
||||
|
|
|
|||
27
packages/secubox-toolbox/tests/test_banner_inline_bundle.py
Normal file
27
packages/secubox-toolbox/tests/test_banner_inline_bundle.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
import sys, pathlib, importlib, re
|
||||
ADDON_DIR = pathlib.Path(__file__).resolve().parents[1] / "mitmproxy_addons"
|
||||
sys.path.insert(0, str(ADDON_DIR))
|
||||
from mitmproxy.test import tflow # noqa: E402
|
||||
|
||||
|
||||
def test_attr_escape():
|
||||
import inject_banner
|
||||
assert inject_banner._attr_escape('a"b<c>&\'') == 'a"b<c>&''
|
||||
|
||||
|
||||
def test_loader_script_inlines_bundle():
|
||||
import inject_banner; importlib.reload(inject_banner)
|
||||
out = inject_banner._loader_script(tflow.tflow()).decode("ascii", "ignore")
|
||||
assert 'src="/__toolbox/loader.js"' in out
|
||||
assert 'data-bundle="' in out # bundle inlined (no fetch needed)
|
||||
m = re.search(r'data-bundle="([^"]*)"', out)
|
||||
assert m and """ in m.group(1) # JSON quotes attr-escaped
|
||||
|
||||
|
||||
def test_loader_js_uses_inline_then_fetch_fallback():
|
||||
from secubox_toolbox import bundle
|
||||
js = bundle.LOADER_JS
|
||||
assert "ds.bundle" in js and "JSON.parse" in js # reads inlined bundle
|
||||
assert "setupReassert" in js # SPA re-assert present
|
||||
assert 'fetch("/__toolbox/bundle' in js # fetch retained as fallback
|
||||
|
|
@ -48,6 +48,11 @@ def test_get_bundle_caches(monkeypatch):
|
|||
|
||||
|
||||
def test_loader_js_is_served_string():
|
||||
assert "addEventListener" not in bundle.LOADER_JS # uses currentScript pattern
|
||||
assert "__toolbox/bundle" in bundle.LOADER_JS
|
||||
# Initial render must NOT depend on a load/DOMContentLoaded event — it uses
|
||||
# currentScript + ready() polling so it works even when injected mid-stream.
|
||||
# (#653: the popstate listener is for SPA re-assert, not initial render, so a
|
||||
# blanket "no addEventListener" ban is too broad — assert the real intent.)
|
||||
assert "DOMContentLoaded" not in bundle.LOADER_JS
|
||||
assert 'addEventListener("load"' not in bundle.LOADER_JS
|
||||
assert "__toolbox/bundle" in bundle.LOADER_JS # fetch retained as fallback
|
||||
assert bundle.LOADER_JS.strip().startswith("(function()")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user