Skip to content

fix(adapter-nextjs): allow Next.js preview/prerelease version tags#660

Open
soleo wants to merge 2 commits into
firebase:mainfrom
soleo:fix/nextjs-preview-version-check
Open

fix(adapter-nextjs): allow Next.js preview/prerelease version tags#660
soleo wants to merge 2 commits into
firebase:mainfrom
soleo:fix/nextjs-preview-version-check

Conversation

@soleo

@soleo soleo commented Jun 30, 2026

Copy link
Copy Markdown

Closes #661

Problem

Next.js now ships builds under a preview tag (e.g. next@preview), which resolves to prerelease versions like 16.3.0-preview. The version guard in checkNextJSVersion uses semver's satisfies(version, SAFE_NEXTJS_VERSIONS), but by default semver excludes prerelease versions from a range unless a matching prerelease comparator already exists for the same [major, minor, patch] tuple.

Since SAFE_NEXTJS_VERSIONS only contains one prerelease comparator (<14.3.0-canary.77), a safe preview build such as 16.3.0-preview does not satisfy >=16.1.0 and is wrongly flagged as vulnerable, blocking deployment with the CVE-2025-55182 error.

Fix

Pass { includePrerelease: true } to satisfies so preview/canary builds are matched against the safe-version range.

I verified against semver@7.7.3 (the pinned version) that this is purely additive for the safe set and changes nothing for the existing canary boundary checks:

version before after safe?
16.3.0-preview ❌ blocked ✅ allowed safe
14.3.0-canary.76 ✅ allowed ✅ allowed safe
14.3.0-canary.77 🚫 blocked 🚫 blocked vulnerable
14.3.0-canary.78 🚫 blocked 🚫 blocked vulnerable
15.0.0-canary.2 🚫 blocked 🚫 blocked vulnerable
16.0.6 🚫 blocked 🚫 blocked vulnerable
16.0.7 / 15.4.8 / 15.0.5 / 14.0.12 safe

All vulnerable versions remain blocked.

Testing

Added a 16.3.0-preview case to the existing block vulnerable nextjs versions unit test. Built @apphosting/common + @apphosting/adapter-nextjs and ran the suite:

block vulnerable nextjs versions
  ✔ check for vulnerable versions
...
6 passing

🤖 Generated with Claude Code

Next.js now ships builds under a preview tag (e.g. next@preview),
which resolves to prerelease versions like "16.3.0-preview". semver's
satisfies() excludes prerelease versions from a range unless a matching
prerelease comparator already exists for the same [major, minor, patch]
tuple, so these safe preview builds were wrongly flagged as vulnerable
and blocked by checkNextJSVersion.

Pass { includePrerelease: true } so preview/canary builds are matched
against SAFE_NEXTJS_VERSIONS. All existing canary boundary checks
(e.g. <14.3.0-canary.77) still behave identically.
@google-cla

google-cla Bot commented Jun 30, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Next.js version check to allow prerelease versions (such as 16.3.0-preview) by passing { includePrerelease: true } to the semver satisfies check, and adds a corresponding test case. However, the reviewer points out that applying this option globally across all safe ranges introduces a security vulnerability, as it would incorrectly allow vulnerable prerelease versions (e.g., 16.1.0-canary.2) to satisfy bounded ranges. The reviewer suggests splitting the ranges to only apply includePrerelease to the open-ended safe range, and adding test cases to verify that vulnerable prerelease versions are correctly blocked.

Comment thread packages/@apphosting/adapter-nextjs/src/utils.ts Outdated
Comment thread packages/@apphosting/adapter-nextjs/src/utils.spec.ts
Per review: applying { includePrerelease: true } to the whole
SAFE_NEXTJS_VERSIONS range relies on semver's prerelease-aware tilde
bounds and loosens the bounded `~`/`<` comparators of older lines.
Instead, run the default strict check (stable versions + canary
boundary) and only enable includePrerelease for the open-ended
>=16.1.0 range, where every prerelease is guaranteed to be on the
patched line.

Adds regression tests asserting prereleases of unpatched minors
(16.1.0-canary.2, 15.1.0-canary.2) stay blocked.
@soleo

soleo commented Jun 30, 2026

Copy link
Copy Markdown
Author

Good catch — applying includePrerelease to the entire range is the wrong design, and I've changed it to scope the flag to the open-ended >=16.1.0 range only (pushed in bc96b48):

const SAFE_NEXTJS_PRERELEASE_RANGE = ">=16.1.0";

if (
  !satisfies(version, SAFE_NEXTJS_VERSIONS) &&                                  // strict: stable + canary boundary
  !satisfies(version, SAFE_NEXTJS_PRERELEASE_RANGE, { includePrerelease: true }) // prereleases of the patched line only
) {
  throw ...
}

This keeps strict prerelease matching for the bounded ~/< comparators, so a prerelease of an unpatched minor can't slip through. I also added regression tests asserting 16.1.0-canary.2 and 15.1.0-canary.2 stay blocked.

One nuance I verified against semver@7.7.3 while testing: those two exact examples were actually already blocked even under the global flag, because semver expands ~16.0.7 to >=16.0.7 <16.1.0-0 (a prerelease-aware upper bound with the -0 suffix), so 16.1.0-canary.2 does not satisfy ~16.0.7 even with includePrerelease: true:

semver.satisfies("16.1.0-canary.2", "~16.0.7", { includePrerelease: true })          // false
semver.satisfies("16.1.0-canary.2", ">=16.0.7 <16.1.0", { includePrerelease: true }) // true

That said, your underlying point stands: the global flag relied on that subtle tilde behavior and did loosen the bounded windows in general (e.g. it would admit 16.0.8-canary.1). The scoped version is strictly more conservative and doesn't depend on the -0 edge semantics, so it's the better fix. All existing checks plus the new adversarial cases pass.

@soleo soleo force-pushed the fix/nextjs-preview-version-check branch from bc96b48 to a85a9e9 Compare June 30, 2026 18:45
@soleo

soleo commented Jul 2, 2026

Copy link
Copy Markdown
Author

@jamesdaniels would you be able to review this PR? just wondering if we can get it merged since it is a very straightforward fix for a version check issue here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

checkNextJSVersion blocks Next.js preview/prerelease tags (e.g. next@preview)

1 participant