From 7b0d4e73227e42d88732b6d9fe643499dd78ec4e Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Fri, 10 Oct 2025 08:11:38 -0300 Subject: [PATCH] fix(core): SHA256 hash for JS scripts CSP on Windows (#14265) * fix(core): SHA256 hash for JS scripts CSP on Windows we hash JS scripts as SHA256 for the Content-Security-Policy (CSP) header. The isolation pattern is broken on Windows due to the hash including carriage return characters, which are not processed when the webview checks the script hash to see if the CSP allows the script. * fmt, clippy --- .changes/fix-csp-windows.md | 5 +++ .changes/normalize_script_for_csp.md | 5 +++ crates/tauri-codegen/src/context.rs | 4 +- crates/tauri-codegen/src/embedded_assets.rs | 10 +++-- crates/tauri-utils/src/html.rs | 42 +++++++++++++++++++++ 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 .changes/fix-csp-windows.md create mode 100644 .changes/normalize_script_for_csp.md diff --git a/.changes/fix-csp-windows.md b/.changes/fix-csp-windows.md new file mode 100644 index 000000000..8321e7b1a --- /dev/null +++ b/.changes/fix-csp-windows.md @@ -0,0 +1,5 @@ +--- +"tauri-codegen": patch:bug +--- + +Fix JavaScript SHA256 hash generation on Windows not ignoring carriage return characters. diff --git a/.changes/normalize_script_for_csp.md b/.changes/normalize_script_for_csp.md new file mode 100644 index 000000000..3fa4bb533 --- /dev/null +++ b/.changes/normalize_script_for_csp.md @@ -0,0 +1,5 @@ +--- +"tauri-utils": patch:feat +--- + +Added `html::normalize_script_for_csp`. diff --git a/crates/tauri-codegen/src/context.rs b/crates/tauri-codegen/src/context.rs index 9176aea64..87a05d702 100644 --- a/crates/tauri-codegen/src/context.rs +++ b/crates/tauri-codegen/src/context.rs @@ -50,7 +50,9 @@ fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut Csp for inline_script_el in inline_script_elements { let script = inline_script_el.as_node().text_contents(); let mut hasher = Sha256::new(); - hasher.update(&script); + hasher.update(tauri_utils::html::normalize_script_for_csp( + script.as_bytes(), + )); let hash = hasher.finalize(); scripts.push(format!( "'sha256-{}'", diff --git a/crates/tauri-codegen/src/embedded_assets.rs b/crates/tauri-codegen/src/embedded_assets.rs index 20b00a105..ede7ee6d9 100644 --- a/crates/tauri-codegen/src/embedded_assets.rs +++ b/crates/tauri-codegen/src/embedded_assets.rs @@ -180,10 +180,12 @@ impl CspHashes { if dangerous_disable_asset_csp_modification.can_modify("script-src") { let mut hasher = Sha256::new(); hasher.update( - &std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead { - path: path.to_path_buf(), - error, - })?, + &std::fs::read(path) + .map(|b| tauri_utils::html::normalize_script_for_csp(&b)) + .map_err(|error| EmbeddedAssetsError::AssetRead { + path: path.to_path_buf(), + error, + })?, ); let hash = hasher.finalize(); self.scripts.push(format!( diff --git a/crates/tauri-utils/src/html.rs b/crates/tauri-utils/src/html.rs index bfa203f00..d29a61d08 100644 --- a/crates/tauri-utils/src/html.rs +++ b/crates/tauri-utils/src/html.rs @@ -286,6 +286,38 @@ pub fn inline_isolation(document: &NodeRef, dir: &Path) { } } +/// Normalize line endings in script content to match what the browser uses for CSP hashing. +/// +/// According to the HTML spec, browsers normalize: +/// - `\r\n` → `\n` +/// - `\r` → `\n` +pub fn normalize_script_for_csp(input: &[u8]) -> Vec { + let mut output = Vec::with_capacity(input.len()); + + let mut i = 0; + while i < input.len() { + match input[i] { + b'\r' => { + if i + 1 < input.len() && input[i + 1] == b'\n' { + // CRLF → LF + output.push(b'\n'); + i += 2; + } else { + // Lone CR → LF + output.push(b'\n'); + i += 1; + } + } + _ => { + output.push(input[i]); + i += 1; + } + } + } + + output +} + #[cfg(test)] mod tests { @@ -307,4 +339,14 @@ mod tests { ); } } + + #[test] + fn normalize_script_for_csp() { + let js = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\r// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\r\n\r\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\r\n return payload\r\n}\r\n"; + let expected = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n return payload\n}\n"; + assert_eq!( + super::normalize_script_for_csp(js.as_bytes()), + expected.as_bytes() + ) + } }