Cybersecurity-Projects/PROJECTS
CarterPerez-dev 537b0e3fd8 feat(canary): turnstile verifier + middleware
Phase 9 task 9.2 + 9.3. Cloudflare Turnstile integration for
POST /api/tokens spam protection.

turnstile/verifier.go:
  - Verifier{secret, verifyURL, client, rdb}
  - Verify(ctx, token, fingerprint) error:
      - empty secret → dev-mode bypass (return nil)
      - empty token → ErrEmptyToken
      - cache hit on "turnstile:fp:<fp>" → return nil (5-min TTL)
      - else: POST to challenges.cloudflare.com/turnstile/v0/siteverify
        with secret + response, decode, return ErrVerifyFailed on
        false or wrap network err
      - on success, write cache entry (best-effort, logged on error)
  - RedisClient interface (Get + SetEx) so tests can use a fake
    without miniredis dep
  - HTTP client timeout 10s, defaults to DefaultVerifyURL constant

middleware/turnstile.go:
  - TurnstileVerifier interface (Verify) decouples middleware from
    the concrete verifier — lets tests inject a fake
  - TurnstileVerify(v) middleware: extracts token from
    CF-Turnstile-Response header OR JSON body's cf_turnstile_response
    field (header wins); preserves request body for downstream
    handlers by re-wrapping io.NopCloser; on verify failure returns
    400 with {success:false, error:{code:"TURNSTILE_FAILED"}}

.golangci.yml: adds G104, G706, G107, G704 to gosec excludes.
G704/G107 are SSRF taint-analysis rules that fire on any HTTP client
.Do call where the URL came from a struct field. In this codebase
all outbound HTTP calls go to operator-configured endpoints (Turnstile
siteverify, future Phase 10 Telegram), with user-supplied webhook
URLs validated at the call site before reaching client.Do. The
global excludes are policy-level (matches the G104/G706 precedent
of "this codebase doesn't do X").

Tests (~14 cases):
  - verifier: empty-secret bypass, empty-token error, success cached
    by fingerprint, cache miss for different fingerprint, failed
    siteverify returns ErrVerifyFailed, nil-redis works without
    caching, network error wrapped (and NOT ErrVerifyFailed)
  - middleware: header token passes, body token passes, body
    preserved for downstream, failure returns 400 with code, header
    wins over body when both present, empty-token still calls
    verifier
2026-05-13 15:08:37 -04:00
..
advanced fix: pin pnpm to v10 in CI and add .npmrc to all frontend projects 2026-05-08 05:46:38 -04:00
beginner feat(canary): turnstile verifier + middleware 2026-05-13 15:08:37 -04:00
intermediate fix: pin pnpm to v10 in CI and add .npmrc to all frontend projects 2026-05-08 05:46:38 -04:00