diff --git a/example-apps/dashproof-lab/public/dashproof-lite.html b/example-apps/dashproof-lab/public/dashproof-lite.html index ef53352..a9e10f5 100644 --- a/example-apps/dashproof-lab/public/dashproof-lite.html +++ b/example-apps/dashproof-lab/public/dashproof-lite.html @@ -217,6 +217,11 @@

History by chainId

// SHA-256 via WebCrypto. Runs entirely in the browser; the file never // leaves the page. async function hashFile(file) { + if (typeof crypto === 'undefined' || !crypto.subtle) { + throw new Error( + 'SHA-256 hashing requires a secure context. Open this page over HTTPS or via http://localhost — the browser disables crypto.subtle on plain http:// origins.', + ); + } const buf = await file.arrayBuffer(); return new Uint8Array(await crypto.subtle.digest('SHA-256', buf)); } diff --git a/example-apps/dashproof-lab/src/lib/hash.ts b/example-apps/dashproof-lab/src/lib/hash.ts index d8e4353..384f4fb 100644 --- a/example-apps/dashproof-lab/src/lib/hash.ts +++ b/example-apps/dashproof-lab/src/lib/hash.ts @@ -1,4 +1,9 @@ export async function hashFile(file: File): Promise { + if (typeof crypto === "undefined" || !crypto.subtle) { + throw new Error( + "SHA-256 hashing requires a secure context. Open this app over HTTPS or via http://localhost — the browser disables crypto.subtle on plain http:// origins.", + ); + } const buffer = typeof file.arrayBuffer === "function" ? await file.arrayBuffer() diff --git a/example-apps/dashproof-lab/test/hashFile.test.ts b/example-apps/dashproof-lab/test/hashFile.test.ts index 5c93395..2a20095 100644 --- a/example-apps/dashproof-lab/test/hashFile.test.ts +++ b/example-apps/dashproof-lab/test/hashFile.test.ts @@ -3,11 +3,21 @@ // which would make this regression test useless. Node 20+ provides a working // global File backed by Undici's Blob implementation. -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import { bytesToHex, hashFile } from "../src/lib/hash"; describe("hashFile", () => { + const originalSubtle = globalThis.crypto?.subtle; + afterEach(() => { + if (globalThis.crypto) { + Object.defineProperty(globalThis.crypto, "subtle", { + configurable: true, + value: originalSubtle, + }); + } + }); + it("hashes binary (non-UTF-8) bytes correctly", async () => { // 0xFF 0xFE is invalid UTF-8 — a TextEncoder-based fallback would // corrupt these bytes into the U+FFFD replacement character before @@ -30,4 +40,15 @@ describe("hashFile", () => { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", ); }); + + it("throws a clear Secure Context error when crypto.subtle is missing", async () => { + Object.defineProperty(globalThis.crypto, "subtle", { + configurable: true, + value: undefined, + }); + const file = new File([Uint8Array.from([0x00])], "x.bin"); + await expect(hashFile(file)).rejects.toThrow( + "SHA-256 hashing requires a secure context. Open this app over HTTPS or via http://localhost — the browser disables crypto.subtle on plain http:// origins.", + ); + }); });