{"record":{"id":"a7e7c62d2e3c5046","repo":"tauri-apps/tauri","slug":"asset-protocol-path-path-is-not-valid-e","errorCode":null,"errorMessage":"asset protocol path \"{path}\" is not valid: {e}","messagePattern":"asset protocol path \"(.+?)\" is not valid: (.+?)","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"crates/tauri/src/protocol/asset.rs","lineNumber":43,"sourceCode":"    },\n  )\n}\n\nfn get_response(\n  request: Request<Vec<u8>>,\n  scope: &scope::fs::Scope,\n  window_origin: &str,\n) -> Result<Response<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {\n  // skip leading `/`\n  let path = percent_encoding::percent_decode(&request.uri().path().as_bytes()[1..])\n    .decode_utf8_lossy()\n    .to_string();\n\n  let mut resp = Response::builder().header(\"Access-Control-Allow-Origin\", window_origin);\n\n  if let Err(e) = SafePathBuf::new(path.clone().into()) {\n    log::error!(\"asset protocol path \\\"{path}\\\" is not valid: {e}\");\n    return resp.status(403).body(Vec::new().into()).map_err(Into::into);\n  }\n\n  if !scope.is_allowed(&path) {\n    log::error!(\"asset protocol not configured to allow the path: {path}\");\n    return resp.status(403).body(Vec::new().into()).map_err(Into::into);\n  }\n\n  // Separate block for easier error handling\n  let mut file = match File::open(path.clone()) {\n    Ok(file) => file,\n    Err(e) => {\n      #[cfg(target_os = \"android\")]\n      {\n        if path.starts_with(\"/storage/emulated/0/Android/data/\") {\n          log::error!(\"Failed to open Android external storage file '{path}': {e}. This may be due to missing storage permissions.\");\n        }\n      }\n      return if e.kind() == std::io::ErrorKind::NotFound {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/tauri-apps/tauri/blob/52e4b6e71d8632a7e648f866c442e287ecddee34/crates/tauri/src/protocol/asset.rs#L25-L61","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst src = convertFileSrc(`${baseDir}/../../${fileName}`); // '..' -> SafePathBuf rejects, 403\n\n// after\nimport { join, resolve } from '@tauri-apps/api/path';\nconst safe = fileName.split(/[\\\\/]/).filter((s) => s && s !== '..').join('/');\nconst abs = await resolve(baseDir, safe); // canonical path, no '..'\nconst src = convertFileSrc(abs);","handlingStrategy":"validation","validationCode":"function isSafeAssetPath(p) {\n  return !p.split(/[\\\\\\\\/]/).includes('..');\n}\n// call before convertFileSrc/fetch\nif (!isSafeAssetPath(filePath)) throw new Error('refusing asset URL with .. segments');","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(convertFileSrc(abs));\n  if (res.status === 403) console.warn('asset protocol rejected the path (traversal or scope)');\n} catch (e) {\n  // network-level failure only; 403/404 arrive as responses, not throws\n}","preventionTips":["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"],"tags":["asset-protocol","path-traversal","runtime","http-403","filesystem"],"backgroundTag":"path-traversal-rejected","analyzedSha":"52e4b6e71d8632a7e648f866c442e287ecddee34","analyzedAt":"2026-08-20T13:59:20.734Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}