On 15 October 2026 GitHub published advisory GHSA-2vc4-c4r6-p6v6 for @opennextjs/cloudflare, rated High and aliased to CVE-2026-100409. It is a good advisory to learn on: short, specific, and honest about the conditions that make you vulnerable. This post reads it field by field.
The advisory says that when cache interception is enabled, the package can interpret percent encoded paths differently during middleware matching and cache lookup. An unauthenticated attacker may then retrieve cached content from routes meant to be protected by middleware.
What's affected
From the OSV record of the advisory:
- Package:
@opennextjs/cloudflareon npm - Introduced: 0.3.0
- Fixed: 1.20.2
- Severity: High, CVSS 3.1 vector
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N - Weaknesses: CWE-177 (improper handling of URL encoding) and CWE-180 (validate before canonicalize)
The vector tells you the shape of the risk: network reachable, low complexity, no privileges, no user interaction, high confidentiality impact, no integrity or availability impact. It leaks data and changes nothing.
The preconditions matter more than the score. You are affected only if you explicitly enable enableCacheInterception (disabled by default), rely on middleware for access control, and have a matching cached route.
Reading the ID and the review status
A GHSA ID has the form GHSA-xxxx-xxxx-xxxx. Each x is a lowercase character from the set 23456789cfghjmpqrvwx. GitHub's own validation pattern is:
GHSA(-[23456789cfghjmpqrvwx]{4}){3}
Advisories come in two kinds. GitHub-reviewed advisories are checked by hand, mapped to packages in a supported ecosystem, and are the ones that trigger Dependabot alerts. Unreviewed advisories are imported automatically from the National Vulnerability Database feed, with no check for validity or completeness, and do not trigger alerts. This advisory carries github_reviewed: true in its record. Not every advisory has a CVE either, since malware advisories, for example, come from other sources.
How to check if you're exposed
Pull the raw record in the OSV format that GitHub publishes advisories in:
curl -s https://api.osv.dev/v1/vulns/GHSA-2vc4-c4r6-p6v6 \
| jq '{id, aliases, published, severity, cwe: .database_specific.cwe_ids, affected: .affected[0].ranges}'
Then check your project:
npm ls @opennextjs/cloudflare
grep -rn "enableCacheInterception" . --include=*.ts --include=*.js --exclude-dir=node_modules
The advisory's own example shows the attack. With a middleware matcher of /admin/:path* and a cached /admin route, a request to GET /%61dmin does not match the matcher, so authorization never runs. The cache then decodes %61dmin to admin and may return the protected page.
Reading the references
The References section of an advisory is a map of the fix. In this one it links the advisory page, the NVD record, two upstream pull requests in the opennextjs-aws repository (numbers 1199 and 1200) and a commit in the Cloudflare adapter repository. Read the pull requests, not just the summary. They show exactly which behavior changed: the patch aligns encoded path handling across middleware matching and cache lookup, and rejects malformed paths instead of partially decoding them.
The advisory also gives a second example worth testing. A request to /%61dmin/%ZZ contains a malformed segment. The previous code decoded each segment on its own, turned %61dmin into admin, kept %ZZ as it was, and carried on with the cache lookup. A partially decoded path is a good fuzzing input for your own routing code.
What to do now
- Run
npm ls @opennextjs/cloudflareand compare against 1.20.2. - Search for
enableCacheInterception: true. If it is absent, the vulnerable path is off. - Upgrade to 1.20.2 or later.
- If you cannot upgrade, remove
enableCacheInterception: true, or enforce authorization inside the route handlers rather than only in middleware, as the advisory suggests. - Re-run your auth tests with encoded variants of protected paths.
How this could have been avoided
- Authorize at the resource. Middleware only checks paths it matches. A check inside the handler runs whatever form of the URL arrived.
- Leave optional features off until needed. The default of disabled meant most deployments were never exposed.
- Normalize a path once. Two components decoding the same path in different ways is the root cause, and a single decode step before both removes it.
- Test encoded paths. A test that requests
/%61dminand expects a 401 would likely have caught this in a pull request.
Why it matters
Reading an advisory means ignoring the headline and going to the conditions. The same High rating means very different things to a team that enabled the feature and a team that did not.
This is also a reachability question. Whether your configuration reaches the vulnerable code decides whether the advisory is an emergency or a ticket.
Try it yourself
Pick one advisory your scanner flagged this week and fill in this list from its OSV record: package, introduced, fixed, preconditions, workaround. If you cannot fill in preconditions, you have not finished reading it.
Click through it at your own pace, or open it full screen.
Quick check
Which part of this advisory tells you whether you are actually vulnerable?
The preconditions in the Impact section: the feature must be enabled, you must rely on middleware for access control, and a matching cached route must exist.
Why did the request to /%61dmin bypass the middleware?
Middleware matched the encoded pathname, which did not match the admin matcher, while the cache layer decoded it to admin and served the cached response.