diff --git a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts index bea48526f..03ebc469b 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts @@ -57,6 +57,21 @@ describe("block vulnerable nextjs versions", () => { assert.doesNotThrow(() => { checkNextJSVersion("16.0.7"); }); + + assert.doesNotThrow(() => { + checkNextJSVersion("16.3.0-preview"); + }); + + // A prerelease of an unpatched minor must NOT slip past the bounded ranges: + // 16.1.0-canary.2 sorts below 16.1.0 (the first patched release) and + // 15.1.0-canary.2 below the patched 15.1.9, so both stay blocked. + assert.throws(() => { + checkNextJSVersion("16.1.0-canary.2"); + }); + + assert.throws(() => { + checkNextJSVersion("15.1.0-canary.2"); + }); }); }); diff --git a/packages/@apphosting/adapter-nextjs/src/utils.ts b/packages/@apphosting/adapter-nextjs/src/utils.ts index 5ea568d7e..3819a0b8f 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.ts @@ -23,11 +23,25 @@ export const { satisfies } = semVer; const SAFE_NEXTJS_VERSIONS = ">=16.1.0 || ~16.0.7 || ~v15.5.7 || ~v15.4.8 || ~v15.3.6 || ~v15.2.6 || ~v15.1.9 || ~v15.0.5 || <14.3.0-canary.77"; +// The latest line is patched from 16.1.0 onward, so any prerelease of a version +// >= 16.1.0 (e.g. the `next@preview` tag, which resolves to "16.3.0-preview") is +// safe. We only enable includePrerelease for this open-ended range. Applying it +// to the whole SAFE_NEXTJS_VERSIONS range would loosen the bounded `~`/`<` +// comparators of older lines, potentially letting a prerelease of an unpatched +// minor slip through. +const SAFE_NEXTJS_PRERELEASE_RANGE = ">=16.1.0"; + export function checkNextJSVersion(version: string | undefined) { if (!version) { return; } - if (!satisfies(version, SAFE_NEXTJS_VERSIONS)) { + // Default (strict) matching covers stable versions and the canary boundary, + // then scoped includePrerelease admits only prereleases of the patched >=16.1.0 + // line. A version is vulnerable only if it satisfies neither. + if ( + !satisfies(version, SAFE_NEXTJS_VERSIONS) && + !satisfies(version, SAFE_NEXTJS_PRERELEASE_RANGE, { includePrerelease: true }) + ) { throw new Error( `CVE-2025-55182: Vulnerable Next version ${version} detected. Deployment blocked. Update your app's dependencies to a patched Next.js version and redeploy: https://nextjs.org/blog/CVE-2025-66478#fixed-versions`, );