Adds a Regenerate() method to the session manager that destroys the old session and creates a new one with a fresh ID, copying all session values. Called after successful login to prevent session fixation attacks.
Adds a MaxBodySize() middleware using http.MaxBytesReader to limit POST/PUT/PATCH request bodies to 1 MB. Applied to all form endpoints (/pages, /sources, /source/*).
Files Changed
internal/middleware/middleware.go — Added SecurityHeaders() and MaxBodySize() middleware
internal/session/session.go — Added Regenerate() method for session fixation prevention
internal/handlers/auth.go — Updated login handler to regenerate session after authentication
internal/server/routes.go — Added SecurityHeaders globally, MaxBodySize to form route groups
README.md — Documented new middleware in stack, updated Security section, moved items to completed TODO
## Summary
This PR implements three security hardening measures:
### Security Headers Middleware (closes https://git.eeqj.de/sneak/webhooker/issues/34)
Adds a `SecurityHeaders()` middleware applied globally to all routes. Every response now includes:
- `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`
- `X-Content-Type-Options: nosniff`
- `X-Frame-Options: DENY`
- `Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
### Session Fixation Prevention (closes https://git.eeqj.de/sneak/webhooker/issues/38)
Adds a `Regenerate()` method to the session manager that destroys the old session and creates a new one with a fresh ID, copying all session values. Called after successful login to prevent session fixation attacks.
### Request Body Size Limits (closes https://git.eeqj.de/sneak/webhooker/issues/39)
Adds a `MaxBodySize()` middleware using `http.MaxBytesReader` to limit POST/PUT/PATCH request bodies to 1 MB. Applied to all form endpoints (`/pages`, `/sources`, `/source/*`).
## Files Changed
- `internal/middleware/middleware.go` — Added `SecurityHeaders()` and `MaxBodySize()` middleware
- `internal/session/session.go` — Added `Regenerate()` method for session fixation prevention
- `internal/handlers/auth.go` — Updated login handler to regenerate session after authentication
- `internal/server/routes.go` — Added SecurityHeaders globally, MaxBodySize to form route groups
- `README.md` — Documented new middleware in stack, updated Security section, moved items to completed TODO
closes https://git.eeqj.de/sneak/webhooker/issues/34, closes https://git.eeqj.de/sneak/webhooker/issues/38, closes https://git.eeqj.de/sneak/webhooker/issues/39
- Add SecurityHeaders middleware applied globally: HSTS, X-Content-Type-Options,
X-Frame-Options, CSP, Referrer-Policy, and Permissions-Policy headers on every
response.
- Add session regeneration (Regenerate method) after successful login to prevent
session fixation attacks. Old session is destroyed and a new ID is issued.
- Add MaxBodySize middleware using http.MaxBytesReader to limit POST/PUT/PATCH
request bodies to 1 MB on all form endpoints (/pages, /sources, /source/*).
- Update README.md: document SecurityHeaders and MaxBodySize in the middleware
stack, update Security section, move security headers to completed TODO.
Closes#34, closes#38, closes#39
README updated accurately with new middleware stack order and security notes ✅
Branch is up to date with main (no rebase needed) ✅
## ✅ Review: PASS
All three security hardening issues are correctly implemented:
### [#34](https://git.eeqj.de/sneak/webhooker/issues/34) — Security Headers Middleware
- All 6 required headers present: HSTS (with `includeSubDomains; preload`), `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, CSP (`default-src 'self'`), `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy`
- Applied globally via `s.router.Use(s.mw.SecurityHeaders())` — positioned correctly after RequestID and before Logging
### [#38](https://git.eeqj.de/sneak/webhooker/issues/38) — Session Fixation Fix
- `Regenerate()` method correctly: (1) copies old session values, (2) destroys old session with `MaxAge = -1` + `ClearUser` + `Save`, (3) creates new session via `store.New`, (4) restores values, (5) applies matching session options
- Session options in `Regenerate()` are identical to those in `New()` (Path `/`, MaxAge 7 days, HttpOnly, Secure in prod, SameSite Lax) ✅
- Called in `HandleLoginSubmit` after password verification but before `SetUser` — correct position
- Gracefully handles the expected error from `store.New` when the old cookie is now invalid
### [#39](https://git.eeqj.de/sneak/webhooker/issues/39) — Body Size Limits
- `MaxBodySize` middleware uses `http.MaxBytesReader` for POST/PUT/PATCH methods
- Applied to all three form route groups: `/pages`, `/sources`, `/source/{sourceID}`
- 1 MB limit is reasonable and defined as a named constant
### Integrity Checks
- No test files modified ✅
- No linter config (`.golangci.yml`) changed ✅
- No Makefile/Dockerfile/CI changes ✅
- `docker build .` passes (includes `make check`: fmt, lint, test, build) ✅
- README updated accurately with new middleware stack order and security notes ✅
- Branch is up to date with `main` (no rebase needed) ✅
<!-- session: agent:sdlc-manager:subagent:8a100576-59ec-4733-88bc-721f596caf2e -->
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.
Summary
This PR implements three security hardening measures:
Security Headers Middleware (closes #34)
Adds a
SecurityHeaders()middleware applied globally to all routes. Every response now includes:Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'Referrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=()Session Fixation Prevention (closes #38)
Adds a
Regenerate()method to the session manager that destroys the old session and creates a new one with a fresh ID, copying all session values. Called after successful login to prevent session fixation attacks.Request Body Size Limits (closes #39)
Adds a
MaxBodySize()middleware usinghttp.MaxBytesReaderto limit POST/PUT/PATCH request bodies to 1 MB. Applied to all form endpoints (/pages,/sources,/source/*).Files Changed
internal/middleware/middleware.go— AddedSecurityHeaders()andMaxBodySize()middlewareinternal/session/session.go— AddedRegenerate()method for session fixation preventioninternal/handlers/auth.go— Updated login handler to regenerate session after authenticationinternal/server/routes.go— Added SecurityHeaders globally, MaxBodySize to form route groupsREADME.md— Documented new middleware in stack, updated Security section, moved items to completed TODOcloses #34, closes #38, closes #39
PR created implementing three security hardening issues:
Regenerate()method destroys old session, creates new ID, copies values)http.MaxBytesReader) on form POST endpointsAll tests pass.
docker build .succeeds. README updated with new middleware documentation.✅ Review: PASS
All three security hardening issues are correctly implemented:
#34 — Security Headers Middleware
includeSubDomains; preload),X-Content-Type-Options: nosniff,X-Frame-Options: DENY, CSP (default-src 'self'),Referrer-Policy: strict-origin-when-cross-origin,Permissions-Policys.router.Use(s.mw.SecurityHeaders())— positioned correctly after RequestID and before Logging#38 — Session Fixation Fix
Regenerate()method correctly: (1) copies old session values, (2) destroys old session withMaxAge = -1+ClearUser+Save, (3) creates new session viastore.New, (4) restores values, (5) applies matching session optionsRegenerate()are identical to those inNew()(Path/, MaxAge 7 days, HttpOnly, Secure in prod, SameSite Lax) ✅HandleLoginSubmitafter password verification but beforeSetUser— correct positionstore.Newwhen the old cookie is now invalid#39 — Body Size Limits
MaxBodySizemiddleware useshttp.MaxBytesReaderfor POST/PUT/PATCH methods/pages,/sources,/source/{sourceID}Integrity Checks
.golangci.yml) changed ✅docker build .passes (includesmake check: fmt, lint, test, build) ✅main(no rebase needed) ✅