From 1dd540e86afed291ccfb3ce4c88db50fd3e93ef1 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 28 May 2026 12:03:35 +0200 Subject: [PATCH] feat(webext): relay via avatar peek/poke + experimental YouTube PO-token capture (ref #401, #402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cookie relay now pokes the avatar credential broker (/api/v1/avatar/cred/poke/youtube, JSON) instead of the PeerTube-specific endpoint — one broker for all services (#402). - content/yt-hook.js (page world) + yt-capture.js (content script): hook YouTube's fetch/XHR to capture the `pot` PO token + visitor_data, stash in extension storage; the relay sends them alongside cookies. EXPERIMENTAL — YouTube obfuscates/rotates these, best-effort (P4). - manifest: content_scripts on youtube.com + web_accessible yt-hook.js. --- packages/secubox-webext/content/yt-capture.js | 22 +++++++++ packages/secubox-webext/content/yt-hook.js | 47 +++++++++++++++++++ packages/secubox-webext/manifest.json | 14 ++++++ packages/secubox-webext/options/options.js | 2 +- packages/secubox-webext/popup/popup.js | 19 ++++++-- 5 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 packages/secubox-webext/content/yt-capture.js create mode 100644 packages/secubox-webext/content/yt-hook.js diff --git a/packages/secubox-webext/content/yt-capture.js b/packages/secubox-webext/content/yt-capture.js new file mode 100644 index 00000000..1ea42813 --- /dev/null +++ b/packages/secubox-webext/content/yt-capture.js @@ -0,0 +1,22 @@ +// SecuBox Companion — YouTube PO-token capture (content script, isolated world) +// Injects a page-world hook that watches YouTube's own streaming requests for +// the `pot` (PO token) param + visitor_data, and stashes them in extension +// storage so the popup can relay them to the avatar broker (#402 / #401 P4). +// EXPERIMENTAL: YouTube obfuscates + rotates these; best-effort only. +const api = globalThis.browser ?? globalThis.chrome; + +try { + const s = document.createElement("script"); + s.src = api.runtime.getURL("content/yt-hook.js"); + s.async = false; + (document.head || document.documentElement).appendChild(s); + s.remove(); +} catch (e) { /* page CSP may block; nothing we can do */ } + +window.addEventListener("message", (e) => { + if (e.source !== window || !e.data || e.data.__secubox !== "yt-pot") return; + const upd = {}; + if (e.data.po_token) { upd.yt_po_token = e.data.po_token; upd.yt_po_token_at = Date.now(); } + if (e.data.visitor_data) upd.yt_visitor_data = e.data.visitor_data; + if (Object.keys(upd).length) api.storage.local.set(upd); +}); diff --git a/packages/secubox-webext/content/yt-hook.js b/packages/secubox-webext/content/yt-hook.js new file mode 100644 index 00000000..09610834 --- /dev/null +++ b/packages/secubox-webext/content/yt-hook.js @@ -0,0 +1,47 @@ +// SecuBox Companion — YouTube PO-token hook (runs in the PAGE/main world). +// Wraps fetch + XHR to spot YouTube's streaming requests, which carry the +// `pot` (PO token) query param. Relays it (+ visitor_data from ytcfg) to the +// content script via window.postMessage. EXPERIMENTAL + YouTube-fragile. +(function () { + "use strict"; + let lastSent = ""; + + function visitorData() { + try { + if (window.ytcfg && typeof window.ytcfg.get === "function") { + return window.ytcfg.get("VISITOR_DATA") || ""; + } + if (window.ytcfg && window.ytcfg.data_) return window.ytcfg.data_.VISITOR_DATA || ""; + } catch (e) { /* ignore */ } + return ""; + } + + function grab(url) { + if (!url) return; + try { + const u = new URL(url, location.href); + if (!/(googlevideo\.com|youtube\.com)/.test(u.hostname)) return; + const pot = u.searchParams.get("pot"); + if (pot && pot.length > 20 && pot !== lastSent) { + lastSent = pot; + window.postMessage( + { __secubox: "yt-pot", po_token: pot, visitor_data: visitorData() }, "*"); + } + } catch (e) { /* ignore */ } + } + + try { + const origFetch = window.fetch; + if (origFetch) { + window.fetch = function (input) { + try { grab(typeof input === "string" ? input : (input && input.url)); } catch (e) {} + return origFetch.apply(this, arguments); + }; + } + const origOpen = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function (method, url) { + try { grab(url); } catch (e) {} + return origOpen.apply(this, arguments); + }; + } catch (e) { /* ignore */ } +})(); diff --git a/packages/secubox-webext/manifest.json b/packages/secubox-webext/manifest.json index 08dbda45..54a5f628 100644 --- a/packages/secubox-webext/manifest.json +++ b/packages/secubox-webext/manifest.json @@ -22,6 +22,20 @@ "background": { "scripts": ["background.js"] }, + "content_scripts": [ + { + "matches": ["https://www.youtube.com/*"], + "js": ["content/yt-capture.js"], + "run_at": "document_start", + "all_frames": true + } + ], + "web_accessible_resources": [ + { + "resources": ["content/yt-hook.js"], + "matches": ["https://www.youtube.com/*"] + } + ], "options_ui": { "page": "options/options.html", "open_in_tab": true diff --git a/packages/secubox-webext/options/options.js b/packages/secubox-webext/options/options.js index c4b2af64..3b4b93a0 100644 --- a/packages/secubox-webext/options/options.js +++ b/packages/secubox-webext/options/options.js @@ -4,7 +4,7 @@ const api = globalThis.browser ?? globalThis.chrome; const DEFAULTS = { hubBase: "", modules: ["hub", "crowdsec", "waf", "wireguard", "peertube", "photoprism", "nextcloud"], - cookieEndpoint: "/api/v1/peertube/import/cookies", + cookieEndpoint: "/api/v1/avatar/cred/poke/youtube", }; async function load() { diff --git a/packages/secubox-webext/popup/popup.js b/packages/secubox-webext/popup/popup.js index deedd7dc..a65c4b2c 100644 --- a/packages/secubox-webext/popup/popup.js +++ b/packages/secubox-webext/popup/popup.js @@ -5,8 +5,9 @@ const DEFAULTS = { hubBase: "", // e.g. "https://admin.gk2.secubox.in" // Modules to probe (name shown + URL slug). Operator can edit in options. modules: ["hub", "crowdsec", "waf", "wireguard", "peertube", "photoprism", "nextcloud"], - // PeerTube cookie-intake endpoint (backend lands with #401 P1.5 / #388). - cookieEndpoint: "/api/v1/peertube/import/cookies", + // Avatar credential-broker poke endpoint (#402): one place to share browser + // auth with any backend service. youtube → PeerTube. + cookieEndpoint: "/api/v1/avatar/cred/poke/youtube", // SecuBox login endpoint (secubox-core JWT; any module's /auth/login works). loginEndpoint: "/api/v1/peertube/auth/login", }; @@ -101,11 +102,19 @@ async function relayCookies(cfg) { status.textContent = "No youtube.com cookies — log into YouTube in this browser first."; return; } - const body = toNetscape(cookies); - status.textContent = `Sending ${cookies.length} cookies…`; + const netscape = toNetscape(cookies); + // Optional PO token captured from a youtube.com tab (P4, experimental). + const { yt_po_token = "", yt_visitor_data = "" } = await api.storage.local.get( + { yt_po_token: "", yt_visitor_data: "" }); + status.textContent = `Poking ${cookies.length} cookies` + (yt_po_token ? " + PO token" : "") + "…"; const r = await fetch(`${cfg.hubBase}${cfg.cookieEndpoint}`, { method: "POST", credentials: "include", - headers: { "Content-Type": "text/plain", ...(await authHeaders()) }, body, + headers: { "Content-Type": "application/json", ...(await authHeaders()) }, + body: JSON.stringify({ + cookies: netscape, + po_token: yt_po_token || undefined, + visitor_data: yt_visitor_data || undefined, + }), }); if (r.status === 401 || r.status === 403) { status.textContent = "SecuBox login required:";