瀏覽代碼

feat: allow limiting dangerousDisableAssetCspModification, closes #3831 (#4021)

Lucas Fernandes Nogueira 3 年之前
父節點
當前提交
164078c0b7

+ 7 - 0
.changes/dangerous-disable-asset-csp-modification-config.md

@@ -0,0 +1,7 @@
+---
+"tauri": patch
+"tauri-utils": patch
+"tauri-codegen": patch
+---
+
+The `dangerous_allow_asset_csp_modification` configuration value has been changed to allow a list of CSP directives to disable.

+ 32 - 25
core/tauri-codegen/src/context.rs

@@ -11,7 +11,7 @@ use sha2::{Digest, Sha256};
 
 use tauri_utils::assets::AssetKey;
 use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
-use tauri_utils::html::{inject_nonce_token, parse as parse_html, NodeRef};
+use tauri_utils::html::{inject_nonce_token, parse as parse_html};
 
 #[cfg(feature = "shell-scope")]
 use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope};
@@ -26,32 +26,14 @@ pub struct ContextData {
   pub root: TokenStream,
 }
 
-fn load_csp(document: &mut NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) {
-  inject_nonce_token(document);
-  if let Ok(inline_script_elements) = document.select("script:not(empty)") {
-    let mut scripts = Vec::new();
-    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);
-      let hash = hasher.finalize();
-      scripts.push(format!("'sha256-{}'", base64::encode(&hash)));
-    }
-    csp_hashes
-      .inline_scripts
-      .entry(key.clone().into())
-      .or_default()
-      .append(&mut scripts);
-  }
-}
-
 fn map_core_assets(
   options: &AssetOptions,
 ) -> impl Fn(&AssetKey, &Path, &mut Vec<u8>, &mut CspHashes) -> Result<(), EmbeddedAssetsError> {
   #[cfg(feature = "isolation")]
   let pattern = tauri_utils::html::PatternObject::from(&options.pattern);
   let csp = options.csp;
-  let dangerous_disable_asset_csp_modification = options.dangerous_disable_asset_csp_modification;
+  let dangerous_disable_asset_csp_modification =
+    options.dangerous_disable_asset_csp_modification.clone();
   move |key, path, input, csp_hashes| {
     if path.extension() == Some(OsStr::new("html")) {
       let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
@@ -61,10 +43,28 @@ fn map_core_assets(
         #[cfg(target_os = "linux")]
         ::tauri_utils::html::inject_csp_token(&mut document);
 
-        if !dangerous_disable_asset_csp_modification {
-          load_csp(&mut document, key, csp_hashes);
+        inject_nonce_token(&mut document, &dangerous_disable_asset_csp_modification);
+
+        if dangerous_disable_asset_csp_modification.can_modify("script-src") {
+          if let Ok(inline_script_elements) = document.select("script:not(empty)") {
+            let mut scripts = Vec::new();
+            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);
+              let hash = hasher.finalize();
+              scripts.push(format!("'sha256-{}'", base64::encode(&hash)));
+            }
+            csp_hashes
+              .inline_scripts
+              .entry(key.clone().into())
+              .or_default()
+              .append(&mut scripts);
+          }
+        }
 
-          #[cfg(feature = "isolation")]
+        #[cfg(feature = "isolation")]
+        if dangerous_disable_asset_csp_modification.can_modify("style-src") {
           if let tauri_utils::html::PatternObject::Isolation { .. } = &pattern {
             // create the csp for the isolation iframe styling now, to make the runtime less complex
             let mut hasher = Sha256::new();
@@ -116,7 +116,14 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   } = data;
 
   let mut options = AssetOptions::new(config.tauri.pattern.clone())
-    .freeze_prototype(config.tauri.security.freeze_prototype);
+    .freeze_prototype(config.tauri.security.freeze_prototype)
+    .dangerous_disable_asset_csp_modification(
+      config
+        .tauri
+        .security
+        .dangerous_disable_asset_csp_modification
+        .clone(),
+    );
   let csp = if dev {
     config
       .tauri

+ 25 - 19
core/tauri-codegen/src/embedded_assets.rs

@@ -11,8 +11,8 @@ use std::{
   fs::File,
   path::{Path, PathBuf},
 };
-use tauri_utils::assets::AssetKey;
 use tauri_utils::config::PatternKind;
+use tauri_utils::{assets::AssetKey, config::DisabledCspModificationKind};
 use thiserror::Error;
 use walkdir::{DirEntry, WalkDir};
 
@@ -123,9 +123,9 @@ impl RawEmbeddedAssets {
 
           // compress all files encountered
           Ok(entry) => {
-            if options.dangerous_disable_asset_csp_modification {
-              Some(Ok((prefix, entry)))
-            } else if let Err(error) = csp_hashes.add_if_applicable(&entry) {
+            if let Err(error) = csp_hashes
+              .add_if_applicable(&entry, &options.dangerous_disable_asset_csp_modification)
+            {
               Some(Err(error))
             } else {
               Some(Ok((prefix, entry)))
@@ -160,22 +160,28 @@ impl CspHashes {
   ///
   /// Note: this only checks the file extension, much like how a browser will assume a .js file is
   /// a JavaScript file unless HTTP headers tell it otherwise.
-  pub fn add_if_applicable(&mut self, entry: &DirEntry) -> Result<(), EmbeddedAssetsError> {
+  pub fn add_if_applicable(
+    &mut self,
+    entry: &DirEntry,
+    dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
+  ) -> Result<(), EmbeddedAssetsError> {
     let path = entry.path();
 
     // we only hash JavaScript files for now, may expand to other CSP hashable types in the future
     if let Some("js") | Some("mjs") = path.extension().and_then(|os| os.to_str()) {
-      let mut hasher = Sha256::new();
-      hasher.update(
-        &std::fs::read(path).map_err(|error| EmbeddedAssetsError::AssetRead {
-          path: path.to_path_buf(),
-          error,
-        })?,
-      );
-      let hash = hasher.finalize();
-      self
-        .scripts
-        .push(format!("'sha256-{}'", base64::encode(hash)))
+      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,
+          })?,
+        );
+        let hash = hasher.finalize();
+        self
+          .scripts
+          .push(format!("'sha256-{}'", base64::encode(hash)));
+      }
     }
 
     Ok(())
@@ -188,7 +194,7 @@ pub struct AssetOptions {
   pub(crate) csp: bool,
   pub(crate) pattern: PatternKind,
   pub(crate) freeze_prototype: bool,
-  pub(crate) dangerous_disable_asset_csp_modification: bool,
+  pub(crate) dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
   #[cfg(feature = "isolation")]
   pub(crate) isolation_schema: String,
 }
@@ -200,7 +206,7 @@ impl AssetOptions {
       csp: false,
       pattern,
       freeze_prototype: false,
-      dangerous_disable_asset_csp_modification: false,
+      dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
       #[cfg(feature = "isolation")]
       isolation_schema: format!("isolation-{}", uuid::Uuid::new_v4()),
     }
@@ -223,7 +229,7 @@ impl AssetOptions {
   /// Instruct the asset handler to **NOT** modify the CSP. This is **NOT** recommended.
   pub fn dangerous_disable_asset_csp_modification(
     mut self,
-    dangerous_disable_asset_csp_modification: bool,
+    dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
   ) -> Self {
     self.dangerous_disable_asset_csp_modification = dangerous_disable_asset_csp_modification;
     self

+ 51 - 3
core/tauri-utils/src/config.rs

@@ -757,6 +757,34 @@ impl Display for Csp {
   }
 }
 
+/// The possible values for the `dangerous_disable_asset_csp_modification` config option.
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+#[cfg_attr(feature = "schema", derive(JsonSchema))]
+pub enum DisabledCspModificationKind {
+  /// If `true`, disables all CSP modification.
+  /// `false` is the default value and it configures Tauri to control the CSP.
+  Flag(bool),
+  /// Disables the given list of CSP directives modifications.
+  List(Vec<String>),
+}
+
+impl DisabledCspModificationKind {
+  /// Determines whether the given CSP directive can be modified or not.
+  pub fn can_modify(&self, directive: &str) -> bool {
+    match self {
+      Self::Flag(f) => !f,
+      Self::List(l) => !l.contains(&directive.into()),
+    }
+  }
+}
+
+impl Default for DisabledCspModificationKind {
+  fn default() -> Self {
+    Self::Flag(false)
+  }
+}
+
 /// Security configuration.
 #[skip_serializing_none]
 #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
@@ -783,10 +811,14 @@ pub struct SecurityConfig {
   /// to only allow loading of your own scripts and styles by injecting nonce and hash sources.
   /// This stricts your CSP, which may introduce issues when using along with other flexing sources.
   ///
+  /// This configuration option allows both a boolean and a list of strings as value.
+  /// A boolean instructs Tauri to disable the injection for all CSP injections,
+  /// and a list of strings indicates the CSP directives that Tauri cannot inject.
+  ///
   /// **WARNING:** Only disable this if you know what you are doing and have properly configured the CSP.
   /// Your application might be vulnerable to XSS attacks without this Tauri protection.
   #[serde(default)]
-  pub dangerous_disable_asset_csp_modification: bool,
+  pub dangerous_disable_asset_csp_modification: DisabledCspModificationKind,
 }
 
 /// Defines an allowlist type.
@@ -2637,12 +2669,28 @@ mod build {
     }
   }
 
+  impl ToTokens for DisabledCspModificationKind {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+      let prefix = quote! { ::tauri::utils::config::DisabledCspModificationKind };
+
+      tokens.append_all(match self {
+        Self::Flag(flag) => {
+          quote! { #prefix::Flag(#flag) }
+        }
+        Self::List(directives) => {
+          let directives = vec_lit(directives, str_lit);
+          quote! { #prefix::List(#directives) }
+        }
+      });
+    }
+  }
+
   impl ToTokens for SecurityConfig {
     fn to_tokens(&self, tokens: &mut TokenStream) {
       let csp = opt_lit(self.csp.as_ref());
       let dev_csp = opt_lit(self.dev_csp.as_ref());
       let freeze_prototype = self.freeze_prototype;
-      let dangerous_disable_asset_csp_modification = self.dangerous_disable_asset_csp_modification;
+      let dangerous_disable_asset_csp_modification = &self.dangerous_disable_asset_csp_modification;
 
       literal_struct!(
         tokens,
@@ -2901,7 +2949,7 @@ mod test {
         csp: None,
         dev_csp: None,
         freeze_prototype: false,
-        dangerous_disable_asset_csp_modification: false,
+        dangerous_disable_asset_csp_modification: DisabledCspModificationKind::Flag(false),
       },
       allowlist: AllowlistConfig::default(),
       system_tray: None,

+ 11 - 4
core/tauri-utils/src/html.rs

@@ -13,7 +13,7 @@ use serde::Serialize;
 #[cfg(feature = "isolation")]
 use serialize_to_javascript::DefaultTemplate;
 
-use crate::config::PatternKind;
+use crate::config::{DisabledCspModificationKind, PatternKind};
 #[cfg(feature = "isolation")]
 use crate::pattern::isolation::IsolationJavascriptCodegen;
 
@@ -59,9 +59,16 @@ fn inject_nonce(document: &mut NodeRef, selector: &str, token: &str) {
 }
 
 /// Inject nonce tokens to all scripts and styles.
-pub fn inject_nonce_token(document: &mut NodeRef) {
-  inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN);
-  inject_nonce(document, "style", STYLE_NONCE_TOKEN);
+pub fn inject_nonce_token(
+  document: &mut NodeRef,
+  dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
+) {
+  if dangerous_disable_asset_csp_modification.can_modify("script-src") {
+    inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN);
+  }
+  if dangerous_disable_asset_csp_modification.can_modify("style-src") {
+    inject_nonce(document, "style", STYLE_NONCE_TOKEN);
+  }
 }
 
 /// Injects a content security policy to the HTML.

+ 25 - 16
core/tauri/src/manager.rs

@@ -79,7 +79,7 @@ fn set_csp<R: Runtime>(
   asset: &mut String,
   assets: Arc<dyn Assets>,
   asset_path: &AssetKey,
-  #[allow(unused_variables)] manager: &WindowManager<R>,
+  manager: &WindowManager<R>,
   csp: Csp,
 ) -> String {
   let mut csp = csp.into();
@@ -103,21 +103,30 @@ fn set_csp<R: Runtime>(
         acc
       });
 
-  replace_csp_nonce(
-    asset,
-    SCRIPT_NONCE_TOKEN,
-    &mut csp,
-    "script-src",
-    hash_strings.script,
-  );
-
-  replace_csp_nonce(
-    asset,
-    STYLE_NONCE_TOKEN,
-    &mut csp,
-    "style-src",
-    hash_strings.style,
-  );
+  let dangerous_disable_asset_csp_modification = &manager
+    .config()
+    .tauri
+    .security
+    .dangerous_disable_asset_csp_modification;
+  if dangerous_disable_asset_csp_modification.can_modify("script-src") {
+    replace_csp_nonce(
+      asset,
+      SCRIPT_NONCE_TOKEN,
+      &mut csp,
+      "script-src",
+      hash_strings.script,
+    );
+  }
+
+  if dangerous_disable_asset_csp_modification.can_modify("style-src") {
+    replace_csp_nonce(
+      asset,
+      STYLE_NONCE_TOKEN,
+      &mut csp,
+      "style-src",
+      hash_strings.style,
+    );
+  }
 
   #[cfg(feature = "isolation")]
   if let Pattern::Isolation { schema, .. } = &manager.inner.pattern {

+ 2 - 2
examples/api/src-tauri/Cargo.lock

@@ -3323,9 +3323,9 @@ dependencies = [
 
 [[package]]
 name = "tao"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd4af2c14087bc642585cf84e3f122d6d120836e08b813991491cb6d3e1cf63"
+checksum = "3765f329d831aa461cd3f0f94b065a9fe37560fd7f8099d5bcf3e95c923071f0"
 dependencies = [
  "bitflags",
  "cairo-rs",

+ 22 - 2
tooling/cli/schema.json

@@ -962,6 +962,22 @@
       },
       "additionalProperties": false
     },
+    "DisabledCspModificationKind": {
+      "description": "The possible values for the `dangerous_disable_asset_csp_modification` config option.",
+      "anyOf": [
+        {
+          "description": "If `true`, disables all CSP modification. `false` is the default value and it configures Tauri to control the CSP.",
+          "type": "boolean"
+        },
+        {
+          "description": "Disables the given list of CSP directives modifications.",
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      ]
+    },
     "FsAllowlistConfig": {
       "description": "Allowlist for the file system APIs.",
       "type": "object",
@@ -1343,9 +1359,13 @@
           ]
         },
         "dangerousDisableAssetCspModification": {
-          "description": "Disables the Tauri-injected CSP sources.\n\nAt compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy to only allow loading of your own scripts and styles by injecting nonce and hash sources. This stricts your CSP, which may introduce issues when using along with other flexing sources.\n\n**WARNING:** Only disable this if you know what you are doing and have properly configured the CSP. Your application might be vulnerable to XSS attacks without this Tauri protection.",
+          "description": "Disables the Tauri-injected CSP sources.\n\nAt compile time, Tauri parses all the frontend assets and changes the Content-Security-Policy to only allow loading of your own scripts and styles by injecting nonce and hash sources. This stricts your CSP, which may introduce issues when using along with other flexing sources.\n\nThis configuration option allows both a boolean and a list of strings as value. A boolean instructs Tauri to disable the injection for all CSP injections, and a list of strings indicates the CSP directives that Tauri cannot inject.\n\n**WARNING:** Only disable this if you know what you are doing and have properly configured the CSP. Your application might be vulnerable to XSS attacks without this Tauri protection.",
           "default": false,
-          "type": "boolean"
+          "allOf": [
+            {
+              "$ref": "#/definitions/DisabledCspModificationKind"
+            }
+          ]
         },
         "devCsp": {
           "description": "The Content Security Policy that will be injected on all HTML files on development.\n\nThis is a really important part of the configuration since it helps you ensure your WebView is secured. See <https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP>.",