From a00597d299fb40d16fa15e8e743a43681cb19bb8 Mon Sep 17 00:00:00 2001 From: "MONTREAL.AI" Date: Sun, 12 Apr 2026 12:46:06 -0400 Subject: [PATCH 1/2] fix: mirror insight template hardening and tighten verifier --- .../insight_browser_v1/bootstrap.js | 68 +++++++++++++++ .../insight_browser_v1/index.html | 84 ++----------------- docs/alpha_agi_insight_v1/bootstrap.js | 68 +++++++++++++++ docs/alpha_agi_insight_v1/index.html | 81 ++---------------- scripts/ensure_insight_csp.py | 18 +++- 5 files changed, 161 insertions(+), 158 deletions(-) create mode 100644 alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/bootstrap.js create mode 100644 docs/alpha_agi_insight_v1/bootstrap.js diff --git a/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/bootstrap.js b/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/bootstrap.js new file mode 100644 index 000000000..ffd1c67a3 --- /dev/null +++ b/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/bootstrap.js @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// Bootstrap globals and service worker wiring for the Insight demo. + +window.PINNER_TOKEN = ""; +window.OTEL_ENDPOINT = ""; +window.IPFS_GATEWAY = ""; + +if (typeof window.toast !== "function") { + window.toast = (msg) => { + const toastNode = document.getElementById("toast"); + if (!toastNode) { + console.warn(msg); + return; + } + + toastNode.textContent = String(msg); + toastNode.classList.add("show"); + const toastFn = window.toast; + if (typeof toastFn.id === "number") { + window.clearTimeout(toastFn.id); + } + toastFn.id = window.setTimeout(() => toastNode.classList.remove("show"), 2000); + }; +} + +const SW_URL = "service-worker.js"; + +const supportsServiceWorker = (() => { + try { + return Boolean(navigator.serviceWorker); + } catch { + return false; + } +})(); + +if (supportsServiceWorker && !navigator.webdriver) { + window.addEventListener("load", async () => { + try { + const response = await fetch(SW_URL); + const buffer = await response.arrayBuffer(); + const digest = await crypto.subtle.digest("SHA-384", buffer); + const digestB64 = btoa(String.fromCharCode(...new Uint8Array(digest))); + const swHash = typeof window.SW_HASH === "string" ? window.SW_HASH : ""; + if (!swHash || `sha384-${digestB64}` !== swHash) { + throw new Error("Service worker hash mismatch"); + } + + const registration = await navigator.serviceWorker.register(SW_URL); + await navigator.serviceWorker.ready; + registration.addEventListener("updatefound", () => { + const installingWorker = registration.installing; + if (!installingWorker) { + return; + } + + installingWorker.addEventListener("statechange", () => { + if (installingWorker.state === "installed") { + window.toast("Update available — reload to use the latest version."); + } + }); + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(err); + window.toast(`Service worker failed: ${message}`); + } + }); +} diff --git a/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/index.html b/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/index.html index 360a381da..4c72581ba 100644 --- a/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/index.html +++ b/alpha_factory_v1/demos/alpha_agi_insight_v1/insight_browser_v1/index.html @@ -5,8 +5,7 @@ α-AGI Insight - - + - + +
@@ -78,81 +78,9 @@ - - - - - - - - - - + + + diff --git a/docs/alpha_agi_insight_v1/bootstrap.js b/docs/alpha_agi_insight_v1/bootstrap.js new file mode 100644 index 000000000..ffd1c67a3 --- /dev/null +++ b/docs/alpha_agi_insight_v1/bootstrap.js @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// Bootstrap globals and service worker wiring for the Insight demo. + +window.PINNER_TOKEN = ""; +window.OTEL_ENDPOINT = ""; +window.IPFS_GATEWAY = ""; + +if (typeof window.toast !== "function") { + window.toast = (msg) => { + const toastNode = document.getElementById("toast"); + if (!toastNode) { + console.warn(msg); + return; + } + + toastNode.textContent = String(msg); + toastNode.classList.add("show"); + const toastFn = window.toast; + if (typeof toastFn.id === "number") { + window.clearTimeout(toastFn.id); + } + toastFn.id = window.setTimeout(() => toastNode.classList.remove("show"), 2000); + }; +} + +const SW_URL = "service-worker.js"; + +const supportsServiceWorker = (() => { + try { + return Boolean(navigator.serviceWorker); + } catch { + return false; + } +})(); + +if (supportsServiceWorker && !navigator.webdriver) { + window.addEventListener("load", async () => { + try { + const response = await fetch(SW_URL); + const buffer = await response.arrayBuffer(); + const digest = await crypto.subtle.digest("SHA-384", buffer); + const digestB64 = btoa(String.fromCharCode(...new Uint8Array(digest))); + const swHash = typeof window.SW_HASH === "string" ? window.SW_HASH : ""; + if (!swHash || `sha384-${digestB64}` !== swHash) { + throw new Error("Service worker hash mismatch"); + } + + const registration = await navigator.serviceWorker.register(SW_URL); + await navigator.serviceWorker.ready; + registration.addEventListener("updatefound", () => { + const installingWorker = registration.installing; + if (!installingWorker) { + return; + } + + installingWorker.addEventListener("statechange", () => { + if (installingWorker.state === "installed") { + window.toast("Update available — reload to use the latest version."); + } + }); + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(err); + window.toast(`Service worker failed: ${message}`); + } + }); +} diff --git a/docs/alpha_agi_insight_v1/index.html b/docs/alpha_agi_insight_v1/index.html index fa7adddb9..d77c5ca1a 100644 --- a/docs/alpha_agi_insight_v1/index.html +++ b/docs/alpha_agi_insight_v1/index.html @@ -5,8 +5,7 @@ α-AGI Insight - - + - + @@ -79,79 +78,9 @@ - - - - - - - + + + - diff --git a/scripts/ensure_insight_csp.py b/scripts/ensure_insight_csp.py index 789d27c51..e81484587 100644 --- a/scripts/ensure_insight_csp.py +++ b/scripts/ensure_insight_csp.py @@ -16,9 +16,14 @@ INLINE_SCRIPT_RE = re.compile(r"]*src)[^>]*>([\s\S]*?)", re.IGNORECASE) -def _hash_snippet(snippet: str) -> str: - digest = hashlib.sha384(snippet.encode()).digest() - return "'sha384-" + base64.b64encode(digest).decode() + "'" +def _hash_snippet(snippet: str, algorithm: str) -> str: + if algorithm == "sha384": + digest = hashlib.sha384(snippet.encode()).digest() + elif algorithm == "sha256": + digest = hashlib.sha256(snippet.encode()).digest() + else: + raise ValueError(f"Unsupported hash algorithm: {algorithm}") + return f"'{algorithm}-" + base64.b64encode(digest).decode() + "'" def _build_base() -> str: @@ -38,7 +43,12 @@ def _build_base() -> str: def _build_csp(html: str) -> str: - hashes = [_hash_snippet(match) for match in INLINE_SCRIPT_RE.findall(html)] + snippets = INLINE_SCRIPT_RE.findall(html) + sha384_hashes = [_hash_snippet(match, "sha384") for match in snippets] + sha256_hashes = [_hash_snippet(match, "sha256") for match in snippets] + hashes = [*sha384_hashes, *sha256_hashes] + if ' - + + diff --git a/docs/alpha_agi_insight_v1/service-worker.js b/docs/alpha_agi_insight_v1/service-worker.js deleted file mode 100644 index 07b56b11f..000000000 --- a/docs/alpha_agi_insight_v1/service-worker.js +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -/* eslint-env serviceworker */ -const WORKBOX_SW_HASH = 'sha384-R7RXlLLrbRAy0JWTwv62SHZwpjwwc7C0wjnLGa5bRxm6YCl5zw87IRvhlleSM5zd'; -import {precacheAndRoute} from 'workbox-precaching'; -import {registerRoute} from 'workbox-routing'; -import {CacheFirst} from 'workbox-strategies'; - -// replaced during build -const CACHE_VERSION = '0.1.0'; -async function init() { - const res = await fetch('lib/workbox-sw.js'); - const buf = await res.arrayBuffer(); - const digest = await crypto.subtle.digest('SHA-384', buf); - const b64 = btoa(String.fromCharCode(...new Uint8Array(digest))); - if (`sha384-${b64}` !== WORKBOX_SW_HASH) { - throw new Error('lib/workbox-sw.js hash mismatch'); - } - importScripts(URL.createObjectURL(new Blob([buf], {type: 'application/javascript'}))); - workbox.core.setCacheNameDetails({prefix: CACHE_VERSION}); - - // include translation JSON files in the precache - precacheAndRoute([]); - - registerRoute( - ({request, url}) => - request.destination === 'script' || - request.destination === 'worker' || - request.destination === 'font' || - url.pathname.endsWith('.wasm') || - (url.pathname.includes('/ipfs/') && url.pathname.endsWith('.json')), - new CacheFirst({cacheName: `${CACHE_VERSION}-assets`}) - ); - - self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); - } - }); - - self.addEventListener('activate', (event) => { - event.waitUntil( - caches.keys().then((names) => - Promise.all( - names.map((name) => { - if (!name.startsWith(CACHE_VERSION)) { - return caches.delete(name); - } - return undefined; - }), - ), - ), - ); - }); -} - -init().catch((err) => { - console.error('Service worker failed to initialize', err); - self.registration.unregister(); -});