codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
EDU›Web security foundations — JWT · OAuth · OWASP›Step 6

Step 6

Anonymous form hardening

0 views

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

← Step 5

Rate limit + CORS + security headers

Step 7 →

Step 7 — Email Verification and OTP