Skip to content

Fix SSRF vulnerability in isSafeUri validation (issue #245)#268

Open
anshul23102 wants to merge 1 commit into
geturbackend:mainfrom
anshul23102:fix/issue-245-ssrf-validation
Open

Fix SSRF vulnerability in isSafeUri validation (issue #245)#268
anshul23102 wants to merge 1 commit into
geturbackend:mainfrom
anshul23102:fix/issue-245-ssrf-validation

Conversation

@anshul23102
Copy link
Copy Markdown
Contributor

@anshul23102 anshul23102 commented Jun 4, 2026

Fixes #245: isSafeUri now properly blocks RFC-1918 private ranges, AWS metadata endpoints, and IPv6 link-local addresses. Prevents Server-Side Request Forgery attacks via external database URI configuration.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced security validation to prevent unauthorized access to internal networks, private IP ranges, and cloud infrastructure endpoints.

…tadata IPs

Issue geturbackend#245: The isSafeUri validation only blocked localhost and 127.0.0.1,
allowing attackers to supply MongoDB URIs pointing to AWS metadata endpoints
(169.254.169.254), private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16),
or link-local addresses, causing the server to initiate connections to internal
infrastructure.

Changes:
- Add validation for AWS metadata IPs (169.254.169.254, fd00:ec2::254)
- Block all RFC-1918 private address ranges using regex patterns
- Block IPv6 link-local addresses (fe80::/10)
- Block IPv6 unique local addresses (fc00::/7)
- Prevents SSRF attacks via external database URI configuration

This prevents authenticated developers from using SSRF to probe internal
services or exfiltrate metadata through crafted MongoDB wire protocol responses.

Fixes geturbackend#245
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR enhances the isSafeUri function to perform stricter SSRF validation by blocking RFC-1918 private IPv4 ranges, IPv6 link-local and unique-local ranges, and cloud metadata endpoints in addition to the previously blocked loopback addresses.

Changes

SSRF Mitigation Enhancement

Layer / File(s) Summary
Enhanced SSRF URI Validation
apps/dashboard-api/src/controllers/project.controller.js
isSafeUri now validates hostnames and IP addresses against RFC-1918 ranges, link-local IPv6 (fe80::/10), unique-local IPv6 (fc00::/7), and cloud metadata endpoints (169.254.169.254, fd00:ec2::254) to prevent SSRF attacks when processing external MongoDB URIs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through networks bright,
Blocking paths that aren't quite right,
RFC-1918 behind the fence,
Cloud metadata kept well-hence,
SSRF dangers out of sight! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix SSRF vulnerability in isSafeUri validation (issue #245)' directly and concisely summarizes the main change—addressing an SSRF vulnerability by improving URI validation logic.
Linked Issues check ✅ Passed The changes implement all coding requirements from issue #245: blocking RFC-1918 ranges, cloud metadata endpoints (169.254.169.254, fd00:ec2::254), IPv6 link-local (fe80::/10), and IPv6 ULA (fc00::/7) addresses within isSafeUri.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the SSRF vulnerability in isSafeUri as specified in issue #245; no unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 491-533: The current isSafeUri only inspects the hostname string
and ignores DNS resolution, allowing DNS-rebinding/internal hostnames to pass;
update isSafeUri to perform asynchronous DNS resolution (e.g.,
dns.promises.lookup / resolve with all records) for the parsed hostname and
validate every resolved IP (IPv4 and IPv6) against the same RFC1918, link-local,
loopback, metadata and unique-local checks, returning false if any resolved
address is unsafe; make isSafeUri async and update all call sites (including the
mongoose.createConnection call path) to await it and reject/throw when a URI is
not safe.
- Around line 522-525: The current check /^fe80:/i only blocks fe80:: addresses
but the IPv6 link-local range is fe80::/10 (fe80 through febf); update the
rejection regex that tests ipAddress to match any address whose second hex byte
is 0x80–0xbf, e.g. replace /^fe80:/i with a pattern like
/^fe(?:8|9|a|b)[0-9a-f]:/i so ipAddress values fe90::, fea0::, febf:: etc. are
also rejected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19cf3cc9-0cdd-4465-ab5f-4316b67b2fce

📥 Commits

Reviewing files that changed from the base of the PR and between 190b325 and 29d4876.

📒 Files selected for processing (1)
  • apps/dashboard-api/src/controllers/project.controller.js

Comment on lines 491 to 533
const isSafeUri = (uri) => {
try {
const parsed = new URL(uri);
const host = parsed.hostname.toLowerCase();
const badHosts = ["localhost", "127.0.0.1", "0.0.0.0", "::1"];
return !badHosts.includes(host);

// Blocked hostnames
const blockedHosts = [
"localhost",
"127.0.0.1",
"0.0.0.0",
"::1",
"169.254.169.254", // AWS metadata service
"fd00:ec2::254", // IPv6 AWS metadata
];

if (blockedHosts.includes(host)) {
return false;
}

// Parse IP address if hostname is an IP
const ipAddress = host;

// Reject RFC-1918 private address ranges
if (
/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ipAddress) || // 10.0.0.0/8
/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(ipAddress) || // 172.16.0.0/12
/^192\.168\.\d{1,3}\.\d{1,3}$/.test(ipAddress) // 192.168.0.0/16
) {
return false;
}

// Reject IPv6 link-local addresses (fe80::/10)
if (/^fe80:/i.test(ipAddress)) {
return false;
}

// Reject IPv6 unique local addresses (fc00::/7)
if (/^f[cd][0-9a-f]{2}:/i.test(ipAddress)) {
return false;
}

return true;
} catch (e) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Hostname inputs can still SSRF internal targets (DNS rebinding/internal DNS).

Line 493/494 validates only the hostname string; it does not resolve hostnames and validate resolved addresses. An attacker-controlled hostname that resolves to RFC1918/link-local/metadata IPs will pass isSafeUri and still reach mongoose.createConnection (Line 559).

Suggested direction
+const dns = require("dns").promises;
+const net = require("net");

-const isSafeUri = (uri) => {
+const isSafeUri = async (uri) => {
   try {
     const parsed = new URL(uri);
     const host = parsed.hostname.toLowerCase();
+    const resolved = net.isIP(host)
+      ? [{ address: host }]
+      : await dns.lookup(host, { all: true, verbatim: true });

-    const ipAddress = host;
+    for (const { address: ipAddress } of resolved) {
+      // apply blockedHosts / RFC1918 / metadata / IPv6 checks to ipAddress
+      // return false on first unsafe address
+    }

     return true;
   } catch (e) {
     return false;
   }
 };

-      if (!isSafeUri(dbUri))
+      if (!(await isSafeUri(dbUri)))
         return res.status(400).json({

Also applies to: 547-549

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 491 -
533, The current isSafeUri only inspects the hostname string and ignores DNS
resolution, allowing DNS-rebinding/internal hostnames to pass; update isSafeUri
to perform asynchronous DNS resolution (e.g., dns.promises.lookup / resolve with
all records) for the parsed hostname and validate every resolved IP (IPv4 and
IPv6) against the same RFC1918, link-local, loopback, metadata and unique-local
checks, returning false if any resolved address is unsafe; make isSafeUri async
and update all call sites (including the mongoose.createConnection call path) to
await it and reject/throw when a URI is not safe.

Comment on lines +522 to +525
// Reject IPv6 link-local addresses (fe80::/10)
if (/^fe80:/i.test(ipAddress)) {
return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

fe80::/10 blocking is incomplete and bypassable.

Line 523 only blocks fe80:*, but link-local IPv6 spans fe80 through febf. This still allows SSRF to link-local addresses like fe90::1.

Suggested fix
-    // Reject IPv6 link-local addresses (fe80::/10)
-    if (/^fe80:/i.test(ipAddress)) {
+    // Reject IPv6 link-local addresses (fe80::/10 => fe80..febf)
+    if (/^fe[89ab][0-9a-f]:/i.test(ipAddress)) {
       return false;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Reject IPv6 link-local addresses (fe80::/10)
if (/^fe80:/i.test(ipAddress)) {
return false;
}
// Reject IPv6 link-local addresses (fe80::/10 => fe80..febf)
if (/^fe[89ab][0-9a-f]:/i.test(ipAddress)) {
return false;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 522 -
525, The current check /^fe80:/i only blocks fe80:: addresses but the IPv6
link-local range is fe80::/10 (fe80 through febf); update the rejection regex
that tests ipAddress to match any address whose second hex byte is 0x80–0xbf,
e.g. replace /^fe80:/i with a pattern like /^fe(?:8|9|a|b)[0-9a-f]:/i so
ipAddress values fe90::, fea0::, febf:: etc. are also rejected.

@anshul23102
Copy link
Copy Markdown
Contributor Author

@geturbackend Could you please add the appropriate labels (gssoc, security, type:bug) to this PR? SSRF fix blocking RFC-1918 ranges and metadata endpoints. Thank you!

@yash-pouranik
Copy link
Copy Markdown
Collaborator

I only add labels after merging.
@anshul23102

@yash-pouranik
Copy link
Copy Markdown
Collaborator

@anshul23102

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] dashboard-api: isSafeUri in project.controller.js does not block RFC-1918 or cloud metadata IP ranges, enabling SSRF via external MongoDB URI

2 participants