mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-29 19:43:27 +00:00
Compare commits
3 Commits
332aae73d7
...
622c53886c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
622c53886c | ||
| aa3a9f5443 | |||
| a0748c2d43 |
|
|
@ -1,3 +1,31 @@
|
|||
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
|
||||
|
||||
* perf(ttfb): TTFB phase 1 — per-host cosmetic decision bundle (#620).
|
||||
|
|
|
|||
|
|
@ -60,15 +60,27 @@ SBXFILTERS
|
|||
fi
|
||||
|
||||
# 4. Storage dir (SQLite + future PDF reports)
|
||||
install -d -m 0750 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox
|
||||
# NOTE: `install -d -m 0750 /var/lib/secubox/<leaf>` re-modes the SHARED
|
||||
# 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).
|
||||
install -d -m 0750 -o secubox-toolbox -g secubox-toolbox /var/cache/secubox/toolbox/media 2>/dev/null || \
|
||||
mkdir -p /var/cache/secubox/toolbox/media
|
||||
mkdir -p /var/cache/secubox/toolbox/media
|
||||
chown secubox-toolbox:secubox-toolbox /var/cache/secubox/toolbox/media 2>/dev/null || true
|
||||
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
|
||||
# release asset (so GET /wg/toolbox.apk serves it locally/offline).
|
||||
# Non-blocking : if there's no release yet / no network, the endpoint
|
||||
# falls back to redirecting to the public release.
|
||||
install -d -m 0755 -o secubox-toolbox -g secubox-toolbox /var/lib/secubox/toolbox/android
|
||||
mkdir -p /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
|
||||
/usr/sbin/secubox-toolbox-fetch-apk 2>&1 | head -2 || true
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -653,8 +653,125 @@ def _client_level(flow) -> str:
|
|||
_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:
|
||||
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:
|
||||
# #620 : already handled by the streaming injector — skip the buffer path.
|
||||
if flow.metadata.get("sbx_streamed"):
|
||||
return
|
||||
if not flow.response:
|
||||
return
|
||||
ct = flow.response.headers.get("content-type", "")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user