Security hardening implementing three issues:
CSRF Protection (#35):
- Session-based CSRF tokens with cryptographically random generation
- Constant-time token comparison to prevent timing attacks
- CSRF middleware applied to /pages, /sources, /source, and /user routes
- Hidden csrf_token field added to all 12+ POST forms in templates
- Excluded from /webhook (inbound) and /api (stateless) routes
SSRF Prevention (#36):
- ValidateTargetURL blocks private/reserved IP ranges at target creation
- Blocked ranges: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7, fe80::/10, plus
multicast, reserved, test-net, and CGN ranges
- SSRF-safe HTTP transport with custom DialContext for defense-in-depth
at delivery time (prevents DNS rebinding attacks)
- Only http/https schemes allowed
Login Rate Limiting (#37):
- Per-IP rate limiter using golang.org/x/time/rate
- 5 attempts per minute per IP on POST /pages/login
- GET requests (form rendering) pass through unaffected
- Automatic cleanup of stale per-IP limiter entries
- X-Forwarded-For and X-Real-IP header support for reverse proxies
Closes#35, closes#36, closes#37
PR ready for review. Implements three security hardening issues:
CSRF protection (#35): Session-based CSRF tokens on all 12+ POST forms. Applied to /pages, /sources, /source, /user routes. Excluded from /webhook and /api.
SSRF prevention (#36): URL validation blocking private/reserved IPs at target creation + SSRF-safe HTTP transport with custom DialContext for defense-in-depth at delivery time.
Login rate limiting (#37): Per-IP rate limiter (5 attempts/min) on POST /pages/login using golang.org/x/time/rate.
All existing tests pass. New tests added for all three features. docker build . passes (lint + tests + build).
PR ready for review. Implements three security hardening issues:
- **CSRF protection** ([#35](https://git.eeqj.de/sneak/webhooker/issues/35)): Session-based CSRF tokens on all 12+ POST forms. Applied to `/pages`, `/sources`, `/source`, `/user` routes. Excluded from `/webhook` and `/api`.
- **SSRF prevention** ([#36](https://git.eeqj.de/sneak/webhooker/issues/36)): URL validation blocking private/reserved IPs at target creation + SSRF-safe HTTP transport with custom DialContext for defense-in-depth at delivery time.
- **Login rate limiting** ([#37](https://git.eeqj.de/sneak/webhooker/issues/37)): Per-IP rate limiter (5 attempts/min) on `POST /pages/login` using `golang.org/x/time/rate`.
All existing tests pass. New tests added for all three features. `docker build .` passes (lint + tests + build).
<!-- session: agent:sdlc-manager:subagent:817efd4a-60c7-494b-a108-720b69ef2c70 -->
Token generation: 256-bit cryptographically random tokens via crypto/rand — correct
Constant-time comparison: Custom secureCompare using XOR accumulation — correct (could use crypto/subtle.ConstantTimeCompare but functionally equivalent)
All 12 POST forms have <input type="hidden" name="csrf_token">:
Rebased onto main to resolve merge conflicts from PR #41 merge.
Conflict resolution:
internal/server/routes.go: Merged both middleware stacks — /pages route group now applies CSRF, MaxBodySize, and LoginRateLimit. /sources and /source/{sourceID} groups retain CSRF, RequireAuth, and MaxBodySize.
README.md: Combined security documentation from both PRs (security headers + body size limits from PR #41, CSRF/SSRF/rate-limiting from this PR). Updated package layout, security section, and TODO checklist.
All tests pass. Docker build succeeds.
Rebased onto `main` to resolve merge conflicts from [PR #41](https://git.eeqj.de/sneak/webhooker/pulls/41) merge.
**Conflict resolution:**
- `internal/server/routes.go`: Merged both middleware stacks — `/pages` route group now applies CSRF, MaxBodySize, and LoginRateLimit. `/sources` and `/source/{sourceID}` groups retain CSRF, RequireAuth, and MaxBodySize.
- `README.md`: Combined security documentation from both PRs (security headers + body size limits from PR #41, CSRF/SSRF/rate-limiting from this PR). Updated package layout, security section, and TODO checklist.
All tests pass. Docker build succeeds.
<!-- session: agent:sdlc-manager:subagent:932b81e8-1544-4beb-940f-d9ef3d60d1e7 -->
Verified all three security features are intact after rebase onto main (which merged PR #41: security headers, session fixation, body limits). docker build . passes.
Middleware Stack Verification ✅
Both PR #41 and PR #42 middleware correctly present in routes.go:
5 attempts/minute per IP using golang.org/x/time/rate token bucket
POST-only (GET requests for login form pass through)
Per-IP limiter map with cleanup goroutine (10-min expiry, 5-min sweep)
IP extraction: X-Forwarded-For → X-Real-IP → RemoteAddr
Tests verify burst behavior, per-IP independence, GET bypass
Additional Checks
.golangci.yml: unchanged ✅
No tests weakened ✅
go.mod adds only golang.org/x/time (required for rate limiter) ✅
README.md updated with security docs and TODO checkboxes ✅
Minor Style Note (non-blocking)
secureCompare() in csrf.go is a hand-rolled constant-time comparison. Consider using crypto/subtle.ConstantTimeCompare from stdlib for the same guarantee with less code. Not a functional issue — the current implementation is correct.
## ✅ Post-Rebase Review PASS — [PR #42](https://git.eeqj.de/sneak/webhooker/pulls/42)
Verified all three security features are intact after rebase onto main (which merged [PR #41](https://git.eeqj.de/sneak/webhooker/pulls/41): security headers, session fixation, body limits). `docker build .` passes.
### Middleware Stack Verification ✅
Both PR #41 and PR #42 middleware correctly present in `routes.go`:
| Layer | Middleware | Source |
|-------|-----------|--------|
| Global | SecurityHeaders | PR #41 |
| `/pages` | CSRF → MaxBodySize | PR #42 + PR #41 |
| `/pages` login group | LoginRateLimit | PR #42 |
| `/user/{username}` | CSRF | PR #42 |
| `/sources` | CSRF → RequireAuth → MaxBodySize | PR #42 + PR #41 |
| `/source/{sourceID}` | CSRF → RequireAuth → MaxBodySize | PR #42 + PR #41 |
| `/webhook/{uuid}` | None (correct — external webhook traffic) | — |
No code lost or duplicated in conflict resolution.
### [#35](https://git.eeqj.de/sneak/webhooker/issues/35) — CSRF Protection ✅
- 12 POST forms across 5 templates, all 12 have `csrf_token` hidden input
- Session-based 256-bit tokens with constant-time comparison
- Applied to `/pages`, `/user`, `/sources`, `/source` routes
- Correctly excluded from `/webhook` (inbound) and `/api` (stateless)
### [#36](https://git.eeqj.de/sneak/webhooker/issues/36) — SSRF Prevention ✅
- All required ranges blocked: `127/8`, `10/8`, `172.16/12`, `192.168/16`, `169.254/16`, `::1/128`, `fc00::/7`, `fe80::/10`
- Additional ranges: `0.0.0.0/8`, `100.64/10` (CGN), multicast, reserved
- **DNS rebinding defense**: `NewSSRFSafeTransport()` re-resolves hostnames at dial time and validates resolved IPs
- Dual enforcement: URL validation at target creation (`HandleTargetCreate`) + transport-level blocking at delivery time (`Engine.client.Transport`)
- Comprehensive tests: 142 lines covering all blocked/allowed ranges, edge cases
### [#37](https://git.eeqj.de/sneak/webhooker/issues/37) — Login Rate Limiting ✅
- 5 attempts/minute per IP using `golang.org/x/time/rate` token bucket
- POST-only (GET requests for login form pass through)
- Per-IP limiter map with cleanup goroutine (10-min expiry, 5-min sweep)
- IP extraction: X-Forwarded-For → X-Real-IP → RemoteAddr
- Tests verify burst behavior, per-IP independence, GET bypass
### Additional Checks
- `.golangci.yml`: unchanged ✅
- No tests weakened ✅
- `go.mod` adds only `golang.org/x/time` (required for rate limiter) ✅
- README.md updated with security docs and TODO checkboxes ✅
### Minor Style Note (non-blocking)
`secureCompare()` in `csrf.go` is a hand-rolled constant-time comparison. Consider using `crypto/subtle.ConstantTimeCompare` from stdlib for the same guarantee with less code. Not a functional issue — the current implementation is correct.
<!-- session: agent:sdlc-manager:subagent:b50dd3c0-8dea-4aa2-8f82-045391d7c03a -->
Remove the hand-rolled secureCompare function and use the standard
library's crypto/subtle.ConstantTimeCompare for CSRF token validation.
Remove the corresponding unit test for the deleted function; CSRF token
comparison is still covered by the integration tests.
Replaced the custom secureCompare function with crypto/subtle.ConstantTimeCompare() from the standard library, as requested. Removed the custom implementation and its dedicated unit test (CSRF token comparison is still fully covered by the integration tests). docker build . passes.
Replaced the custom `secureCompare` function with `crypto/subtle.ConstantTimeCompare()` from the standard library, as requested. Removed the custom implementation and its dedicated unit test (CSRF token comparison is still fully covered by the integration tests). `docker build .` passes.
<!-- session: agent:sdlc-manager:subagent:8a5c9a89-7db4-40c4-a63b-66a69a1c577f -->
Verified the rework requested by sneak ("use the stdlib comparison function and remove ours") and re-verified all three security features. docker build . passes.
wtf did we write our own ratelimiting and csrf middlewares? these are standard web application tasks, isn't there some off the shelf stuff we could have used here given that we're using them in completely standard ways?
wtf did we write our own ratelimiting and csrf middlewares? these are standard web application tasks, isn't there some off the shelf stuff we could have used here given that we're using them in completely standard ways?
Acknowledged — previous rework agent stalled. Re-dispatched a new rework agent to replace custom CSRF and rate-limiting middleware with off-the-shelf Go libraries per your feedback. SSRF prevention stays custom (application-specific). Will update when complete.
Acknowledged — previous rework agent stalled. Re-dispatched a new rework agent to replace custom CSRF and rate-limiting middleware with off-the-shelf Go libraries per your feedback. SSRF prevention stays custom (application-specific). Will update when complete.
Understood — gateway restarts were killing the sub-agents. Re-dispatched rework agent (e13b484d). Working on replacing custom CSRF/rate-limiting with off-the-shelf libs now.
Understood — gateway restarts were killing the sub-agents. Re-dispatched rework agent (e13b484d). Working on replacing custom CSRF/rate-limiting with off-the-shelf libs now.
Replace custom CSRF middleware with gorilla/csrf and custom rate-limiting
middleware with go-chi/httprate, as requested in code review.
CSRF changes:
- Replace session-based CSRF tokens with gorilla/csrf cookie-based
double-submit pattern (HMAC-authenticated cookies)
- Keep same form field name (csrf_token) for template compatibility
- Keep same route exclusions (webhook/API routes)
- In dev mode, mark requests as plaintext HTTP to skip Referer check
Rate limiting changes:
- Replace custom token-bucket rate limiter with httprate sliding-window
counter (per-IP, 5 POST requests/min on login endpoint)
- Remove custom IP extraction (httprate.KeyByRealIP handles
X-Forwarded-For, X-Real-IP, True-Client-IP)
- Remove custom cleanup goroutine (httprate manages its own state)
Kept as-is:
- SSRF prevention code (internal/delivery/ssrf.go) — application-specific
- CSRFToken() wrapper function — handlers unchanged
Updated README security section and architecture overview to reflect
library choices.
SSRF prevention code (internal/delivery/ssrf.go) — application-specific by necessity
Other changes
Added Session.GetKey() method to expose the raw 32-byte auth key for gorilla/csrf cookie signing
Updated all tests for new library behavior
Updated README security section and architecture overview to reference the new libraries
go mod tidy run, all dependencies pinned in go.sum
docker build . passes clean (lint + test + build)
Net result: -191 lines of custom middleware code replaced by 2 well-established library calls.
## Rework: Replace custom CSRF/rate-limiting with off-the-shelf libraries
Addressed the review feedback by replacing both custom middleware implementations with well-established libraries:
### CSRF: `gorilla/csrf` v1.7.3
- Replaced the custom session-based CSRF middleware with [gorilla/csrf](https://github.com/gorilla/csrf), the standard Go CSRF library
- Uses cookie-based double-submit tokens with HMAC authentication (more standard than session-based approach)
- Kept the same `csrf_token` form field name — no template changes needed
- Same route exclusions: webhook and API routes are unprotected, form routes (`/pages`, `/sources`, `/source`, `/user`) are protected
- In dev mode, requests are marked as plaintext HTTP via `csrf.PlaintextHTTPRequest()` to skip the strict TLS Referer check
- `middleware.CSRFToken(r)` wrapper preserved so handlers are unchanged
### Rate Limiting: `go-chi/httprate` v0.15.0
- Replaced the custom token-bucket rate limiter (with manual IP map, cleanup goroutine, etc.) with [go-chi/httprate](https://github.com/go-chi/httprate)
- Uses `httprate.KeyByRealIP` for IP extraction (handles `X-Forwarded-For`, `X-Real-IP`, `True-Client-IP` headers)
- Same policy: 5 POST attempts per minute per IP on the login endpoint
- GET requests pass through unaffected (same behavior as before)
- Removed ~130 lines of custom rate limiter code (IP map, cleanup goroutine, mutex management)
### Kept as-is
- SSRF prevention code (`internal/delivery/ssrf.go`) — application-specific by necessity
### Other changes
- Added `Session.GetKey()` method to expose the raw 32-byte auth key for gorilla/csrf cookie signing
- Updated all tests for new library behavior
- Updated README security section and architecture overview to reference the new libraries
- `go mod tidy` run, all dependencies pinned in `go.sum`
- `docker build .` passes clean (lint + test + build)
Net result: **-191 lines** of custom middleware code replaced by 2 well-established library calls.
All three security features verified. Custom CSRF and rate-limiting code fully replaced with off-the-shelf libraries as requested. docker build . passes. All tests pass.
Rework Verification: Custom → Library Migration ✅
Check
Status
Custom CSRF middleware removed
✅ No secureCompare, GenerateToken, crypto/rand token gen, or validateCSRF in middleware
gorilla/csrf v1.7.3 integrated
✅ Cookie-based double-submit tokens with HMAC signing
Custom rate limiter removed
✅ No x/time/rate, rate.NewLimiter, IP maps, cleanup goroutines
go-chi/httprate v0.15.0 integrated
✅ Sliding-window per-IP limiter via httprate.KeyByRealIP
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Security Hardening
This PR implements three security hardening issues:
CSRF Protection (closes #35)
/pages,/sources,/source, and/userroutescsrf_tokenfield added to all 12+ POST forms in templates/webhook(inbound webhook POSTs) and/api(stateless API)SSRF Prevention (closes #36)
ValidateTargetURL()blocks private/reserved IP ranges at target creation time127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1,fc00::/7,fe80::/10, plus multicast, reserved, test-net, and CGN rangesDialContextin the delivery engine for defense-in-depth (prevents DNS rebinding attacks)httpandhttpsschemes allowedLogin Rate Limiting (closes #37)
golang.org/x/time/ratePOST /pages/loginX-Forwarded-ForandX-Real-IPheader support for reverse proxiesFiles Changed
New files:
internal/middleware/csrf.go+ tests — CSRF middlewareinternal/middleware/ratelimit.go+ tests — Login rate limiterinternal/delivery/ssrf.go+ tests — SSRF validation + safe transportModified files:
internal/server/routes.go— Wire CSRF and rate limit middlewareinternal/handlers/handlers.go— Inject CSRF token into template datainternal/handlers/source_management.go— SSRF validation on target creationinternal/delivery/engine.go— SSRF-safe HTTP transport for productioncsrf_tokenfieldsREADME.md— Updated Security section and TODO checklistdocker build .passes (lint + tests + build).PR ready for review. Implements three security hardening issues:
/pages,/sources,/source,/userroutes. Excluded from/webhookand/api.POST /pages/loginusinggolang.org/x/time/rate.All existing tests pass. New tests added for all three features.
docker build .passes (lint + tests + build).✅ Review PASS — PR #42 (CSRF + SSRF + Rate Limiting)
All three security issues verified against requirements.
docker build .passes.#35 — CSRF Protection ✅
crypto/rand— correctsecureCompareusing XOR accumulation — correct (could usecrypto/subtle.ConstantTimeComparebut functionally equivalent)<input type="hidden" name="csrf_token">:login.html(1 form)navbar.html(2 forms — desktop + mobile logout)sources_new.html(1 form)source_edit.html(1 form)source_detail.html(7 forms — delete, add entrypoint, toggle/delete entrypoint, add target, toggle/delete target)/pages,/sources,/source/{sourceID},/user/{username}routes/webhook/{uuid}(inbound webhook POSTs) and/api/v1(stateless API)#36 — SSRF Prevention ✅
ValidateTargetURLchecks at target creation time;NewSSRFSafeTransportwith customDialContextvalidates resolved IPs at connection time (DNS rebinding defense)#37 — Login Rate Limiting ✅
golang.org/x/time/rate: 5 attempts per minute (rate = 5/60 ≈ 0.0833/sec, burst = 5) — arithmetic correctIntegrity Checks ✅
.golangci.ymlunchangeddocker build .(which runsmake check) passesmain(no rebase needed)golang.org/x/time v0.14.0properly added to go.mod/go.sum@clawbot pls fix conflicts
19e7557e88to7f4c40cacaRebased onto
mainto resolve merge conflicts from PR #41 merge.Conflict resolution:
internal/server/routes.go: Merged both middleware stacks —/pagesroute group now applies CSRF, MaxBodySize, and LoginRateLimit./sourcesand/source/{sourceID}groups retain CSRF, RequireAuth, and MaxBodySize.README.md: Combined security documentation from both PRs (security headers + body size limits from PR #41, CSRF/SSRF/rate-limiting from this PR). Updated package layout, security section, and TODO checklist.All tests pass. Docker build succeeds.
✅ Post-Rebase Review PASS — PR #42
Verified all three security features are intact after rebase onto main (which merged PR #41: security headers, session fixation, body limits).
docker build .passes.Middleware Stack Verification ✅
Both PR #41 and PR #42 middleware correctly present in
routes.go:/pages/pageslogin group/user/{username}/sources/source/{sourceID}/webhook/{uuid}No code lost or duplicated in conflict resolution.
#35 — CSRF Protection ✅
csrf_tokenhidden input/pages,/user,/sources,/sourceroutes/webhook(inbound) and/api(stateless)#36 — SSRF Prevention ✅
127/8,10/8,172.16/12,192.168/16,169.254/16,::1/128,fc00::/7,fe80::/100.0.0.0/8,100.64/10(CGN), multicast, reservedNewSSRFSafeTransport()re-resolves hostnames at dial time and validates resolved IPsHandleTargetCreate) + transport-level blocking at delivery time (Engine.client.Transport)#37 — Login Rate Limiting ✅
golang.org/x/time/ratetoken bucketAdditional Checks
.golangci.yml: unchanged ✅go.modadds onlygolang.org/x/time(required for rate limiter) ✅Minor Style Note (non-blocking)
secureCompare()incsrf.gois a hand-rolled constant-time comparison. Consider usingcrypto/subtle.ConstantTimeComparefrom stdlib for the same guarantee with less code. Not a functional issue — the current implementation is correct.use the stdlib comparison function and remove ours
Replaced the custom
secureComparefunction withcrypto/subtle.ConstantTimeCompare()from the standard library, as requested. Removed the custom implementation and its dedicated unit test (CSRF token comparison is still fully covered by the integration tests).docker build .passes.✅ Review PASS — PR #42 (post-rework: stdlib comparison)
Verified the rework requested by sneak ("use the stdlib comparison function and remove ours") and re-verified all three security features.
docker build .passes.Rework Verification ✅
secureComparefunction removedgrep -rn secureComparereturns nothingcrypto/subtle.ConstantTimeCompare()usedcsrf.go:79—subtle.ConstantTimeCompare([]byte(submitted), []byte(token)) != 1TestSecureCompareremoved#35 — CSRF Protection ✅
crypto/rand— correctcrypto/subtle.ConstantTimeCompare()for constant-time comparison — correct (stdlib, as requested)<input type="hidden" name="csrf_token">: login (1), navbar logout (2), source_detail (7), source_edit (1), sources_new (1)/pages,/sources,/source/{sourceID},/user/{username}routes/webhookand/api#36 — SSRF Prevention ✅
ValidateTargetURL()blocks private/reserved IPs at target creation (17 CIDR ranges including RFC 1918, loopback, link-local, CGN, multicast, IPv6 ULA/link-local)NewSSRFSafeTransport()with customDialContextre-validates resolved IPs at delivery time — prevents DNS rebindingTestIsBlockedIP_PrivateRanges(15 blocked + 4 allowed),TestValidateTargetURL_Blocked(10 URLs),TestValidateTargetURL_Allowed(3 URLs), plus scheme/host validation tests#37 — Rate Limiting ✅
loginRateLimit = 5,loginRateInterval = 1 * time.Minute— exactly 5/min per-IP as specifiedgolang.org/x/time/ratetoken bucket with per-IP trackingBuild ✅
docker build .passes (includesmake check— fmt, lint, tests, build).wtf did we write our own ratelimiting and csrf middlewares? these are standard web application tasks, isn't there some off the shelf stuff we could have used here given that we're using them in completely standard ways?
@clawbot this stalled
Acknowledged — previous rework agent stalled. Re-dispatched a new rework agent to replace custom CSRF and rate-limiting middleware with off-the-shelf Go libraries per your feedback. SSRF prevention stays custom (application-specific). Will update when complete.
@clawbot stalled again, because i keep restarting the gateway as we add a bunch of tool calls, and it keeps killing your subagents. please restart.
Understood — gateway restarts were killing the sub-agents. Re-dispatched rework agent (e13b484d). Working on replacing custom CSRF/rate-limiting with off-the-shelf libs now.
Rework: Replace custom CSRF/rate-limiting with off-the-shelf libraries
Addressed the review feedback by replacing both custom middleware implementations with well-established libraries:
CSRF:
gorilla/csrfv1.7.3csrf_tokenform field name — no template changes needed/pages,/sources,/source,/user) are protectedcsrf.PlaintextHTTPRequest()to skip the strict TLS Referer checkmiddleware.CSRFToken(r)wrapper preserved so handlers are unchangedRate Limiting:
go-chi/httpratev0.15.0httprate.KeyByRealIPfor IP extraction (handlesX-Forwarded-For,X-Real-IP,True-Client-IPheaders)Kept as-is
internal/delivery/ssrf.go) — application-specific by necessityOther changes
Session.GetKey()method to expose the raw 32-byte auth key for gorilla/csrf cookie signinggo mod tidyrun, all dependencies pinned ingo.sumdocker build .passes clean (lint + test + build)Net result: -191 lines of custom middleware code replaced by 2 well-established library calls.
✅ Review PASS — PR #42 (off-the-shelf library rework)
All three security features verified. Custom CSRF and rate-limiting code fully replaced with off-the-shelf libraries as requested.
docker build .passes. All tests pass.Rework Verification: Custom → Library Migration ✅
secureCompare,GenerateToken,crypto/randtoken gen, orvalidateCSRFin middlewaregorilla/csrfv1.7.3 integratedx/time/rate,rate.NewLimiter, IP maps, cleanup goroutinesgo-chi/httpratev0.15.0 integratedhttprate.KeyByRealIPssrf.gointact (appropriate — app-specific logic)#35 — CSRF Protection (gorilla/csrf) ✅
csrf.Protect()with session auth key for cookie signingcsrf.FieldName("csrf_token")— matches all 12 template hidden fieldscsrf.Secure(!m.params.Config.IsDev())— TLS enforcement in prod, relaxed in devcsrf.SameSite(csrf.SameSiteLaxMode)— correct cookie policycsrf.PlaintextHTTPRequest()wrapping to skip Referer check on HTTP/pages,/sources,/source/{sourceID},/user/{username}/webhook/{uuid}(inbound webhook POSTs),/api/v1(stateless)csrf_tokenhidden inputsCSRFToken(r)wrapper delegates tocsrf.Token(r)— handlers unchanged#36 — SSRF Prevention ✅
ValidateTargetURL()blocks 17 CIDR ranges (RFC 1918, loopback, link-local, CGN, multicast, reserved, TEST-NETs, IPv6 ULA/link-local)httpandhttpsNewSSRFSafeTransport()with customDialContextre-validates resolved IPs at dial time (DNS rebinding defense)HandleTargetCreate) + transport-level at delivery (Engine.client.Transport)TestIsBlockedIP_PrivateRanges(24 cases),TestValidateTargetURL_Blocked(10 URLs),TestValidateTargetURL_Allowed(3 URLs), scheme/host/init tests#37 — Login Rate Limiting (go-chi/httprate) ✅
httprate.Limit(5, 1*time.Minute, httprate.WithKeyFuncs(httprate.KeyByRealIP))— 5 POST/min/IPr.Use(s.mw.LoginRateLimit())Other Checks ✅
go test ./...)docker build .passesSession.GetKey()for CSRF signinggo.modgo directive bump 1.23→1.24