From 10b0480a6f88307307f7663bd5ecb7e4ad8832dd Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Feb 2026 02:09:11 +0000 Subject: [PATCH] feat(security): add honeypot field to contact form Added a hidden input field (honeypot) to the Contact form to detect and block automated bot submissions. This enhancement improves availability and integrity by reducing spam without affecting legitimate users. Also updated Contact.test.tsx to include a test case for the honeypot and improve selector robustness. Co-authored-by: ragusa-it <196988693+ragusa-it@users.noreply.github.com> --- src/pages/Contact.module.css | 14 +++++++ src/pages/Contact.tsx | 23 +++++++++++ src/pages/__tests__/Contact.test.tsx | 61 ++++++++++++++++++++-------- 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/pages/Contact.module.css b/src/pages/Contact.module.css index 80dfa15..ff312db 100644 --- a/src/pages/Contact.module.css +++ b/src/pages/Contact.module.css @@ -139,3 +139,17 @@ .infoItem a:hover { text-decoration: underline; } + +.honeypot { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + opacity: 0; + pointer-events: none; +} diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx index 6332455..36c4096 100644 --- a/src/pages/Contact.tsx +++ b/src/pages/Contact.tsx @@ -35,6 +35,7 @@ export function Contact() { subject: "", message: "", }); + const [honeypot, setHoneypot] = useState(""); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [submitStatus, setSubmitStatus] = useState< @@ -79,6 +80,15 @@ export function Contact() { const handleSubmit = async (e: FormEvent) => { e.preventDefault(); + // Honeypot check: If the hidden field is filled, it's a bot. + // We silently "succeed" to fool the bot and protect our resources. + if (honeypot) { + setSubmitStatus("success"); + setFormData({ name: "", email: "", subject: "", message: "" }); + setHoneypot(""); + return; + } + setRateLimitError(false); if (!validateForm()) return; @@ -166,6 +176,19 @@ export function Contact() { className={styles.form} noValidate > + {/* Honeypot field - invisible to users, tempting to bots */} + setHoneypot(e.target.value)} + className={styles.honeypot} + tabIndex={-1} + autoComplete="off" + aria-hidden="true" + data-testid="honeypot-input" + /> + { render(); // Fill out the form - fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'John Doe' } }); - fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'john@example.com' } }); - fireEvent.change(screen.getByLabelText('Subject'), { target: { value: 'Test Subject' } }); - fireEvent.change(screen.getByLabelText('Message'), { target: { value: 'Hello world' } }); + fireEvent.change(screen.getByPlaceholderText('Your Name'), { target: { value: 'John Doe' } }); + fireEvent.change(screen.getByPlaceholderText('Your Email'), { target: { value: 'john@example.com' } }); + fireEvent.change(screen.getByPlaceholderText('Message Subject'), { target: { value: 'Test Subject' } }); + fireEvent.change(screen.getByPlaceholderText('Your Message'), { target: { value: 'Hello world' } }); // Submit fireEvent.click(screen.getByRole('button', { name: 'Send Message' })); @@ -124,10 +124,10 @@ describe('Contact Page', () => { render(); // Fill out with malicious input - fireEvent.change(screen.getByLabelText('Name'), { target: { value: '' } }); - fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'john@example.com' } }); - fireEvent.change(screen.getByLabelText('Subject'), { target: { value: 'Bold' } }); - fireEvent.change(screen.getByLabelText('Message'), { target: { value: '"Quotes"' } }); + fireEvent.change(screen.getByPlaceholderText('Your Name'), { target: { value: '' } }); + fireEvent.change(screen.getByPlaceholderText('Your Email'), { target: { value: 'john@example.com' } }); + fireEvent.change(screen.getByPlaceholderText('Message Subject'), { target: { value: 'Bold' } }); + fireEvent.change(screen.getByPlaceholderText('Your Message'), { target: { value: '"Quotes"' } }); // Submit fireEvent.click(screen.getByRole('button', { name: 'Send Message' })); @@ -161,7 +161,7 @@ describe('Contact Page', () => { const longName = 'a'.repeat(101); // Fill out the form - fireEvent.change(screen.getByLabelText('Name'), { target: { value: longName } }); + fireEvent.change(screen.getByPlaceholderText('Your Name'), { target: { value: longName } }); // Submit fireEvent.click(screen.getByRole('button', { name: 'Send Message' })); @@ -178,10 +178,10 @@ describe('Contact Page', () => { const { container } = render(); // Fill out the form with invalid email (XSS vector) - fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'John Doe' } }); - fireEvent.change(screen.getByLabelText('Email'), { target: { value: '