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

  1. Fix the frontend code that constructs the asset URL so it uses a canonical, absolute path without '..' segments
  2. Use Tauri's convertFileSrc() / path APIs to build asset URLs instead of manual string concatenation
  3. 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

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


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)