What's New in v2.0.0
v2 is a security + architecture sweep. It closes four trivially-exploitable defaults that shipped in v1, refactors Mount so library failures no longer kill the host process, and lands four community-requested features (CVSS scoring + PagerDuty, SSRF-hardened HTTP client, CSP violation receiver, CAPTCHA tier on AuthShield).
Breaking changes
1. Defaults refuse in production
In gin.ReleaseMode, Sentinel now aborts startup if you're still using the built-in default password or JWT secret. The previous behavior — accept a published default and let anyone forge admin tokens — was a footgun. Opt back in only for dev.
1sentinel.Config{2 Dashboard: sentinel.DashboardConfig{3 Password: os.Getenv("SENTINEL_PASSWORD"),4 SecretKey: os.Getenv("SENTINEL_JWT_SECRET"),56 // Dev-only escape hatch:7 // AllowInsecureDefaults: true,8 },9}
2. MountE returns an error
Library callers who don't want a Sentinel-init failure to log.Fatalf the host process should now use MountE. The old Mount remains for backwards compatibility — it just calls MountE and fatal-logs on error.
1if err := sentinel.MountE(r, db, cfg); err != nil {2 log.Printf("sentinel disabled: %v", err)3 // App keeps running without the security layer4}
3. X-Forwarded-For trust requires opt-in
Previously any attacker could send X-Forwarded-For: 8.8.8.8 and spoof their source IP, bypassing IP blocks and per-IP rate limits. v2 ignores proxy headers unless the direct connection originates from a configured trusted proxy.
1WAF: sentinel.WAFConfig{2 Enabled: true,3 TrustedProxies: []string{4 "10.0.0.0/8",5 "172.16.0.0/12",6 // Your load balancer's CIDR7 },8},
4. WAF body-inspection cap
The old WAF inspected the first 10 KB of every request body, then attached thefull body to the handler. Attackers could pad 10 KB of junk and hide the payload at byte 10241. v2 introduces a configurable cap and an optional hard reject.
1WAF: sentinel.WAFConfig{2 MaxBodyBytes: 128 * 1024, // default 64 KB3 RejectOversizedBody: true, // return 413 instead of inspecting partial4},
5. CVSS on every ThreatEvent + PagerDuty sink
Every ThreatEvent now carries a CVSS 3.1 score and canonical vector, picked automatically from the threat type. Use AlertConfig.PagerDuty withMinCVSS to page only on the worst.
1Alerts: sentinel.AlertConfig{2 MinSeverity: sentinel.SeverityHigh, // fills dashboards3 PagerDuty: &sentinel.PagerDutyConfig{4 IntegrationKey: os.Getenv("PD_ROUTING_KEY"),5 MinCVSS: 9.0, // pages only on critical6 },7},
6. SSRF-hardened HTTP client
Webhook delivery, "fetch from URL" features, OEmbed expansion, link previews — anywhere the server makes an outbound request triggered by user input is an SSRF surface. Usesentinel.HTTPClient() as a drop-in *http.Client. Default denylist covers loopback, private ranges, AWS IMDS, GCP metadata, and DNS rebinding.
1client := sentinel.HTTPClient(sentinel.HTTPClientOptions{2 Reporter: pipe, // SSRF attempts show up in the dashboard3})4resp, err := client.Get(userSuppliedURL) // safely
7. CSP violation receiver
Browsers can now POST CSP violation reports to POST /sentinel/csp-report. Both the legacy and Reports API JSON shapes are accepted. Violations flow through the same pipeline as WAF events so they show up in the dashboard threats feed.
1r.Use(middleware.CSPMiddleware(middleware.CSPConfig{2 Enabled: true,3 Mode: middleware.CSPReportOnly,4 Directives: "default-src 'self'; script-src 'self' 'nonce-XYZ'",5 ReportURI: "/sentinel/csp-report",6}))
8. CAPTCHA tier on AuthShield
The middle ground between "fine" and "locked out". When an IP crossesCAPTCHAThreshold failed logins, the next attempt requires a valid CAPTCHA token. Plug in hCaptcha, Cloudflare Turnstile, Google reCAPTCHA v2, or the self-hosted arithmetic challenge for no-third-party setups.
1CAPTCHA: sentinel.CAPTCHAConfig{2 TurnstileSecret: os.Getenv("TURNSTILE_SECRET"),3},4AuthShield: sentinel.AuthShieldConfig{5 Enabled: true,6 MaxFailedAttempts: 5,7 CAPTCHAThreshold: 3, // CAPTCHA from attempt #3, lock at #58},
9. Real Postgres adapter
Previously a stub that silently fell through to in-memory storage whenDriver: sentinel.Postgres was selected. v2 wires a proper Postgres backend through GORM with the same models the SQLite adapter uses.
10. AI daily call cap
AIConfig.MaxCallsPerDay (default 500) caps the total upstream LLM call budget per UTC day so a runaway natural-language-query loop or misbehaving cron can't burn the monthly bill in an afternoon. The cache is applied inside the budget so cached hits don't burn quota.
11. Pipeline drop visibility
GET /sentinel/api/performance/overview now returnspipeline_dropped and pipeline_emitted. Drops happen exactly when an attack is overwhelming the buffer — alert on this.
12. GORM plugin auto-wired
Pass a non-nil *gorm.DB to Mount / MountE and the audit + query-shield plugin is registered automatically. No second-stepdb.Use(...) call required.
Migration checklist
- Set
Dashboard.Password+Dashboard.SecretKeyvia env vars. - Add your load balancer's CIDR to
WAFConfig.TrustedProxies. - If you call
WAFMiddlewaredirectly, passnilas the new fourth argument when you have no custom rule engine. - Decide if you want PagerDuty / CAPTCHA / SSRF guard / CSP receiver — all are additive opt-ins.
- If you were registering the GORM plugin manually, you can drop that call — pass the
*gorm.DBtoMount.