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
This commit is contained in:
Lucas Fernandes Nogueira 2025-10-10 08:11:38 -03:00 committed by GitHub
parent c5008b829d
commit 7b0d4e7322
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 61 additions and 5 deletions

View File

@ -0,0 +1,5 @@
---
"tauri-codegen": patch:bug
---
Fix JavaScript SHA256 hash generation on Windows not ignoring carriage return characters.

View File

@ -0,0 +1,5 @@
---
"tauri-utils": patch:feat
---
Added `html::normalize_script_for_csp`.

View File

@ -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-{}'",

View File

@ -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!(

View File

@ -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<u8> {
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()
)
}
}