Merge branch 'main' into Xe/metarefresh-randomly-refresh-header

Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
This commit is contained in:
Xe Iaso
2025-09-16 16:50:19 -04:00
committed by GitHub
35 changed files with 726 additions and 773 deletions

View File

@@ -299,6 +299,7 @@ func TestCookieSettings(t *testing.T) {
CookieDomain: "127.0.0.1",
CookiePartitioned: true,
CookieSecure: true,
CookieSameSite: http.SameSiteNoneMode,
CookieExpiration: anubis.CookieDefaultExpirationTime,
})
@@ -339,6 +340,65 @@ func TestCookieSettings(t *testing.T) {
if ckie.Secure != srv.opts.CookieSecure {
t.Errorf("wanted secure flag %v, got: %v", srv.opts.CookieSecure, ckie.Secure)
}
if ckie.SameSite != srv.opts.CookieSameSite {
t.Errorf("wanted same site option %v, got: %v", srv.opts.CookieSameSite, ckie.SameSite)
}
}
func TestCookieSettingsSameSiteNoneModeDowngradedToLaxWhenUnsecure(t *testing.T) {
pol := loadPolicies(t, "testdata/zero_difficulty.yaml", 0)
srv := spawnAnubis(t, Options{
Next: http.NewServeMux(),
Policy: pol,
CookieDomain: "127.0.0.1",
CookiePartitioned: true,
CookieSecure: false,
CookieSameSite: http.SameSiteNoneMode,
CookieExpiration: anubis.CookieDefaultExpirationTime,
})
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
defer ts.Close()
cli := httpClient(t)
chall := makeChallenge(t, ts, cli)
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
if resp.StatusCode != http.StatusFound {
resp.Write(os.Stderr)
t.Errorf("wanted %d, got: %d", http.StatusFound, resp.StatusCode)
}
var ckie *http.Cookie
for _, cookie := range resp.Cookies() {
t.Logf("%#v", cookie)
if cookie.Name == anubis.CookieName {
ckie = cookie
break
}
}
if ckie == nil {
t.Errorf("Cookie %q not found", anubis.CookieName)
return
}
if ckie.Domain != "127.0.0.1" {
t.Errorf("cookie domain is wrong, wanted 127.0.0.1, got: %s", ckie.Domain)
}
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)
}
if ckie.SameSite != http.SameSiteLaxMode {
t.Errorf("wanted same site Lax option %v, got: %v", http.SameSiteLaxMode, ckie.SameSite)
}
}
func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {

View File

@@ -43,6 +43,7 @@ type Options struct {
OpenGraph config.OpenGraph
ServeRobotsTXT bool
CookieSecure bool
CookieSameSite http.SameSite
Logger *slog.Logger
PublicUrl string
JWTRestrictionHeader string

View File

@@ -56,6 +56,8 @@ func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
var domain = s.opts.CookieDomain
var name = anubis.CookieName
var path = "/"
var sameSite = s.opts.CookieSameSite
if cookieOpts.Name != "" {
name = cookieOpts.Name
}
@@ -72,11 +74,15 @@ func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
cookieOpts.Expiry = s.opts.CookieExpiration
}
if s.opts.CookieSameSite == http.SameSiteNoneMode && !s.opts.CookieSecure {
sameSite = http.SameSiteLaxMode
}
http.SetCookie(w, &http.Cookie{
Name: name,
Value: cookieOpts.Value,
Expires: time.Now().Add(cookieOpts.Expiry),
SameSite: http.SameSiteNoneMode,
SameSite: sameSite,
Domain: domain,
Secure: s.opts.CookieSecure,
Partitioned: s.opts.CookiePartitioned,
@@ -88,6 +94,8 @@ func (s *Server) ClearCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
var domain = s.opts.CookieDomain
var name = anubis.CookieName
var path = "/"
var sameSite = s.opts.CookieSameSite
if cookieOpts.Name != "" {
name = cookieOpts.Name
}
@@ -99,13 +107,16 @@ func (s *Server) ClearCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
domain = etld
}
}
if s.opts.CookieSameSite == http.SameSiteNoneMode && !s.opts.CookieSecure {
sameSite = http.SameSiteLaxMode
}
http.SetCookie(w, &http.Cookie{
Name: name,
Value: "",
MaxAge: -1,
Expires: time.Now().Add(-1 * time.Minute),
SameSite: http.SameSiteNoneMode,
SameSite: sameSite,
Partitioned: s.opts.CookiePartitioned,
Domain: domain,
Secure: s.opts.CookieSecure,

View File

@@ -27,7 +27,7 @@
"iters_b": "Iters B",
"static_check_endpoint": "Dit is gewoon een controle-eindpunt voor uw reverse proxy om te gebruiken.",
"authorization_required": "Autorisatie vereist",
"cookies_disabled": "Uw browser is geconfigureerd om koekjes uit te schakelen. Anubis heeft koekjes nodig om er zeker van te zijn dat u een geldige klant bent. Schakel cookies in voor dit domein",
"cookies_disabled": "Uw browser is geconfigureerd om cookies uit te schakelen. Anubis heeft cookies nodig om er zeker van te zijn dat u een geldige klant bent. Schakel cookies in voor dit domein",
"access_denied": "Toegang geweigerd: foutcode",
"dronebl_entry": "DroneBL meldde een item",
"see_dronebl_lookup": "zie",
@@ -45,7 +45,7 @@
"celphase": "CELPHASE",
"js_web_crypto_error": "Uw browser heeft geen werkend web.crypto-element. Bekijkt u dit via een beveiligde context?",
"js_web_workers_error": "Je browser ondersteunt geen web-takers (Anubis gebruikt dit om te voorkomen dat je browser bevriest). Heb je een plugin zoals JShelter geïnstalleerd?",
"js_cookies_error": "Uw browser slaat geen cookies op. Anubis gebruikt cookies om te bepalen welke klanten geslaagd zijn voor uitdagingen door een ondertekend token in een koekje op te slaan. Schakel het opslaan van koekjes voor dit domein in. De namen van de koekjes die Anubis opslaat, kunnen zonder voorafgaande kennisgeving variëren. Koekjesnamen en -waarden maken geen deel uit van de openbare API.",
"js_cookies_error": "Uw browser slaat geen cookies op. Anubis gebruikt cookies om te bepalen welke klanten geslaagd zijn voor uitdagingen door een ondertekend token in een cookie op te slaan. Schakel het opslaan van cookies voor dit domein in. De namen van de cookies die Anubis opslaat, kunnen zonder voorafgaande kennisgeving variëren. cookiesnamen en -waarden maken geen deel uit van de openbare API.",
"js_context_not_secure": "Je context is niet veilig!",
"js_context_not_secure_msg": "Probeer verbinding te maken via HTTPS of laat de beheerder weten dat HTTPS moet worden ingesteld. Zie <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a> voor meer informatie.",
"js_calculating": "Berekenen...",

View File

@@ -61,6 +61,8 @@ func (cr *CELRequest) ResolveName(name string) (any, bool) {
switch name {
case "remoteAddress":
return cr.Header.Get("X-Real-Ip"), true
case "contentLength":
return cr.ContentLength, true
case "host":
return cr.Host, true
case "method":

View File

@@ -19,6 +19,7 @@ func BotEnvironment() (*cel.Env, error) {
return New(
// Variables exposed to CEL programs:
cel.Variable("remoteAddress", cel.StringType),
cel.Variable("contentLength", cel.IntType),
cel.Variable("host", cel.StringType),
cel.Variable("method", cel.StringType),
cel.Variable("userAgent", cel.StringType),

View File

@@ -0,0 +1,82 @@
package store
import (
"context"
"time"
"github.com/TecharoHQ/anubis/internal/actorify"
)
type unit struct{}
type ActorifiedStore struct {
Interface
deleteActor *actorify.Actor[string, unit]
getActor *actorify.Actor[string, []byte]
setActor *actorify.Actor[*actorSetReq, unit]
cancel context.CancelFunc
}
type actorSetReq struct {
key string
value []byte
expiry time.Duration
}
func NewActorifiedStore(backend Interface) *ActorifiedStore {
ctx, cancel := context.WithCancel(context.Background())
result := &ActorifiedStore{
Interface: backend,
cancel: cancel,
}
result.deleteActor = actorify.New(ctx, result.actorDelete)
result.getActor = actorify.New(ctx, backend.Get)
result.setActor = actorify.New(ctx, result.actorSet)
return result
}
func (a *ActorifiedStore) Close() { a.cancel() }
func (a *ActorifiedStore) Delete(ctx context.Context, key string) error {
if _, err := a.deleteActor.Call(ctx, key); err != nil {
return err
}
return nil
}
func (a *ActorifiedStore) Get(ctx context.Context, key string) ([]byte, error) {
return a.getActor.Call(ctx, key)
}
func (a *ActorifiedStore) Set(ctx context.Context, key string, value []byte, expiry time.Duration) error {
if _, err := a.setActor.Call(ctx, &actorSetReq{
key: key,
value: value,
expiry: expiry,
}); err != nil {
return err
}
return nil
}
func (a *ActorifiedStore) actorDelete(ctx context.Context, key string) (unit, error) {
if err := a.Interface.Delete(ctx, key); err != nil {
return unit{}, err
}
return unit{}, nil
}
func (a *ActorifiedStore) actorSet(ctx context.Context, req *actorSetReq) (unit, error) {
if err := a.Interface.Set(ctx, req.key, req.value, req.expiry); err != nil {
return unit{}, err
}
return unit{}, nil
}

View File

@@ -48,7 +48,7 @@ func (Factory) Build(ctx context.Context, data json.RawMessage) (store.Interface
go result.cleanupThread(ctx)
return result, nil
return store.NewActorifiedStore(result), nil
}
// Valid parses and validates the bbolt store Config or returns