fix: block localhost and loopback addresses in SSRF protection (#28622)

* fix: block localhost and loopback addresses in SSRF protection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: block loopback IPs by hostname in SSRF protection

Add 127.0.0.1, ::1, [::1], and 0.0.0.0 to blocked hostnames list for
defense-in-depth protection against SSRF attacks targeting localhost.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Romit <romitgabani1.work@gmail.com>
Co-authored-by: Romit <85230081+romitg2@users.noreply.github.com>
This commit is contained in:
Keon
2026-03-28 13:21:58 +00:00
committed by GitHub
co-authored by Claude Opus 4.5 Romit Romit
parent fbf6510dd8
commit ad791f8ea5
2 changed files with 17 additions and 10 deletions
+13 -8
View File
@@ -57,12 +57,16 @@ describe("isPrivateIP", () => {
describe("isBlockedHostname", () => {
it.each([
"localhost", // loopback hostname
"169.254.169.254", // AWS/Azure/DigitalOcean
"metadata.google.internal", // GCP
"169.254.169.254.", // trailing dot normalization
"METADATA.GOOGLE.INTERNAL", // case insensitive
])("blocks cloud metadata endpoint %s", (hostname) => {
"localhost",
"127.0.0.1",
"::1",
"[::1]",
"0.0.0.0",
"169.254.169.254",
"metadata.google.internal",
"169.254.169.254.",
"METADATA.GOOGLE.INTERNAL",
])("blocks %s", (hostname) => {
expect(isBlockedHostname(hostname)).toBe(true);
});
@@ -104,7 +108,8 @@ describe("validateUrlForSSRFSync", () => {
["http://example.com/logo.png", "Only HTTPS URLs are allowed"],
["ftp://example.com/file", "Only HTTPS URLs are allowed"],
["data:text/html,<script>alert(1)</script>", "Non-image data URL"],
["https://127.0.0.1/logo.png", "Private IP address"],
["https://127.0.0.1/logo.png", "Blocked hostname"],
["https://0.0.0.0/logo.png", "Blocked hostname"],
["https://169.254.169.254/latest/meta-data/", "Blocked hostname"],
["https://localhost/logo.png", "Blocked hostname"],
["not-a-url", "Invalid URL format"],
@@ -114,7 +119,7 @@ describe("validateUrlForSSRFSync", () => {
});
it.each([
["https://[::1]/", "Private IP address"],
["https://[::1]/", "Blocked hostname"],
["https://[fe80::1]/path", "Private IP address"],
["https://[fc00::1]:8080/", "Private IP address"],
["https://[::ffff:127.0.0.1]/", "Private IP address"],
+4 -2
View File
@@ -31,8 +31,10 @@ const CLOUD_METADATA_ENDPOINTS: string[] = [
"metadata.google.com", // GCP alternate
];
// Hostnames blocked on Cal.com SaaS (includes metadata + localhost)
const BLOCKED_HOSTNAMES: string[] = [...CLOUD_METADATA_ENDPOINTS, "localhost"];
const LOOPBACK_HOSTNAMES: string[] = ["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"];
// Hostnames blocked on Cal.com SaaS (includes metadata + loopback)
const BLOCKED_HOSTNAMES: string[] = [...CLOUD_METADATA_ENDPOINTS, ...LOOPBACK_HOSTNAMES];
const CAL_AVATAR_PATH_REGEX = /^\/api\/avatar\/.+\.png$/;