Skip to content

Commit 983ccb8

Browse files
authored
feat(core): allow denying paths on the fs and asset scopes (#3607)
1 parent b744cd2 commit 983ccb8

File tree

6 files changed

+158
-17
lines changed

6 files changed

+158
-17
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Allow configuring forbidden paths on the asset and filesystem scopes.

core/tauri-utils/src/config.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -638,9 +638,47 @@ macro_rules! check_feature {
638638
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`,
639639
/// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`,
640640
/// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`.
641-
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
641+
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
642642
#[cfg_attr(feature = "schema", derive(JsonSchema))]
643-
pub struct FsAllowlistScope(pub Vec<PathBuf>);
643+
#[serde(untagged)]
644+
pub enum FsAllowlistScope {
645+
/// A list of paths that are allowed by this scope.
646+
AllowedPaths(Vec<PathBuf>),
647+
/// A complete scope configuration.
648+
Scope {
649+
/// A list of paths that are allowed by this scope.
650+
#[serde(default)]
651+
allow: Vec<PathBuf>,
652+
/// A list of paths that are not allowed by this scope.
653+
/// This gets precedence over the [`Self::allow`] list.
654+
#[serde(default)]
655+
deny: Vec<PathBuf>,
656+
},
657+
}
658+
659+
impl Default for FsAllowlistScope {
660+
fn default() -> Self {
661+
Self::AllowedPaths(Vec::new())
662+
}
663+
}
664+
665+
impl FsAllowlistScope {
666+
/// The list of allowed paths.
667+
pub fn allowed_paths(&self) -> &Vec<PathBuf> {
668+
match self {
669+
Self::AllowedPaths(p) => p,
670+
Self::Scope { allow, .. } => allow,
671+
}
672+
}
673+
674+
/// The list of forbidden paths.
675+
pub fn forbidden_paths(&self) -> Option<&Vec<PathBuf>> {
676+
match self {
677+
Self::AllowedPaths(_) => None,
678+
Self::Scope { deny, .. } => Some(deny),
679+
}
680+
}
681+
}
644682

645683
/// Allowlist for the file system APIs.
646684
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
@@ -2381,8 +2419,19 @@ mod build {
23812419

23822420
impl ToTokens for FsAllowlistScope {
23832421
fn to_tokens(&self, tokens: &mut TokenStream) {
2384-
let allowed_paths = vec_lit(&self.0, path_buf_lit);
2385-
tokens.append_all(quote! { ::tauri::utils::config::FsAllowlistScope(#allowed_paths) })
2422+
let prefix = quote! { ::tauri::utils::config::FsAllowlistScope };
2423+
2424+
tokens.append_all(match self {
2425+
Self::AllowedPaths(allow) => {
2426+
let allowed_paths = vec_lit(allow, path_buf_lit);
2427+
quote! { #prefix::AllowedPaths(#allowed_paths) }
2428+
}
2429+
Self::Scope { allow, deny } => {
2430+
let allow = vec_lit(allow, path_buf_lit);
2431+
let deny = vec_lit(deny, path_buf_lit);
2432+
quote! { #prefix::Scope { allow: #allow, deny: #deny } }
2433+
}
2434+
});
23862435
}
23872436
}
23882437

core/tauri/src/scope/fs.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::api::path::parse as parse_path;
2020
#[derive(Clone)]
2121
pub struct Scope {
2222
allow_patterns: Arc<Mutex<Vec<Pattern>>>,
23+
forbidden_patterns: Arc<Mutex<Vec<Pattern>>>,
2324
}
2425

2526
impl fmt::Debug for Scope {
@@ -35,6 +36,16 @@ impl fmt::Debug for Scope {
3536
.map(|p| p.as_str())
3637
.collect::<Vec<&str>>(),
3738
)
39+
.field(
40+
"forbidden_patterns",
41+
&self
42+
.forbidden_patterns
43+
.lock()
44+
.unwrap()
45+
.iter()
46+
.map(|p| p.as_str())
47+
.collect::<Vec<&str>>(),
48+
)
3849
.finish()
3950
}
4051
}
@@ -58,13 +69,24 @@ impl Scope {
5869
scope: &FsAllowlistScope,
5970
) -> Self {
6071
let mut allow_patterns = Vec::new();
61-
for path in &scope.0 {
72+
for path in scope.allowed_paths() {
6273
if let Ok(path) = parse_path(config, package_info, env, path) {
6374
push_pattern(&mut allow_patterns, path);
6475
}
6576
}
77+
78+
let mut forbidden_patterns = Vec::new();
79+
if let Some(forbidden_paths) = scope.forbidden_paths() {
80+
for path in forbidden_paths {
81+
if let Ok(path) = parse_path(config, package_info, env, path) {
82+
push_pattern(&mut forbidden_patterns, path);
83+
}
84+
}
85+
}
86+
6687
Self {
6788
allow_patterns: Arc::new(Mutex::new(allow_patterns)),
89+
forbidden_patterns: Arc::new(Mutex::new(forbidden_patterns)),
6890
}
6991
}
7092

@@ -89,6 +111,26 @@ impl Scope {
89111
push_pattern(&mut self.allow_patterns.lock().unwrap(), path);
90112
}
91113

114+
/// Set the given directory path to be forbidden by this scope.
115+
///
116+
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
117+
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
118+
let path = path.as_ref().to_path_buf();
119+
let mut list = self.forbidden_patterns.lock().unwrap();
120+
121+
// allow the directory to be read
122+
push_pattern(&mut list, &path);
123+
// allow its files and subdirectories to be read
124+
push_pattern(&mut list, path.join(if recursive { "**" } else { "*" }));
125+
}
126+
127+
/// Set the given file path to be forbidden by this scope.
128+
///
129+
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
130+
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
131+
push_pattern(&mut self.forbidden_patterns.lock().unwrap(), path);
132+
}
133+
92134
/// Determines if the given path is allowed on this scope.
93135
pub fn is_allowed<P: AsRef<Path>>(&self, path: P) -> bool {
94136
let path = path.as_ref();
@@ -100,13 +142,25 @@ impl Scope {
100142

101143
if let Ok(path) = path {
102144
let path: PathBuf = path.components().collect();
103-
let allowed = self
104-
.allow_patterns
145+
146+
let forbidden = self
147+
.forbidden_patterns
105148
.lock()
106149
.unwrap()
107150
.iter()
108151
.any(|p| p.matches_path(&path));
109-
allowed
152+
153+
if forbidden {
154+
false
155+
} else {
156+
let allowed = self
157+
.allow_patterns
158+
.lock()
159+
.unwrap()
160+
.iter()
161+
.any(|p| p.matches_path(&path));
162+
allowed
163+
}
110164
} else {
111165
false
112166
}

examples/api/src-tauri/tauri.conf.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@
8585
"allowlist": {
8686
"all": true,
8787
"fs": {
88-
"scope": ["$APP/db", "$DOWNLOAD/**", "$RESOURCE/**"]
88+
"scope": {
89+
"allow": ["$APP/db/**", "$DOWNLOAD/**", "$RESOURCE/**"],
90+
"deny": ["$APP/db/*.stronghold"]
91+
}
8992
},
9093
"shell": {
9194
"scope": [
@@ -103,7 +106,10 @@
103106
},
104107
"protocol": {
105108
"asset": true,
106-
"assetScope": ["$RESOURCE/**", "$APP/**"]
109+
"assetScope": {
110+
"allow": ["$APP/db/**", "$RESOURCE/**"],
111+
"deny": ["$APP/db/*.stronghold"]
112+
}
107113
},
108114
"http": {
109115
"scope": ["https://jsonplaceholder.typicode.com/todos/*"]
@@ -116,7 +122,7 @@
116122
}
117123
],
118124
"security": {
119-
"csp": "default-src 'self' customprotocol: img-src: 'self'; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; img-src 'self' asset: https://asset.localhost blob: data:; font-src https://fonts.gstatic.com",
125+
"csp": "default-src 'self' customprotocol: asset: img-src: 'self'; style-src 'unsafe-inline' 'self' https://fonts.googleapis.com; img-src 'self' asset: https://asset.localhost blob: data:; font-src https://fonts.gstatic.com",
120126
"freezePrototype": true
121127
},
122128
"systemTray": {

tooling/cli/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tooling/cli/schema.json

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -997,10 +997,37 @@
997997
},
998998
"FsAllowlistScope": {
999999
"description": "Filesystem scope definition. It is a list of glob patterns that restrict the API access from the webview.\n\nEach pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`.",
1000-
"type": "array",
1001-
"items": {
1002-
"type": "string"
1003-
}
1000+
"anyOf": [
1001+
{
1002+
"description": "A list of paths that are allowed by this scope.",
1003+
"type": "array",
1004+
"items": {
1005+
"type": "string"
1006+
}
1007+
},
1008+
{
1009+
"description": "A complete scope configuration.",
1010+
"type": "object",
1011+
"properties": {
1012+
"allow": {
1013+
"description": "A list of paths that are allowed by this scope.",
1014+
"default": [],
1015+
"type": "array",
1016+
"items": {
1017+
"type": "string"
1018+
}
1019+
},
1020+
"deny": {
1021+
"description": "A list of paths that are not allowed by this scope. This gets precedence over the [`Self::allow`] list.",
1022+
"default": [],
1023+
"type": "array",
1024+
"items": {
1025+
"type": "string"
1026+
}
1027+
}
1028+
}
1029+
}
1030+
]
10041031
},
10051032
"GlobalShortcutAllowlistConfig": {
10061033
"description": "Allowlist for the global shortcut APIs.",

0 commit comments

Comments
 (0)