Step 6
Anonymous form hardening
25 min
Anonymous form hardening
Public contact / comment forms are the most abused surfaces. Skip CAPTCHA-first; layered cheap defenses do more.
1. Threat model
| Threat | Trait | Frequency |
|---|---|---|
| Spam bots | auto-submit, many IPs | 90%+ |
| Manual spam | copy-paste | medium |
| Targeted | specific fake reports | low |
| Resource abuse | 1000s/s | rare |
2. Honey-pot (layer 1)
<input type="text" name="website" tabIndex={-1} autoComplete="off" aria-hidden="true"
style={{ position: "absolute", left: "-9999px", opacity: 0 }} />
if (body.website?.trim()) {
return NextResponse.json({ ok: true }, { status: 200 });
// 200 on purpose — no retry
}
3. IP hash (layer 2)
function hashIp(ip: string) {
return createHash("sha256").update(ip).digest("hex").slice(0, 32);
}
- Enables dedup without storing raw IPs
- PIPA / GDPR friendly
4. Rate limit (layer 3)
await redis.incr(`inq:${ipHash}:${Math.floor(Date.now()/60000)}`);
5. zod + length caps
const Schema = z.object({
name: z.string().max(40).optional(),
email: z.string().email().max(120).optional(),
subject: z.string().max(80).optional(),
message: z.string().min(5).max(2000),
website: z.string().optional(),
});
6. XSS — sanitize on display
const safe = DOMPurify.sanitize(marked.parse(message));
<div dangerouslySetInnerHTML={{ __html: safe }} />
7. Minimize PII
CREATE TABLE inquiries (
id BIGSERIAL PRIMARY KEY,
name TEXT, email TEXT,
message TEXT NOT NULL,
user_agent TEXT, ip_hash TEXT,
status VARCHAR(12) NOT NULL DEFAULT 'new'
CHECK (status IN ('new','read','replied','archived')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
8. Status flow
new → read → replied → archived.
9. When CAPTCHA
The six above cover 99%. Add CAPTCHA when:
- Dozens of spam/day sustained
- Targeted abuse
- Money-valued actions
Prefer Cloudflare Turnstile or hCaptcha over reCAPTCHA.
10. Gotchas
- CAPTCHA first hurts UX and fails to many solver services
- Raw IP storage triggers data-retention duties
- 4xx teaches bots to go elsewhere; honey-pot wants 200
- Admin UI is not XSS-safe either
Closing
Honey-pot alone catches ~80% of year-one spam. Pile on more tools only after real logs show the need.
Next
- data/12-multi-pg-pool-orchestration
- backend/13-audit-log-pattern
🎉 You finished Web security foundations — JWT · OAuth · OWASP
What's next? Pick another course below.