Skip to content

feat(feedback): add Cloudflare Turnstile to feedback widget #7249

@MarkusNeusinger

Description

@MarkusNeusinger

Context

After #5662 (feedback widget) shipped and we iterated on it (mini-stack quick-reactions, free-form contact field, debug-page triage), the surface attack increased: a one-tap 👍/👎 submit makes reaction-only spam very cheap.

Current anti-spam stack:

  • Honeypot field (offscreen website)
  • IP-hash rate limit (5/min)
  • Length + reaction allowlist validation
  • NEW Link-stuffing filter (≥2 URLs → silent 200, no DB write)
  • NEW Duplicate message filter (same text + same IP/session within 10 min → silent 200)

These stop low-effort bots but a determined attacker with rotating IPs and stealth headless Chrome can still flood the endpoint.

Proposal

Wire Cloudflare Turnstile into the feedback flow:

  1. Add cf_turnstile_site_key and cf_turnstile_secret_key to core/config.py settings.
  2. Frontend (app/src/components/FeedbackWidget.tsx):
    • Render the invisible Turnstile widget when site key is configured.
    • Include the resulting token in the /feedback POST body (new field turnstile_token).
    • If site key isn't configured (local dev), skip the widget — backend must accept missing token in that mode.
  3. Backend (api/routers/feedback.py):
    • When cf_turnstile_secret_key is set, verify the token via POST https://challenges.cloudflare.com/turnstile/v0/siteverify.
    • On failure (success: false), drop silently (200 ok, no DB write — same pattern as honeypot/link-stuff/dup).
    • Fail-soft when secret is unset (local/CI) so the widget keeps working without configuration overhead.
  4. Update docs/reference/plausible.md only if a new event is needed (e.g. feedback_turnstile_failed for monitoring).

Out of scope

  • Adding Turnstile to other endpoints (issue scope: feedback widget only).
  • Replacing the honeypot — keep it as defense-in-depth.

Acceptance criteria

  • Submissions without a valid Turnstile token are silently dropped when the secret is configured.
  • Submissions work unchanged on local dev (no Cloudflare config).
  • No regression in existing tests; new tests cover the verify-success and verify-failure paths.
  • Plausible-doc update if the failure path is tracked.

Background

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions