SSRF

SSRF in Node.js (Express) remediation guide [CVE-2026-44578]

[Updated May 2026] Updated CVE-2026-44578

Overview

This guide explains a real-world SSRF risk demonstrated by CVE-2026-44578, where Next.js servers with the built-in Node.js proxying behavior could be coerced into proxying requests to arbitrary internal or external destinations via crafted WebSocket upgrade requests. Although Vercel-hosted deployments are not affected, self-hosted Next.js instances running from 13.4.13 up to just before 15.5.16 and on 16.x up to 16.2.4 were exposed to this issue, with fixes landed in 15.5.16 and 16.2.5. The vulnerability is categorized as CWE-918 (Server-Side Request Forgery). In a Node.js Express environment, a similar class of vulnerabilities can arise when developers implement their own proxying or WebSocket upgrade logic that trusts client-supplied destinations. An attacker could trick the server into sending requests to internal services (like metadata endpoints) or external systems, bypassing network boundaries and exposing sensitive data or services. The risk emphasizes the importance of validating every proxy target, restricting where requests can go, and avoiding permissive proxy behavior in custom server code. The guidance here maps the core concepts of the CVE to typical Express patterns to help you remediate in real applications.

Affected Versions

CVE-2026-44578 affected Next.js built-in server from 13.4.13 to before 15.5.16 and also in 16.x up to before 16.2.5; fixed in 15.5.16 and 16.2.5. For Node.js/Express, this note applies to patterns that proxy via upgrade requests; ensure your server code is updated and follows the same mitigation principles.

Code Fix Example

Node.js (Express) API Security Remediation
// Vulnerable
const express = require('express');
const httpProxy = require('http-proxy');
const app = express();
const proxy = httpProxy.createProxyServer({ ws: true });

app.get('/proxy', (req, res) => {
  const target = req.query.target;
  if (!target) return res.status(400).send('target is required');
  // Vulnerable: user-controllable target allows proxying to arbitrary destinations
  proxy.web(req, res, { target }, err => res.status(502).send('Bad Gateway'));
});

app.listen(3000, () => console.log('Proxy listening on 3000'));

// Fixed
const allowedTargets = [ 'http://internal-service.local', 'https://api.example.com' ];
function isAllowedTarget (target) {
  try {
    const url = new URL(target);
    if (!['http:', 'https:'].includes(url.protocol)) return false;
    const host = url.hostname;
    // Simple private IP guard: block common RFC1918 ranges
    if (host === '127.0.0.1' || host.startsWith('10.') || host.startsWith('172.') || host.startsWith('192.168.'))
      return false;
    return allowedTargets.includes(url.origin);
  } catch (e) {
    return false;
  }
}

app.get('/proxy', (req, res) => {
  const target = req.query.target;
  if (!target || !isAllowedTarget(target)) return res.status(400).send('target not allowed');
  proxy.web(req, res, { target }, err => res.status(502).send('Bad Gateway'));
});

CVE References

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