From f7998c9b0882578e839fe28c63d4eebc6b0d813b Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sat, 25 Oct 2025 18:11:42 -0700 Subject: [PATCH] fix: security issue from `nodemailer` --- packages/core/package.json | 2 +- .../core/src/lib/actions/signin/send-token.ts | 23 ++++++++++++++++++- packages/frameworks-sveltekit/package.json | 2 +- packages/next-auth/package.json | 4 ++-- pnpm-lock.yaml | 18 ++++++++++----- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index a88a28bca3..b3a24791c3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -76,7 +76,7 @@ "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", - "nodemailer": "^6.8.0" + "nodemailer": "^7.0.7" }, "peerDependenciesMeta": { "@simplewebauthn/browser": { diff --git a/packages/core/src/lib/actions/signin/send-token.ts b/packages/core/src/lib/actions/signin/send-token.ts index 27024affaf..66dfefd841 100644 --- a/packages/core/src/lib/actions/signin/send-token.ts +++ b/packages/core/src/lib/actions/signin/send-token.ts @@ -93,11 +93,32 @@ export async function sendToken( function defaultNormalizer(email?: string) { if (!email) throw new Error("Missing email from request body.") + + const trimmedEmail = email.toLowerCase().trim() + + // Reject email addresses with quotes to prevent address parser confusion + // This prevents attacks like "attacker@evil.com"@victim.com + if (trimmedEmail.includes('"')) { + throw new Error("Invalid email address format.") + } + // Get the first two elements only, // separated by `@` from user input. - let [local, domain] = email.toLowerCase().trim().split("@") + let [local, domain] = trimmedEmail.split("@") + + // Validate that we have exactly 2 parts (local and domain) + if (!local || !domain || trimmedEmail.split("@").length !== 2) { + throw new Error("Invalid email address format.") + } + // The part before "@" can contain a "," // but we remove it on the domain part domain = domain.split(",")[0] + + // Additional validation: domain should not be empty after comma split + if (!domain) { + throw new Error("Invalid email address format.") + } + return `${local}@${domain}` } diff --git a/packages/frameworks-sveltekit/package.json b/packages/frameworks-sveltekit/package.json index 7b54640c3b..08d06e1e47 100644 --- a/packages/frameworks-sveltekit/package.json +++ b/packages/frameworks-sveltekit/package.json @@ -57,7 +57,7 @@ "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.3", "@sveltejs/kit": "^1.0.0 || ^2.0.0", - "nodemailer": "^6.6.5", + "nodemailer": "^7.0.7", "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0-0" }, "peerDependenciesMeta": { diff --git a/packages/next-auth/package.json b/packages/next-auth/package.json index b1b492d960..481bb15eaa 100644 --- a/packages/next-auth/package.json +++ b/packages/next-auth/package.json @@ -92,7 +92,7 @@ "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0-0", - "nodemailer": "^6.6.5", + "nodemailer": "^7.0.7", "react": "^18.2.0 || ^19.0.0-0" }, "peerDependenciesMeta": { @@ -110,7 +110,7 @@ "@types/react": "18.0.37", "dotenv": "^10.0.0", "next": "15.3.1", - "nodemailer": "^6.9.3", + "nodemailer": "^7.0.7", "react": "^18.2.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9fd85efee..5dc936ae5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -686,8 +686,8 @@ importers: specifier: ^6.0.6 version: 6.0.6 nodemailer: - specifier: ^6.8.0 - version: 6.9.8 + specifier: ^7.0.7 + version: 7.0.10 oauth4webapi: specifier: ^3.3.0 version: 3.3.0 @@ -810,8 +810,8 @@ importers: specifier: ^9.0.3 version: 9.0.3(encoding@0.1.13) nodemailer: - specifier: ^6.6.5 - version: 6.9.8 + specifier: ^7.0.7 + version: 7.0.10 set-cookie-parser: specifier: ^2.7.0 version: 2.7.0 @@ -875,8 +875,8 @@ importers: specifier: 15.3.1 version: 15.3.1(@opentelemetry/api@1.7.0)(@playwright/test@1.41.2)(react-dom@19.0.0-rc-935180c7e0-20240524(react@18.3.1))(react@18.3.1)(sass@1.70.0) nodemailer: - specifier: ^6.9.3 - version: 6.9.8 + specifier: ^7.0.7 + version: 7.0.10 react: specifier: ^18.2.0 version: 18.3.1 @@ -10707,6 +10707,10 @@ packages: resolution: {integrity: sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==} engines: {node: '>=6.0.0'} + nodemailer@7.0.10: + resolution: {integrity: sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==} + engines: {node: '>=6.0.0'} + nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -27101,6 +27105,8 @@ snapshots: nodemailer@6.9.8: {} + nodemailer@7.0.10: {} + nopt@5.0.0: dependencies: abbrev: 1.1.1