All checks were successful
check / check (push) Successful in 39s
libsodium ships a WASM build and a wasm2js translation in one file, tries WASM first, and silently falls back if instantiation throws. Under a plain script-src 'self' the fallback was taken on every popup load, announced by nothing but an uncaught CompileError. Measured on the vault's own Argon2id parameters (OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE), node 22: WASM 141-198ms per derivation, wasm2js 3204-3660ms. The work factor is identical either way — it is set by the ops and memory parameters, not by wall time — so the fallback bought no security and cost about 3.5s on every operation that asks for the password, which is every signature. Both manifests now declare script-src 'self' 'wasm-unsafe-eval'; object-src 'self' for extension pages: an object under content_security_policy.extension_pages for Chrome MV3, a bare string for Firefox MV2. The keyword permits compiling WebAssembly and nothing else — not eval() of strings, not inline script, not remote script — and reaching it requires already executing script in an extension page. 'unsafe-eval' is not granted. The silence is what made this dangerous, so the fallback is now loud at three levels: tests/manifest.test.js pins both policies to exactly that token set, failing make check if the grant is dropped or if anything is added beside it; tests/vaultBackend.test.js asserts the unit tests exercise the WASM backend, with a self-validating check that libsodium never swapped its fallback in; and the e2e suite compiles a WebAssembly module inside the real popup under the real manifest, with the harness allowlist entry that used to excuse the CompileError now deleted. The runtime fallback itself is kept — a wallet that refuses to decrypt is worse than a slow one — but vault.js now reports the backend and logs an error when it is not WASM.
53 lines
2.6 KiB
JavaScript
53 lines
2.6 KiB
JavaScript
// The unit tests must exercise the libsodium backend that actually ships
|
|
// (#182). Before this, they could not: node compiles WebAssembly happily,
|
|
// the extension CSP refused it, and so the browser silently ran the
|
|
// wasm2js translation while every test ran the WASM build.
|
|
//
|
|
// With 'wasm-unsafe-eval' in both manifests the two agree, and these tests
|
|
// hold that agreement in place from the node side. tests/manifest.test.js
|
|
// holds up the CSP end of it, and the end-to-end suite observes the real
|
|
// popup.
|
|
|
|
const { cryptoBackend } = require("../src/shared/vault");
|
|
|
|
// The module libsodium-wrappers-sumo itself requires and drives. Not a new
|
|
// dependency: it is inspected here, never used to perform crypto, because
|
|
// it is the only thing that can say which backend is loaded.
|
|
const SODIUM_CORE = "libsodium-sumo";
|
|
|
|
describe("libsodium backend", () => {
|
|
test("this realm compiles WebAssembly, so the tests run the WASM build", async () => {
|
|
await expect(cryptoBackend()).resolves.toBe("wasm");
|
|
});
|
|
|
|
test("libsodium did not swap in the wasm2js fallback", async () => {
|
|
const core = require(SODIUM_CORE);
|
|
await require("libsodium-wrappers-sumo").ready;
|
|
// useBackupModule is the entry point to the fallback; taking it
|
|
// replaces the module's exports with the translation's, and the
|
|
// entry point goes with them. Still present after ready means the
|
|
// WASM module is the one in place. The test below is what keeps
|
|
// that inference honest.
|
|
expect(typeof core.useBackupModule).toBe("function");
|
|
});
|
|
|
|
// Deliberately last, and deliberately destructive: it takes the
|
|
// fallback, which replaces the loaded module for the rest of this
|
|
// file. Jest gives each test file its own module registry, so nothing
|
|
// outside sees it.
|
|
//
|
|
// Without this, the check above would be a claim about libsodium's
|
|
// internals with nothing holding it to account: if a future version
|
|
// kept useBackupModule on the fallback module too, the marker would
|
|
// quietly become true in both backends and the test would pass while
|
|
// measuring nothing. Forcing the fallback and watching the marker
|
|
// disappear is what makes its presence mean something.
|
|
test("the fallback marker distinguishes the two backends", async () => {
|
|
const core = require(SODIUM_CORE);
|
|
await require("libsodium-wrappers-sumo").ready;
|
|
expect(typeof core.useBackupModule).toBe("function");
|
|
await core.useBackupModule();
|
|
expect(typeof core.useBackupModule).toBe("undefined");
|
|
});
|
|
});
|