Skip to content

Commit 164078c

Browse files
authored
feat: allow limiting dangerousDisableAssetCspModification, closes #3831 (#4021)
1 parent a6f1734 commit 164078c

File tree

8 files changed

+175
-71
lines changed

8 files changed

+175
-71
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"tauri": patch
3+
"tauri-utils": patch
4+
"tauri-codegen": patch
5+
---
6+
7+
The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable.

core/tauri-codegen/src/context.rs

+32-25
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use sha2::{Digest, Sha256};
1111

1212
use tauri_utils::assets::AssetKey;
1313
use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
14-
use tauri_utils::html::{inject_nonce_token, parse as parse_html, NodeRef};
14+
use tauri_utils::html::{inject_nonce_token, parse as parse_html};
1515

1616
#[cfg(feature = "shell-scope")]
1717
use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope};
@@ -26,32 +26,14 @@ pub struct ContextData {
2626
pub root: TokenStream,
2727
}
2828

29-
fn load_csp(document: &mut NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) {
30-
inject_nonce_token(document);
31-
if let Ok(inline_script_elements) = document.select("script:not(empty)") {
32-
let mut scripts = Vec::new();
33-
for inline_script_el in inline_script_elements {
34-
let script = inline_script_el.as_node().text_contents();
35-
let mut hasher = Sha256::new();
36-
hasher.update(&script);
37-
let hash = hasher.finalize();
38-
scripts.push(format!("'sha256-{}'", base64::encode(&hash)));
39-
}
40-
csp_hashes
41-
.inline_scripts
42-
.entry(key.clone().into())
43-
.or_default()
44-
.append(&mut scripts);
45-
}
46-
}
47-
4829
fn map_core_assets(
4930
options: &AssetOptions,
5031
) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
5132
#[cfg(feature = "isolation")]
5233
let pattern = tauri_utils::html::PatternObject::from(&options.pattern);
5334
let csp = options.csp;
54-
let dangerous_disable_asset_csp_modification = options.dangerous_disable_asset_csp_modification;
35+
let dangerous_disable_asset_csp_modification =
36+
options.dangerous_disable_asset_csp_modification.clone();
5537
move |key, path, input, csp_hashes| {
5638
if path.extension() == Some(OsStr::new("html")) {
5739
let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
@@ -61,10 +43,28 @@ fn map_core_assets(
6143
#[cfg(target_os = "linux")]
6244
::tauri_utils::html::inject_csp_token(&mut document);
6345

64-
if !dangerous_disable_asset_csp_modification {
65-
load_csp(&mut document, key, csp_hashes);
46+
inject_nonce_token(&mut document, &dangerous_disable_asset_csp_modification);
47+
48+
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
49+
if let Ok(inline_script_elements) = document.select("script:not(empty)") {
50+
let mut scripts = Vec::new();
51+
for inline_script_el in inline_script_elements {
52+
let script = inline_script_el.as_node().text_contents();
53+
let mut hasher = Sha256::new();
54+
hasher.update(&script);
55+
let hash = hasher.finalize();
56+
scripts.push(format!("'sha256-{}'", base64::encode(&hash)));
57+
}
58+
csp_hashes
59+
.inline_scripts
60+
.entry(key.clone().into())
61+
.or_default()
62+
.append(&mut scripts);
63+
}
64+
}
6665

67-
#[cfg(feature = "isolation")]
66+
#[cfg(feature = "isolation")]
67+
if dangerous_disable_asset_csp_modification.can_modify("style-src") {
6868
if let tauri_utils::html::PatternObject::Isolation { .. } = &pattern {
6969
// create the csp for the isolation iframe styling now, to make the runtime less complex
7070
let mut hasher = Sha256::new();
@@ -116,7 +116,14 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
116116
} = data;
117117

118118
let mut options = AssetOptions::new(config.tauri.pattern.clone())
119-
.freeze_prototype(config.tauri.security.freeze_prototype);
119+
.freeze_prototype(config.tauri.security.freeze_prototype)
120+
.dangerous_disable_asset_csp_modification(
121+
config
122+
.tauri
123+
.security
124+
.dangerous_disable_asset_csp_modification
125+
.clone(),
126+
);
120127
let csp = if dev {
121128
config
122129
.tauri

core/tauri-codegen/src/embedded_assets.rs

+25-19
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use std::{
1111
fs::File,
1212
path::{Path, PathBuf},
1313
};
14-
use tauri_utils::assets::AssetKey;
1514
use tauri_utils::config::PatternKind;
15+
use tauri_utils::{assets::AssetKey, config::DisabledCspModificationKind};
1616
use thiserror::Error;
1717
use walkdir::{DirEntry, WalkDir};
1818

@@ -123,9 +123,9 @@ impl RawEmbeddedAssets {
123123

124124
// compress all files encountered
125125
Ok(entry) => {
126-
if options.dangerous_disable_asset_csp_modification {
127-
Some(Ok((prefix, entry)))
128-
} else if let Err(error) = csp_hashes.add_if_applicable(&entry) {
126+
if let Err(error) = csp_hashes
127+
.add_if_applicable(&entry, &options.dangerous_disable_asset_csp_modification)
128+
{
129129
Some(Err(error))
130130
} else {
131131
Some(Ok((prefix, entry)))
@@ -160,22 +160,28 @@ impl CspHashes {
160160
///
161161
/// Note: this only checks the file extension, much like how a browser will assume a .js file is
162162
/// a JavaScript file unless HTTP headers tell it otherwise.
163-
pub fn add_if_applicable(&mut self, entry: &DirEntry) -> Result<(), EmbeddedAssetsError> {
163+
pub fn add_if_applicable(
164+
&mut self,
165+
entry: &DirEntry,
166+
dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
167+
) -> Result<(), EmbeddedAssetsError> {
164168
let path = entry.path();
165169

166170
// we only hash JavaScript files for now, may expand to other CSP hashable types in the future
167171
if let Some("js") | Some("mjs") = path.extension().and_then(|os| os.to_str()) {
168-
let mut hasher = Sha256::new();
169-
hasher.update(
170-
&std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
171-
path: path.to_path_buf(),
172-
error,
173-
})?,
174-
);
175-
let hash = hasher.finalize();
176-
self
177-
.scripts
178-
.push(format!("'sha256-{}'", base64::encode(hash)))
172+
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
173+
let mut hasher = Sha256::new();
174+
hasher.update(
175+
&std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
176+
path: path.to_path_buf(),
177+
error,
178+
})?,
179+
);
180+
let hash = hasher.finalize();
181+
self
182+
.scripts
183+
.push(format!("'sha256-{}'", base64::encode(hash)));
184+
}
179185
}
180186

181187
Ok(())
@@ -188,7 +194,7 @@ pub struct AssetOptions {
188194
pub(crate) csp: bool,
189195
pub(crate) pattern: PatternKind,
190196
pub(crate) freeze_prototype: bool,
191-
pub(crate) dangerous_disable_asset_csp_modification: bool,
197+
pub(crate) dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
192198
#[cfg(feature = "isolation")]
193199
pub(crate) isolation_schema: String,
194200
}
@@ -200,7 +206,7 @@ impl AssetOptions {
200206
csp: false,
201207
pattern,
202208
freeze_prototype: false,
203-
dangerous_disable_asset_csp_modification: false,
209+
dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
204210
#[cfg(feature = "isolation")]
205211
isolation_schema: format!("isolation-{}", uuid::Uuid::new_v4()),
206212
}
@@ -223,7 +229,7 @@ impl AssetOptions {
223229
/// Instruct the asset handler to **NOT** modify the CSP. This is **NOT** recommended.
224230
pub fn dangerous_disable_asset_csp_modification(
225231
mut self,
226-
dangerous_disable_asset_csp_modification: bool,
232+
dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
227233
) -> Self {
228234
self.dangerous_disable_asset_csp_modification = dangerous_disable_asset_csp_modification;
229235
self

core/tauri-utils/src/config.rs

+51-3
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,34 @@ impl Display for Csp {
757757
}
758758
}
759759

760+
/// The possible values for the `dangerous_disable_asset_csp_modification` config option.
761+
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
762+
#[serde(untagged)]
763+
#[cfg_attr(feature = "schema", derive(JsonSchema))]
764+
pub enum DisabledCspModificationKind {
765+
/// If `true`, disables all CSP modification.
766+
/// `false` is the default value and it configures Tauri to control the CSP.
767+
Flag(bool),
768+
/// Disables the given list of CSP directives modifications.
769+
List(Vec<String>),
770+
}
771+
772+
impl DisabledCspModificationKind {
773+
/// Determines whether the given CSP directive can be modified or not.
774+
pub fn can_modify(&self, directive: &str) -> bool {
775+
match self {
776+
Self::Flag(f) => !f,
777+
Self::List(l) => !l.contains(&directive.into()),
778+
}
779+
}
780+
}
781+
782+
impl Default for DisabledCspModificationKind {
783+
fn default() -> Self {
784+
Self::Flag(false)
785+
}
786+
}
787+
760788
/// Security configuration.
761789
#[skip_serializing_none]
762790
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
@@ -783,10 +811,14 @@ pub struct SecurityConfig {
783811
/// to only allow loading of your own scripts and styles by injecting nonce and hash sources.
784812
/// This stricts your CSP, which may introduce issues when using along with other flexing sources.
785813
///
814+
/// This configuration option allows both a boolean and a list of strings as value.
815+
/// A boolean instructs Tauri to disable the injection for all CSP injections,
816+
/// and a list of strings indicates the CSP directives that Tauri cannot inject.
817+
///
786818
/// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.
787819
/// Your application might be vulnerable to XSS attacks without this Tauri protection.
788820
#[serde(default)]
789-
pub dangerous_disable_asset_csp_modification: bool,
821+
pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
790822
}
791823

792824
/// Defines an allowlist type.
@@ -2637,12 +2669,28 @@ mod build {
26372669
}
26382670
}
26392671

2672+
impl ToTokens for DisabledCspModificationKind {
2673+
fn to_tokens(&self, tokens: &mut TokenStream) {
2674+
let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
2675+
2676+
tokens.append_all(match self {
2677+
Self::Flag(flag) => {
2678+
quote! { #prefix::Flag(#flag) }
2679+
}
2680+
Self::List(directives) => {
2681+
let directives = vec_lit(directives, str_lit);
2682+
quote! { #prefix::List(#directives) }
2683+
}
2684+
});
2685+
}
2686+
}
2687+
26402688
impl ToTokens for SecurityConfig {
26412689
fn to_tokens(&self, tokens: &mut TokenStream) {
26422690
let csp = opt_lit(self.csp.as_ref());
26432691
let dev_csp = opt_lit(self.dev_csp.as_ref());
26442692
let freeze_prototype = self.freeze_prototype;
2645-
let dangerous_disable_asset_csp_modification = self.dangerous_disable_asset_csp_modification;
2693+
let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
26462694

26472695
literal_struct!(
26482696
tokens,
@@ -2901,7 +2949,7 @@ mod test {
29012949
csp: None,
29022950
dev_csp: None,
29032951
freeze_prototype: false,
2904-
dangerous_disable_asset_csp_modification: false,
2952+
dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
29052953
},
29062954
allowlist: AllowlistConfig::default(),
29072955
system_tray: None,

core/tauri-utils/src/html.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use serde::Serialize;
1313
#[cfg(feature = "isolation")]
1414
use serialize_to_javascript::DefaultTemplate;
1515

16-
use crate::config::PatternKind;
16+
use crate::config::{DisabledCspModificationKind, PatternKind};
1717
#[cfg(feature = "isolation")]
1818
use crate::pattern::isolation::IsolationJavascriptCodegen;
1919

@@ -59,9 +59,16 @@ fn inject_nonce(document: &mut NodeRef, selector: &str, token: &str) {
5959
}
6060

6161
/// Inject nonce tokens to all scripts and styles.
62-
pub fn inject_nonce_token(document: &mut NodeRef) {
63-
inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN);
64-
inject_nonce(document, "style", STYLE_NONCE_TOKEN);
62+
pub fn inject_nonce_token(
63+
document: &mut NodeRef,
64+
dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
65+
) {
66+
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
67+
inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN);
68+
}
69+
if dangerous_disable_asset_csp_modification.can_modify("style-src") {
70+
inject_nonce(document, "style", STYLE_NONCE_TOKEN);
71+
}
6572
}
6673

6774
/// Injects a content security policy to the HTML.

core/tauri/src/manager.rs

+25-16
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ fn set_csp<R: Runtime>(
7979
asset: &mut String,
8080
assets: Arc<dyn Assets>,
8181
asset_path: &AssetKey,
82-
#[allow(unused_variables)] manager: &WindowManager<R>,
82+
manager: &WindowManager<R>,
8383
csp: Csp,
8484
) -> String {
8585
let mut csp = csp.into();
@@ -103,21 +103,30 @@ fn set_csp<R: Runtime>(
103103
acc
104104
});
105105

106-
replace_csp_nonce(
107-
asset,
108-
SCRIPT_NONCE_TOKEN,
109-
&mut csp,
110-
"script-src",
111-
hash_strings.script,
112-
);
113-
114-
replace_csp_nonce(
115-
asset,
116-
STYLE_NONCE_TOKEN,
117-
&mut csp,
118-
"style-src",
119-
hash_strings.style,
120-
);
106+
let dangerous_disable_asset_csp_modification = &manager
107+
.config()
108+
.tauri
109+
.security
110+
.dangerous_disable_asset_csp_modification;
111+
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
112+
replace_csp_nonce(
113+
asset,
114+
SCRIPT_NONCE_TOKEN,
115+
&mut csp,
116+
"script-src",
117+
hash_strings.script,
118+
);
119+
}
120+
121+
if dangerous_disable_asset_csp_modification.can_modify("style-src") {
122+
replace_csp_nonce(
123+
asset,
124+
STYLE_NONCE_TOKEN,
125+
&mut csp,
126+
"style-src",
127+
hash_strings.style,
128+
);
129+
}
121130

122131
#[cfg(feature = "isolation")]
123132
if let Pattern::Isolation { schema, .. } = &manager.inner.pattern {

examples/api/src-tauri/Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)