Back to Blog
critical SEVERITY10 min read

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

A critical SSRF vulnerability in `js/cd-player/playlist-importer.js` allowed attacker-controlled URLs from third-party Meting APIs to be stored and later fetched by users' browsers, potentially exposing internal network resources. The fix introduces an `isSafeUrl()` validation function that enforces HTTPS-only URLs before any track audio or cover art URL is accepted into the application. This change closes the attack path without altering the normal playlist import workflow.

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

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in a JavaScript playlist importer (`js/cd-player/playlist-importer.js`). The `convertToLocalFormat()` function stored `audioUrl` and `coverUrl` fields from third-party Meting API responses without validating their protocol or domain, allowing an attacker who controls the API response to inject `file://`, `http://`, or internal-network URLs. The fix adds an `isSafeUrl()` helper that uses the native `URL` constructor to enforce `https:` protocol on every URL before it is stored, and skips any track whose audio URL fails this check.

Vulnerability at a Glance

cweCWE-918
fixAdded `isSafeUrl()` to enforce `https:` protocol on all stored URLs; tracks with invalid audio URLs are skipped and invalid cover URLs are set to `null`
riskAttacker-controlled URLs can redirect user browsers to internal network resources or sensitive local files
languageJavaScript
root cause`convertToLocalFormat()` stored `track.url` and `track.pic` from Meting API responses without any protocol or domain validation
vulnerabilityServer-Side Request Forgery (SSRF) via unvalidated third-party API URLs

The Vulnerability at a Glance

Field Detail
Vulnerability Server-Side Request Forgery (SSRF) via unvalidated third-party API URLs
CWE CWE-918
Language JavaScript
Risk Attacker-controlled URLs can redirect user browsers to internal network resources or sensitive local files
Root cause convertToLocalFormat() stored track.url and track.pic from Meting API responses without any protocol or domain validation
Fix Added isSafeUrl() to enforce https: protocol on all stored URLs; tracks with invalid audio URLs are skipped and invalid cover URLs are set to null

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?
SSRF is an attack where an application fetches a URL supplied by an attacker, causing the request to target internal network resources, cloud metadata endpoints, or local files that would otherwise be inaccessible from the outside.

How do you prevent SSRF in JavaScript URL handling?
Parse every externally supplied URL with the native URL constructor, enforce https: protocol, and optionally allowlist trusted domains before storing or fetching any URL.

What CWE is SSRF?
SSRF is classified as CWE-918: Server-Side Request Forgery.

Is checking for http vs https enough to prevent SSRF?
Enforcing HTTPS blocks plaintext and non-HTTP schemes (like file:// or ftp://), which eliminates the most common SSRF vectors. For stronger protection, combine protocol enforcement with an allowlist of trusted domains.

Can static analysis detect SSRF in JavaScript?
Yes. Tools like Semgrep and multi-agent AI scanners can trace tainted data from external API responses to fetch/URL storage sinks and flag missing protocol validation, as demonstrated by the scanner that caught this exact issue.


Introduction

The js/cd-player/playlist-importer.js file is responsible for fetching playlist data from third-party Meting APIs and converting those API responses into the internal track format used by the CD player. That sounds routine — but a flaw in the convertToLocalFormat() function created a meaningful security risk: every URL returned by the Meting API was stored verbatim, with no check on the protocol or origin of the URL.

This matters because those stored URLs — specifically audioUrl and coverUrl — are later fetched directly from the user's browser when they save music locally. If an attacker can influence what the Meting API returns (or operate a malicious Meting-compatible server), they can inject arbitrary URLs, including file:// paths, http:// addresses pointing to internal services, or cloud metadata endpoints like http://169.254.169.254/latest/meta-data/. The application would then faithfully pass those URLs to the browser for fetching, turning the user's own machine into a vehicle for probing internal infrastructure.


The Vulnerability Explained

The Vulnerable Code

Before the fix, convertToLocalFormat() in playlist-importer.js (around line 112) looked like this:

for (const track of tracks) {
  // Skip tracks without valid URL
  if (!track.url) {
    console.warn('Skipping track without URL:', track.name);
    continue;
  }

  // ... (track conversion logic)

  audioUrl: track.url,
  coverUrl: track.pic, // Store original URL, don't resolve yet

The only guard on track.url was a truthiness check — if the string was non-empty, it passed straight through into audioUrl. The coverUrl field (track.pic) had no check at all. Both values came directly from the Meting API response, which is an external, third-party data source.

Why This Is a Problem

The Meting API is a music metadata aggregation service. The application queries it via fetchPlaylistWithFallback(), which iterates over multiple API servers. Any of those servers could, in theory, be compromised or replaced with an attacker-controlled endpoint. Even without a full server compromise, a man-in-the-middle on an HTTP Meting endpoint could inject malicious URLs into the response.

Once a malicious URL is stored in audioUrl or coverUrl, it waits silently in the track record. When the user later triggers a "save locally" action, the application fetches those URLs from the user's browser context. This is the classic SSRF pattern applied to a client-side application: the browser becomes the unwitting HTTP client, sending requests to wherever the attacker points it.

Concrete Attack Scenario

  1. An attacker operates a Meting-compatible API server or intercepts traffic to an existing one.
  2. They craft a playlist response where track.url is set to http://192.168.1.1/admin (a common router admin panel) and track.pic is set to file:///etc/passwd.
  3. The user imports this playlist. convertToLocalFormat() stores both URLs without complaint.
  4. When the user clicks "Save locally," the browser fetches http://192.168.1.1/admin, potentially leaking router configuration data, and attempts to load file:///etc/passwd as cover art, potentially exposing local file contents.

For users on corporate networks, this attack surface extends to internal APIs, development servers, and cloud metadata services — all reachable from the user's machine but not from the public internet.


The Fix

The New isSafeUrl() Function

The fix introduces a small, focused validation helper added just before convertToLocalFormat():

/**
 * Validate that a URL uses HTTPS to prevent SSRF via attacker-controlled URLs
 * @param {string} url - URL to validate
 * @returns {boolean}
 */
function isSafeUrl(url) {
  try {
    return new URL(url).protocol === 'https:';
  } catch {
    return false;
  }
}

This function uses the native URL constructor, which is the correct tool for parsing URLs in JavaScript. If the input is not a valid URL (e.g., a relative path, a garbled string, or a javascript: URI), the constructor throws and isSafeUrl returns false. If it is a valid URL but uses any protocol other than https: — including http:, file:, ftp:, data:, or blob: — it also returns false. Only well-formed HTTPS URLs pass.

Before and After

Before (vulnerable):

// Skip tracks without valid URL
if (!track.url) {
  console.warn('Skipping track without URL:', track.name);
  continue;
}

// ...
audioUrl: track.url,
coverUrl: track.pic, // Store original URL, don't resolve yet

After (fixed):

// Skip tracks without valid HTTPS URL (prevents SSRF via attacker-controlled URLs)
if (!track.url || !isSafeUrl(track.url)) {
  console.warn('Skipping track without valid HTTPS URL:', track.name);
  continue;
}

// ...
audioUrl: track.url,
coverUrl: isSafeUrl(track.pic) ? track.pic : null, // Only store HTTPS URLs

Why Each Change Matters

  • track.url guard (line 115): Adding !isSafeUrl(track.url) means any track whose audio URL is not HTTPS is skipped entirely. This is the right call: a track with a file:// or http:// audio URL is either malicious or broken, and neither case should be imported.

  • coverUrl assignment (line 135): Cover art is optional metadata, so the fix doesn't skip the whole track if track.pic is invalid — it simply stores null instead. This preserves the user experience (the track is still importable) while eliminating the SSRF vector for cover art URLs.

Together, these two changes ensure that no non-HTTPS URL ever enters the application's internal track representation, regardless of what the Meting API returns.


Prevention & Best Practices

1. Always Validate URLs from External Sources at the Point of Ingestion

The moment data arrives from a third-party API, treat it as untrusted. Apply validation before storing it, not at the point of use. In this codebase, the fix correctly validates URLs inside convertToLocalFormat(), which is the ingestion boundary.

2. Use the Native URL Constructor for Parsing

Avoid regex-based URL validation. The native URL constructor correctly handles edge cases like unusual schemes, encoded characters, and malformed inputs. It's also well-tested and maintained by the JavaScript runtime.

// ✅ Correct approach
function isSafeUrl(url) {
  try {
    return new URL(url).protocol === 'https:';
  } catch {
    return false;
  }
}

// ❌ Fragile approach
const isHttps = url.startsWith('https://'); // Can be bypassed with HTTPS://

3. Consider Domain Allowlisting for Stronger Protection

The current fix enforces HTTPS, which blocks the most common SSRF vectors. For even stronger protection, consider allowlisting the specific CDN domains that Meting APIs are known to serve content from:

const ALLOWED_DOMAINS = ['music.163.com', 'y.qq.com', 'kugou.com'];

function isSafeUrl(url) {
  try {
    const parsed = new URL(url);
    return parsed.protocol === 'https:' && ALLOWED_DOMAINS.includes(parsed.hostname);
  } catch {
    return false;
  }
}

This is a defense-in-depth measure that limits the blast radius if a trusted Meting server is ever compromised.

4. Apply Content Security Policy (CSP) Headers

For web applications, a strict Content-Security-Policy header can limit which domains the browser is permitted to fetch resources from, providing a second layer of defense even if a malicious URL slips through application-level validation.

5. Audit All URL Storage Points

Search your codebase for patterns where URLs from API responses are stored directly:

# Find potential URL storage patterns
grep -rn "\.url\s*=" src/
grep -rn "\.pic\s*=" src/
grep -rn "audioUrl\|coverUrl\|imageUrl" src/

Any assignment of an externally sourced URL to a stored field is a candidate for isSafeUrl() validation.

Relevant Standards

  • OWASP SSRF Prevention Cheat Sheet: Recommends validating URL schemes, using allowlists, and blocking requests to internal IP ranges.
  • CWE-918: Server-Side Request Forgery — the authoritative classification for this vulnerability class.

Key Takeaways

  • The convertToLocalFormat() function was the exact injection point: It processed raw Meting API responses and stored track.url as audioUrl and track.pic as coverUrl without any protocol check — a single function was the entire attack surface.
  • Cover art URLs are just as dangerous as audio URLs: The track.pic field had no validation at all before this fix. Attackers could use cover art URLs to probe internal HTTP services just as effectively as audio URLs.
  • The URL constructor is the right tool: Using new URL(url).protocol is more reliable than string prefix checks because it handles encoding, case normalization, and malformed inputs correctly.
  • Skipping vs. nulling is a deliberate UX choice: Tracks with invalid audio URLs are skipped entirely (no audio = no track), but tracks with invalid cover URLs are kept with coverUrl: null (no art is acceptable, no audio is not). This distinction preserves user experience without sacrificing security.
  • Third-party API responses are untrusted input: Even APIs you intentionally integrate with can be compromised, intercepted, or replaced. Validate their output at the boundary, every time.

How Orbis AppSec Detected This

  • Source: The track.url and track.pic fields in the Meting API JSON response, received by fetchPlaylistWithFallback() and passed to convertToLocalFormat()
  • Sink: The audioUrl: track.url and coverUrl: track.pic assignments in convertToLocalFormat() at js/cd-player/playlist-importer.js:135, where URLs are stored for later browser-initiated fetching
  • Missing control: No protocol validation, no domain allowlisting, and no scheme restriction on externally supplied URLs before storage
  • CWE: CWE-918 — Server-Side Request Forgery (SSRF)
  • Fix: Added isSafeUrl() to enforce https: protocol using the native URL constructor, applied to both track.url (skip track if invalid) and track.pic (store null if invalid)

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 is a reminder that SSRF risks aren't limited to server-side code. In a client-side JavaScript application, any URL stored from an external source and later fetched by the browser carries the same risk profile: the browser becomes the HTTP client, and the user's local network becomes the attack surface.

The fix in playlist-importer.js is clean and minimal. A single isSafeUrl() function, applied at two precise points in convertToLocalFormat(), closes the vulnerability without changing any observable behavior for legitimate Meting API responses — which will always use HTTPS. The change is a good model for how to handle externally sourced URLs throughout any JavaScript application: validate at ingestion, use the native URL constructor, and enforce the strictest protocol you can justify.

If your application stores or fetches URLs from third-party APIs, now is a good time to audit those paths with the same lens.


References

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?

SSRF is an attack where an application fetches a URL supplied by an attacker, causing the request to target internal network resources, cloud metadata endpoints, or local files that would otherwise be inaccessible from the outside.

How do you prevent SSRF in JavaScript URL handling?

Parse every externally supplied URL with the native `URL` constructor, enforce `https:` protocol, and optionally allowlist trusted domains before storing or fetching any URL.

What CWE is SSRF?

SSRF is classified as CWE-918: Server-Side Request Forgery.

Is checking for `http` vs `https` enough to prevent SSRF?

Enforcing HTTPS blocks plaintext and non-HTTP schemes (like `file://` or `ftp://`), which eliminates the most common SSRF vectors. For stronger protection, combine protocol enforcement with an allowlist of trusted domains.

Can static analysis detect SSRF in JavaScript?

Yes. Tools like Semgrep and multi-agent AI scanners can trace tainted data from external API responses to fetch/URL storage sinks and flag missing protocol validation, as demonstrated by the scanner that caught this exact issue.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #8

Related Articles

high

How SSRF and Credential Leakage happens in Node.js axios and how to fix it

CVE-2025-27152 is a high-severity vulnerability in axios versions prior to 1.8.2 that allows Server-Side Request Forgery (SSRF) and credential leakage when absolute URLs are passed in requests. By upgrading from the vulnerable `^1.7.4` range (which resolved to `1.7.9`) to the pinned `1.8.2`, the attack surface for intercepting or redirecting authenticated HTTP requests is eliminated. Any Node.js application that passes user-influenced URLs to axios is potentially affected.

critical

How Server-Side Request Forgery happens in Browser Extensions and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability in `offscreen.js` allowed attackers to supply malicious feed URLs that the browser extension would fetch without validation, potentially exposing internal network services including cloud metadata endpoints. The fix introduces a dedicated `validateFeedUrl` utility and disables automatic redirect following, closing the attack vector before requests leave the extension. This kind of vulnerability is especially dangerous in browser extensions becau

critical

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

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `functions/stream/createProxyResponse.js`, where the `location` parameter was passed directly to `fetch()` without any URL validation. This allowed attackers to weaponize the proxy function to reach internal network resources, cloud metadata endpoints, and arbitrary external services. The fix adds protocol validation using the `URL` constructor before any fetch operation is performed.

critical

How Unvalidated Update URLs Happen in Node.js Agent Updaters and How to Fix Them

A critical vulnerability in `agent/src/updater.js` allowed an attacker who could modify the agent's configuration to redirect software update downloads to an attacker-controlled server, enabling remote code execution via a crafted tarball. The fix introduces strict hostname validation — including private network awareness — so the updater only fetches from trusted origins. This kind of supply-chain attack vector is easy to overlook but catastrophic in production agent deployments.

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A high-severity vulnerability (CVE-2026-69192) was discovered in the ip-address library version 10.1.0, where inconsistent IP address parsing could lead to Server-Side Request Forgery (SSRF) and trust-boundary bypass attacks. The vulnerability was fixed by upgrading ip-address from 10.1.0 to 10.3.1 in the gateway-workflow-dispatcher-v2.js component, preventing attackers from bypassing IP validation checks and accessing internal resources.

critical

How Archive Path Traversal Happens in Node.js and How to Fix It

CVE-2026-53486 is a critical path traversal vulnerability in the Decompress library, where crafted archive entries can write files and symbolic links outside the intended extraction directory. This vulnerability was transitively introduced through `@vitest/browser` and related packages pinned at version 4.1.5, and was resolved by upgrading to 4.1.6 and 5.0.0-beta.3. Left unpatched, an attacker who controls an archive file processed by any downstream consumer of this dependency chain could overwr