fix(csp,upload): allow WASM in CSP + delta-worker liveness watchdog
Root cause of folder uploads "freezing at ~95%": the global Content-Security-
Policy `script-src` was `'self'` + inline-script hashes with NO
`'wasm-unsafe-eval'`. Chromium therefore blocked `WebAssembly.instantiate`
("Wasm code generation disallowed by embedder"), so the vendored BLAKE3/FastCDC
WASM threw on instantiation — both on the main thread (instant by-hash uploads
and the batch dedup check) and inside the delta-upload worker. Every file then
fell back to a plain byte upload, and the backend logs showed 0 check-batch /
0 negotiate calls. Large files (32 MB service logs) compounded it and the
session token expired mid-upload, so the last handful failed.
- web/mod.rs: add `'wasm-unsafe-eval'` to `script-src`. WASM-only, safe variant
— does NOT enable `eval()`/`new Function()`. Restores instant uploads, delta
(sub-file dedup), and the client hashing the idempotent re-upload relies on.
- deltaUpload.ts: liveness watchdog on the delta worker. A healthy worker posts
progress sub-second; if it goes silent for 20 s it is wedged (WASM init or
chunking hung without throwing) — disable delta for this file AND every later
one so they fall straight through to a plain upload instead of each burning
the full 120 s+ delta timeout. Defense-in-depth so a broken WASM path can
never again freeze an upload for minutes.
cargo test: pass. npm run check: clean, 58 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,14 +80,33 @@ export function tryDeltaUpload(
|
||||
const timeoutMs = DELTA_TIMEOUT_BASE_MS + Math.ceil(sizeGB) * DELTA_TIMEOUT_PER_GB_MS;
|
||||
let savedBytes = 0;
|
||||
|
||||
let stallTimer: ReturnType<typeof setTimeout>;
|
||||
const settle = (answer: DeltaUploadAnswer | null) => {
|
||||
clearTimeout(timer);
|
||||
clearTimeout(stallTimer);
|
||||
worker.terminate();
|
||||
resolve(answer);
|
||||
};
|
||||
const timer = setTimeout(() => settle(null), timeoutMs);
|
||||
|
||||
// Liveness watchdog: a healthy worker posts progress sub-second while it
|
||||
// hashes and uploads. If it goes SILENT this long it is wedged (WASM init
|
||||
// or chunking hung without throwing, emitting neither fallback nor error)
|
||||
// — exactly what freezes a folder upload ~2 min per large file. Disable
|
||||
// delta for this file AND every later one so they fall straight to a plain
|
||||
// upload instead of each burning the full size-scaled delta timeout.
|
||||
const STALL_MS = 20_000;
|
||||
const armStall = () => {
|
||||
clearTimeout(stallTimer);
|
||||
stallTimer = setTimeout(() => {
|
||||
usable = false;
|
||||
settle(null);
|
||||
}, STALL_MS);
|
||||
};
|
||||
armStall();
|
||||
|
||||
worker.onmessage = (event: MessageEvent<WorkerMsg>) => {
|
||||
armStall(); // worker is alive — reset the liveness watchdog
|
||||
const msg = event.data;
|
||||
if (msg.type === 'progress') {
|
||||
savedBytes = msg.reusedBytes;
|
||||
|
||||
@@ -112,7 +112,14 @@ pub fn content_security_policy(config: &AppConfig) -> String {
|
||||
);
|
||||
}
|
||||
|
||||
let mut script_src = String::from("script-src 'self'");
|
||||
// `'wasm-unsafe-eval'` is required for WebAssembly compilation/instantiation
|
||||
// under a strict CSP (Chromium blocks `WebAssembly.instantiate` otherwise with
|
||||
// "Wasm code generation disallowed by embedder"). The frontend instantiates the
|
||||
// vendored BLAKE3/FastCDC WASM both on the main thread (instant by-hash uploads)
|
||||
// and inside the delta-upload worker; without this they throw and every large
|
||||
// file silently falls back to a plain byte upload. It is the WASM-only, safe
|
||||
// variant — it does NOT permit `eval()`/`new Function()` (no `'unsafe-eval'`).
|
||||
let mut script_src = String::from("script-src 'self' 'wasm-unsafe-eval'");
|
||||
for hash in &hashes {
|
||||
script_src.push(' ');
|
||||
script_src.push_str(hash);
|
||||
|
||||
Reference in New Issue
Block a user