[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
directive is script-src, which is strict — only same-origin scripts, the
hash of the inline theme-bootstrap below, and the nostr-tools CDN
fallbacks (js/nostrToolsBootstrap.js). No 'unsafe-inline'/'unsafe-eval'
(verified: no bundle uses eval/Function/wasm). This blocks injected
fallbacks (js/nostrToolsBootstrap.js). No 'unsafe-inline'/'unsafe-eval'.
'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
hijack, so an injected payload cannot execute and read the in-memory
storage master key / signer / DM plaintext. connect/img/media stay broad
@@ -19,7 +23,7 @@
-->
<meta
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" />
<title>bitvid | Decentralized Video Sharing</title>
+12
View File
@@ -87,6 +87,18 @@ for (const file of ["index.html", "embed.html"]) {
["'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`, () => {
+14 -14
View File
@@ -3,8 +3,8 @@
"version": "0.1.0",
"source": {
"repository": "https://github.com/PR0M3TH3AN/bitlogin.git",
"commit": "205b0678cc67ecbde18278b8f46e7f8270fb519c",
"commitDate": "2026-07-31T22:04:01-04:00",
"commit": "30b4f34300f0cb6195f4bd54652ec75e814889dd",
"commitDate": "2026-08-04T15:41:24-04:00",
"dirty": false,
"provenance": "attributed",
"toolchain": {
@@ -13,25 +13,25 @@
}
},
"files": {
"bitlogin-shared--Zs4uvXt.js": {
"sha256": "550434b2ed3df975fc1d8896dca02f8622f2819d1fd9fa288049f2c22baa6bc4",
"bytes": 483718
"bitlogin-shared-CEfP5Eg9.js": {
"sha256": "6e19179b46deab241ffa9baded2ab0245122cf88e5b4191fcd4ca905b81a67be",
"bytes": 289142
},
"bitlogin-shared-BxVSgyOf.js": {
"sha256": "d7f157c2de3d4b81de0055df2582306df9d862706d1dad35ff4096d06e1c1c93",
"bitlogin-shared-Co4_WTb5.js": {
"sha256": "ffecf43e4856105d485402c7d46ca6950b621965a85c5962daf0177f0e538237",
"bytes": 545712
},
"bitlogin-shared-DTz11m56.js": {
"sha256": "c52f485c6390658e8da75d7c31f01167eb4f8c22d836f9d92e22c2b0885bf79e",
"bytes": 289118
"bitlogin-shared-DeDxZ3Jc.js": {
"sha256": "290c293f288a16f5c753d19b0eecee795ea07b5fe31219812f260aae407de29b",
"bytes": 483718
},
"bitlogin.js": {
"sha256": "56eeb7584b0117e2e360c80d37a5bfddb2e1f7a8ee6d94995c24043c0e87b906",
"bytes": 77303
"sha256": "823e4ec24a8e34d96428d0c933cf16be1755a80ff105dcf52dd96391e52da513",
"bytes": 146039
},
"cryptoWorker.js": {
"sha256": "4d569dac3b808d118bf432d628f084c8c1d8ba76d94cf101030ace1077c63b36",
"bytes": 110827
"sha256": "adea9e4934e7b6df024cc97385ce227ad4870dad436ee8266fbe644cd5cec5b5",
"bytes": 126329
}
}
}
@@ -10406,7 +10406,7 @@ function J1(e, t) {
function Y1(e, t) {
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;
function g1() {
return Ur || (Ur = 1, Ut = function e(t) {
@@ -10423,7 +10423,7 @@ function g1() {
}), Ut;
}
var A1 = g1();
const $1 = /* @__PURE__ */ Kr(A1);
const W1 = /* @__PURE__ */ Kr(A1);
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function Ue(e) {
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,
chain: Wt,
checksum: p1,
@@ -10654,7 +10654,7 @@ const W1 = {
if (e.length > 0 && !n.test(e))
throw new Error("invalid base64");
return Uint8Array.fromBase64(e, { alphabet: r, lastChunkHandling: "strict" });
}, e2 = I1 ? {
}, t2 = I1 ? {
encode(e) {
return f1(e), e.toBase64();
},
@@ -10740,19 +10740,19 @@ function y1(e) {
};
}
const nr = /* @__PURE__ */ y1("bech32");
function t2(e) {
function r2(e) {
return nr.encodeFromBytes("npub", Je(e));
}
function r2(e) {
function n2(e) {
return nr.encodeFromBytes("nsec", e);
}
function n2(e) {
function i2(e) {
const { prefix: t, bytes: n } = nr.decodeToBytes(e);
if (t !== "nsec" || n.length !== 32)
throw new Error("Not a valid nsec.");
return n;
}
function i2(e) {
function a2(e) {
return {
schema: kn,
recovery_public_key: e.recoveryPublicKeyHex,
@@ -10767,7 +10767,7 @@ class oe extends Error {
super(t), this.name = "RecoveryExportParseError";
}
}
function a2(e) {
function o2(e) {
if (typeof e != "object" || e === null)
throw new oe("Recovery export must be a JSON object.");
const t = e;
@@ -10792,68 +10792,69 @@ function a2(e) {
return t;
}
export {
Rr as $,
ft as A,
x1 as B,
rt as C,
L1 as D,
q1 as $,
bt as A,
Q1 as B,
ft as C,
T1 as D,
w1 as E,
E1 as F,
pt as G,
Ce as H,
D1 as I,
pe as J,
x1 as F,
rt as G,
E1 as H,
pt as I,
Ce as J,
_1 as K,
W1 as L,
he as M,
We as N,
n2 as O,
Je as P,
i1 as Q,
D1 as L,
pe as M,
e2 as N,
he as O,
We as P,
i2 as Q,
oe as R,
Z1 as S,
N1 as T,
C1 as U,
K1 as V,
_r as W,
O1 as X,
v1 as Y,
q1 as Z,
jr as _,
t2 as a,
qr as a0,
mn as a1,
Rt as a2,
$1 as S,
Je as T,
i1 as U,
N1 as V,
C1 as W,
K1 as X,
_r as Y,
O1 as Z,
v1 as _,
qe as a,
Rr as a0,
qr as a1,
mn as a2,
mt as a3,
e2 as a4,
t2 as a4,
G1 as a5,
J1 as a6,
i2 as a7,
r2 as a8,
Kr as a9,
P1 as a6,
J1 as a7,
a2 as a8,
n2 as a9,
Kr as aa,
jt as b,
qe as c,
M1 as d,
Rt as c,
S1 as d,
b1 as e,
j1 as f,
r2 as f,
V1 as g,
R1 as h,
jr as h,
H1 as i,
P1 as j,
$1 as k,
k1 as l,
S1 as m,
F1 as n,
U1 as o,
a2 as p,
s1 as q,
M1 as j,
j1 as k,
R1 as l,
L1 as m,
W1 as n,
k1 as o,
o2 as p,
F1 as q,
B1 as r,
z1 as s,
X1 as t,
U1 as t,
De as u,
Fr as v,
T1 as w,
Y1 as x,
bt as y,
Q1 as z
s1 as w,
Z1 as x,
X1 as y,
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 = {
debug() {
}
@@ -7918,7 +7918,7 @@ let ke;
async function l2() {
try {
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;
} catch (e) {
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) {
for (var Ie = 0; Ie < je.length; 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