diff --git a/package-lock.json b/package-lock.json index ea2b4803d..ff8c44f91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25617,11 +25617,12 @@ } }, "packages/@apphosting/adapter-nextjs": { - "version": "14.0.20", + "version": "14.0.21", "license": "Apache-2.0", "dependencies": { "@apphosting/common": "*", "fs-extra": "^11.1.1", + "semver": "^7.7.3", "yaml": "^2.3.4" }, "bin": { diff --git a/packages/@apphosting/adapter-nextjs/package.json b/packages/@apphosting/adapter-nextjs/package.json index 145e71837..034c02952 100644 --- a/packages/@apphosting/adapter-nextjs/package.json +++ b/packages/@apphosting/adapter-nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@apphosting/adapter-nextjs", - "version": "14.0.20", + "version": "14.0.21", "main": "dist/index.js", "description": "Experimental addon to the Firebase CLI to add web framework support", "repository": { @@ -45,7 +45,8 @@ "dependencies": { "@apphosting/common": "*", "fs-extra": "^11.1.1", - "yaml": "^2.3.4" + "yaml": "^2.3.4", + "semver": "^7.7.3" }, "peerDependencies": { "next": "*" diff --git a/packages/@apphosting/adapter-nextjs/src/bin/build.ts b/packages/@apphosting/adapter-nextjs/src/bin/build.ts index 1572adee7..3d46dbd03 100644 --- a/packages/@apphosting/adapter-nextjs/src/bin/build.ts +++ b/packages/@apphosting/adapter-nextjs/src/bin/build.ts @@ -6,6 +6,7 @@ import { validateOutputDirectory, getAdapterMetadata, exists, + checkNextJSVersion, } from "../utils.js"; import { join } from "path"; import { getBuildOptions, runBuild } from "@apphosting/common"; @@ -24,6 +25,7 @@ process.env.NEXT_PRIVATE_STANDALONE = "true"; // Opt-out sending telemetry to Vercel process.env.NEXT_TELEMETRY_DISABLED = "1"; +checkNextJSVersion(process.env.FRAMEWORK_VERSION); const nextConfig = await loadConfig(root, opts.projectDirectory); /** diff --git a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts index 5ae25a08d..bea48526f 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts @@ -6,6 +6,60 @@ import path from "path"; import os from "os"; import { RoutesManifest, MiddlewareManifest } from "../src/interfaces.js"; +describe("block vulnerable nextjs versions", () => { + it("check for vulnerable versions", async () => { + const { checkNextJSVersion } = await importUtils; + + assert.throws(() => { + checkNextJSVersion("15.0.0"); + }); + + assert.doesNotThrow(() => { + checkNextJSVersion(undefined); + }); + + assert.doesNotThrow(() => { + checkNextJSVersion("15.0.5"); + }); + + assert.throws(() => { + checkNextJSVersion("15.4.7"); + }); + + assert.doesNotThrow(() => { + checkNextJSVersion("15.4.8"); + }); + + assert.doesNotThrow(() => { + checkNextJSVersion("14.0.12"); + }); + + assert.throws(() => { + checkNextJSVersion("14.3.0-canary.77"); + }); + + assert.throws(() => { + checkNextJSVersion("14.3.0-canary.78"); + }); + + assert.doesNotThrow(() => { + checkNextJSVersion("14.3.0-canary.76"); + }); + + assert.throws(() => { + checkNextJSVersion("15.0.0-canary.2"); + }); + + assert.throws(() => { + checkNextJSVersion("16.0.6"); + }); + + assert.doesNotThrow(() => { + checkNextJSVersion("16.0.7"); + }); + }); +}); + describe("manifest utils", () => { let tmpDir: string; let distDir: string; diff --git a/packages/@apphosting/adapter-nextjs/src/utils.ts b/packages/@apphosting/adapter-nextjs/src/utils.ts index 3c6ab5486..5ea568d7e 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.ts @@ -1,4 +1,5 @@ import fsExtra from "fs-extra"; +import semVer from "semver"; import { createRequire } from "node:module"; import { join, dirname, relative, normalize } from "path"; import { fileURLToPath } from "url"; @@ -17,6 +18,21 @@ import { OutputBundleConfig, updateOrCreateGitignore } from "@apphosting/common" // fs-extra is CJS, readJson can't be imported using shorthand export const { copy, exists, writeFile, readJson, readdir, readFileSync, existsSync, ensureDir } = fsExtra; +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"; + +export function checkNextJSVersion(version: string | undefined) { + if (!version) { + return; + } + if (!satisfies(version, SAFE_NEXTJS_VERSIONS)) { + 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`, + ); + } +} // Loads the user's next.config.js file. export async function loadConfig(root: string, projectRoot: string): Promise {