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

v2 introduces several breaking changes. The summary is in this page; full details and a migration diff live in CHANGELOG.md.

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"),
5
6 // 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 layer
4}

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 CIDR
7 },
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 KB
3 RejectOversizedBody: true, // return 413 instead of inspecting partial
4},

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 dashboards
3 PagerDuty: &sentinel.PagerDutyConfig{
4 IntegrationKey: os.Getenv("PD_ROUTING_KEY"),
5 MinCVSS: 9.0, // pages only on critical
6 },
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 dashboard
3})
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 #5
8},

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

  1. Set Dashboard.Password + Dashboard.SecretKey via env vars.
  2. Add your load balancer's CIDR to WAFConfig.TrustedProxies.
  3. If you call WAFMiddleware directly, pass nil as the new fourth argument when you have no custom rule engine.
  4. Decide if you want PagerDuty / CAPTCHA / SSRF guard / CSP receiver — all are additive opt-ins.
  5. If you were registering the GORM plugin manually, you can drop that call — pass the *gorm.DB to Mount.

Built with by JB