fix: always set Secure/HttpOnly/SameSite on session cookies (closes #47)
All checks were successful
check / check (push) Successful in 2m4s

Resolve the two remaining gosec G124 findings (internal/session/
session.go:84 and :128): session cookies are now unconditionally
Secure, HttpOnly, and SameSite=Strict on both the CreateSession
set-cookie path and the ClearSession delete-cookie path. gosec requires
these attributes to be constant, and there is no legitimate
configuration in which the authentication cookie should be weaker, so
the former secure toggle (wired to !config.Debug) is removed rather
than kept as a variable.

The toggle parameter on NewManager is retained as an ignored blank
parameter so existing call sites (including tests) keep compiling;
removing it is tracked as a Future Step in TODO.md. Local development
over http://localhost keeps working because browsers treat localhost as
a trustworthy origin and accept Secure cookies there.

Update TODO.md per its Workflow section: record this step as completed,
promote the manual auth/URL-flow test pass to Next Step, and correct
the stale Status text (make check is now green).
This commit is contained in:
2026-08-07 21:14:16 +07:00
parent ca15f52dcc
commit 02ca16a68a
3 changed files with 36 additions and 29 deletions

View File

@@ -94,8 +94,10 @@ func (s *Handlers) initImageService() error {
s.imgSvc = svc
s.log.Info("image service initialized")
// Initialize session manager (signing key is validated at config load time)
sessMgr, err := session.NewManager(s.config.SigningKey, !s.config.Debug)
// Initialize session manager (signing key is validated at config load
// time). The second argument is ignored: session cookies are always
// Secure/HttpOnly/SameSite=Strict.
sessMgr, err := session.NewManager(s.config.SigningKey, true)
if err != nil {
return err
}

View File

@@ -36,14 +36,18 @@ type Data struct {
// Manager handles session creation and validation using encrypted cookies.
type Manager struct {
sc *securecookie.SecureCookie
secure bool // Set Secure flag on cookies (should be true in production)
sameSite http.SameSite
sc *securecookie.SecureCookie
}
// NewManager creates a session manager with keys derived from the signing key.
// Set secure=true in production to require HTTPS for cookies.
func NewManager(signingKey string, secure bool) (*Manager, error) {
//
// Session cookies always carry the Secure, HttpOnly, and SameSite=Strict
// attributes; this cannot be configured. Browsers treat http://localhost as a
// trustworthy origin and accept Secure cookies there, so local development
// keeps working. The second parameter is the former secure toggle: it is
// ignored and retained only so existing call sites keep compiling; it will be
// removed in a follow-up change.
func NewManager(signingKey string, _ bool) (*Manager, error) {
masterKey := []byte(signingKey)
// Derive separate keys for HMAC (hash) and encryption (block)
@@ -61,9 +65,7 @@ func NewManager(signingKey string, secure bool) (*Manager, error) {
sc.MaxAge(int(SessionTTL.Seconds()))
return &Manager{
sc: sc,
secure: secure,
sameSite: http.SameSiteStrictMode,
sc: sc,
}, nil
}
@@ -87,8 +89,8 @@ func (m *Manager) CreateSession(w http.ResponseWriter) error {
Path: "/",
MaxAge: int(SessionTTL.Seconds()),
HttpOnly: true,
Secure: m.secure,
SameSite: m.sameSite,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
return nil
@@ -131,8 +133,8 @@ func (m *Manager) ClearSession(w http.ResponseWriter) {
Path: "/",
MaxAge: -1, // Delete immediately
HttpOnly: true,
Secure: m.secure,
SameSite: m.sameSite,
Secure: true,
SameSite: http.SameSiteStrictMode,
})
}