Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(smtp): add SSRF validation and genericize network error messages
Prevent SSRF via user-controlled smtpHost by validating with
validateDatabaseHost before creating the nodemailer transporter.
Collapse distinct network error messages (ECONNREFUSED, ECONNRESET,
ETIMEDOUT) into a single generic message to prevent port-state leakage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • Loading branch information
waleedlatif1 and claude committed Mar 27, 2026
commit 89b46991f5c9dc53d572b4a2126ed0716f387e6d
35 changes: 16 additions & 19 deletions apps/sim/app/api/tools/smtp/send/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import nodemailer from 'nodemailer'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { validateDatabaseHost } from '@/lib/core/security/input-validation.server'
import { generateRequestId } from '@/lib/core/utils/request'
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
Expand Down Expand Up @@ -56,6 +57,15 @@ export async function POST(request: NextRequest) {
const body = await request.json()
const validatedData = SmtpSendSchema.parse(body)

const hostValidation = await validateDatabaseHost(validatedData.smtpHost, 'smtpHost')
if (!hostValidation.isValid) {
logger.warn(`[${requestId}] SMTP host validation failed`, {
host: validatedData.smtpHost,
error: hostValidation.error,
})
return NextResponse.json({ success: false, error: hostValidation.error }, { status: 400 })
}
Comment thread
waleedlatif1 marked this conversation as resolved.

logger.info(`[${requestId}] Sending email via SMTP`, {
host: validatedData.smtpHost,
port: validatedData.smtpPort,
Expand Down Expand Up @@ -189,32 +199,19 @@ export async function POST(request: NextRequest) {
if (isNodeError(error)) {
if (error.code === 'EAUTH') {
errorMessage = 'SMTP authentication failed - check username and password'
} else if (error.code === 'ECONNECTION' || error.code === 'ECONNREFUSED') {
} else if (
error.code === 'ECONNECTION' ||
error.code === 'ECONNREFUSED' ||
error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT'
) {
errorMessage = 'Could not connect to SMTP server - check host and port'
} else if (error.code === 'ECONNRESET') {
errorMessage = 'Connection was reset by SMTP server'
} else if (error.code === 'ETIMEDOUT') {
errorMessage = 'SMTP server connection timeout'
}
}

// Check for SMTP response codes
const hasResponseCode = (err: unknown): err is { responseCode: number } => {
return typeof err === 'object' && err !== null && 'responseCode' in err
}

if (hasResponseCode(error)) {
if (error.responseCode >= 500) {
errorMessage = 'SMTP server error - please try again later'
} else if (error.responseCode >= 400) {
errorMessage = 'Email rejected by SMTP server - check recipient addresses'
Comment thread
waleedlatif1 marked this conversation as resolved.
}
}

logger.error(`[${requestId}] Error sending email via SMTP:`, {
error: error instanceof Error ? error.message : String(error),
code: isNodeError(error) ? error.code : undefined,
responseCode: hasResponseCode(error) ? error.responseCode : undefined,
})

return NextResponse.json(
Comment thread
waleedlatif1 marked this conversation as resolved.
Expand Down