NextAuth 安全升级撞上 sidebase 旧入口
一次依赖安全升级,真正麻烦的地方经常在版本号之外:改完以后,构建链路里可能冒出一个看起来和安全漏洞无关的警告。
这次就是这样。IDE 根据 Mend 对 CVE-2023-48309 的记录提示 next-auth 版本过旧,需要升到修复版本。项目本身是 Nuxt,用 @sidebase/nuxt-auth 接 next-auth。把 next-auth 从 4.21.1 升到 4.24.14 之后,安全告警消失了,但 dev server 里出现了新的解析警告:
Could not resolve import "next-auth/core"这个警告很容易让人误判成「next-auth 升坏了」或者「sidebase 不能升级」。实际原因更窄:@sidebase/nuxt-auth 内部还在 import 一个 next-auth 旧版本曾经公开导出的子路径,但 next-auth 4.24.x 已经不再把这个子路径写进 package exports。
先把依赖关系看清楚
项目里直接依赖的是两层:
{
"dependencies": {
"@sidebase/nuxt-auth": "^1.3.0",
"next-auth": "4.24.14"
}
}这里有两个容易混在一起的问题。
第一个问题是 peer dependency。@sidebase/nuxt-auth@1.3.0 的 peer 仍然声明 next-auth~4.21.1。也就是说,即使安装层面可以用 next-auth@4.24.14,pnpm 仍会提醒「这个 peer 不在声明范围内」。这个提醒不是运行时错误,但需要显式记录我们为什么这样做。
第二个问题是 package exports。@sidebase/nuxt-auth@1.3.0 的构建产物里还有这一行:
import { AuthHandler } from "next-auth/core"而 next-auth@4.24.14 的 package.json exports 里没有 ./core。
这两个问题不是一回事。peer warning 可以通过 pnpm 配置解释并压住;next-auth/core 的 import warning 需要让 Vite / Nitro 找到兼容入口。
对比几个版本的源码
我把 next-auth@4.21.1、4.24.5、4.24.14 的 tarball 拉下来,对比了 package.json 的 exports 和 core/index.d.ts。
4.21.1 里,./core 还是公开入口:
{
".": "./index.js",
"./jwt": "./jwt/index.js",
"./react": "./react/index.js",
"./core": "./core/index.js",
"./next": "./next/index.js"
}到了 4.24.5 和 4.24.14,./core 不在 exports 里了:
{
".": {
"types": "./index.d.ts",
"default": "./index.js"
},
"./adapters": {
"types": "./adapters.d.ts"
},
"./jwt": {
"types": "./jwt/index.d.ts",
"default": "./jwt/index.js"
}
}但注意,这不等于包里已经删掉了 core/index.js。4.24.14 的包内文件仍然存在,core/index.d.ts 里也仍然有 AuthHandler:
export interface NextAuthHandlerParams {
req: Request | RequestInternal
options: AuthOptions
}
export declare function AuthHandler<Body extends string | Record<string, any> | any[]>(
params: NextAuthHandlerParams
): Promise<ResponseInternal<Body>>这说明 @sidebase/nuxt-auth 当前消费的符号还在,只是入口不再作为公开 subpath 暴露。对我们来说,最小修复是在构建工具层做一个窄 alias,既避开改 sidebase 源码,也避免降回有漏洞版本。
用窄 alias 恢复旧入口
修复代码放在 Nuxt 配置里:
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
const require = createRequire(import.meta.url)
const NEXT_AUTH_CORE_ENTRY = join(dirname(require.resolve('next-auth')), 'core/index.js')
export default defineNuxtConfig({
nitro: {
alias: {
'next-auth/core': NEXT_AUTH_CORE_ENTRY,
},
},
vite: {
resolve: {
alias: {
'next-auth/core': NEXT_AUTH_CORE_ENTRY,
},
},
},
})这里刻意没有把 alias 写成一个宽泛规则。只处理 next-auth/core 这一个旧入口,其他 next-auth 公开入口继续按包自己的 exports 走。
这条兼容层的边界也要写在代码注释里:它是给 @sidebase/nuxt-auth@1.3.0 的过渡 shim。等上游不再 import next-auth/core,这个 alias 就应该删除。
pnpm v11 的配置位置也要注意
一开始我把 peer 兼容规则写进了 package.json 的 pnpm 字段。pnpm 直接给了 warning:
The "pnpm" field in package.json is no longer read by pnpm.
The following keys were ignored: "pnpm.overrides", "pnpm.peerDependencyRules".pnpm 文档里已经说明,从 v11 开始不再读取 package.json 的 pnpm 字段,设置要放到 pnpm-workspace.yaml。settings 文档里的 overrides 也使用 workspace 级配置。
最终配置是:
overrides:
next-auth: 4.24.14
'@sidebase/nuxt-auth>next-auth': 4.24.14
peerDependencyRules:
allowedVersions:
'@sidebase/nuxt-auth>next-auth': 4.24.14这里 overrides 负责安装结果,peerDependencyRules.allowedVersions 负责告诉 pnpm:这个 peer 范围偏离是我们有意接受的。修完后再跑:
pnpm peers check输出变成:
No peer dependency issues found怎么确认没有影响主登录链路
这类依赖升级不能只看 pnpm install 成功。至少要看四件事。
第一,确认安装结果只有一个目标版本:
pnpm list next-auth @sidebase/nuxt-auth --depth 1结果里 @sidebase/nuxt-auth@1.3.0 的 peer 和项目直接依赖都解析到 next-auth@4.24.14。
第二,确认项目自身没有直接依赖旧私有入口。代码里业务侧继续 import next-auth/providers/*、next-auth/jwt、session 相关公开入口;next-auth/core 只出现在 sidebase 的构建产物里。
第三,确认 dev server 不再报解析 warning:
pnpm dev --host 127.0.0.1 --port 3017启动后没有再看到 Could not resolve import "next-auth/core"。如果仍有 AUTH_NO_ORIGIN、缺少 OAuth provider 环境变量、或者项目自己的资源生成提示,需要和这次修复分开看。
第四,跑类型和针对性测试:
pnpm exec nuxi prepare
pnpm typecheck
pnpm exec eslint nuxt.config.ts
pnpm exec vitest run \
tests/unit/server/utils/apple-provider.test.ts \
tests/unit/server/utils/line-provider.test.ts \
tests/unit/server/utils/tiktok-provider.test.ts \
tests/unit/composables/use-session-fetch.test.ts \
tests/unit/composables/auth-invalidation.test.ts \
tests/unit/utils/request-unauthorized.test.ts这些命令能覆盖 Nuxt 生成类型、配置语法、provider 工厂、session fetch、登录失效处理等和认证链路更接近的部分。
全量测试如果失败,也要先分清是不是当前升级引入的。比如这次全量 unit 里失败的是一个架构边界测试,它在检查一批已经 HTTP 化的 gRPC/Nitro 旧路由是否还残留,和 next-auth 升级没有直接关系。这个问题应该单独处理,不能混进安全升级补丁里。
audit 结果也要分层看
升级后再跑:
pnpm audit --prod --jsonmodule_name === "next-auth" 的 advisory 已经为空,说明这次目标 CVE 的直接包版本问题解决了。
但 audit 里仍可能出现 next、postcss、uuid 等 transitive advisory。原因是 next-auth 自己带了 next peer / 相关依赖链,而 Nuxt 项目运行时并不是用 Next.js 做页面服务。它们不能简单忽略,但也不应该被误读成「next-auth 仍然没升级成功」。安全修复要分层处理:直接漏洞先闭环,传递依赖再按实际运行面评估。
这次留下的规则
这次之后,我会把这类升级按下面的顺序处理:
- 先查安全公告要求的最低修复版本,不急着只改到最新。
- 看 wrapper 包的 latest 版本、peer 范围和源码 import,分清 peer warning 和运行时解析 warning。
- 对 package exports 相关问题,优先做窄 alias,不要为了一个旧入口放宽整个包的解析。
- pnpm v11 项目里,
overrides和peerDependencyRules放pnpm-workspace.yaml,不要再写进package.json的pnpm字段。 - 修复说明里写清楚删除条件:当上游 wrapper 不再依赖旧入口时,compat alias 应该移除。
依赖升级看起来只是版本号,但真正危险的是边界不清:安全问题、peer 声明、package exports、构建工具解析、运行时登录链路,任何一层混在一起都会让修复变成一坨。把每层证据拆开,最后的改动反而可以很小。