mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 06:30:34 +00:00
Compare commits
1 Commits
622c53886c
...
332aae73d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332aae73d7 |
|
|
@ -1,31 +1,3 @@
|
||||||
secubox-toolbox (2.6.40-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* fix(postinst): stop clobbering shared parent dir modes (#620). The
|
|
||||||
`install -d -m 0750 /var/lib/secubox/toolbox` (and the media/android
|
|
||||||
variants) re-moded the SHARED /var/lib/secubox (and /var/cache/secubox)
|
|
||||||
parents to 0750, breaking traversal for other secubox-* daemons and taking
|
|
||||||
kbin to HTTP 500. Now: create leaves via mkdir + explicit chown/chmod, and
|
|
||||||
re-assert the shared parents are 0755. (Same #511 traversal class; a wider
|
|
||||||
sweep of ~12 packages with the same footgun is tracked separately.)
|
|
||||||
|
|
||||||
-- Gerald Kerma <devel@cybermind.fr> Mon, 16 Jun 2026 19:30:00 +0200
|
|
||||||
|
|
||||||
secubox-toolbox (2.6.39-1~bookworm1) bookworm; urgency=medium
|
|
||||||
|
|
||||||
* perf(ttfb): TTFB phase 2 — streaming loader inject (#620), gated on the
|
|
||||||
stream_inject filter (default OFF; OFF path byte-identical to before).
|
|
||||||
- inject_banner.request(): strip Accept-Encoding for top-level HTML
|
|
||||||
navigations so the document returns identity-encoded (injectable).
|
|
||||||
- inject_banner.responseheaders(): set flow.response.stream to inject a
|
|
||||||
tiny /__toolbox/loader.js <script> into the first chunk holding
|
|
||||||
<head>/<body> — no full-body buffer (TTFB ≈ passthrough). Compressed
|
|
||||||
responses fall back to the legacy buffer path.
|
|
||||||
- response(): skip the buffer path when streamed.
|
|
||||||
- The loader renders the banner + per-page tracker/cookie stats
|
|
||||||
client-side (Resource Timing / document.cookie).
|
|
||||||
|
|
||||||
-- Gerald Kerma <devel@cybermind.fr> Mon, 16 Jun 2026 19:00:00 +0200
|
|
||||||
|
|
||||||
secubox-toolbox (2.6.38-1~bookworm1) bookworm; urgency=medium
|
secubox-toolbox (2.6.38-1~bookworm1) bookworm; urgency=medium
|
||||||
|
|
||||||
* perf(ttfb): TTFB phase 1 — per-host cosmetic decision bundle (#620).
|
* perf(ttfb): TTFB phase 1 — per-host cosmetic decision bundle (#620).
|
||||||
|
|
|
||||||
|
|
@ -60,27 +60,15 @@ SBXFILTERS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. Storage dir (SQLite + future PDF reports)
|
# 4. Storage dir (SQLite + future PDF reports)
|
||||||
# NOTE: `install -d -m 0750 /var/lib/secubox/<leaf>` re-modes the SHARED
|
install -d -m 0750 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox
|
||||||
# parent /var/lib/secubox to 0750, breaking traversal for every other
|
|
||||||
# secubox-* daemon (this took kbin down — #620). Create the leaf without
|
|
||||||
# clobbering the parent, then re-assert the parent is traversable.
|
|
||||||
mkdir -p /var/lib/secubox/toolbox
|
|
||||||
chown secubox-toolbox:secubox-toolbox /var/lib/secubox/toolbox 2>/dev/null || true
|
|
||||||
chmod 0750 /var/lib/secubox/toolbox 2>/dev/null || true
|
|
||||||
# #577 : shared media proxy-cache dir (opt-in via filters ; 2 GB LRU).
|
# #577 : shared media proxy-cache dir (opt-in via filters ; 2 GB LRU).
|
||||||
mkdir -p /var/cache/secubox/toolbox/media
|
install -d -m 0750 -o secubox-toolbox -g secubox-toolbox /var/cache/secubox/toolbox/media 2>/dev/null || \
|
||||||
chown secubox-toolbox:secubox-toolbox /var/cache/secubox/toolbox/media 2>/dev/null || true
|
mkdir -p /var/cache/secubox/toolbox/media
|
||||||
chmod 0750 /var/cache/secubox/toolbox/media 2>/dev/null || true
|
|
||||||
# Keep the shared parents traversable for other daemons.
|
|
||||||
[ -d /var/lib/secubox ] && chmod 0755 /var/lib/secubox 2>/dev/null || true
|
|
||||||
[ -d /var/cache/secubox ] && chmod 0755 /var/cache/secubox 2>/dev/null || true
|
|
||||||
# #536 : Android APK serve dir + best-effort fetch of the latest
|
# #536 : Android APK serve dir + best-effort fetch of the latest
|
||||||
# release asset (so GET /wg/toolbox.apk serves it locally/offline).
|
# release asset (so GET /wg/toolbox.apk serves it locally/offline).
|
||||||
# Non-blocking : if there's no release yet / no network, the endpoint
|
# Non-blocking : if there's no release yet / no network, the endpoint
|
||||||
# falls back to redirecting to the public release.
|
# falls back to redirecting to the public release.
|
||||||
mkdir -p /var/lib/secubox/toolbox/android
|
install -d -m 0755 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox/android
|
||||||
chown secubox-toolbox:secubox-toolbox /var/lib/secubox/toolbox/android 2>/dev/null || true
|
|
||||||
chmod 0755 /var/lib/secubox/toolbox/android 2>/dev/null || true
|
|
||||||
if [ -x /usr/sbin/secubox-toolbox-fetch-apk ]; then
|
if [ -x /usr/sbin/secubox-toolbox-fetch-apk ]; then
|
||||||
/usr/sbin/secubox-toolbox-fetch-apk 2>&1 | head -2 || true
|
/usr/sbin/secubox-toolbox-fetch-apk 2>&1 | head -2 || true
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -653,125 +653,8 @@ def _client_level(flow) -> str:
|
||||||
_MAX_INJECT_BYTES = 2 * 1024 * 1024 # Phase 10 perf cap : skip injection on huge bodies
|
_MAX_INJECT_BYTES = 2 * 1024 * 1024 # Phase 10 perf cap : skip injection on huge bodies
|
||||||
|
|
||||||
|
|
||||||
# ── #620 phase 2 : streaming loader inject (TTFB) ───────────────────────────
|
|
||||||
# When the `stream_inject` filter is ON, we stop buffering the whole HTML body
|
|
||||||
# to insert the banner. Instead we (1) strip Accept-Encoding on the document
|
|
||||||
# request so the response is identity-encoded, and (2) stream the response,
|
|
||||||
# injecting a tiny loader <script> into the first chunk that contains <head>/
|
|
||||||
# <body>. The loader (served at /__toolbox/loader.js) fetches the per-host
|
|
||||||
# bundle and renders the banner + per-page stats client-side — off the proxy
|
|
||||||
# critical path. Gated, fail-open: any miss falls back to passthrough (no
|
|
||||||
# banner on that page) or to the legacy buffer path when the body is compressed.
|
|
||||||
|
|
||||||
def _stream_enabled() -> bool:
|
|
||||||
try:
|
|
||||||
import sys as _sys
|
|
||||||
if "/usr/lib/secubox/toolbox" not in _sys.path:
|
|
||||||
_sys.path.insert(0, "/usr/lib/secubox/toolbox")
|
|
||||||
from secubox_toolbox.filters import get_filters as _gf
|
|
||||||
return bool(_gf().get("stream_inject", False))
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _loader_script(flow) -> bytes:
|
|
||||||
"""Tiny loader <script> tag carrying the client identity + WG flag."""
|
|
||||||
mh, wg = "", "0"
|
|
||||||
try:
|
|
||||||
ip = flow.client_conn.peername[0] if flow.client_conn.peername else None
|
|
||||||
if ip and ip.startswith("10.99.1."):
|
|
||||||
wg = "1"
|
|
||||||
if _HAS_LEVEL and ip:
|
|
||||||
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))
|
|
||||||
return b"<!-- " + _GUARD + b" -->" + tag.encode("ascii", "ignore")
|
|
||||||
|
|
||||||
|
|
||||||
class _LoaderInjector:
|
|
||||||
"""Stream modifier: inject `script` once, in the first chunk holding
|
|
||||||
<head>/<body>. Never holds bytes back (TTFB-safe); gives up after 128 KB."""
|
|
||||||
|
|
||||||
def __init__(self, script: bytes):
|
|
||||||
self.script = script
|
|
||||||
self.done = False
|
|
||||||
self.seen = 0
|
|
||||||
|
|
||||||
def __call__(self, data: bytes) -> bytes:
|
|
||||||
if self.done or not data or self.seen > 131072:
|
|
||||||
self.seen += len(data or b"")
|
|
||||||
return data
|
|
||||||
self.seen += len(data)
|
|
||||||
low = data.lower()
|
|
||||||
h = low.find(b"<head")
|
|
||||||
if h >= 0:
|
|
||||||
j = data.find(b">", h)
|
|
||||||
if j >= 0:
|
|
||||||
self.done = True
|
|
||||||
return data[:j + 1] + self.script + data[j + 1:]
|
|
||||||
b = low.find(b"<body")
|
|
||||||
if b >= 0:
|
|
||||||
self.done = True
|
|
||||||
return data[:b] + self.script + data[b:]
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class InjectBanner:
|
class InjectBanner:
|
||||||
def request(self, flow: http.HTTPFlow) -> None:
|
|
||||||
# #620 : for top-level HTML navigations, ask upstream for identity
|
|
||||||
# encoding so we can stream-inject the loader without decompressing.
|
|
||||||
if not _stream_enabled():
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
req = flow.request
|
|
||||||
accept = (req.headers.get("accept", "") or "").lower()
|
|
||||||
dest = (req.headers.get("sec-fetch-dest", "") or "").lower()
|
|
||||||
if dest == "document" or "text/html" in accept:
|
|
||||||
if "accept-encoding" in req.headers:
|
|
||||||
req.headers["accept-encoding"] = "identity"
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def responseheaders(self, flow: http.HTTPFlow) -> None:
|
|
||||||
# #620 : set up streaming injection BEFORE the body is read. Falls back
|
|
||||||
# to the legacy buffer path (response hook) if anything disqualifies.
|
|
||||||
if not _stream_enabled():
|
|
||||||
return
|
|
||||||
resp = flow.response
|
|
||||||
if not resp:
|
|
||||||
return
|
|
||||||
ct = (resp.headers.get("content-type", "") or "").lower()
|
|
||||||
if "text/html" not in ct:
|
|
||||||
return
|
|
||||||
if resp.status_code < 200 or resp.status_code >= 400:
|
|
||||||
return
|
|
||||||
# Compressed (upstream ignored our identity request) → let the buffer
|
|
||||||
# path handle it (mitmproxy auto-decodes there). Don't stream.
|
|
||||||
if resp.headers.get("content-encoding"):
|
|
||||||
return
|
|
||||||
if _client_level(flow) not in ("r2", "r3"):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
import sys as _sys
|
|
||||||
if "/usr/lib/secubox/toolbox" not in _sys.path:
|
|
||||||
_sys.path.insert(0, "/usr/lib/secubox/toolbox")
|
|
||||||
from secubox_toolbox.filters import get_filters as _gf
|
|
||||||
if not _gf().get("banner", True):
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
resp.stream = _LoaderInjector(_loader_script(flow))
|
|
||||||
flow.metadata["sbx_streamed"] = True
|
|
||||||
except Exception as e:
|
|
||||||
log.warning("stream-inject setup failed for %s: %s", flow.request.host, e)
|
|
||||||
|
|
||||||
def response(self, flow: http.HTTPFlow) -> None:
|
def response(self, flow: http.HTTPFlow) -> None:
|
||||||
# #620 : already handled by the streaming injector — skip the buffer path.
|
|
||||||
if flow.metadata.get("sbx_streamed"):
|
|
||||||
return
|
|
||||||
if not flow.response:
|
if not flow.response:
|
||||||
return
|
return
|
||||||
ct = flow.response.headers.get("content-type", "")
|
ct = flow.response.headers.get("content-type", "")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user