tauri-apps/tauri · error
asset protocol not configured to allow the path: {path}
Error message
asset protocol not configured to allow the path: {path} What it means
Runtime 403 from the asset protocol handler. The path passed SafePathBuf validation but scope.is_allowed(&path) returned false, meaning no pattern under app > security > assetProtocol > scope in the Tauri configuration matches the requested path. Tauri deliberately gates all asset protocol reads behind this filesystem scope.
Source
Thrown at crates/tauri/src/protocol/asset.rs:48
request: Request<Vec<u8>>,
scope: &scope::fs::Scope,
window_origin: &str,
) -> Result<Response<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {
// skip leading `/`
let path = percent_encoding::percent_decode(&request.uri().path().as_bytes()[1..])
.decode_utf8_lossy()
.to_string();
let mut resp = Response::builder().header("Access-Control-Allow-Origin", window_origin);
if let Err(e) = SafePathBuf::new(path.clone().into()) {
log::error!("asset protocol path \"{path}\" is not valid: {e}");
return resp.status(403).body(Vec::new().into()).map_err(Into::into);
}
if !scope.is_allowed(&path) {
log::error!("asset protocol not configured to allow the path: {path}");
return resp.status(403).body(Vec::new().into()).map_err(Into::into);
}
// Separate block for easier error handling
let mut file = match File::open(path.clone()) {
Ok(file) => file,
Err(e) => {
#[cfg(target_os = "android")]
{
if path.starts_with("/storage/emulated/0/Android/data/") {
log::error!("Failed to open Android external storage file '{path}': {e}. This may be due to missing storage permissions.");
}
}
return if e.kind() == std::io::ErrorKind::NotFound {
log::error!("File does not exist at path: {path}");
return resp.status(404).body(Vec::new().into()).map_err(Into::into);
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
log::error!("Missing OS permission to access path \"{path}\": {e}");
return resp.status(403).body(Vec::new().into()).map_err(Into::into);View on GitHub (pinned to 52e4b6e71d)
Solutions
- Add the directory to app > security > assetProtocol > scope in tauri.conf.json, e.g. ["$HOME/media/**", "$DOWNLOAD/**"]
- Request the absolute path the scope actually matches (resolve relative paths first) and verify glob patterns include '**' for recursion
- Restart `tauri dev` / rebuild after changing the config so the scope is regenerated
Example fix
// before (tauri.conf.json)
{ "app": { "security": { "assetProtocol": { "enable": true, "scope": ["$APPDATA/**"] } } } }
// fetch(convertFileSrc('/home/me/video.mp4')) -> 403
// after
{ "app": { "security": { "assetProtocol": { "enable": true, "scope": ["$APPDATA/**", "$HOME/videos/**"] } } } } Defensive patterns
Strategy: validation
Validate before calling
// know your scope: mirror tauri.conf.json assetProtocol.scope in a constant
const ASSET_ROOTS = ['/home/me/videos/', '/home/me/media/']; // e.g. from $HOME/videos/**
const inScope = (p) => ASSET_ROOTS.some((r) => p.startsWith(r));
if (!inScope(filePath)) throw new Error(`path outside assetProtocol scope: ${filePath}`); Try / catch
const res = await fetch(convertFileSrc(filePath));
if (res.status === 403) {
// path is either invalid or not in assetProtocol scope: surface a friendly message
throw new Error('This file is outside the app\'s allowed folders');
} Prevention
- Define app > security > assetProtocol > scope once with explicit $VARIABLE/** roots and reuse the same roots in the frontend
- Restart `tauri dev` after any scope change - the config is compiled into the binary
- Remember fs plugin permissions do not extend to the asset protocol; only the assetProtocol scope counts here
When it happens
Trigger: convertFileSrc('/absolute/path') where the path matches none of the configured scope patterns; relative paths that resolve against the process working directory instead of the intended base; symlinks whose canonical target falls outside the scope; scope entries written without recursive glob (missing '**').
Common situations: Forgetting to add the media/downloads directory to assetProtocol.scope; assuming the fs plugin's scope or capability permissions also cover the asset protocol (they do not); using $HOME/$DOWNLOAD variables with wrong syntax; editing tauri.conf.json but not restarting the dev server since the config is baked at build time.
Related errors
- asset protocol path "{path}" is not valid: {e}
- Missing OS permission to access path "{path}": {e}
- failed to serialize scope
- File does not exist at path: {path}
- cannot use both `resources` and `resources_map`
AI-assisted analysis of tauri-apps/tauri@52e4b6e71d (2026-08-20).
Data as JSON: /api/errors/d69bc7af3e3765a8.
Report an issue: GitHub.