diff --git a/package-lock.json b/package-lock.json index 97de26a30..12f10b561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25634,7 +25634,7 @@ "@types/tmp": "*", "mocha": "*", "next": "~14.0.0", - "semver": "*", + "semver": "^7.7.3", "tmp": "*", "ts-mocha": "*", "ts-node": "*", @@ -25650,6 +25650,19 @@ } } }, + "packages/@apphosting/adapter-nextjs/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "packages/@apphosting/build": { "version": "0.1.7", "license": "Apache-2.0", diff --git a/packages/@apphosting/adapter-nextjs/package.json b/packages/@apphosting/adapter-nextjs/package.json index 3490ab470..e7e995bd6 100644 --- a/packages/@apphosting/adapter-nextjs/package.json +++ b/packages/@apphosting/adapter-nextjs/package.json @@ -61,7 +61,7 @@ "@types/tmp": "*", "mocha": "*", "next": "~14.0.0", - "semver": "*", + "semver": "^7.7.3", "tmp": "*", "ts-mocha": "*", "ts-node": "*", 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..233e6aab1 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts @@ -6,6 +6,52 @@ 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.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..607ea581e 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 { diff --git a/starters/nextjs/basic/package-lock.json b/starters/nextjs/basic/package-lock.json index 94ee91cf8..86a7b8d0e 100644 --- a/starters/nextjs/basic/package-lock.json +++ b/starters/nextjs/basic/package-lock.json @@ -8,7 +8,7 @@ "name": "firebase-app-hosting-nextjs", "version": "1.0.0", "dependencies": { - "next": "15.0.0", + "next": "15.0.5", "react": "^18", "react-dom": "^18" }, @@ -530,9 +530,10 @@ } }, "node_modules/@next/env": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.0.tgz", - "integrity": "sha512-Mcv8ZVmEgTO3bePiH/eJ7zHqQEs2gCqZ0UId2RxHmDDc7Pw6ngfSrOFlxG8XDpaex+n2G+TKPsQAf28MO+88Gw==" + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.5.tgz", + "integrity": "sha512-rDeqk/QF6OxTSvQItPdtyR0O4QN5L2a794F4+i8/syHN92DqFXcLNhZgLtYhW3rrJ23vRR7B5wIamsgGM4I6UQ==", + "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "15.0.0", @@ -572,12 +573,13 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.0.tgz", - "integrity": "sha512-Gjgs3N7cFa40a9QT9AEHnuGKq69/bvIOn0SLGDV+ordq07QOP4k1GDOVedMHEjVeqy1HBLkL8rXnNTuMZIv79A==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.5.tgz", + "integrity": "sha512-BrNm/9BZoV6QEFKFZdgZRyYwhdhxV8GhW+U4D5cdkT4Wefj7YflAUZNx2FWyBPp7utBPCgJXnVbVLhlDoIfKFg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -587,12 +589,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.0.tgz", - "integrity": "sha512-BUtTvY5u9s5berAuOEydAUlVMjnl6ZjXS+xVrMt317mglYZ2XXjY8YRDCaz9vYMjBNPXH8Gh75Cew5CMdVbWTw==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.5.tgz", + "integrity": "sha512-SkpRdqyJLhmU6Ip0dHrZ5mLMQgTU0MlTASRwqCj6NXQJ04eS4QzBgEUUOPX+tsUOQ+KSVMgX/iQaWgQHNMyyCQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -602,12 +605,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.0.tgz", - "integrity": "sha512-sbCoEpuWUBpYoLSgYrk0CkBv8RFv4ZlPxbwqRHr/BWDBJppTBtF53EvsntlfzQJ9fosYX12xnS6ltxYYwsMBjg==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.5.tgz", + "integrity": "sha512-nk+6BAIkIHTeQg+U1uqGpZ8K1KSAbhq80EkSgpgPC6wBmRkEeBitn4yL9C0fUiEPeZ3zN4yrvI635GG/H2QmSQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -617,12 +621,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.0.tgz", - "integrity": "sha512-JAw84qfL81aQCirXKP4VkgmhiDpXJupGjt8ITUkHrOVlBd+3h5kjfPva5M0tH2F9KKSgJQHEo3F5S5tDH9h2ww==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.5.tgz", + "integrity": "sha512-CozywhydLroNNz1AMKdKKVBuRc0UIBG7TlVgXXn51MdZo4sMbfApOlQFUyuAbKJbe67vd39Yib2lVVVDfLTtfw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -632,12 +637,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.0.tgz", - "integrity": "sha512-r5Smd03PfxrGKMewdRf2RVNA1CU5l2rRlvZLQYZSv7FUsXD5bKEcOZ/6/98aqRwL7diXOwD8TCWJk1NbhATQHg==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.5.tgz", + "integrity": "sha512-VWfvl8toyC/5Rn1GgKfiASYgssCsxz4GtwK2cFKmmnyGfoKubFc6DfCI5MzBoe2Q2gzd2CeZDoT1BhuutSiL7A==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -647,12 +653,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.0.tgz", - "integrity": "sha512-fM6qocafz4Xjhh79CuoQNeGPhDHGBBUbdVtgNFJOUM8Ih5ZpaDZlTvqvqsh5IoO06CGomxurEGqGz/4eR/FaMQ==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.5.tgz", + "integrity": "sha512-xCD/V4Z55eFtG2SNyXgG3ciIikcxNe4FgmgcW4xTaEcLY59ZJVLxx4PLve2vDgp7xqvwDD4vvUsJuFMuQ12oGg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -662,12 +669,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.0.tgz", - "integrity": "sha512-ZOd7c/Lz1lv7qP/KzR513XEa7QzW5/P0AH3A5eR1+Z/KmDOvMucht0AozccPc0TqhdV1xaXmC0Fdx0hoNzk6ng==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.5.tgz", + "integrity": "sha512-OmKXP/mUzY+AiDFk9PR3RoM6YfgzNYhtSbfvTUDk3PxoCLKnwTZ8xsFoWX2ph/RFC25QucTeAFepouGGsdBPAg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -677,12 +685,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.0.tgz", - "integrity": "sha512-2RVWcLtsqg4LtaoJ3j7RoKpnWHgcrz5XvuUGE7vBYU2i6M2XeD9Y8RlLaF770LEIScrrl8MdWsp6odtC6sZccg==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.5.tgz", + "integrity": "sha512-O34P9asvZtdNQ+4sEczSLruYvM7XEQKY/FCwRAeQQnrWW3tol3VEuv2GtnFb1YHsP3lZtagd11UYJqrs0Y0r2A==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -3176,11 +3185,12 @@ "dev": true }, "node_modules/next": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/next/-/next-15.0.0.tgz", - "integrity": "sha512-/ivqF6gCShXpKwY9hfrIQYh8YMge8L3W+w1oRLv/POmK4MOQnh+FscZ8a0fRFTSQWE+2z9ctNYvELD9vP2FV+A==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/next/-/next-15.0.5.tgz", + "integrity": "sha512-WTh/Rmxkn4J4vwSYiqEZGzoxjid83iCyN0qg7oJFKzHjYCzy5mwBRqWVlFotM9nAnxGGv5MzbMa4gMu88qeGLA==", + "license": "MIT", "dependencies": { - "@next/env": "15.0.0", + "@next/env": "15.0.5", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.13", "busboy": "1.6.0", @@ -3192,25 +3202,25 @@ "next": "dist/bin/next" }, "engines": { - "node": ">=18.18.0" + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.0.0", - "@next/swc-darwin-x64": "15.0.0", - "@next/swc-linux-arm64-gnu": "15.0.0", - "@next/swc-linux-arm64-musl": "15.0.0", - "@next/swc-linux-x64-gnu": "15.0.0", - "@next/swc-linux-x64-musl": "15.0.0", - "@next/swc-win32-arm64-msvc": "15.0.0", - "@next/swc-win32-x64-msvc": "15.0.0", + "@next/swc-darwin-arm64": "15.0.5", + "@next/swc-darwin-x64": "15.0.5", + "@next/swc-linux-arm64-gnu": "15.0.5", + "@next/swc-linux-arm64-musl": "15.0.5", + "@next/swc-linux-x64-gnu": "15.0.5", + "@next/swc-linux-x64-musl": "15.0.5", + "@next/swc-win32-arm64-msvc": "15.0.5", + "@next/swc-win32-x64-msvc": "15.0.5", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-65a56d0e-20241020", - "react-dom": "^18.2.0 || 19.0.0-rc-65a56d0e-20241020", + "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { diff --git a/starters/nextjs/basic/package.json b/starters/nextjs/basic/package.json index ccfea656c..d06f8ac62 100644 --- a/starters/nextjs/basic/package.json +++ b/starters/nextjs/basic/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "next": "15.0.0", + "next": "15.0.5", "react": "^18", "react-dom": "^18" },