fix(security): harden auth cookie to always set Secure attribute
- Remove the conditional secure=false branch from setSecureCookie that allowed cookies to be issued without the Secure flag when requests arrived over HTTP from localhost or RFC 1918 private addresses - Pass the literal true to c.SetCookie directly, eliminating the dataflow path that triggered CodeQL go/cookie-secure-not-set (CWE-614) - Remove the now-dead codeql suppression comment; the root cause is gone, not merely silenced - Update setSecureCookie doc comment to reflect that Secure is always true: all major browsers (Chrome 66+, Firefox 75+, Safari 14+) honour the Secure attribute on localhost HTTP connections, and direct HTTP-on-private-IP access without TLS is an unsupported deployment model for Charon which is designed to sit behind Caddy TLS termination - Update the five TestSetSecureCookie HTTP/local tests that previously asserted Secure=false to now assert Secure=true, reflecting the elimination of the insecure code path - Add Secure=true assertion to TestClearSecureCookie to provide explicit coverage of the clear-cookie path
This commit is contained in:
@@ -127,18 +127,15 @@ func isLocalRequest(c *gin.Context) bool {
|
||||
|
||||
// setSecureCookie sets an auth cookie with security best practices
|
||||
// - HttpOnly: prevents JavaScript access (XSS protection)
|
||||
// - Secure: true for HTTPS; false for local/private network HTTP requests
|
||||
// - Secure: always true (all major browsers honour Secure on localhost HTTP;
|
||||
// HTTP-on-private-IP without TLS is an unsupported deployment)
|
||||
// - SameSite: Lax for any local/private-network request (regardless of scheme),
|
||||
// Strict otherwise (public HTTPS only)
|
||||
func setSecureCookie(c *gin.Context, name, value string, maxAge int) {
|
||||
scheme := requestScheme(c)
|
||||
secure := true
|
||||
sameSite := http.SameSiteStrictMode
|
||||
if scheme != "https" {
|
||||
sameSite = http.SameSiteLaxMode
|
||||
if isLocalRequest(c) {
|
||||
secure = false
|
||||
}
|
||||
}
|
||||
|
||||
if isLocalRequest(c) {
|
||||
@@ -149,14 +146,13 @@ func setSecureCookie(c *gin.Context, name, value string, maxAge int) {
|
||||
domain := ""
|
||||
|
||||
c.SetSameSite(sameSite)
|
||||
// secure is intentionally false for local/private network HTTP requests; always true for external or HTTPS requests.
|
||||
c.SetCookie( // codeql[go/cookie-secure-not-set]
|
||||
c.SetCookie(
|
||||
name, // name
|
||||
value, // value
|
||||
maxAge, // maxAge in seconds
|
||||
"/", // path
|
||||
domain, // domain (empty = current host)
|
||||
secure, // secure
|
||||
true, // secure
|
||||
true, // httpOnly (no JS access)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestSetSecureCookie_HTTP_Loopback_Insecure(t *testing.T) {
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
cookie := cookies[0]
|
||||
assert.False(t, cookie.Secure)
|
||||
assert.True(t, cookie.Secure)
|
||||
assert.Equal(t, http.SameSiteLaxMode, cookie.SameSite)
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ func TestSetSecureCookie_HTTP_PrivateIP_Insecure(t *testing.T) {
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
cookie := cookies[0]
|
||||
assert.False(t, cookie.Secure)
|
||||
assert.True(t, cookie.Secure)
|
||||
assert.Equal(t, http.SameSiteLaxMode, cookie.SameSite)
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ func TestSetSecureCookie_HTTP_10Network_Insecure(t *testing.T) {
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
cookie := cookies[0]
|
||||
assert.False(t, cookie.Secure)
|
||||
assert.True(t, cookie.Secure)
|
||||
assert.Equal(t, http.SameSiteLaxMode, cookie.SameSite)
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ func TestSetSecureCookie_HTTP_172Network_Insecure(t *testing.T) {
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
cookie := cookies[0]
|
||||
assert.False(t, cookie.Secure)
|
||||
assert.True(t, cookie.Secure)
|
||||
assert.Equal(t, http.SameSiteLaxMode, cookie.SameSite)
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ func TestSetSecureCookie_HTTP_IPv6ULA_Insecure(t *testing.T) {
|
||||
cookies := recorder.Result().Cookies()
|
||||
require.Len(t, cookies, 1)
|
||||
cookie := cookies[0]
|
||||
assert.False(t, cookie.Secure)
|
||||
assert.True(t, cookie.Secure)
|
||||
assert.Equal(t, http.SameSiteLaxMode, cookie.SameSite)
|
||||
}
|
||||
|
||||
@@ -439,6 +439,7 @@ func TestClearSecureCookie(t *testing.T) {
|
||||
require.Len(t, cookies, 1)
|
||||
assert.Equal(t, "auth_token", cookies[0].Name)
|
||||
assert.Equal(t, -1, cookies[0].MaxAge)
|
||||
assert.True(t, cookies[0].Secure)
|
||||
}
|
||||
|
||||
func TestAuthHandler_Login_Errors(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user