Back to Blog
high SEVERITY5 min read

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

A critical Server-Side Request Forgery (SSRF) vulnerability in the ldfetch CLI tool allowed attackers to access internal cloud metadata services and local files through unvalidated URL arguments. The fix introduces strict protocol validation with an explicit opt-in flag for local file access, transforming a dangerous default into a secure-by-design implementation.

O
By Orbis AppSec
Published September 7, 2026Reviewed September 7, 2026

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in the Node.js ldfetch CLI tool where `bin/ldfetch.js` accepted arbitrary URLs from command-line arguments without validation. Attackers could exploit this to access AWS metadata services (169.254.169.254), internal network resources, or local files via `file://` URLs. The fix implements protocol whitelist validation through a new `isAllowedProtocol` module, restricts fetcher initialization to `http://` and `https://` by default, and adds an explicit `--local-files` flag that developers must consciously enable to allow `file://` access.

Vulnerability at a Glance

cweCWE-918
fixProtocol whitelist validation with explicit opt-in for local file access via `--local-files` flag
riskCloud credential theft, internal network reconnaissance, local file disclosure
languageJavaScript (Node.js)
root causeUnvalidated URL argument passed directly to HTTP fetcher without protocol or destination restrictions
vulnerabilityServer-Side Request Forgery (SSRF)

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

In the ldfetch repository, we discovered a high severity Server-Side Request Forgery (SSRF) vulnerability in bin/ldfetch.js that allowed attackers to pivot through the CLI tool and access internal cloud infrastructure. The vulnerability resided in how the tool handled its primary command-line argument—a URL that was passed directly to the fetcher without any validation of protocol, destination, or intent.

What makes this case particularly instructive is how a seemingly simple command-line utility, designed to fetch Linked Data resources from the web, became a potential gateway for cloud credential theft and internal network reconnaissance.

The Vulnerability Explained

The ldfetch CLI tool is designed to retrieve RDF data from URLs. Users invoke it with:

node bin/ldfetch.js https://example.org/data.ttl

The vulnerability existed in the argument handling code at lines 16-21 of bin/ldfetch.js:

program
  .option('-p, --predicates <predicates ...>', 'Some predicates can be followed [predicates]', list)
  .option('--frame <jsonldframe|file>', 'Add a JSON-LD frame')
  .arguments('<url>')
  .action(function (argUrl) {
    //TODO: check whether starts with http(s)?  <-- The TODO that never happened
    url = argUrl;
  })
  .parse(process.argv);

The commented TODO—//TODO: check whether starts with http(s)?—is a stark reminder of how postponed security checks become exploitable vulnerabilities. The url variable was captured from user input and later used to initialize fetch operations without any intermediate validation.

The fetcher initialization at line 40 compounded the problem:

var fetch = new ldfetch();  // No restrictions on what URLs could be fetched

This meant an attacker could execute:

# Steal AWS IAM credentials from EC2 metadata service
node bin/ldfetch.js http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Access internal admin panels
node bin/ldfetch.js http://localhost:8080/admin

# Read arbitrary local files
node bin/ldfetch.js file:///etc/passwd

The 169.254.169.254 IP address is particularly dangerous—it's the link-local address used by AWS, Azure, and GCP for instance metadata services. Successful exploitation would yield temporary IAM credentials, potentially granting attackers persistent access to cloud infrastructure.

The Fix

The remediation transformed the security model from permissive-by-default to secure-by-design with explicit opt-ins for dangerous functionality.

Protocol Validation Module

The fix introduces a dedicated validation module (lib/allowedProtocol.js) imported at line 4:

var isAllowedProtocol = require('../lib/allowedProtocol.js');

Restricted Fetcher Initialization

The fetcher is now initialized with explicit security controls at line 30:

var fetch = new ldfetch({ localFiles: !!options.localFiles });

The localFiles option is only enabled when users explicitly pass the --local-files flag, making dangerous functionality a conscious security decision.

Explicit Local File Opt-In

A new command-line option was added at line 19:

.option('-l, --local-files', 'Allow fetching file:// URLs (disabled by default; only use with trusted input)')

This follows the principle of secure defaults: file:// URLs are blocked unless explicitly requested, and the help text warns users to "only use with trusted input."

Before vs. After

Aspect Before (Vulnerable) After (Fixed)
URL validation None—any URL accepted Protocol whitelist enforced
file:// access Always allowed Requires --local-files flag
Fetcher config new ldfetch() new ldfetch({ localFiles: !!options.localFiles })
User intent Implicit Explicit for dangerous operations

The documentation was also updated to clarify the new security behavior:

By default, only `http://` and `https://` URLs are fetched. Pass `--local-files` 
to also allow `file://` URLs... (disabled by default for security; only use with 
trusted input).

Prevention & Best Practices

SSRF Defense in Depth

  1. Protocol Whitelisting: Restrict allowed URL schemes to only those necessary (http://, https://). Never allow file://, ftp://, gopher://, or other schemes unless absolutely required.

  2. IP Range Denylists: Block requests to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8) and cloud metadata addresses (169.254.169.254).

  3. DNS Rebinding Protection: Resolve hostnames to IPs before validation and re-verify after DNS resolution to prevent rebinding attacks.

  4. Principle of Least Privilege: Run services with minimal network access. The ldfetch fix embodies this by requiring explicit flags for elevated privileges.

Detection Tools

  • Static Analysis: Tools like Semgrep, CodeQL, and the multi_agent_ai scanner used here can detect tainted input reaching URL fetch operations
  • OWASP ZAP: Dynamic testing for SSRF vulnerabilities
  • Cloud Security Posture Management (CSPM): Detect overprivileged instance metadata access

Relevant Standards

  • CWE-918: Server-Side Request Forgery (SSRF)
  • OWASP Top 10 2021: A10:2021 – Server-Side Request Forgery (SSRF)
  • OWASP SSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html

Key Takeaways

  • TODO comments are security debt: The //TODO: check whether starts with http(s)? comment identified the exact vulnerability that was later exploited—security checks should never be postponed
  • The ldfetch constructor now requires explicit localFiles configuration: Default-deny is enforced through !!options.localFiles, ensuring file:// access is opt-in only
  • Command-line tools need the same validation as web applications: CLI arguments are untrusted input and require identical sanitization to HTTP parameters
  • Cloud metadata services are high-value SSRF targets: The 169.254.169.254 address should be explicitly blocked in all URL-handling code
  • Documentation drives secure behavior: The README update explaining the --local-files flag helps users understand security implications

How Orbis AppSec Detected This

Field Details
Source Command-line argument argUrl captured in program.action() at bin/ldfetch.js:20
Sink new ldfetch() instantiation and subsequent fetch operations on unvalidated URLs
Missing control No protocol validation, no IP range restrictions, no destination allowlisting
CWE CWE-918: Server-Side Request Forgery (SSRF)
Fix Implemented protocol whitelist via isAllowedProtocol module and added explicit --local-files opt-in flag with secure default-deny behavior

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

The ldfetch SSRF vulnerability demonstrates how even simple CLI tools can become attack vectors when they handle URLs without validation. The fix's elegance lies in its minimal footprint—adding a protocol check and an explicit flag—while fundamentally changing the security posture from permissive to restrictive.

For developers building similar tools, the lesson is clear: treat all URL inputs as potentially malicious. Whether from HTTP headers, form fields, or command-line arguments, URLs can target internal infrastructure, cloud metadata services, and local file systems. The secure-by-default pattern used here—requiring explicit flags for dangerous functionality—should be your standard approach.

References

  • CWE-918: Server-Side Request Forgery (SSRF): https://cwe.mitre.org/data/definitions/918.html
  • OWASP SSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
  • AWS Metadata Service documentation and security best practices: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
  • Semgrep rule for SSRF detection: https://semgrep.dev/r?q=ssrf
  • fix: fix security issue in ldfetch.js

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF)?

SSRF is a vulnerability where an attacker tricks a server into making unintended requests to internal services, cloud metadata endpoints, or restricted network resources by manipulating URL parameters that the server processes.

How do you prevent SSRF in Node.js?

Implement strict protocol validation (allow only http:// and https://), whitelist allowed domains/IPs, reject private IP ranges and cloud metadata addresses, and require explicit flags for dangerous functionality like local file access.

What CWE is Server-Side Request Forgery?

CWE-918: Server-Side Request Forgery (SSRF)

Is HTTPS-only validation enough to prevent SSRF?

No, HTTPS-only doesn't prevent attacks against internal HTTPS services, cloud metadata endpoints, or localhost services. You must also validate destination IP ranges and implement network segmentation.

Can static analysis detect SSRF?

Yes, static analysis tools can detect SSRF by tracing tainted input from command-line arguments or HTTP parameters to URL fetch operations without intermediate validation, as demonstrated by the multi_agent_ai scanner that flagged this vulnerability.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #58

Related Articles

critical

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

The order-flow service in a Node.js e-commerce backend built an outbound fetch() URL by directly concatenating a configurable `sendingOrder.url` value with a query string, with no validation of protocol or destination. This allowed order data—including customer and payment-adjacent information—to be silently redirected to an attacker-controlled endpoint simply by changing a config value or environment variable.

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 (SSRF) happens in JavaScript and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in playground.html where the `__forEachRdfMessageChunkFromUrl` function fetched user-controlled URLs without validating against private IP ranges or internal network addresses. The fix introduces a comprehensive `__isBlockedFetchUrl` validation function that blocks requests to localhost, private IP ranges, and link-local addresses before any fetch occurs.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.