Back to Blog
critical SEVERITY5 min read

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.

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

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in a Node.js/Bun.js API proxy that allowed attackers to manipulate URL paths to redirect requests to unintended hosts. The vulnerable code in server.js concatenated user-controlled path segments directly into target URLs without validating the final destination origin. The fix parses the constructed URL as a URL object and validates that `targetUrl.origin` equals `"https://openrouter.ai"` before forwarding the request.

Vulnerability at a Glance

cweCWE-918
fixParse URL and validate origin matches expected upstream before proxying
riskAttackers can redirect proxy requests to internal services or cloud metadata endpoints
languageJavaScript (Bun.js)
root causeURL constructed from user input without origin validation
vulnerabilityServer-Side Request Forgery (SSRF)

Introduction

The server.js file in this Node.js library implements an API proxy that forwards client requests to OpenRouter's API. However, a flaw in how the proxy constructed target URLs from the request pathname created a critical Server-Side Request Forgery vulnerability at line 92.

The vulnerable code extracted the path after /api and concatenated it directly into the target URL:

const targetPath = pathname.slice(4); // remove /api prefix
const targetUrl = `https://openrouter.ai/api${targetPath}${url.search}`;

This pattern seems safe at first glance—after all, the base URL is hardcoded to openrouter.ai. But URL parsing rules create an unexpected attack vector that could allow requests to any host on the internet, making this a serious concern for any downstream consumers of this package.

The Vulnerability Explained

How String Concatenation Betrays You

The vulnerability lies in how browsers and HTTP clients parse URLs. When you concatenate user input into a URL string, the resulting URL might resolve to a completely different host than you intended.

Consider what happens when an attacker sends a request to:

/api/%2F%2Fevil.com%2Fmalicious

The %2F is URL-encoded /. After the proxy processes this:

  1. pathname.slice(4) extracts %2F%2Fevil.com%2Fmalicious
  2. String concatenation produces: https://openrouter.ai/api%2F%2Fevil.com%2Fmalicious
  3. When this string is used to make an HTTP request, URL parsing may decode and interpret //evil.com as a protocol-relative URL or authority component

Even more concerning, attackers could target cloud metadata services:

/api/%2F%2F169.254.169.254%2Flatest%2Fmeta-data%2F

This could allow access to AWS/GCP/Azure instance metadata, potentially exposing IAM credentials, API keys, and other sensitive configuration.

Real-World Attack Scenario

Imagine this library is used in a production application running on AWS EC2:

  1. Attacker discovers the /api/* proxy endpoint
  2. Attacker crafts a request: GET /api/../../../latest/meta-data/iam/security-credentials/
  3. The proxy forwards this to what it thinks is OpenRouter, but URL parsing tricks redirect it to 169.254.169.254
  4. The response contains temporary AWS credentials
  5. Attacker now has access to AWS resources with the EC2 instance's IAM role permissions

This is not theoretical—SSRF attacks against cloud metadata services are one of the most common vectors for cloud account compromise.

The Fix

The fix adds explicit origin validation after URL construction. Here's the before and after:

Before (Vulnerable)

const targetPath = pathname.slice(4); // remove /api prefix
const targetUrl = `https://openrouter.ai/api${targetPath}${url.search}`;

After (Secure)

const targetPath = pathname.slice(4); // remove /api prefix
const targetUrl = new URL(`https://openrouter.ai/api${targetPath}${url.search}`);
// Ensure path manipulation (e.g. encoded "//host" tricks) can never
// redirect the proxy to a different origin than the intended upstream.
if (targetUrl.origin !== "https://openrouter.ai") {
  return new Response("Forbidden", { status: 403 });
}

Why This Works

The key insight is using the URL constructor to parse the concatenated string, then checking the origin property. The URL object applies standard URL parsing rules, resolving any encoding tricks or path traversal attempts. If the final parsed origin doesn't match https://openrouter.ai, the request is blocked with a 403 Forbidden response.

This approach is robust because:

  1. It validates after parsing: No matter what encoding tricks an attacker uses, the final resolved origin is checked
  2. It uses the URL API: The standard URL constructor handles all edge cases in URL parsing
  3. It fails closed: Any unexpected origin results in rejection, not a best-effort forward
  4. It preserves functionality: Legitimate requests to OpenRouter's API continue to work unchanged

Prevention & Best Practices

1. Never Trust String Concatenation for URLs

Always use the URL API to construct and validate URLs:

// Bad
const url = `https://api.example.com/${userInput}`;

// Good
const url = new URL(userInput, 'https://api.example.com/');
if (url.origin !== 'https://api.example.com') {
  throw new Error('Invalid URL');
}

2. Implement Allowlists for Proxy Destinations

If your proxy needs to support multiple hosts, use an explicit allowlist:

const ALLOWED_ORIGINS = new Set([
  'https://openrouter.ai',
  'https://api.openai.com'
]);

if (!ALLOWED_ORIGINS.has(targetUrl.origin)) {
  return new Response("Forbidden", { status: 403 });
}

3. Block Private IP Ranges

For additional defense in depth, block requests to private networks:

import { isPrivate } from 'ip'; // or implement your own check

const hostname = targetUrl.hostname;
if (isPrivate(hostname) || hostname === 'localhost' || hostname === '169.254.169.254') {
  return new Response("Forbidden", { status: 403 });
}

4. Use Network-Level Controls

In production environments, configure firewall rules to prevent your application servers from accessing cloud metadata endpoints and internal services they don't need.

Key Takeaways

  • URL string concatenation with user input is dangerous — even with a hardcoded base URL, encoding tricks can redirect to arbitrary hosts
  • The server.js proxy at line 92 was vulnerable because it concatenated targetPath without validating the final parsed origin
  • Always validate URL.origin after construction when building URLs from untrusted input
  • Cloud metadata endpoints (169.254.169.254) are prime SSRF targets that can expose IAM credentials
  • This fix preserves all legitimate functionality while blocking malicious path manipulation attempts

How Orbis AppSec Detected This

  • Source: HTTP request path parameter extracted via pathname.slice(4) in server.js
  • Sink: URL string concatenation used in HTTP proxy request at server.js:92
  • Missing control: No validation that the constructed URL's origin matched the intended upstream host
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Added URL parsing and origin validation to ensure proxied requests only reach https://openrouter.ai

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 demonstrates why URL construction requires careful handling. What appeared to be a safely hardcoded proxy destination was actually exploitable through URL encoding tricks. The fix—parsing the URL and validating its origin—is simple, robust, and preserves all legitimate functionality.

For developers building API proxies or any code that constructs URLs from user input: always validate the final parsed URL, not just the input string. The URL API is your friend—use it to parse, then verify the result matches your expectations before making any requests.

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 an unintended destination, often targeting internal services or cloud metadata endpoints that are otherwise inaccessible from the internet.

How do you prevent SSRF in Node.js?

Prevent SSRF by validating URL origins after construction, using allowlists for permitted hosts, avoiding string concatenation for URL building, and blocking requests to private IP ranges and cloud metadata addresses.

What CWE is SSRF?

SSRF is classified as CWE-918: Server-Side Request Forgery. It falls under the broader category of request handling vulnerabilities where user input influences server-initiated requests.

Is URL path validation enough to prevent SSRF?

No, path validation alone is insufficient. Attackers can use URL encoding tricks (like `%2F%2F` for `//`) to manipulate the parsed host. You must validate the final resolved origin after URL parsing, not just the raw path string.

Can static analysis detect SSRF?

Yes, static analysis tools can detect SSRF by tracing data flow from user inputs to HTTP request functions. Tools like Semgrep, CodeQL, and specialized SAST scanners flag patterns where untrusted data flows into URL construction without validation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

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.

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.

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 CORS Misconfiguration happens in Node.js with Hono and how to fix it

CVE-2026-54290 is a HIGH severity CORS misconfiguration in the Hono web framework where the CORS middleware incorrectly reflects any `Origin` header back to the client — including credentials — when the `origin` option defaults to a wildcard. Upgrading `hono` from `4.12.16` to `4.12.34` in `package-lock.json` and pinning the version via `overrides` in `package.json` closes the vulnerability. Left unpatched, this flaw could allow malicious cross-origin sites to make credentialed requests and read