tauri-apps/tauri · warning
asset protocol path " " is not valid
Error message
asset protocol path "{path}" is not valid What it means
Tauri's asset:// protocol handler validates every requested path with SafePathBuf before serving it. If the path fails validation (e.g. contains traversal components, null bytes, or is not a valid path), the handler logs this error and returns an empty HTTP 403 response instead of the asset.
Solutions
- Fix the frontend code that constructs the asset URL so it uses a canonical, absolute path without '..' segments
- Use Tauri's convertFileSrc() / path APIs to build asset URLs instead of manual string concatenation
- If a legit path is being rejected, check it for special characters (nulls, redundant separators) and normalize it before requesting
Example fix
// before
const url = `asset://localhost/${filePickerResult.path}` // may contain ../
// after
import { convertFileSrc } from '@tauri-apps/api/core';
const url = convertFileSrc(filePickerResult.path); Defensive patterns
Strategy: validation
Validate before calling
import { normalize } from '@tauri-apps/api/path';
const clean = await normalize(userPath);
if (clean.split(/[\\/]/).includes('..')) throw new Error('traversal in asset path'); Type guard
function isSafeAssetPath(p: string): boolean {
return typeof p === 'string' && p.length > 0 && !p.includes('..') && !p.includes('\0');
} Try / catch
const resp = await fetch(assetUrl);
if (resp.status === 403) {
console.error('Asset path rejected (invalid or outside scope):', assetUrl);
} Prevention
- Always build asset URLs with convertFileSrc(), never string concatenation
- Normalize paths before embedding them in asset:// URLs
- Never pass raw user/webview input as an asset path
When it happens
Trigger: The webview requests an asset:// URL whose path component fails SafePathBuf::new — e.g. path traversal like '../', percent-encoded traversal sequences, null bytes, or an empty/absolute-path edge case — via get() or multi_range_request().
Common situations: Frontend code builds asset URLs by string concatenation and accidentally includes '..' segments; a compromised webview probes the protocol with malicious paths; unencoding bugs that leave %2e%2e in the URL.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- asset protocol not configured to allow the path
- asset protocol path " " is not valid
- failed to get random bytes
- File does not exist at path
- Missing OS permission to access path
AI-assisted analysis of tauri-apps/tauri@460ec35447 (2026-09-18).
Data as JSON: /api/errors/aab7ddbfe38fc473.
Report an issue: GitHub.
Appendix: 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."
);
}View on GitHub (pinned to 460ec35447)