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() {
+