Skip to content

Commit 42aafd6

Browse files
committed
Fix: fix the connect failure
1 parent b8cadd9 commit 42aafd6

3 files changed

Lines changed: 76 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ What that enables (and does not):
3737

3838
Store reviewers: justify `<all_urls>` as required for universal autofill + WebAuthn interception; optional site-specific host permissions would break first-visit save/fill.
3939

40+
## Content security policy (`wasm-unsafe-eval`)
41+
42+
The manifest sets `content_security_policy.extension_pages` to
43+
`script-src 'self' 'wasm-unsafe-eval'; object-src 'self'`.
44+
45+
Argon2id runs through `hash-wasm`, which compiles a WebAssembly module, and the
46+
service worker is the only place the master key is ever derived. MV3's default
47+
extension CSP is `script-src 'self'`, which makes `WebAssembly.compile` throw —
48+
so registration and every unlock/login fail in a real browser even though the
49+
Node test suite passes (Node has no extension CSP). `'wasm-unsafe-eval'` is the
50+
narrow carve-out for this: it permits WASM compilation only, **not** `eval()`.
51+
`script-src` stays otherwise pinned to `'self'` — no remote origins, no
52+
`'unsafe-inline'`.
53+
54+
`src/manifest_csp.test.ts` locks this in, and asserts the build copies the CSP
55+
into `dist/manifest.json`. Keep it green; without it the extension cannot unlock.
56+
4057
### Passkeys smoke test
4158

4259
```bash

‎manifest.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"notifications"
2323
],
2424
"host_permissions": ["<all_urls>"],
25+
"content_security_policy": {
26+
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
27+
},
2528
"background": {
2629
"service_worker": "src/background/index.ts",
2730
"type": "module"

‎src/manifest_csp.test.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it } from "vitest";
2+
import { readFileSync } from "node:fs";
3+
import { fileURLToPath } from "node:url";
4+
import { dirname, resolve } from "node:path";
5+
6+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
7+
8+
type Manifest = {
9+
content_security_policy?: { extension_pages?: string };
10+
background?: { service_worker?: string };
11+
};
12+
13+
function loadManifest(which: "manifest.json" | "dist/manifest.json"): Manifest {
14+
return JSON.parse(readFileSync(resolve(ROOT, which), "utf8")) as Manifest;
15+
}
16+
17+
/**
18+
* Argon2id (hash-wasm) derives the master key through WebAssembly, and the
19+
* service worker is the only place that key is ever produced. MV3's default
20+
* extension_pages CSP is `script-src 'self'`, which makes
21+
* `WebAssembly.compile` throw — so REGISTER and every unlock/login failed in a
22+
* real browser even though the whole Node test suite passed.
23+
*
24+
* `'wasm-unsafe-eval'` is the narrow allowance for this: it permits WASM
25+
* compilation only, not `eval()`.
26+
*/
27+
describe("manifest CSP", () => {
28+
const cspOf = (m: Manifest) => m.content_security_policy?.extension_pages ?? "";
29+
30+
it("allows wasm compilation so Argon2id can run in the service worker", () => {
31+
const csp = cspOf(loadManifest("manifest.json"));
32+
expect(csp).not.toBe("");
33+
expect(csp).toContain("'wasm-unsafe-eval'");
34+
});
35+
36+
it("keeps script-src otherwise locked to 'self'", () => {
37+
const csp = cspOf(loadManifest("manifest.json"));
38+
const scriptSrc = /script-src([^;]*)/.exec(csp)?.[1] ?? "";
39+
const sources = scriptSrc.split(/\s+/).filter(Boolean);
40+
// 'self' + the wasm carve-out. Nothing else — no 'unsafe-eval', no
41+
// remote origins, no wildcards.
42+
expect(sources.sort()).toEqual(["'self'", "'wasm-unsafe-eval'"]);
43+
});
44+
45+
it("does not allow unsafe-eval or remote script", () => {
46+
const csp = cspOf(loadManifest("manifest.json"));
47+
expect(csp).not.toContain("'unsafe-eval'");
48+
expect(csp).not.toContain("'unsafe-inline'");
49+
expect(csp).not.toMatch(/script-src[^;]*https?:/);
50+
});
51+
52+
it("survives the build (vite copies the CSP into dist/manifest.json)", () => {
53+
const dist = loadManifest("dist/manifest.json");
54+
expect(cspOf(dist)).toContain("'wasm-unsafe-eval'");
55+
});
56+
});

0 commit comments

Comments
 (0)