tauri-apps/tauri · error
asset protocol path "{path}" is not valid: {e}
Error message
asset protocol path "{path}" is not valid: {e} What it means
Runtime 403 returned by Tauri's custom asset protocol handler (asset:// on Linux/Windows, http://asset.localhost on macOS/Windows WebView2). After percent-decoding the request URI path (leading '/' skipped), the handler validates it with SafePathBuf, which rejects any path containing '..' (ParentDir) components to prevent directory traversal. The message prints the offending path plus the static reason string, and an empty 403 body is returned.
Source
Thrown at crates/tauri/src/protocol/asset.rs:43
},
)
}
fn get_response(
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 {View on GitHub (pinned to 52e4b6e71d)
Solutions
- Normalize/resolve the path before calling convertFileSrc so no '..' component remains (e.g. resolve via the @tauri-apps/api path plugin or normalize on the JS side)
- Build URLs from a fixed allowed base directory joined with sanitized relative parts (strip '..', leading '/', empty segments)
- If the target file is legitimately needed, request its canonical absolute path directly and make sure that path is covered by the assetProtocol scope
Example fix
// before
const src = convertFileSrc(`${baseDir}/../../${fileName}`); // '..' -> SafePathBuf rejects, 403
// after
import { join, resolve } from '@tauri-apps/api/path';
const safe = fileName.split(/[\\/]/).filter((s) => s && s !== '..').join('/');
const abs = await resolve(baseDir, safe); // canonical path, no '..'
const src = convertFileSrc(abs); Defensive patterns
Strategy: validation
Validate before calling
function isSafeAssetPath(p) {
return !p.split(/[\\\\/]/).includes('..');
}
// call before convertFileSrc/fetch
if (!isSafeAssetPath(filePath)) throw new Error('refusing asset URL with .. segments'); Try / catch
try {
const res = await fetch(convertFileSrc(abs));
if (res.status === 403) console.warn('asset protocol rejected the path (traversal or scope)');
} catch (e) {
// network-level failure only; 403/404 arrive as responses, not throws
} Prevention
- Always resolve paths to canonical absolute form before calling convertFileSrc
- Strip '..', empty and leading-slash segments from user-supplied filenames
- Keep file lists server-side and hand the frontend opaque ids instead of raw paths
When it happens
Trigger: Fetching an asset URL built with convertFileSrc() whose decoded path contains a parent-directory segment, e.g. '/home/user/app/../../secret.txt' or './uploads/../config.json'. Hand-built asset:// URLs containing literal or percent-encoded '..' segments (e.g. %2E%2E) decode to a traversal path and fail this check before scope or file access is even attempted.
Common situations: Concatenating a user-supplied filename onto a base directory without normalization; frontend path-building helpers that keep '..' segments; double-encoding bugs where an encoded slash/segment survives decoding; porting code that relied on the server resolving relative segments.
Related errors
- File does not exist at path: {path}
- Missing OS permission to access path "{path}": {e}
- asset protocol not configured to allow the path: {path}
- Couldn't find capabilities directory at {}
- No file found in {} matching {}
AI-assisted analysis of tauri-apps/tauri@52e4b6e71d (2026-08-20).
Data as JSON: /api/errors/a7e7c62d2e3c5046.
Report an issue: GitHub.