Harden UI: login brute-force throttle, security headers, Secure cookie flag
- New in-memory login throttle (handler/login_throttle.go): 5 failed attempts per IP or per username within 5 minutes locks that key out for 5 minutes, applied to both /login and the TOTP verification step, which previously had no rate limiting at all - router.New now adds middleware.Secure with X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and HSTS (only when cookies are Secure, implying an HTTPS deployment). No CSP: the existing templates rely on inline <script> blocks, so a CSP strict enough to matter would need 'unsafe-inline' anyway - All session/auth cookies now set Secure based on the new --cookie-secure flag / WGUI_COOKIE_SECURE env var (default true) so the session cookie is never sent over plain HTTP unless explicitly opted into an HTTP-only LAN deployment Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ATVUwTa4Pqwq26orW5BcDW
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1539c589a1
commit
4d171b4ff7
@@ -48,6 +48,17 @@ func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c
|
||||
return tmpl.ExecuteTemplate(w, "base.html", data)
|
||||
}
|
||||
|
||||
// hstsMaxAge returns a one-year HSTS max-age when cookies are marked Secure
|
||||
// (implying an HTTPS deployment), or 0 (no HSTS header at all) otherwise -
|
||||
// sending HSTS over a deliberately HTTP-only deployment would be pointless
|
||||
// and could lock out an admin who later can't reach it over HTTPS.
|
||||
func hstsMaxAge(cookieSecure bool) int {
|
||||
if cookieSecure {
|
||||
return 31536000
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// New function
|
||||
func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo.Echo {
|
||||
e := echo.New()
|
||||
@@ -57,6 +68,7 @@ func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo
|
||||
cookieStore := sessions.NewCookieStore(secret[:32], secret[32:])
|
||||
cookieStore.Options.Path = cookiePath
|
||||
cookieStore.Options.HttpOnly = true
|
||||
cookieStore.Options.Secure = util.CookieSecure
|
||||
cookieStore.MaxAge(86400 * 7)
|
||||
|
||||
e.Use(session.Middleware(cookieStore))
|
||||
@@ -166,6 +178,18 @@ func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo
|
||||
e.Logger.SetLevel(lvl)
|
||||
e.Pre(middleware.RemoveTrailingSlash())
|
||||
e.Use(middleware.LoggerWithConfig(logConfig))
|
||||
// Basic hardening headers. No Content-Security-Policy here: the
|
||||
// existing templates rely heavily on inline <script> blocks, and a CSP
|
||||
// strict enough to matter would need 'unsafe-inline' anyway, making it
|
||||
// mostly cosmetic - not worth the risk of quietly breaking the UI.
|
||||
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
|
||||
XSSProtection: "1; mode=block",
|
||||
ContentTypeNosniff: "nosniff",
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
ReferrerPolicy: "same-origin",
|
||||
HSTSMaxAge: hstsMaxAge(util.CookieSecure),
|
||||
HSTSExcludeSubdomains: false,
|
||||
}))
|
||||
e.HideBanner = true
|
||||
e.HidePort = lvl > log.INFO // hide the port output if the log level is higher than INFO
|
||||
e.Validator = NewValidator()
|
||||
|
||||
Reference in New Issue
Block a user