Back to Blog
critical SEVERITY6 min read

How unvalidated URL input handling happens in SvelteKit with Tauri and how to fix it

A critical vulnerability in `src/routes/+page.svelte` allowed attackers to supply arbitrary URLs—including `http://` and local file paths—through query parameters and drag-drop events, which were then fetched without validation. The fix restricts input to HTTPS-only URLs and removes the dangerous local file fetch path entirely, eliminating both SSRF and local file disclosure attack vectors.

O
By Orbis AppSec
Published August 4, 2026Reviewed August 4, 2026

Answer Summary

This is an unvalidated URL input vulnerability (CWE-918: Server-Side Request Forgery) in a SvelteKit application using Tauri. The `+page.svelte` route accepted `http://`, `https://`, and local file paths from user-controlled input and passed them directly to `fetch()` or the Tauri backend without domain allowlisting. The fix restricts the accepted scheme to `https://` only and removes the code branch that fetched arbitrary local file paths via `fetch(inputPath)`.

Vulnerability at a Glance

cweCWE-918 (Server-Side Request Forgery)
fixRestrict accepted input to https:// only and remove the local file fetch code path
riskAttackers can force the application to fetch internal resources or read local files
languageTypeScript/Svelte (SvelteKit + Tauri)
root causeAccepting http:// URLs and arbitrary local paths from user input without validation
vulnerabilityUnvalidated URL/path input leading to SSRF and local file disclosure

How Unvalidated URL Input Handling Happens in SvelteKit with Tauri and How to Fix It

Introduction

The src/routes/+page.svelte file handles user-provided file paths and URLs from both query parameters and drag-drop events—a common pattern in desktop applications built with Tauri. However, a flaw at line 113 created a critical security risk: the route accepted http:// URLs, https:// URLs, and arbitrary local Unix file paths, then fetched their contents without any domain allowlisting or scheme restriction.

The vulnerable conditional statement:

if (inputPath.startsWith('http://') || inputPath.startsWith('https://') || (inputPath.startsWith('/') && !isLocalUnixPath)) {

This single condition opened three distinct attack surfaces: plaintext HTTP fetching (vulnerable to man-in-the-middle attacks), arbitrary server-side requests via the Tauri backend, and direct local file reads through the browser's fetch() API. For a desktop application that processes URLs from external sources, this is a textbook Server-Side Request Forgery (SSRF) combined with local file disclosure.

The Vulnerability Explained

Let's examine the vulnerable code block in detail:

if (inputPath.startsWith('http://') || inputPath.startsWith('https://') || (inputPath.startsWith('/') && !isLocalUnixPath)) {
  let unityRes = null;
  let isUnity = false;
  let shouldInvokeBackend = false;
  try {
    showSpinner = true;
    let bytes;
    if (inputPath.startsWith('http://') || inputPath.startsWith('https://')) {
      const fetched = await invoke('fetch_url_bytes', { url: inputPath });
      bytes = new Uint8Array(fetched);
    } else {
      const fetchRes = await fetch(inputPath);
      if (!fetchRes.ok) {
        throw new Error(`HTTP error status: ${fetchRes.status}`);
      }
      const buffer = await fetchRes.arrayBuffer();
      bytes = new Uint8Array(buffer);
    }

There are three critical problems here:

1. HTTP scheme accepted: The code forwards http:// URLs to the Tauri backend via invoke('fetch_url_bytes', { url: inputPath }). An attacker who controls the inputPath (via query parameter or crafted drag-drop payload) could target internal network services like http://169.254.169.254/latest/meta-data/ (AWS metadata endpoint) or http://localhost:8080/admin.

2. Arbitrary local file paths fetched: The else branch catches paths starting with / that don't match the isLocalUnixPath check (which only covers /Users/, /home/, /tmp/, and /private/). A path like /etc/passwd or /proc/self/environ would bypass the local path check and be fetched directly via fetch(inputPath), potentially exposing sensitive system files.

3. No domain allowlisting: Even for HTTPS URLs, there's no validation of the target domain. The Tauri backend's fetch_url_bytes command will fetch any URL it receives.

Attack Scenario

An attacker crafts a URL to the application with a query parameter containing:

?path=http://169.254.169.254/latest/meta-data/iam/security-credentials/

The application's route handler picks up this value as inputPath, passes the startsWith('http://') check, and invokes the Tauri backend to fetch the AWS instance metadata—returning IAM credentials to the attacker. Alternatively, using a drag-drop event with a path like /etc/shadow (which doesn't match /Users/, /home/, /tmp/, or /private/) triggers the direct fetch() call, potentially exposing password hashes.

The Fix

The fix makes two surgical changes that eliminate both attack vectors:

Before:

if (inputPath.startsWith('http://') || inputPath.startsWith('https://') || (inputPath.startsWith('/') && !isLocalUnixPath)) {
  // ...
  if (inputPath.startsWith('http://') || inputPath.startsWith('https://')) {
    const fetched = await invoke('fetch_url_bytes', { url: inputPath });
    bytes = new Uint8Array(fetched);
  } else {
    const fetchRes = await fetch(inputPath);
    if (!fetchRes.ok) {
      throw new Error(`HTTP error status: ${fetchRes.status}`);
    }
    const buffer = await fetchRes.arrayBuffer();
    bytes = new Uint8Array(buffer);
  }

After:

if (inputPath.startsWith('https://')) {
  // ...
  const fetched = await invoke('fetch_url_bytes', { url: inputPath });
  bytes = new Uint8Array(fetched);

The changes are:

  1. Scheme restriction: The outer conditional now only accepts https:// — removing both http:// (plaintext, MITM-vulnerable) and the local path condition entirely.

  2. Removal of local file fetch branch: The entire else block that called fetch(inputPath) for local file paths is deleted. There is no longer any code path that reads arbitrary local files based on user input.

  3. Simplified logic: With only HTTPS accepted, the inner conditional is unnecessary. The code now always uses invoke('fetch_url_bytes', { url: inputPath }) for the single remaining case.

This fix preserves all legitimate functionality—users can still load assets from HTTPS URLs—while eliminating the ability to target internal services via HTTP or read local files.

Prevention & Best Practices

  1. Principle of least privilege for URL schemes: Only accept the minimum set of URL schemes your application needs. If you only need HTTPS, reject everything else at the input boundary.

  2. Domain allowlisting: Even with HTTPS-only, consider maintaining an allowlist of permitted domains. This prevents SSRF against arbitrary external services.

  3. Never pass user input to fetch() for local paths: The browser's fetch() API can access local resources in certain contexts (especially in Electron/Tauri apps). User-controlled paths should never reach this API without strict validation.

  4. Defense in depth with Tauri's allowlist: Tauri provides an allowlist configuration for IPC commands. Ensure fetch_url_bytes has server-side URL validation in the Rust backend as well.

  5. Input validation at the boundary: Validate URLs immediately when they enter the application (from query params, drag-drop, or any other source) rather than deep in the processing logic.

Key Takeaways

  • The isLocalUnixPath check in +page.svelte was a denylist, not an allowlist—it only blocked four specific path prefixes, leaving /etc/, /proc/, /var/, and countless other sensitive paths accessible.
  • Accepting http:// URLs in a Tauri invoke() call creates an SSRF primitive that automated exploit tools can chain with cloud metadata endpoints or internal services.
  • The fetch(inputPath) call for local paths was the most dangerous sink—it allowed direct file content exfiltration from the user's filesystem.
  • Removing an entire code branch is sometimes the best fix—the local file fetch path served no legitimate purpose that couldn't be handled through safer mechanisms.
  • Query parameters and drag-drop events are both attacker-controlled input sources in desktop applications and must be treated with the same suspicion as HTTP request bodies in web apps.

How Orbis AppSec Detected This

  • Source: User-controlled inputPath value from URL query parameters and drag-drop events in src/routes/+page.svelte
  • Sink: invoke('fetch_url_bytes', { url: inputPath }) and fetch(inputPath) at lines 118-126
  • Missing control: No URL scheme restriction (http:// accepted), no domain allowlisting, and no validation preventing local file path access via fetch()
  • CWE: CWE-918 (Server-Side Request Forgery) and CWE-73 (External Control of File Name or Path)
  • Fix: Restricted accepted input to https:// scheme only and removed the local file fetch code path entirely

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates how a single overly-permissive conditional in a route handler can create multiple attack vectors simultaneously. By accepting http://, https://, and local file paths without validation, the application exposed itself to SSRF, man-in-the-middle attacks, and local file disclosure. The fix is elegant in its simplicity: restrict to HTTPS only and remove the dangerous local file fetch path. When handling user-controlled URLs in desktop applications built with frameworks like Tauri, always apply the principle of least privilege to URL schemes and validate inputs at the boundary before they reach any fetch or invoke call.

References

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #22

Related Articles

high

ip-address 10.2.0 SSRF: Inconsistent Parsing Bypasses IP Checks

The `ip-address` npm package version 10.2.0 contains an inconsistent parsing vulnerability that allows attackers to bypass IP-based access controls. By representing IPv4 addresses in IPv4-mapped IPv6 notation, attackers can trick applications into allowing requests to blocked internal addresses. Upgrading to 10.3.1 resolves this through stricter address normalization.

medium

How gitlab.bandit.B501 happens in Python and how to fix it

The `proverbia-scraper.py` script disabled TLS certificate verification on its `requests.get()` call and silenced the resulting security warnings, exposing the scraper to man-in-the-middle attacks. The fix removes the `verify=False` flag and the warning suppression, restoring proper certificate validation while keeping the existing 30-second timeout intact.

high

How Server-Side Request Forgery (SSRF) happens in Go HTTP handlers and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `internal/web/controller/server.go` where the `applySubTemplate` endpoint accepted arbitrary URLs from user input and passed them directly to `serverService.ApplySubTemplateFromGithub()` without any host validation. An attacker could exploit this to make the server issue HTTP requests to internal network resources, cloud metadata endpoints, or redirect-controlled destinations. The fix introduces a strict allowlist that restrict

critical

How SSRF via Vulnerable Dependency Versions Happens in Node.js and How to Fix It

A permissive semver range in `package.json` allowed npm to install axios versions vulnerable to SSRF (CVE-2024-39338). By bumping the minimum version from `^1.6.0` to `^1.7.4`, all downstream consumers of this SDK are now protected from server-side request forgery attacks. This critical fix required changing just one line in the dependency manifest.

critical

How Server-Side Request Forgery happens in Python FastAPI and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in app.py where the `/parse` and `/parse-video` endpoints accepted user-supplied URLs with only substring validation. The application checked if 'doubao.com' appeared anywhere in the URL string, allowing attackers to bypass this check and access internal services, cloud metadata endpoints, or scan the internal network. The fix implemented proper hostname parsing with an allowlist of legitimate domains.

critical

How Server-Side Request Forgery happens in Node.js maintenance scripts and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `maintenance/getImages.js`, where the `getImage()` function passed database-sourced URLs directly to `axios.get()` without any validation. An attacker who could modify the elements database could redirect these requests to internal network resources — including AWS cloud metadata endpoints — potentially exposing IAM credentials and other sensitive infrastructure data. The fix introduces a strict URL allowlist that limi