fix(lib): make Anubis less paranoid (#365)

Previously Anubis would aggressively make sure that the client cookie
matched exactly what it should. This has turned out to be too paranoid
in practice and has caused problems with Happy Eyeballs et. al.

This is a potential fix to #303 and #289.
This commit is contained in:
Xe Iaso 2025-04-25 15:02:55 -04:00 committed by GitHub
parent 24f8ba729b
commit c669b47b57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 3 additions and 52 deletions

View File

@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Set or append to `X-Forwarded-For` header unless the remote connects over a loopback address [#328](https://github.com/TecharoHQ/anubis/issues/328)
- Fixed mojeekbot user agent regex
- Added support for running anubis behind a base path (e.g. `/myapp`)
- Reduce Anubis' paranoia with user cookies ([#365](https://github.com/TecharoHQ/anubis/pull/365))
## v1.16.0

View File

@ -241,6 +241,6 @@ In case your service needs it for risk calculation reasons, Anubis exposes infor
| :---------------- | :--------------------------------------------------- | :--------------- |
| `X-Anubis-Rule` | The name of the rule that was matched | `bot/lightpanda` |
| `X-Anubis-Action` | The action that Anubis took in response to that rule | `CHALLENGE` |
| `X-Anubis-Status` | The status and how strict Anubis was in its checks | `PASS-FULL` |
| `X-Anubis-Status` | The status and how strict Anubis was in its checks | `PASS` |
Policy rules are matched using [Go's standard library regular expressions package](https://pkg.go.dev/regexp). You can mess around with the syntax at [regex101.com](https://regex101.com), make sure to select the Golang option.

View File

@ -353,48 +353,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
return
}
if randomJitter() {
r.Header.Add("X-Anubis-Status", "PASS-BRIEF")
lg.Debug("cookie is not enrolled into secondary screening")
s.ServeHTTPNext(w, r)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
lg.Debug("invalid token claims type", "path", r.URL.Path)
s.ClearCookie(w)
s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
challenge := s.challengeFor(r, rule.Challenge.Difficulty)
if claims["challenge"] != challenge {
lg.Debug("invalid challenge", "path", r.URL.Path)
s.ClearCookie(w)
s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
var nonce int
if v, ok := claims["nonce"].(float64); ok {
nonce = int(v)
}
calcString := fmt.Sprintf("%s%d", challenge, nonce)
calculated := internal.SHA256sum(calcString)
if subtle.ConstantTimeCompare([]byte(claims["response"].(string)), []byte(calculated)) != 1 {
lg.Debug("invalid response", "path", r.URL.Path)
failedValidations.Inc()
s.ClearCookie(w)
s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
slog.Debug("all checks passed")
r.Header.Add("X-Anubis-Status", "PASS-FULL")
r.Header.Add("X-Anubis-Status", "PASS")
s.ServeHTTPNext(w, r)
}

View File

@ -1,9 +0,0 @@
package lib
import (
"math/rand"
)
func randomJitter() bool {
return rand.Intn(100) > 10
}