From 292c470adafa519cd63e17fd8a3bbc33870658e4 Mon Sep 17 00:00:00 2001 From: Victor Fernandes Date: Mon, 30 Jun 2025 14:58:31 -0400 Subject: [PATCH] Set cookies to have the Secure flag default to true (#739) * Set Cookies to use the Secure Flag and default SameSite to None * Add secure flag test * Updated changelog and documentation for secure flag option --- cmd/anubis/main.go | 2 ++ docs/docs/CHANGELOG.md | 3 +++ docs/docs/admin/installation.mdx | 1 + lib/anubis_test.go | 5 +++++ lib/config.go | 1 + lib/http.go | 32 ++++++++++++++++++++------------ 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index 7bb20c6..beae534 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -51,6 +51,7 @@ var ( cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis") cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support") hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set") + cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies") ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned") ed25519PrivateKeyHexFile = flag.String("ed25519-private-key-hex-file", "", "file name containing value for ed25519-private-key-hex") metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to") @@ -403,6 +404,7 @@ func main() { Target: *target, WebmasterEmail: *webmasterEmail, OpenGraph: policy.OpenGraph, + CookieSecure: *cookieSecure, }) if err != nil { log.Fatalf("can't construct libanubis.Server: %v", err) diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 2075860..a9394bc 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +- Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) +- Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained) - Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)). - Implement localization system. Find locale files in lib/localization/locales/. diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx index 15656b2..3910bc0 100644 --- a/docs/docs/admin/installation.mdx +++ b/docs/docs/admin/installation.mdx @@ -67,6 +67,7 @@ Anubis uses these environment variables for configuration: | `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. | | `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. | | `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. | +| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false | | `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | | `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. | | `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. | diff --git a/lib/anubis_test.go b/lib/anubis_test.go index 111ca6b..7ed0426 100644 --- a/lib/anubis_test.go +++ b/lib/anubis_test.go @@ -262,6 +262,7 @@ func TestCookieSettings(t *testing.T) { CookieDomain: "127.0.0.1", CookiePartitioned: true, + CookieSecure: true, CookieExpiration: anubis.CookieDefaultExpirationTime, }) @@ -309,6 +310,10 @@ func TestCookieSettings(t *testing.T) { if ckie.Partitioned != srv.opts.CookiePartitioned { t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned) } + + if ckie.Secure != srv.opts.CookieSecure { + t.Errorf("wanted secure flag %v, got: %v", srv.opts.CookieSecure, ckie.Secure) + } } func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) { diff --git a/lib/config.go b/lib/config.go index 4c322f3..b4f1b91 100644 --- a/lib/config.go +++ b/lib/config.go @@ -44,6 +44,7 @@ type Options struct { StripBasePrefix bool OpenGraph config.OpenGraph ServeRobotsTXT bool + CookieSecure bool } func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) { diff --git a/lib/http.go b/lib/http.go index 0d8d9e7..fd2056b 100644 --- a/lib/http.go +++ b/lib/http.go @@ -23,10 +23,11 @@ import ( var domainMatchRegexp = regexp.MustCompile(`^((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) type CookieOpts struct { - Value string - Host string - Path string - Name string + Value string + Host string + Path string + Name string + Expiry time.Duration } func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) { @@ -45,12 +46,17 @@ func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) { } } + if cookieOpts.Expiry == 0 { + cookieOpts.Expiry = s.opts.CookieExpiration + } + http.SetCookie(w, &http.Cookie{ Name: name, Value: cookieOpts.Value, - Expires: time.Now().Add(s.opts.CookieExpiration), - SameSite: http.SameSiteLaxMode, + Expires: time.Now().Add(cookieOpts.Expiry), + SameSite: http.SameSiteNoneMode, Domain: domain, + Secure: s.opts.CookieSecure, Partitioned: s.opts.CookiePartitioned, Path: path, }) @@ -77,9 +83,10 @@ func (s *Server) ClearCookie(w http.ResponseWriter, cookieOpts CookieOpts) { Value: "", MaxAge: -1, Expires: time.Now().Add(-1 * time.Minute), - SameSite: http.SameSiteLaxMode, + SameSite: http.SameSiteNoneMode, Partitioned: s.opts.CookiePartitioned, Domain: domain, + Secure: s.opts.CookieSecure, Path: path, }) } @@ -132,11 +139,12 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic } } - http.SetCookie(w, &http.Cookie{ - Name: anubis.TestCookieName, - Value: challengeStr, - Expires: time.Now().Add(30 * time.Minute), - Path: "/", + s.SetCookie(w, CookieOpts{ + Value: challengeStr, + Host: r.Host, + Path: "/", + Name: anubis.TestCookieName, + Expiry: 30 * time.Minute, }) impl, ok := challenge.Get(rule.Challenge.Algorithm)