Weighs the soul of incoming HTTP requests to stop AI crawlers

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

authored by

Victor Fernandes and committed by
GitHub
292c470a 12453fdc

+32 -12
+2
cmd/anubis/main.go
··· 51 cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis") 52 cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support") 53 hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set") 54 ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned") 55 ed25519PrivateKeyHexFile = flag.String("ed25519-private-key-hex-file", "", "file name containing value for ed25519-private-key-hex") 56 metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to") ··· 403 Target: *target, 404 WebmasterEmail: *webmasterEmail, 405 OpenGraph: policy.OpenGraph, 406 }) 407 if err != nil { 408 log.Fatalf("can't construct libanubis.Server: %v", err)
··· 51 cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis") 52 cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support") 53 hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set") 54 + cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies") 55 ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned") 56 ed25519PrivateKeyHexFile = flag.String("ed25519-private-key-hex-file", "", "file name containing value for ed25519-private-key-hex") 57 metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to") ··· 404 Target: *target, 405 WebmasterEmail: *webmasterEmail, 406 OpenGraph: policy.OpenGraph, 407 + CookieSecure: *cookieSecure, 408 }) 409 if err != nil { 410 log.Fatalf("can't construct libanubis.Server: %v", err)
+3
docs/docs/CHANGELOG.md
··· 10 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 11 12 ## [Unreleased] 13 14 - Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)). 15 - Implement localization system. Find locale files in lib/localization/locales/.
··· 10 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 11 12 ## [Unreleased] 13 + <!-- This changes the project to: --> 14 + - 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) 15 + - Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained) 16 17 - Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)). 18 - Implement localization system. Find locale files in lib/localization/locales/.
+1
docs/docs/admin/installation.mdx
··· 67 | `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`. | 68 | `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. | 69 | `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. | 70 | `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | 71 | `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. | 72 | `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. |
··· 67 | `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`. | 68 | `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. | 69 | `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. | 70 + | `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 | 71 | `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | 72 | `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. | 73 | `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. |
+5
lib/anubis_test.go
··· 262 263 CookieDomain: "127.0.0.1", 264 CookiePartitioned: true, 265 CookieExpiration: anubis.CookieDefaultExpirationTime, 266 }) 267 ··· 308 309 if ckie.Partitioned != srv.opts.CookiePartitioned { 310 t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned) 311 } 312 } 313
··· 262 263 CookieDomain: "127.0.0.1", 264 CookiePartitioned: true, 265 + CookieSecure: true, 266 CookieExpiration: anubis.CookieDefaultExpirationTime, 267 }) 268 ··· 309 310 if ckie.Partitioned != srv.opts.CookiePartitioned { 311 t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned) 312 + } 313 + 314 + if ckie.Secure != srv.opts.CookieSecure { 315 + t.Errorf("wanted secure flag %v, got: %v", srv.opts.CookieSecure, ckie.Secure) 316 } 317 } 318
+1
lib/config.go
··· 44 StripBasePrefix bool 45 OpenGraph config.OpenGraph 46 ServeRobotsTXT bool 47 } 48 49 func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
··· 44 StripBasePrefix bool 45 OpenGraph config.OpenGraph 46 ServeRobotsTXT bool 47 + CookieSecure bool 48 } 49 50 func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
+20 -12
lib/http.go
··· 23 var domainMatchRegexp = regexp.MustCompile(`^((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) 24 25 type CookieOpts struct { 26 - Value string 27 - Host string 28 - Path string 29 - Name string 30 } 31 32 func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) { ··· 45 } 46 } 47 48 http.SetCookie(w, &http.Cookie{ 49 Name: name, 50 Value: cookieOpts.Value, 51 - Expires: time.Now().Add(s.opts.CookieExpiration), 52 - SameSite: http.SameSiteLaxMode, 53 Domain: domain, 54 Partitioned: s.opts.CookiePartitioned, 55 Path: path, 56 }) ··· 77 Value: "", 78 MaxAge: -1, 79 Expires: time.Now().Add(-1 * time.Minute), 80 - SameSite: http.SameSiteLaxMode, 81 Partitioned: s.opts.CookiePartitioned, 82 Domain: domain, 83 Path: path, 84 }) 85 } ··· 132 } 133 } 134 135 - http.SetCookie(w, &http.Cookie{ 136 - Name: anubis.TestCookieName, 137 - Value: challengeStr, 138 - Expires: time.Now().Add(30 * time.Minute), 139 - Path: "/", 140 }) 141 142 impl, ok := challenge.Get(rule.Challenge.Algorithm)
··· 23 var domainMatchRegexp = regexp.MustCompile(`^((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`) 24 25 type CookieOpts struct { 26 + Value string 27 + Host string 28 + Path string 29 + Name string 30 + Expiry time.Duration 31 } 32 33 func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) { ··· 46 } 47 } 48 49 + if cookieOpts.Expiry == 0 { 50 + cookieOpts.Expiry = s.opts.CookieExpiration 51 + } 52 + 53 http.SetCookie(w, &http.Cookie{ 54 Name: name, 55 Value: cookieOpts.Value, 56 + Expires: time.Now().Add(cookieOpts.Expiry), 57 + SameSite: http.SameSiteNoneMode, 58 Domain: domain, 59 + Secure: s.opts.CookieSecure, 60 Partitioned: s.opts.CookiePartitioned, 61 Path: path, 62 }) ··· 83 Value: "", 84 MaxAge: -1, 85 Expires: time.Now().Add(-1 * time.Minute), 86 + SameSite: http.SameSiteNoneMode, 87 Partitioned: s.opts.CookiePartitioned, 88 Domain: domain, 89 + Secure: s.opts.CookieSecure, 90 Path: path, 91 }) 92 } ··· 139 } 140 } 141 142 + s.SetCookie(w, CookieOpts{ 143 + Value: challengeStr, 144 + Host: r.Host, 145 + Path: "/", 146 + Name: anubis.TestCookieName, 147 + Expiry: 30 * time.Minute, 148 }) 149 150 impl, ok := challenge.Get(rule.Challenge.Algorithm)