[auth] Vendor BitLogin 30b4f34 and allow WASM in the app CSP

Picks up BitLogin's login-surface expansion (NIP-07 extension, NIP-46
remote signer with nostrconnect QR, passkey sign-in), three rounds of
adversarial-audit fixes, and the mandatory recovery-phrase ceremony for
passkey-created accounts. Vendored via scripts/build-bitlogin-widget.mjs,
which pins the source commit in vendor/bitlogin.integrity.json; the
shared-chunk hashes changed, so three old chunks are removed and three
new ones added.

CSP fix, required for sign-in to work at all: BitLogin's crypto worker
compiles an Argon2id WASM module, and index.html's script-src had no
'wasm-unsafe-eval', so WebAssembly.compile would be blocked and every
BitLogin password flow would fail -- silently, because the page still
loads. This exact omission broke sign-in on bitlogin.network for four
days. The keyword permits WASM compilation only; JS eval stays blocked,
and the CSP comment's stale "no bundle uses wasm" claim is corrected.

tests/csp-policy.test.mjs now pins the keyword for index.html so it
cannot be dropped again. Baseline verified unchanged: npm run test:unit
green, npm run lint clean, and the raw tests/*.test.mjs glob fails the
same 10 pre-existing files before and after this change.

test_integrity_note:
  change_type: ["new_tests"]
  scenarios:
    - id: SCN-csp-allows-wasm-for-bitlogin
      given: "index.html mounts <bitlogin-auth>, whose worker compiles an Argon2id WASM module"
      when: "the page's Content-Security-Policy script-src is evaluated"
      then: "'wasm-unsafe-eval' is present, so WebAssembly.compile is permitted while 'unsafe-eval' remains absent"
  observable_outcomes:
    - "script-src directive parsed from the index.html CSP meta tag contains 'wasm-unsafe-eval'"
    - "existing assertions still forbid 'unsafe-inline' and 'unsafe-eval'"
  determinism_controls:
    - "pure static parse of a committed HTML file; no clock, network, or browser"
  anti_cheat_rationale:
    prevents:
      - "silently relaxing script-src to make sign-in work"
      - "dropping the keyword and shipping a login page that cannot run Argon2id"
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: ""
This commit is contained in:
thePR0M3TH3AN committed 2026-08-04 16:13:09 -04:00
1 parent fbac54dd48
commit 1ddfa95a84
8 files changed
+3589 -1250

No files matched your search

+7 -3
View File
@@ -6,8 +6,12 @@
Content-Security-Policy: defense-in-depth against XSS. The critical Content-Security-Policy: defense-in-depth against XSS. The critical
directive is script-src, which is strict — only same-origin scripts, the directive is script-src, which is strict — only same-origin scripts, the
hash of the inline theme-bootstrap below, and the nostr-tools CDN hash of the inline theme-bootstrap below, and the nostr-tools CDN
fallbacks (js/nostrToolsBootstrap.js). No 'unsafe-inline'/'unsafe-eval' fallbacks (js/nostrToolsBootstrap.js). No 'unsafe-inline'/'unsafe-eval'.
(verified: no bundle uses eval/Function/wasm). This blocks injected 'wasm-unsafe-eval' IS required: BitLogin's crypto worker compiles an
Argon2id WASM module, and a script-src without it blocks
WebAssembly.compile and breaks every BitLogin password flow. That
keyword permits WASM compilation only — JS eval stays blocked. This
blocks injected
script and object elements, inline handlers (none exist), and base-tag script and object elements, inline handlers (none exist), and base-tag
hijack, so an injected payload cannot execute and read the in-memory hijack, so an injected payload cannot execute and read the in-memory
storage master key / signer / DM plaintext. connect/img/media stay broad storage master key / signer / DM plaintext. connect/img/media stay broad
@@ -19,7 +23,7 @@
--> -->
<meta <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'sha256-dnv/l3q8gLqdw0etekr5/atoIRbM/wSVSgQjZEsC7G0=' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob: mediastream:; connect-src 'self' https: wss: ws://localhost:* ws://127.0.0.1:* blob: data:; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; manifest-src 'self'" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-dnv/l3q8gLqdw0etekr5/atoIRbM/wSVSgQjZEsC7G0=' https://cdn.jsdelivr.net https://esm.sh; style-src 'self' 'unsafe-inline'; img-src 'self' https: data: blob:; media-src 'self' https: blob: mediastream:; connect-src 'self' https: wss: ws://localhost:* ws://127.0.0.1:* blob: data:; worker-src 'self' blob:; font-src 'self' data:; frame-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; manifest-src 'self'"
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Decentralized Video Sharing</title> <title>bitvid | Decentralized Video Sharing</title>
+12
View File
@@ -87,6 +87,18 @@ for (const file of ["index.html", "embed.html"]) {
["'self'"], ["'self'"],
"base-uri must be 'self'", "base-uri must be 'self'",
); );
// index.html mounts <bitlogin-auth> (components/login-modal.html), whose
// crypto worker compiles an Argon2id WASM module. Without
// 'wasm-unsafe-eval' the browser blocks WebAssembly.compile and EVERY
// BitLogin password flow fails -- silently, since the page still loads.
// This exact omission broke sign-in on bitlogin.network for four days.
if (file === "index.html") {
assert.ok(
scriptSrc.includes("'wasm-unsafe-eval'"),
"script-src must include 'wasm-unsafe-eval' or BitLogin sign-in breaks",
);
}
}); });
test(`${file}: every inline <script> is hash-allowlisted in script-src`, () => { test(`${file}: every inline <script> is hash-allowlisted in script-src`, () => {
+14 -14
View File
@@ -3,8 +3,8 @@
"version": "0.1.0", "version": "0.1.0",
"source": { "source": {
"repository": "https://github.com/PR0M3TH3AN/bitlogin.git", "repository": "https://github.com/PR0M3TH3AN/bitlogin.git",
"commit": "205b0678cc67ecbde18278b8f46e7f8270fb519c", "commit": "30b4f34300f0cb6195f4bd54652ec75e814889dd",
"commitDate": "2026-07-31T22:04:01-04:00", "commitDate": "2026-08-04T15:41:24-04:00",
"dirty": false, "dirty": false,
"provenance": "attributed", "provenance": "attributed",
"toolchain": { "toolchain": {
@@ -13,25 +13,25 @@
} }
}, },
"files": { "files": {
"bitlogin-shared--Zs4uvXt.js": { "bitlogin-shared-CEfP5Eg9.js": {
"sha256": "550434b2ed3df975fc1d8896dca02f8622f2819d1fd9fa288049f2c22baa6bc4", "sha256": "6e19179b46deab241ffa9baded2ab0245122cf88e5b4191fcd4ca905b81a67be",
"bytes": 483718 "bytes": 289142
}, },
"bitlogin-shared-BxVSgyOf.js": { "bitlogin-shared-Co4_WTb5.js": {
"sha256": "d7f157c2de3d4b81de0055df2582306df9d862706d1dad35ff4096d06e1c1c93", "sha256": "ffecf43e4856105d485402c7d46ca6950b621965a85c5962daf0177f0e538237",
"bytes": 545712 "bytes": 545712
}, },
"bitlogin-shared-DTz11m56.js": { "bitlogin-shared-DeDxZ3Jc.js": {
"sha256": "c52f485c6390658e8da75d7c31f01167eb4f8c22d836f9d92e22c2b0885bf79e", "sha256": "290c293f288a16f5c753d19b0eecee795ea07b5fe31219812f260aae407de29b",
"bytes": 289118 "bytes": 483718
}, },
"bitlogin.js": { "bitlogin.js": {
"sha256": "56eeb7584b0117e2e360c80d37a5bfddb2e1f7a8ee6d94995c24043c0e87b906", "sha256": "823e4ec24a8e34d96428d0c933cf16be1755a80ff105dcf52dd96391e52da513",
"bytes": 77303 "bytes": 146039
}, },
"cryptoWorker.js": { "cryptoWorker.js": {
"sha256": "4d569dac3b808d118bf432d628f084c8c1d8ba76d94cf101030ace1077c63b36", "sha256": "adea9e4934e7b6df024cc97385ce227ad4870dad436ee8266fbe644cd5cec5b5",
"bytes": 110827 "bytes": 126329
} }
} }
} }
@@ -10406,7 +10406,7 @@ function J1(e, t) {
function Y1(e, t) { function Y1(e, t) {
return e.tags.find((n) => n[0] === t)?.[1]; return e.tags.find((n) => n[0] === t)?.[1];
} }
const M1 = 0, q1 = 5, j1 = 10002, R1 = 10050, _1 = 22242, P1 = 30078, L1 = "bitlogin:password:v1", T1 = "bitlogin:recovery:v1", X1 = "bitlogin.credential.v1", Z1 = "bitlogin.recovery.v1", kn = "bitlogin.recovery-export.v3"; const M1 = 0, q1 = 5, j1 = 10002, R1 = 10050, _1 = 22242, P1 = 24133, L1 = 30078, T1 = "bitlogin:password:v1", X1 = "bitlogin:recovery:v1", Z1 = "bitlogin.credential.v1", $1 = "bitlogin.recovery.v1", kn = "bitlogin.recovery-export.v3";
var Ut, Ur; var Ut, Ur;
function g1() { function g1() {
return Ur || (Ur = 1, Ut = function e(t) { return Ur || (Ur = 1, Ut = function e(t) {
@@ -10423,7 +10423,7 @@ function g1() {
}), Ut; }), Ut;
} }
var A1 = g1(); var A1 = g1();
const $1 = /* @__PURE__ */ Kr(A1); const W1 = /* @__PURE__ */ Kr(A1);
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */ /*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function Ue(e) { function Ue(e) {
return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array"; return e instanceof Uint8Array || ArrayBuffer.isView(e) && e.constructor.name === "Uint8Array";
@@ -10638,7 +10638,7 @@ function p1(e, t) {
} }
}; };
} }
const W1 = { const e2 = {
alphabet: er, alphabet: er,
chain: Wt, chain: Wt,
checksum: p1, checksum: p1,
@@ -10654,7 +10654,7 @@ const W1 = {
if (e.length > 0 && !n.test(e)) if (e.length > 0 && !n.test(e))
throw new Error("invalid base64"); throw new Error("invalid base64");
return Uint8Array.fromBase64(e, { alphabet: r, lastChunkHandling: "strict" }); return Uint8Array.fromBase64(e, { alphabet: r, lastChunkHandling: "strict" });
}, e2 = I1 ? { }, t2 = I1 ? {
encode(e) { encode(e) {
return f1(e), e.toBase64(); return f1(e), e.toBase64();
}, },
@@ -10740,19 +10740,19 @@ function y1(e) {
}; };
} }
const nr = /* @__PURE__ */ y1("bech32"); const nr = /* @__PURE__ */ y1("bech32");
function t2(e) { function r2(e) {
return nr.encodeFromBytes("npub", Je(e)); return nr.encodeFromBytes("npub", Je(e));
} }
function r2(e) { function n2(e) {
return nr.encodeFromBytes("nsec", e); return nr.encodeFromBytes("nsec", e);
} }
function n2(e) { function i2(e) {
const { prefix: t, bytes: n } = nr.decodeToBytes(e); const { prefix: t, bytes: n } = nr.decodeToBytes(e);
if (t !== "nsec" || n.length !== 32) if (t !== "nsec" || n.length !== 32)
throw new Error("Not a valid nsec."); throw new Error("Not a valid nsec.");
return n; return n;
} }
function i2(e) { function a2(e) {
return { return {
schema: kn, schema: kn,
recovery_public_key: e.recoveryPublicKeyHex, recovery_public_key: e.recoveryPublicKeyHex,
@@ -10767,7 +10767,7 @@ class oe extends Error {
super(t), this.name = "RecoveryExportParseError"; super(t), this.name = "RecoveryExportParseError";
} }
} }
function a2(e) { function o2(e) {
if (typeof e != "object" || e === null) if (typeof e != "object" || e === null)
throw new oe("Recovery export must be a JSON object."); throw new oe("Recovery export must be a JSON object.");
const t = e; const t = e;
@@ -10792,68 +10792,69 @@ function a2(e) {
return t; return t;
} }
export { export {
Rr as $, q1 as $,
ft as A, bt as A,
x1 as B, Q1 as B,
rt as C, ft as C,
L1 as D, T1 as D,
w1 as E, w1 as E,
E1 as F, x1 as F,
pt as G, rt as G,
Ce as H, E1 as H,
D1 as I, pt as I,
pe as J, Ce as J,
_1 as K, _1 as K,
W1 as L, D1 as L,
he as M, pe as M,
We as N, e2 as N,
n2 as O, he as O,
Je as P, We as P,
i1 as Q, i2 as Q,
oe as R, oe as R,
Z1 as S, $1 as S,
N1 as T, Je as T,
C1 as U, i1 as U,
K1 as V, N1 as V,
_r as W, C1 as W,
O1 as X, K1 as X,
v1 as Y, _r as Y,
q1 as Z, O1 as Z,
jr as _, v1 as _,
t2 as a, qe as a,
qr as a0, Rr as a0,
mn as a1, qr as a1,
Rt as a2, mn as a2,
mt as a3, mt as a3,
e2 as a4, t2 as a4,
G1 as a5, G1 as a5,
J1 as a6, P1 as a6,
i2 as a7, J1 as a7,
r2 as a8, a2 as a8,
Kr as a9, n2 as a9,
Kr as aa,
jt as b, jt as b,
qe as c, Rt as c,
M1 as d, S1 as d,
b1 as e, b1 as e,
j1 as f, r2 as f,
V1 as g, V1 as g,
R1 as h, jr as h,
H1 as i, H1 as i,
P1 as j, M1 as j,
$1 as k, j1 as k,
k1 as l, R1 as l,
S1 as m, L1 as m,
F1 as n, W1 as n,
U1 as o, k1 as o,
a2 as p, o2 as p,
s1 as q, F1 as q,
B1 as r, B1 as r,
z1 as s, z1 as s,
X1 as t, U1 as t,
De as u, De as u,
Fr as v, Fr as v,
T1 as w, s1 as w,
Y1 as x, Z1 as x,
bt as y, X1 as y,
Q1 as z Y1 as z
}; };
@@ -1,4 +1,4 @@
import { a9 as D2 } from "./bitlogin-shared-DTz11m56.js"; import { aa as D2 } from "./bitlogin-shared-CEfP5Eg9.js";
const wl = { const wl = {
debug() { debug() {
} }
@@ -7918,7 +7918,7 @@ let ke;
async function l2() { async function l2() {
try { try {
if (ke) return ke; if (ke) return ke;
const e = (await import("./bitlogin-shared--Zs4uvXt.js").then((t) => t.i)).default; const e = (await import("./bitlogin-shared-DeDxZ3Jc.js").then((t) => t.i)).default;
return ke = new e(), ke; return ke = new e(), ke;
} catch (e) { } catch (e) {
throw console.error(e), new Error("LNC is not available"); throw console.error(e), new Error("LNC is not available");
@@ -1,4 +1,4 @@
import { a9 as Ye } from "./bitlogin-shared-DTz11m56.js"; import { aa as Ye } from "./bitlogin-shared-CEfP5Eg9.js";
function We(we, je) { function We(we, je) {
for (var Ie = 0; Ie < je.length; Ie++) { for (var Ie = 0; Ie < je.length; Ie++) {
const Ae = je[Ie]; const Ae = je[Ie];
+2087 -218
View File
File diff suppressed because it is too large. Load diff
+1405 -952
View File
File diff suppressed because it is too large. Load diff