SSRF

SSRF Mitigation in Node.js Express Clerk [CVE-2026-34076]

[Updated month year] Updated CVE-2026-34076

Overview

The CVE-2026-34076 vulnerability describes an SSRF weakness in Clerk’s server-side proxy logic (clerkFrontendApiProxy) where a client-controlled request path could direct the proxy to fetch resources from arbitrary destinations. In vulnerable revisions, the proxy could inadvertently forward the application's Clerk-Secret-Key to an attacker-controlled server, enabling exfiltration of sensitive credentials. This class of flaw maps to CWE-918 (Server-Side Request Forgery). The issue affected multiple Clerk runtimes and was addressed with patches in specific releases: @clerk/hono 0.1.5, @clerk/express 2.0.7, @clerk/backend 3.2.3, and @clerk/fastify 3.1.5. In Node.js with Express, SSRF can materialize when a proxy implementation trusts untrusted input to construct the target URL and forwards internal secrets, effectively leaking credentials to an attacker. The real-world impact includes credential leakage, potential account takeover, domain-wide access, and broader network access depending on the proxy target and downstream services.

Affected Versions

@clerk/hono: 0.1.0 to before 0.1.5; @clerk/express: 2.0.0 to before 2.0.7; @clerk/backend: 3.0.0 to before 3.2.3; @clerk/fastify: 3.1.0 to before 3.1.5

Code Fix Example

Node.js (Express) API Security Remediation
/* Vulnerable pattern (do not copy into prod) */
const express = require('express');
const app = express();

// Vulnerable: forwards arbitrary client-provided URL and includes Clerk secret key in headers
app.get('/vuln-proxy', async (req, res) => {
  const target = req.query.url;
  if (!target) return res.status(400).send('Missing url');
  try {
    const r = await fetch(target, {
      headers: {
        ...req.headers,
        'Clerk-Secret-Key': process.env.CLERK_SECRET_KEY
      }
    });
    const text = await r.text();
    res.status(r.status).send(text);
  } catch (e) {
    res.status(500).send('Proxy error');
  }
});

/* Fixed pattern */
app.get('/fix-proxy', async (req, res) => {
  const target = req.query.url;
  if (!target) return res.status(400).send('Missing url');
  // Validate destination against a whitelist and ensure URL is well-formed
  try {
    const u = new URL(target);
    const allowedOrigins = [
      'https://clerk.example.com',
      'https://api.clerk.dev'
    ];
    if (!allowedOrigins.includes(u.origin)) {
      return res.status(400).send('Destination not allowed');
    }
  } catch {
    return res.status(400).send('Invalid URL');
  }
  // Do not forward sensitive headers
  const forwardHeaders = { ...req.headers };
  delete forwardHeaders['clerk-secret-key'];
  delete forwardHeaders['authorization'];

  try {
    const r = await fetch(target, { headers: forwardHeaders });
    const text = await r.text();
    res.status(r.status).send(text);
  } catch (e) {
    res.status(500).send('Proxy error');
  }
});

app.listen(process.env.PORT || 3000);

CVE References

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