fix(forms): harden honeypot hiding on contact + inline lead forms
Deploy to Production / Build & Verify (push) Has been cancelled
Deploy to Production / Pre-Deploy Tests (push) Has been cancelled
Deploy to Production / Deploy to Railway (push) Has been cancelled
Deploy to Production / Deploy to Render (push) Has been cancelled
Deploy to Production / Deploy to VPS (PM2) (push) Has been cancelled
Deploy to Production / Deploy to Fly.io (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Has been cancelled
Deploy to Production / Notify on Failure (push) Has been cancelled
E2E Test Suite / Smoke Tests (P0) (push) Has been cancelled
E2E Test Suite / Critical User Journeys (push) Has been cancelled
E2E Test Suite / API Integration Tests (push) Has been cancelled
E2E Test Suite / Form Interaction Tests (push) Has been cancelled
E2E Test Suite / Destructive & Chaos Tests (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (chromium) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (firefox) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (webkit) (push) Has been cancelled
E2E Test Suite / Mobile Device Tests (push) Has been cancelled
E2E Test Suite / Security Header Tests (push) Has been cancelled
E2E Test Suite / Test Report Summary (push) Has been cancelled
Ping Search Engines / Notify Search Engines (push) Failing after 12m31s

Both honeypots now use off-screen positioning (position:absolute; left:-9999px;
width:1px; height:1px; overflow:hidden) instead of relying on a parent's
aria-hidden alone, and the input itself carries tabindex="-1",
autocomplete="off", and aria-hidden="true". Labels are sr-only and sit as
siblings of the input (no nested label/input). Avoid display:none and
visibility:hidden so bots that skip those still trip the trap.

Verified: rendered honeypot HTML matches spec on both /contact and the
homepage inline lead form, and /api/contact still 200s a populated `website`
silently (bot path) while accepting empty `website` payloads as real leads.
This commit is contained in:
platform-mcp
2026-06-17 12:57:47 +05:30
parent 16c5f79f2b
commit 462d2058e1
2 changed files with 36 additions and 15 deletions
+14 -3
View File
@@ -54,15 +54,26 @@
data-testid="inline-lead-form"
novalidate
>
<!-- Honeypot (visually & SR-hidden — bots fill it, real users do not). -->
<div class="absolute -left-[9999px] top-auto w-px h-px overflow-hidden" aria-hidden="true">
<label for="inline-lead-website">Website</label>
<!--
Honeypot — off-screen positioned, NOT display:none / visibility:hidden
(some bots skip those entirely). aria-hidden + sr-only label keep it
silent for screen readers; tabindex="-1" + autocomplete="off" keep it
out of the tab order and browser autofill. Bots that blindly fill
every field will populate `website`, and /api/contact silently drops
the submission.
-->
<div
class="absolute -left-[9999px] top-auto w-px h-px overflow-hidden"
aria-hidden="true"
>
<label for="inline-lead-website" class="sr-only">Website</label>
<input
type="text"
id="inline-lead-website"
name="website"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
/>
</div>
+22 -12
View File
@@ -156,18 +156,28 @@ const trustStats = [
</div>
<form id="contact-form" class="space-y-5" novalidate>
<!-- Honeypot field (anti-spam) - hidden from users -->
<div class="absolute -left-[9999px]" aria-hidden="true">
<label for="website">
Website
<input
type="text"
id="website"
name="website"
tabindex="-1"
autocomplete="off"
/>
</label>
<!--
Honeypot field (anti-spam).
Hidden from sighted users via off-screen positioning (NOT display:none /
visibility:hidden — some bots skip those entirely and never trip the trap).
The wrapper carries aria-hidden so screen-readers don't announce the label
or the input, and tabindex="-1" + autocomplete="off" keep it out of the
tab order and browser autofill. Bots that blindly fill every field will
set a value here, and /api/contact silently discards the submission.
-->
<div
class="absolute -left-[9999px] top-auto w-px h-px overflow-hidden"
aria-hidden="true"
>
<label for="website" class="sr-only">Website</label>
<input
type="text"
id="website"
name="website"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
/>
</div>
<!-- Name + Email row -->