mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-09-19 17:37:29 +02:00
fix: restrict asset protocol to vaults
This commit is contained in:
@@ -14,6 +14,9 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tauri = { version = "2", features = ["test"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = ["protocol-asset", "tray-icon", "image-png"] }
|
tauri = { version = "2", features = ["protocol-asset", "tray-icon", "image-png"] }
|
||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use tauri::{AppHandle, Manager, Runtime};
|
||||||
|
|
||||||
|
/// Grants asset-protocol access only to a vault selected by the user.
|
||||||
|
/// `.helixnotes` is added explicitly because Unix glob matching excludes hidden paths.
|
||||||
|
pub fn allow_vault_assets<R: Runtime>(app: &AppHandle<R>, vault_path: &Path) -> Result<(), String> {
|
||||||
|
// Keep the canonical native path: Tauri's scope normalizes Unix paths plus
|
||||||
|
// Windows drive, verbatim, and UNC prefixes when matching asset requests.
|
||||||
|
let vault = std::fs::canonicalize(vault_path).map_err(|error| {
|
||||||
|
format!(
|
||||||
|
"Failed to resolve vault asset path '{}': {error}",
|
||||||
|
vault_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let scope = app.asset_protocol_scope();
|
||||||
|
scope
|
||||||
|
.allow_directory(&vault, true)
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
scope
|
||||||
|
.allow_directory(vault.join(".helixnotes"), true)
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
fn test_directory(name: &str) -> std::path::PathBuf {
|
||||||
|
std::env::temp_dir().join(format!("helixnotes-{name}-{}", uuid::Uuid::new_v4()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_asset_scope_does_not_expose_the_filesystem() {
|
||||||
|
let config: serde_json::Value =
|
||||||
|
serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
|
||||||
|
let scope = &config["app"]["security"]["assetProtocol"]["scope"];
|
||||||
|
|
||||||
|
assert_eq!(scope["allow"], serde_json::json!([]));
|
||||||
|
assert_eq!(scope["deny"], serde_json::json!([]));
|
||||||
|
assert_eq!(scope["requireLiteralLeadingDot"], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runtime_scope_allows_only_the_selected_vault() {
|
||||||
|
let root = test_directory("asset-scope");
|
||||||
|
let vault = root.join("Vault [Cross Platform]");
|
||||||
|
let attachments = vault.join(".helixnotes").join("attachments");
|
||||||
|
fs::create_dir_all(&attachments).unwrap();
|
||||||
|
let note_image = vault.join("images").join("note image.png");
|
||||||
|
fs::create_dir_all(note_image.parent().unwrap()).unwrap();
|
||||||
|
fs::write(¬e_image, b"image").unwrap();
|
||||||
|
let attachment = attachments.join("attachment.png");
|
||||||
|
fs::write(&attachment, b"attachment").unwrap();
|
||||||
|
let outside = root.join("outside.png");
|
||||||
|
fs::write(&outside, b"outside").unwrap();
|
||||||
|
|
||||||
|
let app = tauri::test::mock_app();
|
||||||
|
allow_vault_assets(app.handle(), &vault).unwrap();
|
||||||
|
let scope = app.asset_protocol_scope();
|
||||||
|
|
||||||
|
assert!(scope.is_allowed(¬e_image));
|
||||||
|
assert!(scope.is_allowed(&attachment));
|
||||||
|
assert!(!scope.is_allowed(&outside));
|
||||||
|
|
||||||
|
fs::remove_dir_all(root).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::asset_scope;
|
||||||
use crate::search::SearchIndex;
|
use crate::search::SearchIndex;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
@@ -94,6 +95,7 @@ fn open_vault_path(
|
|||||||
search.rebuild(&path)?;
|
search.rebuild(&path)?;
|
||||||
|
|
||||||
let new_watcher = watcher::start_watcher(app.clone(), path.clone())?;
|
let new_watcher = watcher::start_watcher(app.clone(), path.clone())?;
|
||||||
|
asset_scope::allow_vault_assets(&app, Path::new(&path))?;
|
||||||
|
|
||||||
// Update config. External vaults use the bookmark as their stable identity;
|
// Update config. External vaults use the bookmark as their stable identity;
|
||||||
// the resolved path is refreshed whenever the vault opens.
|
// the resolved path is refreshed whenever the vault opens.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod ai;
|
mod ai;
|
||||||
|
mod asset_scope;
|
||||||
mod backup;
|
mod backup;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod history;
|
mod history;
|
||||||
@@ -93,6 +94,20 @@ pub fn run() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let active_vault = app
|
||||||
|
.state::<AppState>()
|
||||||
|
.config
|
||||||
|
.lock()
|
||||||
|
.ok()
|
||||||
|
.and_then(|config| config.active_vault.clone());
|
||||||
|
if let Some(vault_path) = active_vault {
|
||||||
|
if let Err(error) =
|
||||||
|
asset_scope::allow_vault_assets(app.handle(), std::path::Path::new(&vault_path))
|
||||||
|
{
|
||||||
|
log::warn!("Failed to restore the vault asset scope: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
if show_tray {
|
if show_tray {
|
||||||
setup_tray(app)?;
|
setup_tray(app)?;
|
||||||
|
|||||||
@@ -27,7 +27,11 @@
|
|||||||
"csp": "default-src 'self'; img-src 'self' asset: http://asset.localhost imgproxy: http://imgproxy.localhost https: blob: data:; font-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self' asset: http://asset.localhost",
|
"csp": "default-src 'self'; img-src 'self' asset: http://asset.localhost imgproxy: http://imgproxy.localhost https: blob: data:; font-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-src 'self' asset: http://asset.localhost",
|
||||||
"assetProtocol": {
|
"assetProtocol": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"scope": ["**/*", "/**", "**/.helixnotes/**"]
|
"scope": {
|
||||||
|
"allow": [],
|
||||||
|
"deny": [],
|
||||||
|
"requireLiteralLeadingDot": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user