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.