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:
pathname.slice(4)extracts%2F%2Fevil.com%2Fmalicious- String concatenation produces:
https://openrouter.ai/api%2F%2Fevil.com%2Fmalicious - When this string is used to make an HTTP request, URL parsing may decode and interpret
//evil.comas 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:
- Attacker discovers the
/api/*proxy endpoint - Attacker crafts a request:
GET /api/../../../latest/meta-data/iam/security-credentials/ - The proxy forwards this to what it thinks is OpenRouter, but URL parsing tricks redirect it to
169.254.169.254 - The response contains temporary AWS credentials
- 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:
- It validates after parsing: No matter what encoding tricks an attacker uses, the final resolved origin is checked
- It uses the URL API: The standard
URLconstructor handles all edge cases in URL parsing - It fails closed: Any unexpected origin results in rejection, not a best-effort forward
- 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.jsproxy at line 92 was vulnerable because it concatenatedtargetPathwithout validating the final parsed origin - Always validate
URL.originafter 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)inserver.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.