Back to Blog
critical SEVERITY5 min read

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

A critical SSRF vulnerability was discovered in `fetch-worker.js` where URLs from `sources.txt` were fetched without any validation, allowing attackers to target internal services and cloud metadata endpoints. The fix implements a robust URL allowlist that enforces HTTPS and blocks requests to private IP ranges, localhost, and link-local addresses.

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

Answer Summary

Server-Side Request Forgery (SSRF) in Node.js (CWE-918) occurs when an application fetches URLs from untrusted sources without validation. In this case, `fetch-worker.js` read URLs from `sources.txt` and fetched them directly, allowing attackers to access internal services. The fix adds an `isAllowedUrl()` function that validates the protocol is HTTPS and blocks private IP ranges (127.x, 10.x, 192.168.x, 172.16-31.x, 169.254.x) before any fetch occurs.

Vulnerability at a Glance

cweCWE-918
fixAdded isAllowedUrl() function enforcing HTTPS and blocking private IP ranges
riskAccess to internal services, cloud metadata, and sensitive endpoints
languageJavaScript (Node.js)
root causeURLs from sources.txt fetched without domain or IP validation
vulnerabilityServer-Side Request Forgery (SSRF)

Introduction

The fetch-worker.js file handles background URL fetching using Node.js worker threads, processing URLs from a sources.txt file. However, a critical flaw in the message handler at line 4 created a severe security risk: URLs were passed directly to fetch operations without any validation of the target domain or IP address.

This meant that if an attacker could modify sources.txt—through a separate file write vulnerability, supply chain attack, or compromised configuration—they could force the application to make requests to internal services, cloud metadata endpoints like http://169.254.169.254/, or other sensitive network resources that should never be accessible from the outside.

The vulnerable code pattern was deceptively simple:

parentPort.on('message', async (message) => {
    const { id, url, userAgent } = message;
    // url is used directly without any validation
    // ... fetch operation proceeds
});

The Vulnerability Explained

Server-Side Request Forgery (SSRF) occurs when an application can be tricked into making HTTP requests to unintended destinations. In this case, the fetch-worker.js worker thread accepted any URL passed through the parentPort.on('message') handler and would attempt to fetch it without question.

The Dangerous Code Path

Looking at the original code:

const { parentPort } = require('worker_threads');

parentPort.on('message', async (message) => {
    const { id, url, userAgent } = message;
    // No validation whatsoever - any URL is accepted
    let headerUA = userAgent || 'unknown';
    // ... proceeds to fetch the URL
});

The url variable extracted from the message could contain:
- Internal IP addresses: http://192.168.1.1/admin
- Localhost services: http://127.0.0.1:8080/internal-api
- Cloud metadata endpoints: http://169.254.169.254/latest/meta-data/
- Internal Kubernetes services: http://kubernetes.default.svc/api/v1/secrets

Attack Scenario

Consider this attack against a Jellyfin server deployment:

  1. An attacker identifies that sources.txt is used to configure external data sources
  2. Through a configuration vulnerability or social engineering, they add a malicious entry: http://169.254.169.254/latest/meta-data/iam/security-credentials/
  3. The fetch worker processes this URL and retrieves AWS IAM credentials
  4. The response containing sensitive credentials is returned through the worker's message system
  5. The attacker now has temporary AWS credentials with whatever permissions the instance role provides

This is particularly dangerous because:
- The request originates from within the trusted network perimeter
- Cloud metadata services don't require authentication from the instance
- The application appears to be functioning normally while exfiltrating sensitive data

The Fix

The fix introduces a new isAllowedUrl() function that implements a strict allowlist approach, validating URLs before any fetch operation occurs.

Before (Vulnerable)

parentPort.on('message', async (message) => {
    const { id, url, userAgent } = message;
    // Immediately proceeds to use the URL
    let headerUA = userAgent || 'unknown';

After (Secure)

function isAllowedUrl(url) {
    let parsed;
    try { parsed = new URL(url); } catch { return false; }
    if (parsed.protocol !== 'https:') return false;
    const host = parsed.hostname;
    if (/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|169\.254\.|0\.0\.0\.0)/i.test(host)) return false;
    return true;
}

parentPort.on('message', async (message) => {
    const { id, url, userAgent } = message;

    if (!isAllowedUrl(url)) {
        parentPort.postMessage({ id, success: false, error: 'URL not allowed', url });
        return;
    }
    // Only now proceeds with the fetch

How Each Change Protects Against SSRF

  1. URL Parsing with try/catch: Malformed URLs that could bypass string-based checks are rejected immediately
  2. HTTPS-only enforcement: parsed.protocol !== 'https:' blocks HTTP, file://, gopher://, and other dangerous protocols
  3. Private IP blocking: The regex pattern blocks:
    - localhost - the loopback hostname
    - 127. - IPv4 loopback range
    - 10. - Class A private network
    - 192.168. - Class C private network
    - 172.16-31. - Class B private networks
    - 169.254. - Link-local and cloud metadata range
    - 0.0.0.0 - All interfaces binding address

  4. Early return with error: Invalid URLs trigger an immediate response with success: false and a clear error message, preventing any network request

Key Takeaways

  • Never fetch URLs from sources.txt or similar configuration files without validation—the fetch-worker.js pattern of reading and fetching is common but dangerous
  • The 169.254.x.x range is critical to block—this is where cloud metadata services live on AWS, GCP, and Azure
  • HTTPS enforcement provides defense in depth—it prevents protocol smuggling attacks and ensures encrypted transport
  • Worker threads don't provide security isolation—the parentPort.on('message') handler needs the same input validation as any other entry point
  • Regex-based IP blocking must cover all private ranges—missing even one range (like 172.16-31.x) leaves a gap attackers will find

How Orbis AppSec Detected This

  • Source: URLs read from sources.txt and passed via parentPort.on('message') in fetch-worker.js
  • Sink: The fetch operation that would make HTTP requests to attacker-controlled destinations
  • Missing control: No validation of URL protocol, hostname, or IP address before fetching
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Added isAllowedUrl() function that enforces HTTPS protocol and blocks private IP ranges before any fetch operation

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 SSRF vulnerability in fetch-worker.js demonstrates how a simple oversight—trusting URLs from a configuration file—can create a critical security risk. The fix is elegant and focused: a 14-line isAllowedUrl() function that validates protocol and blocks private networks before any fetch occurs.

For Node.js developers building applications that fetch external resources, remember: every URL from an external source is potentially malicious. Validate the protocol, check the destination, and when in doubt, use an explicit allowlist of permitted domains.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #34

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