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

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?

SSRF is a vulnerability where an attacker can make a server-side application send requests to unintended locations, such as internal services, cloud metadata endpoints, or local files, by manipulating URL inputs that the application processes without proper validation.

How do you prevent SSRF in SvelteKit/Tauri applications?

Restrict accepted URL schemes to HTTPS only, implement domain allowlisting, validate and sanitize all URL inputs before passing them to fetch or backend invocations, and never allow user-controlled paths to be fetched directly from the local filesystem.

What CWE is unvalidated URL input?

CWE-918 (Server-Side Request Forgery) covers cases where an application fetches resources from user-supplied URLs without adequate validation. CWE-73 (External Control of File Name or Path) also applies when local file paths are accepted.

Is restricting to HTTPS enough to prevent SSRF?

Restricting to HTTPS eliminates plaintext HTTP and local file access vectors, but a complete defense also requires domain allowlisting, IP address validation (blocking private/internal ranges), and redirect following controls.

Can static analysis detect unvalidated URL input vulnerabilities?

Yes, static analysis tools can trace data flow from user-controlled sources (query parameters, drag-drop events) to dangerous sinks (fetch, invoke) and flag cases where no validation or allowlisting is applied.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #22

Related Articles

critical

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

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `server.js` and `worker.js`, where user-supplied `config` URL parameters were passed directly to `fetchWithAuth()` without any validation. This allowed attackers to force the application to make requests to internal network addresses, cloud metadata endpoints like `169.254.169.254`, or `file://` URIs. The fix introduces an `isAllowedUrl()` allowlist function that rejects private IP ranges, loopback addresses, and non-HTTP(S) pr

high

How Nodemailer raw option bypass happens in Node.js and how to fix it

A high-severity vulnerability in Nodemailer versions prior to 9.0.1 allowed attackers to bypass the `disableFileAccess` and `disableUrlAccess` security controls using the message-level `raw` option. This bypass enabled arbitrary file reads from the server and full-response Server-Side Request Forgery (SSRF) attacks, potentially exposing sensitive configuration files and internal network resources. The fix involves upgrading Nodemailer from version 8.0.7 to 9.0.1.

critical

How Server-Side Request Forgery (SSRF) happens in JavaScript fetch() and how to fix it

A critical Server-Side Request Forgery vulnerability in `popup.js` allowed attackers to inject malicious URLs from scraped webpages directly into fetch() calls, potentially accessing internal network resources and AWS metadata endpoints. The fix adds URL validation to ensure only HTTP/HTTPS protocols are used and blocks requests to private IP ranges and localhost addresses.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in axios versions prior to 1.15.1 allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This meant HTTP requests intended to stay internal could be routed through an attacker-controlled proxy, potentially exposing sensitive data. The fix upgrades axios to version 1.15.1, which correctly validates URLs against NO_PROXY rules.

critical

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

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `hubcmdui/routes/httpProxy.js` where the `/proxy/test` endpoint passed a user-controlled `testUrl` parameter directly to `axios.get()` without any validation. An attacker could exploit this to probe internal infrastructure — including AWS metadata endpoints, Redis instances, and private network ranges. The fix introduces a strict URL validation function that blocks private IP ranges, loopback addresses, and non-HTTP(S)

critical

How SQL injection happens in Node.js string interpolation and how to fix it

A critical SQL injection vulnerability was discovered in the `getScript()` method of `src/core/statistics.js`, where the `metadata_id` variable was directly interpolated into DELETE and UPDATE SQL statements without any validation. An attacker controlling this parameter could inject malicious SQL payloads to delete entire tables or exfiltrate sensitive data. The fix implements strict input validation using `parseInt()` and regex patterns to ensure only safe values reach the database queries.