From 8931e0eb2bf265818bb37bead362024e5a85477c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:07:04 +0000 Subject: [PATCH 1/3] Initial plan From dec09d7f4abadb62bab24e560aafd1ea937e9dfc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:11:07 +0000 Subject: [PATCH 2/3] Fix HTML injection (XSS) in email templates and add input validation - Add escapeHtml() to sanitize user input before embedding in HTML emails - Apply sanitization in server/index.js, netlify/functions/contact.js, and project/server/index.js - Add missing server-side input validation in server/index.js - Prevent internal error message leakage to clients Co-authored-by: kulharshit21 <124128807+kulharshit21@users.noreply.github.com> --- netlify/functions/contact.js | 28 +++++++++++++++++++++------- project/server/index.js | 29 ++++++++++++++++++++++++----- server/index.js | 33 ++++++++++++++++++++++++++------- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/netlify/functions/contact.js b/netlify/functions/contact.js index 9e6de2f..9dcb15d 100644 --- a/netlify/functions/contact.js +++ b/netlify/functions/contact.js @@ -4,6 +4,15 @@ import nodemailer from 'nodemailer'; const rateLimitMap = new Map(); const RATE_LIMIT_WINDOW = 60000; // 1 minute +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + export async function handler(event) { // Only allow POST if (event.httpMethod !== 'POST') { @@ -53,6 +62,11 @@ export async function handler(event) { }; } + const safeName = escapeHtml(name); + const safeEmail = escapeHtml(email); + const safeSubject = escapeHtml(subject); + const safeMessage = escapeHtml(message).replace(/\n/g, '
'); + const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: +process.env.SMTP_PORT, @@ -68,13 +82,13 @@ export async function handler(event) { from: `Portfolio Contact <${process.env.SMTP_USER}>`, to: process.env.RECIPIENT, replyTo: email, - subject: `[Portfolio] ${subject} (from ${name})`, + subject: `[Portfolio] ${safeName} - ${safeSubject}`, html: `

New message from your portfolio

-

Name: ${name}

-

Email: ${email}

-

Subject: ${subject}

-

Message:
${message.replace(/\n/g, '
')}

+

Name: ${safeName}

+

Email: ${safeEmail}

+

Subject: ${safeSubject}

+

Message:
${safeMessage}

`, }); @@ -87,13 +101,13 @@ export async function handler(event) {

Message Received

-

Dear ${name},

+

Dear ${safeName},

Thank you for taking the time to reach out through my portfolio website. I have successfully received your message and truly appreciate your interest.

Your Message Details:

-

Subject: ${subject}

+

Subject: ${safeSubject}

I make it a priority to respond to all inquiries promptly and will get back to you as soon as possible, typically within 24-48 hours.

diff --git a/project/server/index.js b/project/server/index.js index 2c486e0..5f63a2e 100644 --- a/project/server/index.js +++ b/project/server/index.js @@ -9,6 +9,15 @@ const app = express(); app.use(cors()); app.use(express.json()); +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: +process.env.SMTP_PORT, @@ -21,21 +30,31 @@ const transporter = nodemailer.createTransport({ app.post('/api/contact', async (req, res) => { const { name, email, subject, message } = req.body; + + if (!name || !email || !subject || !message) { + return res.status(400).json({ ok: false, error: 'All fields are required' }); + } + + const safeName = escapeHtml(name); + const safeEmail = escapeHtml(email); + const safeSubject = escapeHtml(subject); + const safeMessage = escapeHtml(message).replace(/\n/g, '
'); + try { await transporter.sendMail({ from: `Portfolio Contact <${process.env.SMTP_USER}>`, to: process.env.RECIPIENT, - subject: `[Portfolio] ${subject} (from ${name})`, + subject: `[Portfolio] ${safeName} - ${safeSubject}`, html: ` -

Name: ${name}

-

Email: ${email}

-

Message:
${message.replace(/\n/g, '
')}

+

Name: ${safeName}

+

Email: ${safeEmail}

+

Message:
${safeMessage}

` }); res.json({ ok: true }); } catch (error) { console.error(error); - res.status(500).json({ ok: false, error: error.message }); + res.status(500).json({ ok: false, error: 'Failed to send email. Please try again later.' }); } }); diff --git a/server/index.js b/server/index.js index ca67713..c7d8434 100644 --- a/server/index.js +++ b/server/index.js @@ -12,6 +12,15 @@ const app = express(); app.use(cors()); app.use(express.json()); +function escapeHtml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: +process.env.SMTP_PORT, @@ -24,16 +33,26 @@ const transporter = nodemailer.createTransport({ app.post('/api/contact', async (req, res) => { const { name, email, subject, message } = req.body; + + if (!name || !email || !subject || !message) { + return res.status(400).json({ ok: false, error: 'All fields are required' }); + } + + const safeName = escapeHtml(name); + const safeEmail = escapeHtml(email); + const safeSubject = escapeHtml(subject); + const safeMessage = escapeHtml(message).replace(/\n/g, '
'); + try { // Send notification email to yourself await transporter.sendMail({ from: `Portfolio Contact <${process.env.SMTP_USER}>`, to: process.env.RECIPIENT, - subject: `[Portfolio] ${subject} (from ${name})`, + subject: `[Portfolio] ${safeName} - ${safeSubject}`, html: ` -

Name: ${name}

-

Email: ${email}

-

Message:
${message.replace(/\n/g, '
')}

+

Name: ${safeName}

+

Email: ${safeEmail}

+

Message:
${safeMessage}

` }); @@ -46,13 +65,13 @@ app.post('/api/contact', async (req, res) => {

Message Received

-

Dear ${name},

+

Dear ${safeName},

Thank you for taking the time to reach out through my portfolio website. I have successfully received your message and truly appreciate your interest.

Your Message Details:

-

Subject: ${subject}

+

Subject: ${safeSubject}

I make it a priority to respond to all inquiries promptly and will get back to you as soon as possible, typically within 24-48 hours.

@@ -82,7 +101,7 @@ app.post('/api/contact', async (req, res) => { res.json({ ok: true }); } catch (error) { console.error(error); - res.status(500).json({ ok: false, error: error.message }); + res.status(500).json({ ok: false, error: 'Failed to send email. Please try again later.' }); } }); From bbee6375d892a001967e63603d5e43883c08e2c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:11:59 +0000 Subject: [PATCH 3/3] Add email validation and trim whitespace in input validation Co-authored-by: kulharshit21 <124128807+kulharshit21@users.noreply.github.com> --- netlify/functions/contact.js | 19 ++++++++++++++----- project/server/index.js | 15 ++++++++++----- server/index.js | 15 ++++++++++----- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/netlify/functions/contact.js b/netlify/functions/contact.js index 9dcb15d..2ef9f10 100644 --- a/netlify/functions/contact.js +++ b/netlify/functions/contact.js @@ -54,7 +54,7 @@ export async function handler(event) { try { const { name, email, subject, message } = JSON.parse(event.body); - if (!name || !email || !subject || !message) { + if (!name?.trim() || !email?.trim() || !subject?.trim() || !message?.trim()) { return { statusCode: 400, headers, @@ -62,10 +62,19 @@ export async function handler(event) { }; } - const safeName = escapeHtml(name); - const safeEmail = escapeHtml(email); - const safeSubject = escapeHtml(subject); - const safeMessage = escapeHtml(message).replace(/\n/g, '
'); + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return { + statusCode: 400, + headers, + body: JSON.stringify({ ok: false, error: 'Invalid email address' }), + }; + } + + const safeName = escapeHtml(name.trim()); + const safeEmail = escapeHtml(email.trim()); + const safeSubject = escapeHtml(subject.trim()); + const safeMessage = escapeHtml(message.trim()).replace(/\n/g, '
'); const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, diff --git a/project/server/index.js b/project/server/index.js index 5f63a2e..cad0a4b 100644 --- a/project/server/index.js +++ b/project/server/index.js @@ -31,14 +31,19 @@ const transporter = nodemailer.createTransport({ app.post('/api/contact', async (req, res) => { const { name, email, subject, message } = req.body; - if (!name || !email || !subject || !message) { + if (!name?.trim() || !email?.trim() || !subject?.trim() || !message?.trim()) { return res.status(400).json({ ok: false, error: 'All fields are required' }); } - const safeName = escapeHtml(name); - const safeEmail = escapeHtml(email); - const safeSubject = escapeHtml(subject); - const safeMessage = escapeHtml(message).replace(/\n/g, '
'); + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ ok: false, error: 'Invalid email address' }); + } + + const safeName = escapeHtml(name.trim()); + const safeEmail = escapeHtml(email.trim()); + const safeSubject = escapeHtml(subject.trim()); + const safeMessage = escapeHtml(message.trim()).replace(/\n/g, '
'); try { await transporter.sendMail({ diff --git a/server/index.js b/server/index.js index c7d8434..59c17b7 100644 --- a/server/index.js +++ b/server/index.js @@ -34,14 +34,19 @@ const transporter = nodemailer.createTransport({ app.post('/api/contact', async (req, res) => { const { name, email, subject, message } = req.body; - if (!name || !email || !subject || !message) { + if (!name?.trim() || !email?.trim() || !subject?.trim() || !message?.trim()) { return res.status(400).json({ ok: false, error: 'All fields are required' }); } - const safeName = escapeHtml(name); - const safeEmail = escapeHtml(email); - const safeSubject = escapeHtml(subject); - const safeMessage = escapeHtml(message).replace(/\n/g, '
'); + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ ok: false, error: 'Invalid email address' }); + } + + const safeName = escapeHtml(name.trim()); + const safeEmail = escapeHtml(email.trim()); + const safeSubject = escapeHtml(subject.trim()); + const safeMessage = escapeHtml(message.trim()).replace(/\n/g, '
'); try { // Send notification email to yourself