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 (SSRF) happens in Node.js IP address parsing and how to fix it

A critical SSRF vulnerability (CVE-2026-69192) was discovered in the ip-address npm package version 10.2.0, which could allow attackers to bypass IP address validation and access internal services. The fix upgrades the dependency to version 10.3.1, which properly handles edge cases in IP address parsing that previously allowed trust-boundary bypasses.

critical

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

A critical Server-Side Request Forgery (SSRF) vulnerability in the compass-guarded-transfer CLI tool allowed attackers to make HTTP requests to internal services and cloud metadata endpoints. The `normalizeInput` function in `run-transfer.mjs` validated that URLs started with "https://" but failed to prevent requests to private IP ranges like AWS metadata (169.254.169.254) or localhost, enabling potential credential theft and internal network reconnaissance.

high

How Message-Level Raw Option Bypass happens in Node.js Nodemailer and how to fix it

A high-severity vulnerability in Nodemailer (versions before 9.0.0) allowed the `raw` message option to completely bypass `disableFileAccess` and `disableUrlAccess` security controls, enabling attackers to read arbitrary files from the server filesystem and perform full-response Server-Side Request Forgery (SSRF) in delivered email messages. Upgrading from `^8.0.10` to `^9.0.4` in `backend/package-lock.json` closes this exploit primitive by enforcing access restrictions consistently across all m

high

How Octal/Decimal IP Parsing Ambiguity happens in JavaScript and how to fix it

CVE-2026-69192 is a high-severity vulnerability in the `ip-address` npm package (versions before 10.3.1) where IPv4 addresses with leading-zero octets — like `010.0.0.1` — are parsed as decimal by the library but interpreted as octal by OS-level resolvers, creating a dangerous mismatch. This discrepancy can allow attackers to bypass IP-based access controls and trust boundaries, potentially enabling Server-Side Request Forgery (SSRF) attacks. Upgrading to `ip-address@10.3.1` in the SAP BW Query

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

critical

How Sensitive Data Exposure happens in Python web applications and how to fix it

A critical sensitive data exposure vulnerability was discovered in `nodes/google_gemini.py` where the Google Gemini API key was returned in plaintext through a web endpoint. The fix masks the token in API responses, preventing credential theft from any client that queries the token endpoint. This protects downstream users of this Node.js library from unauthorized access to their Google Gemini services.