From 5142cf6b2a0f44686eeda1ed07905ee3560fd78e Mon Sep 17 00:00:00 2001 From: Luke Tornquist Date: Fri, 9 Oct 2026 15:23:27 -0400 Subject: [PATCH] test(desktop): move CSP guards from Rust to the desktop JS suite (#8172) ## Summary Moves the packaged-app CSP guards from `desktop/src-tauri/tests/csp.rs` to `desktop/src/app/contentSecurityPolicy.test.mjs`. No production code changes beyond one added `export`. `csp.rs` never touched Rust app code. All seven tests parse `tauri.conf.json` and compare CSP strings. The MediaPipe check also `include_str!`'d `desktop/src/features/profile/lib/animatedAvatarCapture.ts` and pulled the `MEDIAPIPE_WASM_BASE` literal out by splitting the text. That made a Tauri test depend on a frontend file. With path-filtered CI (#8156), a change to only that `.ts` file would skip the check. The JS version imports `MEDIAPIPE_WASM_BASE` directly (it's now exported) and reads `src-tauri/tauri.conf.json` from disk. The `desktop/**` CI filter covers both files, so the check runs in Desktop JS whenever either one changes, and the filter needs no cross-boundary special case. The seven tests and their assertions carry over one-for-one, along with their rationale comments. **What got simpler:** we delete a 204-line Rust integration test, the `include_str!` of a frontend file, and the hand-rolled string-literal scraper, and the CI path filter doesn't need to know about this file. ### Related issue Addresses the CSP coupling raised in review of #8156. Related: #4614 (introduced the guards). ### Testing - New suite on Linux: 7/7 pass. - I falsified each test by mutating the real policy or code, one mutation at a time. Each mutation fails exactly the matching test, and the suite passes again once restored: - moved `MEDIAPIPE_WASM_BASE` outside `@mediapipe/` - dropped `'wasm-unsafe-eval'` - dropped the MediaPipe scope from `script-src` - added a bare `https://cdn.jsdelivr.net/` origin - added `'unsafe-inline'` - removed `img-src` - dropped `ws:` from `connect-src` - made the pinned-source predicate always pass - Blox `just ci` at tree `5664c6ce` (same tree as head `f798f94b`): - Passed: fmt, clippy, desktop/Tauri/web checks, security-review/file-size checks, `desktop-test` (6,819 + 139 jsdom), `desktop-build`, `desktop-tauri-check`, `desktop-tauri-test`, `web-build`. - `test-unit`: 94/95 suites pass. The two failing `buzz-acp` `git_bootstrap` tests fail because the workstation's Git 2.43 predates the credential `authtype` protocol. They pass 4/4 with Git 2.51, and this PR touches no `crates/`. - Not run: `mobile-check`/`mobile-test`, because the workstation can't reach pub.dev. No `mobile/` changes. - Hosted CI green: https://github.com/block/buzz/actions/runs/37801901659. This PR selects the Tauri lanes because it deletes `src-tauri/tests/csp.rs`. Later edits that only touch the constant select Desktop JS only. --------- Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> --- desktop/src-tauri/tests/csp.rs | 204 ------------------ .../src/app/contentSecurityPolicy.test.mjs | 181 ++++++++++++++++ .../profile/lib/animatedAvatarCapture.ts | 5 +- 3 files changed, 184 insertions(+), 206 deletions(-) delete mode 100644 desktop/src-tauri/tests/csp.rs create mode 100644 desktop/src/app/contentSecurityPolicy.test.mjs diff --git a/desktop/src-tauri/tests/csp.rs b/desktop/src-tauri/tests/csp.rs deleted file mode 100644 index 3497c36863..0000000000 --- a/desktop/src-tauri/tests/csp.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Guards on the packaged-app Content-Security-Policy in `tauri.conf.json`. -//! -//! The CSP is only enforced on assets Tauri itself serves, so neither -//! `just dev` (loads the Vite `devUrl`) nor the Playwright suite (runs under -//! `vite preview`) can catch a policy that breaks the app. These tests pin the -//! non-obvious sources the frontend actually needs, so a future tightening -//! fails here instead of in a signed build. -//! -//! Kept as an integration test so the policy can be checked without the app -//! crate having to declare a test-only module. - -use std::collections::HashMap; - -const TAURI_CONF: &str = include_str!("../tauri.conf.json"); - -fn csp_directives() -> HashMap> { - let conf: serde_json::Value = - serde_json::from_str(TAURI_CONF).expect("tauri.conf.json is valid JSON"); - let csp = conf["app"]["security"]["csp"] - .as_str() - .expect("app.security.csp is set as a policy string"); - - csp.split(';') - .filter_map(|directive| { - let mut parts = directive.split_whitespace(); - let name = parts.next()?; - Some((name.to_owned(), parts.map(str::to_owned).collect())) - }) - .collect() -} - -fn sources(directive: &str) -> Vec { - csp_directives() - .remove(directive) - .unwrap_or_else(|| panic!("csp is missing the {directive} directive")) -} - -#[test] -fn script_src_allows_wasm_instantiation() { - // Shiki's default engine (Oniguruma) instantiates inlined WebAssembly for - // every code block; MediaPipe selfie segmentation does the same. Without - // this token both silently degrade — highlighting drops to plain text and - // animated avatars keep their background. - assert!(sources("script-src").contains(&"'wasm-unsafe-eval'".to_owned())); -} - -/// The `MEDIAPIPE_WASM_BASE` literal the frontend hands to `FilesetResolver`. -fn mediapipe_wasm_base() -> String { - const CAPTURE: &str = include_str!("../../src/features/profile/lib/animatedAvatarCapture.ts"); - - let after = CAPTURE - .split_once("const MEDIAPIPE_WASM_BASE =") - .expect("animatedAvatarCapture.ts declares MEDIAPIPE_WASM_BASE") - .1; - let url = after - .split_once('"') - .expect("MEDIAPIPE_WASM_BASE is a double-quoted string literal") - .1; - url.split_once('"') - .expect("MEDIAPIPE_WASM_BASE literal is terminated") - .0 - .to_owned() -} - -/// The npm scope the MediaPipe loader must come from. A CSP source ending in -/// `/` is a path *prefix* — paths can't be wildcarded — so this admits any -/// `@mediapipe` package while excluding the rest of what jsDelivr serves. -const MEDIAPIPE_SCOPE: &str = "https://cdn.jsdelivr.net/npm/@mediapipe/"; - -#[test] -fn script_src_scopes_the_mediapipe_loader() { - // `FilesetResolver.forVisionTasks` loads `vision_wasm[_nosimd]_internal.js` - // via a `