Frontend Labs
⋯

Day 80 — API Abuse Prevention

Learning Objectives

  • Implement credential stuffing defense: independent per-username + per-IP rate limiting
  • Add constant-time auth responses (timing attack prevention)
  • Build honeypot detection for automated scanners

Exercise: Abuse Prevention System

package abuse

import (
    "fmt"
    "net/http"
    "sync"
    "time"
)

type AbuseDetector struct {
    // Credential stuffing: limit by username AND by IP independently
    failsByUser map[string]*counter
    failsByIP   map[string]*counter
    mu          sync.Mutex

    // Honeypot: any IP that hits a hidden endpoint gets scored
    honeypotsHit sync.Map // IP → hit count
}

type counter struct {
    count    int
    window   time.Time
    lockout  time.Time
}

func (ad *AbuseDetector) RecordAuthFailure(username, ip string) (lockedOut bool) {
    ad.mu.Lock()
    defer ad.mu.Unlock()

    now := time.Now()
    resetWindow := 15 * time.Minute
    lockoutDuration := 30 * time.Minute
    maxFails := 5

    for key, m := range map[string]*map[string]*counter{
        username: &ad.failsByUser,
        ip:       &ad.failsByIP,
    } {
        mp := *m
        if mp == nil {
            if key == username { ad.failsByUser = make(map[string]*counter) }
            if key == ip { ad.failsByIP = make(map[string]*counter) }
            mp = *m
        }

        if _, ok := mp[key]; !ok {
            mp[key] = &counter{window: now.Add(resetWindow)}
        }
        c := mp[key]

        if now.Before(c.lockout) {
            return true // currently locked out
        }
        if now.After(c.window) {
            c.count = 0
            c.window = now.Add(resetWindow)
        }
        c.count++
        if c.count >= maxFails {
            c.lockout = now.Add(lockoutDuration)
            fmt.Printf("[ABUSE] lockout: %s (after %d failures)\n", key, c.count)
            return true
        }
    }
    return false
}

// ConstantTimeAuthHandler wraps an auth handler to always respond in ~200ms
// Prevents timing attacks that reveal whether username exists
func ConstantTimeAuthHandler(minDuration time.Duration, h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        h.ServeHTTP(w, r)
        elapsed := time.Since(start)
        if elapsed < minDuration {
            time.Sleep(minDuration - elapsed)
        }
    })
}

// HoneypotHandler marks any client that calls this endpoint as a scanner
func HoneypotHandler(detector *AbuseDetector) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        ip := realClientIP(r)
        detector.honeypotsHit.Store(ip, true)
        fmt.Printf("[HONEYPOT] scanner detected: %s hit %s\n", ip, r.URL.Path)
        // Return plausible 200 — don't alert the scanner
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(200)
        fmt.Fprint(w, `{"status":"ok","data":[]}`)
    }
}

func (ad *AbuseDetector) IsScanner(ip string) bool {
    _, ok := ad.honeypotsHit.Load(ip)
    return ok
}

Real-World Context

Credential stuffing (automated login attempts with breached credentials) accounts for billions of dollars in account takeover fraud. The Cloudflare 2023 report found 34% of all login traffic was credential stuffing. Independent per-username + per-IP rate limiting is required — otherwise an attacker can test 1000 usernames at 1 req/s (below IP threshold) or test 1 username from 1000 IPs. The honeypot endpoint is how you identify automated scanners — only humans (or broken code) hit undocumented endpoints.


Generated from week11.md · 8 Oct 2026 16:36