diff --git a/app/actions/contact.ts b/app/actions/contact.ts index 43a027a98..08a6d688d 100644 --- a/app/actions/contact.ts +++ b/app/actions/contact.ts @@ -25,11 +25,23 @@ export async function sendContactFormAction(formData: FormData) { // Track attempt services.analytics.track('contact-form-attempt'); - // Anti-spam Honeypot Check - const honeypot = formData.get('company_website') as string; - if (honeypot) { - logger.warn('Spam detected via honeypot in contact request', { email: formData.get('email') }); - // Silently succeed to fool the bot without doing actual work + // Anti-spam guard: honeypot, time-trap, email validation, link limit, IP rate limit + const { checkContactSubmission } = await import('@/lib/antispam/contact-guard'); + const verdict = checkContactSubmission({ + honeypot: (formData.get('company_website') as string) || null, + formLoadedAt: Number(formData.get('form_loaded_at')) || null, + now: Date.now(), + ip: requestHeaders.get('x-forwarded-for')?.split(',')[0]?.trim() || null, + email: (formData.get('email') as string) || null, + message: (formData.get('message') as string) || null, + }); + + if (!verdict.allowed) { + logger.warn('Spam blocked by anti-spam guard', { + reason: verdict.reason, + email: formData.get('email'), + }); + // Silently succeed to fool bots without doing actual work return { success: true }; } diff --git a/components/ContactForm.tsx b/components/ContactForm.tsx index d74346b77..a0e53ffa9 100644 --- a/components/ContactForm.tsx +++ b/components/ContactForm.tsx @@ -12,6 +12,7 @@ export default function ContactForm() { const { trackEvent } = useAnalytics(); const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle'); const [hasStarted, setHasStarted] = useState(false); + const [formLoadedAt] = useState(() => Date.now()); const handleFocus = (fieldId: string) => { // Initial form start @@ -152,6 +153,8 @@ export default function ContactForm() { style={{ display: 'none' }} aria-hidden="true" /> + {/* Anti-spam time-trap: server rejects submissions faster than a human could fill the form */} +
('idle'); const [hasStarted, setHasStarted] = useState(false); + const [formLoadedAt] = useState(() => Date.now()); const handleFocus = (fieldId: string) => { // Initial form start @@ -172,8 +173,10 @@ export default function RequestQuoteForm({ productName }: RequestQuoteFormProps) tabIndex={-1} autoComplete="off" style={{ display: 'none' }} - aria-hidden="true" - /> + aria-hidden="true" + /> + {/* Anti-spam time-trap: server rejects submissions faster than a human could fill the form */} +
diff --git a/components/forms/ContactForm.tsx b/components/forms/ContactForm.tsx index 01e3f99d6..fab6efe0c 100644 --- a/components/forms/ContactForm.tsx +++ b/components/forms/ContactForm.tsx @@ -7,6 +7,7 @@ import { sendContactFormAction } from '@/app/actions/contact'; export function ContactForm() { const [status, setStatus] = React.useState<'idle' | 'loading' | 'success' | 'error'>('idle'); + const [formLoadedAt] = React.useState(() => Date.now()); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -132,6 +133,7 @@ export function ContactForm() {
+