// Copyright (C) 2026 Doan Luu Duc Tai // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { createServer } from 'node:http'; import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import assert from 'node:assert/strict'; import { chromium } from '../../../.tools/browser/node_modules/playwright/index.mjs'; const assets = new Map([ ['/wasi.mjs', [resolve('crates/chat-crypto/browser/wasi.mjs'), 'text/javascript']], ['/probe.wasm', [resolve('target/wasm32-wasip1/release/stack-chat-crypto.wasm'), 'application/wasm']], ]); const server = createServer(async (request, response) => { response.setHeader('Cache-Control', 'no-store'); if (request.url === '/') { response.setHeader('Content-Type', 'text/html'); response.end('Local cryptographic compatibility test'); return; } const asset = assets.get(request.url); if (!asset) { response.writeHead(404); response.end(); return; } response.setHeader('Content-Type', asset[1]); response.end(await readFile(asset[0])); }); await new Promise(resolveReady => server.listen(0, '127.0.0.1', resolveReady)); const origin = `http://127.0.0.1:${server.address().port}`; const browser = await chromium.launch({headless: true, channel: "chromium"}); const deadline = setTimeout(() => browser.close(), 120000); try { const page = await browser.newPage(); const external = []; page.on('request', request => { if (!request.url().startsWith(origin + '/')) external.push(request.url()); }); await page.goto(origin); const result = await page.evaluate(async () => { await import('/wasi.mjs'); const { runProbe } = globalThis.stackChatWasi; const bytes = await (await fetch('/probe.wasm', { cache: 'no-store' })).arrayBuffer(); const valid = await runProbe(bytes); const again = await runProbe(bytes); const getRandomValues = crypto.getRandomValues; let rejected = false; crypto.getRandomValues = () => { throw new Error('Entropy unavailable'); }; try { await runProbe(bytes); } catch (error) { rejected = error.message.includes("Entropy unavailable"); } finally { crypto.getRandomValues = getRandomValues; } const modified = new Uint8Array(bytes.slice(0)); const pattern = new TextEncoder().encode('random_get'); const offset = modified.findIndex((value, index) => pattern.every((byte, position) => modified[index + position] === byte)); if (offset < 0) throw new Error('Expected entropy import was not found'); modified.set(new TextEncoder().encode('reject_get'), offset); let unknownImportRejected = false; try { await runProbe(modified); } catch (error) { unknownImportRejected = error.message.includes('Unsupported WASI import: wasi_snapshot_preview1.reject_get'); } return { valid, again, rejected, unknownImportRejected }; }); assert.match(result.valid.output, /^PASS: backend=OpenSSL 3\.6\.5;/); assert.match(result.again.output, /^PASS: backend=OpenSSL 3\.6\.5;/); assert.ok(result.valid.randomCalls > 0 && result.valid.randomBytes > 0); assert.ok(result.rejected, 'Unavailable browser entropy must fail closed'); assert.ok(result.unknownImportRejected, 'Unknown host imports must fail closed'); assert.deepEqual(external, []); assert.deepEqual(await page.context().cookies(), []); assert.equal(await page.evaluate(() => localStorage.length + sessionStorage.length), 0); assert.deepEqual(await page.evaluate(async () => indexedDB.databases()), []); assert.equal(await page.evaluate(async () => (await navigator.serviceWorker.getRegistrations()).length), 0); console.log(result.valid.output.trim()); console.log(`Browser CSPRNG: ${result.valid.randomCalls} calls, ${result.valid.randomBytes} bytes; missing entropy and unknown imports rejected.`); console.log('PASS: actual Chromium WASM execution; no external requests, cookies, web storage, or IndexedDB.'); } finally { clearTimeout(deadline); await browser.close(); await new Promise(resolveClosed => server.close(resolveClosed)); }