Back to Blog
critical SEVERITY6 min read

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

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in the `recon.mjs` script, where a fetch wrapper accepted arbitrary URLs without validation. This allowed attackers to access internal infrastructure and cloud metadata services. The fix implements comprehensive URL validation that blocks internal IP ranges, loopback addresses, and dangerous protocols before any network request is made.

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

Answer Summary

Server-Side Request Forgery (SSRF) in Node.js occurs when a fetch wrapper accepts arbitrary URLs without validation, allowing attackers to access internal services like cloud metadata endpoints. This vulnerability (CWE-918) was found in `recon.mjs` where URLs passed to fetch were not validated against internal IP ranges. The fix implements URL validation that blocks loopback addresses (127.0.0.1, localhost, ::1), RFC1918 private ranges (10.x.x.x, 192.168.x.x), and cloud metadata IPs (169.254.169.254) before any request is made.

Vulnerability at a Glance

cweCWE-918
fixImplement URL validation that blocks internal IPs, loopback addresses, and dangerous protocols before fetch execution
riskAttackers can access internal services, cloud metadata, and bypass network security controls
languageJavaScript (Node.js)
root causeFetch wrapper accepts arbitrary URLs without validating against internal IP ranges or blocked protocols
vulnerabilityServer-Side Request Forgery (SSRF)

Introduction

The recon.mjs script in this Node.js library serves as a site crawler for migration workflows, fetching and analyzing content from target URLs. However, a critical flaw in its fetch wrapper function created a severe security risk: the script accepted arbitrary URLs without any validation, enabling Server-Side Request Forgery (SSRF) attacks that could compromise internal infrastructure.

At line 60 of template/scripts/recon.mjs, the fetch wrapper blindly followed any URL provided to it—including internal IP addresses, cloud metadata endpoints, and localhost services. For a library consumed by downstream applications, this vulnerability meant that any attacker who could influence the target URL parameter could pivot through the server to access protected internal resources.

The Vulnerability Explained

Server-Side Request Forgery occurs when an application makes HTTP requests to URLs controlled by an attacker. In recon.mjs, the fetch wrapper was designed to crawl websites during migration, but it lacked any safeguards against malicious destinations.

The vulnerable pattern looked like this:

// Vulnerable: fetch wrapper accepts any URL without validation
async function fetchPage(url) {
  const response = await fetch(url, { redirect: 'follow' });
  return response;
}

This code has three critical problems:
1. No URL validation - Any URL is accepted, including internal addresses
2. Automatic redirect following - Attackers can use open redirects to bypass initial checks
3. No protocol restrictions - File and other dangerous protocols might be accessible

Attack Scenario

Consider an attacker who can control the target site being migrated or compromise a data source that feeds URLs to the recon script. They could provide URLs like:

  • http://169.254.169.254/latest/meta-data/ - AWS instance metadata containing IAM credentials
  • http://127.0.0.1:8080/admin - Internal admin panels
  • http://10.0.5.20/internal-api - Private network services
  • http://[::ffff:127.0.0.1]/ - IPv4-mapped IPv6 loopback bypass

The attacker doesn't need direct access to internal services—they simply trick the server into making requests on their behalf, effectively using the application as a proxy into the internal network.

Real-World Impact

For this Node.js library, the impact extends to every downstream consumer:
- Cloud credential theft: AWS, GCP, and Azure all expose instance metadata at 169.254.169.254
- Internal service enumeration: Attackers can probe internal networks for vulnerable services
- Data exfiltration: Internal APIs may return sensitive data without authentication when accessed from "trusted" internal IPs
- Lateral movement: Compromised credentials or tokens enable further attacks

The Fix

The fix implements a comprehensive SSRF guard that validates URLs before any fetch request is made. The key insight is that all dangerous URLs can be blocked at the URL parsing stage, without requiring network access.

New Test Coverage

The PR adds extensive test cases that verify the SSRF guard blocks all known bypass techniques:

describe('recon — SSRF guard');

gate('no target at all is a usage error', {
  script: 'recon.mjs',
  files: {},
  args: [],
  expect: 1,
  contains: 'usage:',
});

for (const [label, target] of [
  ['loopback', 'http://127.0.0.1/'],
  ['loopback, written short', 'http://127.1/'],
  ['loopback, written as an integer', 'http://2130706433/'],
  ['loopback, written in octal', 'http://0177.0.0.1/'],
  ['loopback over IPv6', 'http://[::1]/'],
  ['loopback as IPv4-mapped IPv6', 'http://[::ffff:127.0.0.1]/'],
  ['localhost by name', 'http://localhost:8080/'],
  ['the cloud metadata address', 'http://169.254.169.254/latest/meta-data/'],
  ['an RFC1918 address', 'http://10.0.5.20/'],
  ['a private 192.168 address', ...],
]) {
  // Each blocked URL causes exit code 1
}

What Changed

The fix removes recon.mjs from the UNCOVERED list (line 952 in the diff), indicating it now has proper test coverage:

 const UNCOVERED = {
   'verify.mjs': NETWORK,
-  'recon.mjs': NETWORK,
   'shots.mjs': NETWORK,

The SSRF guard implements these protections:

  1. Loopback blocking: Catches 127.0.0.1, 127.1, decimal notation (2130706433), and octal notation (0177.0.0.1)
  2. IPv6 handling: Blocks ::1 and IPv4-mapped addresses like ::ffff:127.0.0.1
  3. Hostname resolution: Blocks localhost regardless of port
  4. Cloud metadata protection: Explicitly blocks 169.254.169.254
  5. Private range blocking: Blocks RFC1918 addresses (10.x.x.x, 192.168.x.x, 172.16-31.x.x)

Important Limitation

As noted in the code comments, redirect-based SSRF bypasses cannot be tested offline:

/* ⚠ THE REDIRECT REFUSAL IS NOT COVERED HERE and cannot be: it needs a real
 *   server issuing a 302 to an internal address. It does not exit 1 — it
 *   returns null and adds a note — so the ledger does not demand it, but do
 *   not read this block as proof that the redirect path works.
 */

This is an honest acknowledgment that while the initial URL validation is thoroughly tested, redirect-following behavior requires integration testing with live servers.

Prevention & Best Practices

URL Validation Pattern

Always validate URLs before making server-side requests:

function isUrlSafe(urlString) {
  const url = new URL(urlString);

  // Block dangerous protocols
  if (!['http:', 'https:'].includes(url.protocol)) {
    return false;
  }

  // Resolve hostname to IP and check ranges
  const ip = resolveHostname(url.hostname);
  if (isInternalIP(ip)) {
    return false;
  }

  return true;
}

Defense in Depth

  1. Allowlist over blocklist: When possible, maintain a list of allowed domains rather than trying to block all dangerous ones
  2. Disable redirects: Use { redirect: 'manual' } and validate redirect targets
  3. Network segmentation: Run services that make outbound requests in isolated network segments
  4. Egress filtering: Configure firewalls to block outbound requests to internal ranges
  5. Metadata service protection: Use IMDSv2 on AWS, which requires session tokens

Tools for Detection

  • Semgrep: Use rules like javascript.fetch.security.ssrf to detect unvalidated fetch calls
  • ESLint plugins: Security-focused plugins can flag dangerous patterns
  • Dynamic testing: Tools like Burp Suite can test for SSRF during penetration testing

Key Takeaways

  • The recon.mjs fetch wrapper required validation before any URL was passed to fetch() - this is the fundamental fix
  • IP address bypass techniques are numerous - blocking "127.0.0.1" alone is insufficient; you must handle decimal, octal, IPv6, and mapped address formats
  • Cloud metadata endpoints (169.254.169.254) are prime SSRF targets - always explicitly block this address range
  • Library vulnerabilities affect all downstream consumers - this Node.js library's SSRF exposed every application that used it
  • Test coverage must include bypass attempts - the fix includes tests for 10+ different loopback representations

How Orbis AppSec Detected This

  • Source: URL parameter passed to the recon.mjs script from external input (target site configuration or data sources)
  • Sink: fetch() call in the fetch wrapper function at template/scripts/recon.mjs:60 that follows redirects automatically
  • Missing control: No URL validation, IP range blocking, or protocol allowlisting before the fetch request
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Implemented comprehensive URL validation that blocks internal IP ranges, loopback addresses (including bypass variants), cloud metadata endpoints, and validates protocols before any fetch is executed

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 recon.mjs demonstrates why URL validation is critical for any server-side code that makes HTTP requests based on external input. The fix shows that comprehensive protection requires handling numerous IP address representations and bypass techniques—simple string matching against "localhost" or "127.0.0.1" is never sufficient.

For developers building similar functionality, the key lesson is to validate URLs at the parsing stage, before any network request is made. Use established libraries for IP range checking, explicitly block cloud metadata addresses, and consider using allowlists when the set of valid destinations is known.

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 HTTP requests to arbitrary destinations, potentially accessing internal services, cloud metadata endpoints, or other protected resources that should not be externally accessible.

How do you prevent SSRF in Node.js?

Prevent SSRF by validating URLs before making requests: block internal IP ranges (127.0.0.1, 10.x.x.x, 192.168.x.x, 169.254.x.x), use URL allowlists, disable automatic redirect following, and validate protocols to only allow http/https.

What CWE is SSRF?

SSRF is classified as CWE-918: Server-Side Request Forgery (SSRF), which describes vulnerabilities where an attacker can abuse server functionality to make requests to unintended locations.

Is blocking localhost enough to prevent SSRF?

No, blocking localhost alone is insufficient. Attackers can bypass this using alternative representations like 127.1, 0177.0.0.1 (octal), 2130706433 (decimal), IPv6 addresses (::1, ::ffff:127.0.0.1), or DNS rebinding attacks. Comprehensive IP range validation is required.

Can static analysis detect SSRF?

Yes, static analysis tools can detect SSRF by tracing data flow from user-controlled inputs to HTTP request functions. Tools like Semgrep, CodeQL, and specialized security scanners can identify fetch/request calls that accept unvalidated URLs.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

high

How Octal IP Address Parsing Leads to SSRF in Node.js and How to Fix It

CVE-2026-69192 reveals a critical inconsistency in the `ip-address` library where Address4 decodes leading-zero octets as decimal while DNS resolvers interpret them as octal, creating a dangerous parsing divergence. This mismatch allows attackers to bypass IP-based access controls and perform Server-Side Request Forgery (SSRF) attacks. The fix upgrades `ip-address` from 9.0.5 to 10.3.1, aligning parsing behavior with standard resolver implementations.

high

How IP Address Parsing Inconsistencies Cause SSRF and Trust-Boundary Bypass in Node.js Applications

The `ip-address` library version 10.2.0 contained a critical parsing inconsistency where the `Address4` decoder interpreted leading-zero octets as decimal numbers, while most DNS resolvers and network systems interpreted them as octal. This mismatch allowed attackers to bypass IP-based access controls and SSRF filters. Upgrading to version 10.3.1 fixes this vulnerability by aligning the library's parsing behavior with standard resolver behavior.

critical

How Server-Side Request Forgery (SSRF) Happens in Node.js fetch Tools and How to Fix It

A critical Server-Side Request Forgery (SSRF) vulnerability in `plugins/tools/fetch.js` allowed attackers to access internal resources and cloud metadata endpoints by passing arbitrary URLs to the fetch command. The fix adds hostname resolution and private IP range validation before executing any HTTP requests, preventing attackers from targeting internal infrastructure.

critical

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

A critical SSRF vulnerability was discovered in server.js where the API proxy endpoint constructed target URLs from user-controlled path parameters without validating the final origin. Attackers could use URL encoding tricks like `/api/%2F%2Fevil.com` to redirect proxy requests to arbitrary hosts, potentially accessing cloud metadata services or internal resources. The fix adds origin validation to ensure all proxied requests only reach the intended openrouter.ai upstream.

high

How Octal vs. Decimal IP Address Parsing Inconsistency Enables SSRF in Node.js and How to Fix It

The `ip-address` npm package (version 10.2.0) parsed IPv4 addresses with leading-zero octets as decimal numbers, while operating system resolvers interpret them as octal. This inconsistency (CVE-2026-69192) allows attackers to bypass SSRF protections and trust-boundary checks by crafting IP addresses that appear safe to the library but resolve to internal network addresses. The fix upgrades `ip-address` to version 10.3.1, which correctly rejects or normalizes ambiguous octal notation.

critical

How API Key Exposure in URL Parameters happens in Python and how to fix it

The Wine Cellar Home Assistant integration exposed Gemini API keys by transmitting them as URL query parameters in HTTP requests. This critical vulnerability allowed API keys to be logged in server logs, proxy caches, and browser history. The fix moved authentication to the secure `x-goog-api-key` HTTP header, preventing credential leakage.