{"record":{"id":"aab7ddbfe38fc473","repo":"tauri-apps/tauri","slug":"asset-protocol-path-path-is-not-valid","errorCode":null,"errorMessage":"asset protocol path \"{path}\" is not valid","messagePattern":"asset protocol path \"(.+?)\" is not valid","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","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!(\n            \"Failed to open Android external storage file '{path}': {e}. This may be due to missing storage permissions.\"\n          );\n        }","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/tauri-apps/tauri/blob/460ec35447493200d64290dd7f015d5a91d0fd58/crates/tauri/src/protocol/asset.rs#L25-L61","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before\nconst url = `asset://localhost/${filePickerResult.path}` // may contain ../\n// after\nimport { convertFileSrc } from '@tauri-apps/api/core';\nconst url = convertFileSrc(filePickerResult.path);","handlingStrategy":"validation","validationCode":"import { normalize } from '@tauri-apps/api/path';\nconst clean = await normalize(userPath);\nif (clean.split(/[\\\\/]/).includes('..')) throw new Error('traversal in asset path');","typeGuard":"function isSafeAssetPath(p: string): boolean {\n  return typeof p === 'string' && p.length > 0 && !p.includes('..') && !p.includes('\\0');\n}","tryCatchPattern":"const resp = await fetch(assetUrl);\nif (resp.status === 403) {\n  console.error('Asset path rejected (invalid or outside scope):', assetUrl);\n}","preventionTips":["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"],"tags":["tauri","security","http-403","asset-protocol"],"backgroundTag":"path-traversal-blocked","analyzedSha":"460ec35447493200d64290dd7f015d5a91d0fd58","analyzedAt":"2026-09-18T23:55:51.277Z","contentChangedAt":"2026-09-18T23:55:51.277Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}