SSRF

SSRF remediation for Node.js (Express) [May 2026] [CVE-2026-43929]

[May 2026] Updated CVE-2026-43929

Overview

The CVE-2026-43929 advisory describes a vulnerability in the ssrfcheck library (version 1.3.0 and earlier) used by Node.js/Express applications to guard outbound HTTP requests. When a user-supplied URL is checked with isSSRFSafeURL(), an attacker could supply a URL that encodes a private IP address as an IPv4-mapped IPv6 address inside brackets, such as http://[::ffff:127.0.0.1]/. The Node.js WHATWG URL parser normalizes this input to a compressed IPv6 form like [::ffff:7f00:1] before the library’s regex, which only matched explicit dot-notation IPv4 addresses. As a result, the check fails to detect the private/internal target, leaving SSRF protections ineffective and allowing outward requests to internal services or metadata endpoints (e.g., 169.254.169.254). This bypass leads to exposure of internal networks, data exfiltration, and potential further compromise. The issue is tracked under CWE-184 and CWE-918, and affects applications relying solely on isSSRFSafeURL() for SSRF defense.

Affected Versions

ssrfcheck <= 1.3.0

Code Fix Example

Node.js (Express) API Security Remediation
Vulnerable:
const ssrfcheck = require('ssrfcheck');
const https = require('https');

function fetchVulnerable(target) {
  // relies on old SSRF guard that can be bypassed via IPv4-mapped IPv6
  if (!ssrfcheck.isSSRFSafeURL(target)) {
    throw new Error('Unsafe URL');
  }
  return https.get(target, (res) => res);
}

Fixed:
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');

async function isSafeURL(urlString) {
  try {
    const u = new URL(urlString);
    const host = u.hostname;

    // If host is an IP literal, validate against private/forbidden ranges
    if (ipaddr.isValid(host)) {
      const ip = ipaddr.parse(host);
      const r = ip.range();
      if (r === 'private' || r === 'loopback' || r === 'linkLocal') {
        return false;
      }
      if (host === '169.254.169.254') {
        // Cloud metadata address
        return false;
      }
    } else {
      // Resolve DNS to IPs and validate each address
      const [v4, v6] = await Promise.all([
        dns.resolve4(host).catch(() => []),
        dns.resolve6(host).catch(() => [])
      ]);
      const addrs = [...(v4 || []), ...(v6 || [])];
      for (const a of addrs) {
        if (ipaddr.isValid(a)) {
          const ip = ipaddr.parse(a);
          const r = ip.range();
          if (r === 'private' || r === 'loopback' || r === 'linkLocal') {
            return false;
          }
          if (a === '169.254.169.254') {
            return false;
          }
        }
      }
    }
    return true;
  } catch {
    return false;
  }
}

async function fetchFixed(target) {
  if (!await isSafeURL(target)) {
    throw new Error('Unsafe URL');
  }
  const https = require('https');
  return https.get(target, (res) => res);
}

CVE References

Choose which optional cookies to allow. You can change this any time.