Sentinel WAF

Rate limit, AuthShield, Anomaly.

8 minmedium

Sentinel is Grit's in-process Web Application Firewall (WAF). It does rate limiting, AuthShield (brute-force lockouts), Anomaly detection, and Geo blocking — all in middleware that runs in sub-millisecond time. This lesson covers what it ships and how to configure it.

The Sentinel dashboard

Open http://localhost:8080/sentinel/ui. Log in with SENTINEL_USERNAME / SENTINEL_PASSWORD from your .env. You'll see:

  • Live request rate + p95 latency
  • Blocked requests (by reason: WAF rule, rate limit, AuthShield)
  • Recent suspicious IPs
  • Per-route rate-limit counters

What it intercepts

apps/api/internal/routes/routes.go (excerpt)
sentinel.MountE(r, db, sentinel.Config{
Dashboard: sentinel.DashboardConfig{
Username: cfg.SentinelUsername,
Password: cfg.SentinelPassword,
SecretKey: cfg.SentinelSecretKey,
AllowInsecureDefaults: cfg.AppEnv != "production",
},
WAF: sentinel.WAFConfig{
Enabled: true,
Mode: sentinel.ModeBlock, // or ModeLog
},
RateLimit: sentinel.RateLimitConfig{
Enabled: true,
ByIP: &sentinel.Limit{Requests: 100, Window: time.Minute},
ByRoute: map[string]sentinel.Limit{
"/api/auth/login": {Requests: 5, Window: 15 * time.Minute},
},
},
AuthShield: sentinel.AuthShieldConfig{
Enabled: true,
LoginRoute: "/api/auth/login",
},
Anomaly: sentinel.AnomalyConfig{Enabled: true},
Geo: sentinel.GeoConfig{Enabled: true},
})

WAF — basic injection / scanner blocks

OWASP CRS-lite ruleset embedded. Blocks:

  • SQLi probes (' OR 1=1--, UNION SELECT, time-based)
  • XSS payloads in query / body
  • Path traversal (../../etc/passwd)
  • Dotfile probes (.git, .env, .bak)

Two modes: ModeBlock (return 403 + record) or ModeLog (record but pass through — useful during rollout).

Rate limit — per-IP + per-route

The default config:

  • 100 req/min per IP across all routes
  • 5 req/15min per IP on /api/auth/login
  • Custom rates per route via the ByRoute map

Exceeded → 429 Too Many Requests. Counters live in Redis (shared across API replicas).

AuthShield — account-level brute-force

After N failed logins for an email, the account enters a cool-down regardless of source IP. This defends against distributed credential-stuffing where every attempt comes from a different IP.

6 failed login attempts for alex@example.com
→ AuthShield locks the account for 15 minutes
→ Even valid password retries return "invalid credentials"
→ Audit log records the lock event
→ Sentinel dashboard shows it

Anomaly + Geo

  • Anomaly: behavioural rules — rapid 4xx bursts, suspicious user-agent strings, scanner signatures. Bots get throttled before they exhaust your rate-limit budget.
  • Geo: country-level allow/block list. "Block everything outside the US" or "flag high-fraud-rate countries for manual review" — both are config flags.
Trust your proxy header chain. If you're behind a reverse proxy (Traefik, Cloudflare), set SENTINEL_TRUSTED_PROXIES to your LB's CIDR. Without it, the WAF sees every request as coming from the LB's IP — your rate limit immediately exhausts.

The dashboard credential gate

Sentinel refuses to mount in production with the default password sentinel. v3.25.1 of Grit fixes this by generating a random password at scaffold time, but if you somehow kept the default, the API won't start in APP_ENV=production. Set strong credentials or set DEMO_MODE=true for the public-demo carve-out.

Quick check

Six failed logins in a row from your IP. Sentinel takes action. What's the response?

Try it

Trip the rate limit:

  1. Run 6 login attempts in quick succession with curl:
Terminal
$for i in 1 2 3 4 5 6; do
$ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:8080/api/auth/login \
$ -H 'Content-Type: application/json' \
$ -d '{"email":"alex@example.com","password":"wrong"}'
$done
  1. You should see five 401s, then a 429.
  2. Open the Sentinel dashboard and look at the rate-limit page.
  3. Paste the curl output and a dashboard screenshot in notes.md.

What's next

Security on. Now to see what's actually happening — Pulse's p50/p95/p99 dashboards, per-route metrics, slow-query visibility.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub