{"record":{"id":"d69bc7af3e3765a8","repo":"tauri-apps/tauri","slug":"asset-protocol-not-configured-to-allow-the-path","errorCode":null,"errorMessage":"asset protocol not configured to allow the path: {path}","messagePattern":"asset protocol not configured to allow the path: (.+?)","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"crates/tauri/src/protocol/asset.rs","lineNumber":48,"sourceCode":"  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 {\n        log::error!(\"File does not exist at path: {path}\");\n        return resp.status(404).body(Vec::new().into()).map_err(Into::into);\n      } else if e.kind() == std::io::ErrorKind::PermissionDenied {\n        log::error!(\"Missing OS permission to access path \\\"{path}\\\": {e}\");\n        return resp.status(403).body(Vec::new().into()).map_err(Into::into);","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/tauri-apps/tauri/blob/52e4b6e71d8632a7e648f866c442e287ecddee34/crates/tauri/src/protocol/asset.rs#L30-L66","documentation":"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.","triggerScenarios":"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 '**').","commonSituations":"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.","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"],"exampleFix":"// before (tauri.conf.json)\n{ \"app\": { \"security\": { \"assetProtocol\": { \"enable\": true, \"scope\": [\"$APPDATA/**\"] } } } }\n// fetch(convertFileSrc('/home/me/video.mp4')) -> 403\n\n// after\n{ \"app\": { \"security\": { \"assetProtocol\": { \"enable\": true, \"scope\": [\"$APPDATA/**\", \"$HOME/videos/**\"] } } } }","handlingStrategy":"validation","validationCode":"// know your scope: mirror tauri.conf.json assetProtocol.scope in a constant\nconst ASSET_ROOTS = ['/home/me/videos/', '/home/me/media/']; // e.g. from $HOME/videos/**\nconst inScope = (p) => ASSET_ROOTS.some((r) => p.startsWith(r));\nif (!inScope(filePath)) throw new Error(`path outside assetProtocol scope: ${filePath}`);","typeGuard":null,"tryCatchPattern":"const res = await fetch(convertFileSrc(filePath));\nif (res.status === 403) {\n  // path is either invalid or not in assetProtocol scope: surface a friendly message\n  throw new Error('This file is outside the app\\'s allowed folders');\n}","preventionTips":["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"],"tags":["asset-protocol","scope","acl","http-403","config"],"backgroundTag":"filesystem-scope-denied","analyzedSha":"52e4b6e71d8632a7e648f866c442e287ecddee34","analyzedAt":"2026-08-20T13:59:20.734Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}