From 63591866aa1b7bfef869630ba196885baf690e50 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 12 Sep 2025 12:43:08 -0400 Subject: [PATCH 01/14] fix(decaymap): fix lock convoy (#1106) * fix(decaymap): fix lock convoy Ref #1103 This uses the actor pattern to delay deletion instead of making things fight over a lock. It also properly fixes locking logic to prevent the convoy problem. Signed-off-by: Xe Iaso * docs: update CHANGELOG Signed-off-by: Xe Iaso --------- Signed-off-by: Xe Iaso --- decaymap/decaymap.go | 123 +++++++++++++++++++++++++++++--------- decaymap/decaymap_test.go | 15 +++++ docs/docs/CHANGELOG.md | 1 + 3 files changed, 112 insertions(+), 27 deletions(-) diff --git a/decaymap/decaymap.go b/decaymap/decaymap.go index a6b67274..dd3ea41f 100644 --- a/decaymap/decaymap.go +++ b/decaymap/decaymap.go @@ -14,6 +14,12 @@ func Zilch[T any]() T { type Impl[K comparable, V any] struct { data map[K]decayMapEntry[V] lock sync.RWMutex + + // deleteCh receives decay-deletion requests from readers. + deleteCh chan deleteReq[K] + // stopCh stops the background cleanup worker. + stopCh chan struct{} + wg sync.WaitGroup } type decayMapEntry[V any] struct { @@ -21,30 +27,38 @@ type decayMapEntry[V any] struct { expiry time.Time } +// deleteReq is a request to remove a key if its expiry timestamp still matches +// the observed one. This prevents racing with concurrent Set updates. +type deleteReq[K comparable] struct { + key K + expiry time.Time +} + // New creates a new DecayMap of key type K and value type V. // // Key types must be comparable to work with maps. func New[K comparable, V any]() *Impl[K, V] { - return &Impl[K, V]{ - data: make(map[K]decayMapEntry[V]), + m := &Impl[K, V]{ + data: make(map[K]decayMapEntry[V]), + deleteCh: make(chan deleteReq[K], 1024), + stopCh: make(chan struct{}), } + m.wg.Add(1) + go m.cleanupWorker() + return m } // expire forcibly expires a key by setting its time-to-live one second in the past. func (m *Impl[K, V]) expire(key K) bool { - m.lock.RLock() + // Use a single write lock to avoid RUnlock->Lock convoy. + m.lock.Lock() + defer m.lock.Unlock() val, ok := m.data[key] - m.lock.RUnlock() - if !ok { return false } - - m.lock.Lock() val.expiry = time.Now().Add(-1 * time.Second) m.data[key] = val - m.lock.Unlock() - return true } @@ -53,19 +67,14 @@ func (m *Impl[K, V]) expire(key K) bool { // If the value does not exist, return false. Return true after // deletion. func (m *Impl[K, V]) Delete(key K) bool { - m.lock.RLock() - _, ok := m.data[key] - m.lock.RUnlock() - - if !ok { - return false - } - + // Use a single write lock to avoid RUnlock->Lock convoy. m.lock.Lock() - delete(m.data, key) - m.lock.Unlock() - - return true + defer m.lock.Unlock() + _, ok := m.data[key] + if ok { + delete(m.data, key) + } + return ok } // Get gets a value from the DecayMap by key. @@ -81,13 +90,12 @@ func (m *Impl[K, V]) Get(key K) (V, bool) { } if time.Now().After(value.expiry) { - m.lock.Lock() - // Since previously reading m.data[key], the value may have been updated. - // Delete the entry only if the expiry time is still the same. - if m.data[key].expiry.Equal(value.expiry) { - delete(m.data, key) + // Defer decay deletion to the background worker to avoid convoy. + select { + case m.deleteCh <- deleteReq[K]{key: key, expiry: value.expiry}: + default: + // Channel full: drop request; a future Cleanup() or Get will retry. } - m.lock.Unlock() return Zilch[V](), false } @@ -125,3 +133,64 @@ func (m *Impl[K, V]) Len() int { defer m.lock.RUnlock() return len(m.data) } + +// Close stops the background cleanup worker. It's optional to call; maps live +// for the process lifetime in many cases. Call in tests or when you know you no +// longer need the map to avoid goroutine leaks. +func (m *Impl[K, V]) Close() { + close(m.stopCh) + m.wg.Wait() +} + +// cleanupWorker batches decay deletions to minimize lock contention. +func (m *Impl[K, V]) cleanupWorker() { + defer m.wg.Done() + batch := make([]deleteReq[K], 0, 64) + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() + + flush := func() { + if len(batch) == 0 { + return + } + m.applyDeletes(batch) + // reset batch without reallocating + batch = batch[:0] + } + + for { + select { + case req := <-m.deleteCh: + batch = append(batch, req) + case <-ticker.C: + flush() + case <-m.stopCh: + // Drain any remaining requests then exit + for { + select { + case req := <-m.deleteCh: + batch = append(batch, req) + default: + flush() + return + } + } + } + } +} + +func (m *Impl[K, V]) applyDeletes(batch []deleteReq[K]) { + now := time.Now() + m.lock.Lock() + for _, req := range batch { + entry, ok := m.data[req.key] + if !ok { + continue + } + // Only delete if the expiry is unchanged and already past. + if entry.expiry.Equal(req.expiry) && now.After(entry.expiry) { + delete(m.data, req.key) + } + } + m.lock.Unlock() +} diff --git a/decaymap/decaymap_test.go b/decaymap/decaymap_test.go index c1830ed4..e9bc824d 100644 --- a/decaymap/decaymap_test.go +++ b/decaymap/decaymap_test.go @@ -7,6 +7,7 @@ import ( func TestImpl(t *testing.T) { dm := New[string, string]() + t.Cleanup(dm.Close) dm.Set("test", "hi", 5*time.Minute) @@ -28,10 +29,24 @@ func TestImpl(t *testing.T) { if ok { t.Error("got value even though it was supposed to be expired") } + + // Deletion of expired entries after Get is deferred to a background worker. + // Assert it eventually disappears from the map. + deadline := time.Now().Add(200 * time.Millisecond) + for time.Now().Before(deadline) { + if dm.Len() == 0 { + break + } + time.Sleep(5 * time.Millisecond) + } + if dm.Len() != 0 { + t.Fatalf("expected background cleanup to remove expired key; len=%d", dm.Len()) + } } func TestCleanup(t *testing.T) { dm := New[string, string]() + t.Cleanup(dm.Close) dm.Set("test1", "hi1", 1*time.Second) dm.Set("test2", "hi2", 2*time.Second) diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index e5c6cea7..8f717c6d 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +- Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)) - Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086)) - Add validation warning when persistent storage is used without setting signing keys - Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925)) From 401e18f29fa2f956405df089cea7d559491c8df4 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 12 Sep 2025 14:35:22 -0400 Subject: [PATCH 02/14] feat(store/bbolt): implement actor pattern (#1107) * feat(store/bbolt): implement actor pattern Signed-off-by: Xe Iaso * docs(internal/actorify): document package Signed-off-by: Xe Iaso * Update metadata check-spelling run (pull_request) for Xe/actorify Signed-off-by: check-spelling-bot on-behalf-of: @check-spelling --------- Signed-off-by: Xe Iaso Signed-off-by: check-spelling-bot --- .github/actions/spelling/expect.txt | 4 ++ docs/docs/CHANGELOG.md | 11 +-- internal/actorify/actorify.go | 107 ++++++++++++++++++++++++++++ lib/store/actorifiedstore.go | 82 +++++++++++++++++++++ lib/store/bbolt/factory.go | 2 +- 5 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 internal/actorify/actorify.go create mode 100644 lib/store/actorifiedstore.go diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 9cd15606..f5bb8ffb 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -1,4 +1,7 @@ acs +Actorified +actorifiedstore +actorify Aibrew alibaba alrest @@ -157,6 +160,7 @@ ifm Imagesift imgproxy impressum +inbox inp internets IPTo diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 8f717c6d..a5be9d55 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,13 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 -- Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)) -- Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086)) -- Add validation warning when persistent storage is used without setting signing keys -- Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925)) +- Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)). +- Fix lock convoy problem in bbolt by implementing the actor pattern ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)). +- Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086)). +- Add validation warning when persistent storage is used without setting signing keys. +- Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925)). - Add the [`s3api` storage backend](./admin/policies.mdx#s3api) to allow Anubis to use S3 API compatible object storage as its storage backend. - Make `cmd/containerbuild` support commas for separating elements of the `--docker-tags` argument as well as newlines. -- Add the `DIFFICULTY_IN_JWT` option, which allows one to add the `difficulty` field in the JWT claims which indicates the difficulty of the token ([#1063](https://github.com/TecharoHQ/anubis/pull/1063)) +- Add the `DIFFICULTY_IN_JWT` option, which allows one to add the `difficulty` field in the JWT claims which indicates the difficulty of the token ([#1063](https://github.com/TecharoHQ/anubis/pull/1063)). - Ported the client-side JS to TypeScript to avoid egregious errors in the future. - Fixes concurrency problems with very old browsers ([#1082](https://github.com/TecharoHQ/anubis/issues/1082)). diff --git a/internal/actorify/actorify.go b/internal/actorify/actorify.go new file mode 100644 index 00000000..907a2cb9 --- /dev/null +++ b/internal/actorify/actorify.go @@ -0,0 +1,107 @@ +// Package actorify lets you transform a parallel operation into a serialized +// operation via the Actor pattern[1]. +// +// [1]: https://en.wikipedia.org/wiki/Actor_model +package actorify + +import ( + "context" + "errors" +) + +func z[Z any]() Z { + var z Z + return z +} + +var ( + // ErrActorDied is returned when the actor inbox or reply channel was closed. + ErrActorDied = errors.New("actorify: the actor inbox or reply channel was closed") +) + +// Handler is a function alias for the underlying logic the Actor should call. +type Handler[Input, Output any] func(ctx context.Context, input Input) (Output, error) + +// Actor is a serializing wrapper that runs a function in a background goroutine. +// Whenever the Call method is invoked, a message is sent to the actor's inbox and then +// the callee waits for a response. Depending on how busy the actor is, this may take +// a moment. +type Actor[Input, Output any] struct { + handler Handler[Input, Output] + inbox chan *message[Input, Output] +} + +type message[Input, Output any] struct { + ctx context.Context + arg Input + reply chan reply[Output] +} + +type reply[Output any] struct { + output Output + err error +} + +// New constructs a new Actor and starts its background thread. Cancel the context and you cancel +// the Actor. +func New[Input, Output any](ctx context.Context, handler Handler[Input, Output]) *Actor[Input, Output] { + result := &Actor[Input, Output]{ + handler: handler, + inbox: make(chan *message[Input, Output], 32), + } + + go result.handle(ctx) + + return result +} + +func (a *Actor[Input, Output]) handle(ctx context.Context) { + for { + select { + case <-ctx.Done(): + close(a.inbox) + return + case msg, ok := <-a.inbox: + if !ok { + if msg.reply != nil { + close(msg.reply) + } + + return + } + + result, err := a.handler(msg.ctx, msg.arg) + + reply := reply[Output]{ + output: result, + err: err, + } + + msg.reply <- reply + } + } +} + +// Call calls the Actor with a given Input and returns the handler's Output. +// +// This only works with unary functions by design. If you need to have more inputs, define +// a struct type to use as a container. +func (a *Actor[Input, Output]) Call(ctx context.Context, input Input) (Output, error) { + replyCh := make(chan reply[Output]) + + a.inbox <- &message[Input, Output]{ + arg: input, + reply: replyCh, + } + + select { + case reply, ok := <-replyCh: + if !ok { + return z[Output](), ErrActorDied + } + + return reply.output, reply.err + case <-ctx.Done(): + return z[Output](), context.Cause(ctx) + } +} diff --git a/lib/store/actorifiedstore.go b/lib/store/actorifiedstore.go new file mode 100644 index 00000000..d495736e --- /dev/null +++ b/lib/store/actorifiedstore.go @@ -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 +} diff --git a/lib/store/bbolt/factory.go b/lib/store/bbolt/factory.go index 74c924c3..04879b7e 100644 --- a/lib/store/bbolt/factory.go +++ b/lib/store/bbolt/factory.go @@ -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 From 29ae2a4b8782f61aaab16f0def64e963023ecf15 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Sat, 13 Sep 2025 18:56:54 +0800 Subject: [PATCH 03/14] feat: fallback to SameSite Lax mode if cookie is not secure (#1105) Also, will allow to set cookie `SameSite` mode on command line or environment. Note that `None` mode will be forced to ``Lax`` if cookie is set to not be secure. Signed-off-by: Valentin Lab --- cmd/anubis/main.go | 18 ++++++++++ docs/docs/CHANGELOG.md | 1 + docs/docs/admin/installation.mdx | 1 + lib/anubis_test.go | 60 ++++++++++++++++++++++++++++++++ lib/config.go | 1 + lib/http.go | 15 ++++++-- 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index 14f45609..59dd56f9 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -56,6 +56,7 @@ var ( forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header") 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") + cookieSameSite = flag.String("cookie-same-site", "None", "sets the same site option on Anubis cookies, will auto-downgrade None to Lax if cookie-secure is false. Valid values are None, Lax, Strict, and Default.") 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") @@ -143,6 +144,22 @@ func parseBindNetFromAddr(address string) (string, string) { return "", address } +func parseSameSite(s string) (http.SameSite) { + switch strings.ToLower(s) { + case "none": + return http.SameSiteNoneMode + case "lax": + return http.SameSiteLaxMode + case "strict": + return http.SameSiteStrictMode + case "default": + return http.SameSiteDefaultMode + default: + log.Fatalf("invalid cookie same-site mode: %s, valid values are None, Lax, Strict, and Default", s) + } + return http.SameSiteDefaultMode +} + func setupListener(network string, address string) (net.Listener, string) { formattedAddress := "" @@ -432,6 +449,7 @@ func main() { WebmasterEmail: *webmasterEmail, OpenGraph: policy.OpenGraph, CookieSecure: *cookieSecure, + CookieSameSite: parseSameSite(*cookieSameSite), PublicUrl: *publicUrl, JWTRestrictionHeader: *jwtRestrictionHeader, DifficultyInJWT: *difficultyInJWT, diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index a5be9d55..c5a0da83 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +- Add `COOKIE_SAME_SITE_MODE` to force anubis cookies SameSite value, and downgrade automatically from `None` to `Lax` if cookie is insecure. - Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)). - Fix lock convoy problem in bbolt by implementing the actor pattern ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)). - Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086)). diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx index efb0fce3..b4e0caa6 100644 --- a/docs/docs/admin/installation.mdx +++ b/docs/docs/admin/installation.mdx @@ -69,6 +69,7 @@ Anubis uses these environment variables for configuration: | `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_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. | | `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 | +| `COOKIE_SAME_SITE` | `None` | Controls the cookie’s [`SameSite` attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value). Allowed: `None`, `Lax`, `Strict`, `Default`. `None` permits cross-site use but modern browsers require it to be **Secure**—so if `COOKIE_SECURE=false` or you serve over plain HTTP, use `Lax` (recommended) or `Strict` or the cookie will be rejected. `Default` uses the Go runtime’s `SameSiteDefaultMode`. `None` will be downgraded to `Lax` automatically if cookie is set NOT to be secure. | | `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | | `DIFFICULTY_IN_JWT` | `false` | If set to `true`, adds the `difficulty` field into JWT claims, which indicates the difficulty the token has been generated. This may be useful for statistics and debugging. | | `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. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. | diff --git a/lib/anubis_test.go b/lib/anubis_test.go index 0f6ef7f5..133c56d0 100644 --- a/lib/anubis_test.go +++ b/lib/anubis_test.go @@ -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) { diff --git a/lib/config.go b/lib/config.go index c9437e67..7ab312d1 100644 --- a/lib/config.go +++ b/lib/config.go @@ -43,6 +43,7 @@ type Options struct { OpenGraph config.OpenGraph ServeRobotsTXT bool CookieSecure bool + CookieSameSite http.SameSite Logger *slog.Logger PublicUrl string JWTRestrictionHeader string diff --git a/lib/http.go b/lib/http.go index 61107079..7209d582 100644 --- a/lib/http.go +++ b/lib/http.go @@ -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, From 0037e214a16a316198e84e377aab3746afdff8cc Mon Sep 17 00:00:00 2001 From: agoujot <145840578+agoujot@users.noreply.github.com> Date: Sat, 13 Sep 2025 23:31:36 +0200 Subject: [PATCH 04/14] add link to preact in challenge list (#1111) Preact was added in 1.22, but it currently isn't listed in the "Challenges" page. Signed-off-by: agoujot <145840578+agoujot@users.noreply.github.com> --- docs/docs/admin/configuration/challenges/index.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/admin/configuration/challenges/index.mdx b/docs/docs/admin/configuration/challenges/index.mdx index 1fae4bd6..4695883a 100644 --- a/docs/docs/admin/configuration/challenges/index.mdx +++ b/docs/docs/admin/configuration/challenges/index.mdx @@ -3,6 +3,7 @@ Anubis supports multiple challenge methods: - [Meta Refresh](./metarefresh.mdx) +- [Preact](./preact.mdx) - [Proof of Work](./proof-of-work.mdx) Read the documentation to know which method is best for you. From 6c283d0cd9e0e93b679411f2a82547a2e3a08a90 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Sat, 13 Sep 2025 20:15:23 -0400 Subject: [PATCH 05/14] ci: add aarch64 for ssh CI (#1112) * ci: add aarch64 for ssh CI Signed-off-by: Xe Iaso * ci: better comment aile and t-elos' roles Signed-off-by: Xe Iaso * ci: fix aile Signed-off-by: Xe Iaso * ci: update ssh known hosts secret Signed-off-by: Xe Iaso * ci(ssh): replace raw connection strings with arch-quirks Signed-off-by: Xe Iaso * ci(ssh): disable this check in PRs again Signed-off-by: Xe Iaso --------- Signed-off-by: Xe Iaso --- .github/workflows/ssh-ci.yml | 8 +++++--- test/ssh-ci/rigging.sh | 25 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ssh-ci.yml b/.github/workflows/ssh-ci.yml index b396c467..d3a8661e 100644 --- a/.github/workflows/ssh-ci.yml +++ b/.github/workflows/ssh-ci.yml @@ -12,12 +12,14 @@ permissions: jobs: ssh: if: github.repository == 'TecharoHQ/anubis' - runs-on: ubuntu-24.04 + runs-on: alrest-techarohq strategy: matrix: host: - - ubuntu@riscv64.techaro.lol - - ci@ppc64le.techaro.lol + - riscv64 + - ppc64le + - aarch64-4k + - aarch64-16k steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/test/ssh-ci/rigging.sh b/test/ssh-ci/rigging.sh index 9aebdb59..284164ef 100644 --- a/test/ssh-ci/rigging.sh +++ b/test/ssh-ci/rigging.sh @@ -4,30 +4,37 @@ set -euo pipefail [ ! -z "${DEBUG:-}" ] && set -x if [ "$#" -ne 1 ]; then - echo "Usage: rigging.sh " + echo "Usage: rigging.sh " fi +declare -A Hosts + +Hosts["riscv64"]="ubuntu@riscv64.techaro.lol" # GOARCH=riscv64 GOOS=linux +Hosts["ppc64le"]="ci@ppc64le.techaro.lol" # GOARCH=ppc64le GOOS=linux +Hosts["aarch64-4k"]="rocky@192.168.2.52" # GOARCH=arm64 GOOS=linux 4k page size +Hosts["aarch64-16k"]="ci@192.168.2.28" # GOARCH=arm64 GOOS=linux 16k page size + CIRunnerImage="ghcr.io/techarohq/anubis/ci-runner:latest" RunID=${GITHUB_RUN_ID:-$(uuidgen)} RunFolder="anubis/runs/${RunID}" -Target="${1}" +Target="${Hosts["$1"]}" ssh "${Target}" uname -av ssh "${Target}" mkdir -p "${RunFolder}" git archive HEAD | ssh "${Target}" tar xC "${RunFolder}" -ssh "${Target}" << EOF +ssh "${Target}" < Date: Sat, 13 Sep 2025 23:03:46 -0400 Subject: [PATCH 06/14] ci(ssh): don't print uname -av output (#1114) Signed-off-by: Xe Iaso --- test/ssh-ci/rigging.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ssh-ci/rigging.sh b/test/ssh-ci/rigging.sh index 284164ef..2e35af7e 100644 --- a/test/ssh-ci/rigging.sh +++ b/test/ssh-ci/rigging.sh @@ -19,7 +19,7 @@ RunID=${GITHUB_RUN_ID:-$(uuidgen)} RunFolder="anubis/runs/${RunID}" Target="${Hosts["$1"]}" -ssh "${Target}" uname -av +ssh "${Target}" uname -av >/dev/null ssh "${Target}" mkdir -p "${RunFolder}" git archive HEAD | ssh "${Target}" tar xC "${RunFolder}" From 88b3e457eeeafd6e0868dc9cf6ea6398406744f0 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Sun, 14 Sep 2025 20:13:14 +0000 Subject: [PATCH 07/14] docs: update BotStopper docs based on new features Signed-off-by: Xe Iaso --- docs/docs/admin/botstopper.mdx | 38 ++++++++- docs/docs/admin/installation.mdx | 81 +++++++++++-------- docs/src/components/EnterpriseOnly/index.jsx | 11 +++ .../EnterpriseOnly/styles.module.css | 18 +++++ 4 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 docs/src/components/EnterpriseOnly/index.jsx create mode 100644 docs/src/components/EnterpriseOnly/styles.module.css diff --git a/docs/docs/admin/botstopper.mdx b/docs/docs/admin/botstopper.mdx index 4175ed43..d98df8db 100644 --- a/docs/docs/admin/botstopper.mdx +++ b/docs/docs/admin/botstopper.mdx @@ -126,6 +126,34 @@ Your directory tree should look like this, assuming your data is in `./your_fold For an example directory tree using some off-the-shelf images the Tango icon set, see the [testdata](https://github.com/TecharoHQ/botstopper/tree/main/testdata/static/img) folder. +### Header-based overlay dispatch + +If you run BotStopper in a multi-tenant environment where each tenant needs its own branding, BotStopper supports the ability to use request header values to direct asset reads to different folders under your `OVERLAY_FOLDER`. One of the most common ways to do this is based on the HTTP Host of the request. For example, if you set `ASSET_LOOKUP_HEADER=Host` in BotStopper's environment: + +```text +$OVERLAY_FOLDER +├── static +│ ├── css +│ │ ├── custom.css +│ │ └── eyesore.css +│ └── img +│ ├── happy.webp +│ ├── pensive.webp +│ └── reject.webp +└── test.anubis.techaro.lol + └── static + ├── css + │ └── custom.css + └── img + ├── happy.webp + ├── pensive.webp + └── reject.webp +``` + +Requests to `test.anubis.techaro.lol` will load assets in `$OVERLAY_FOLDER/test.anubis.techaro.lol/static` and all other requests will load them from `$OVERLAY_FOLDER/static`. + +For an example, look at [the testdata folder in the BotStopper repo](https://github.com/TecharoHQ/botstopper/tree/main/testdata). + ### Custom CSS CSS customization is done mainly with CSS variables. View [the example custom CSS file](https://github.com/TecharoHQ/botstopper/blob/main/testdata/static/css/custom.css) for more information about what can be customized. @@ -199,7 +227,9 @@ $ du -hs * ## Custom HTML templates -If you need to completely control the HTML layout of all Anubis pages, you can customize the entire page with `USE_TEMPLATES=true`. This uses Go's standard library [html/template](https://pkg.go.dev/html/template) package to template HTML responses. In order to use this, you must define the following templates: +If you need to completely control the HTML layout of all Anubis pages, you can customize the entire page with `USE_TEMPLATES=true`. This uses Go's standard library [html/template](https://pkg.go.dev/html/template) package to template HTML responses. Your templates can contain whatever HTML you want. The only catch is that you MUST include `{{ .Head }}` in the `` element for challenge pages, and you MUST include `{{ .Body }}` in the `` element for all pages. + +In order to use this, you must define the following templates: | Template path | Usage | | :----------------------------------------- | :---------------------------------------------- | @@ -207,6 +237,12 @@ If you need to completely control the HTML layout of all Anubis pages, you can c | `$OVERLAY_FOLDER/templates/error.tmpl` | Error pages | | `$OVERLAY_FOLDER/templates/impressum.tmpl` | [Impressum](./configuration/impressum.mdx) page | +:::note + +Currently HTML templates don't work together with [Header-based overlay dispatch](#header-based-overlay-dispatch). This is a known issue that will be fixed soon. If you enable header-based overlay dispatch, BotStopper will use the global `templates` folder instead of using the templates present in the overlay. + +::: + Here are minimal (but working) examples for each template:
diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx index b4e0caa6..b82fe7b9 100644 --- a/docs/docs/admin/installation.mdx +++ b/docs/docs/admin/installation.mdx @@ -2,8 +2,16 @@ title: Setting up Anubis --- +import EnterpriseOnly from "@site/src/components/EnterpriseOnly"; import RandomKey from "@site/src/components/RandomKey"; +export const EO = () => ( + <> + +
+ +); + Anubis is meant to sit between your reverse proxy (such as Nginx or Caddy) and your target service. One instance of Anubis must be used per service you are protecting.
@@ -58,40 +66,45 @@ Currently the following settings are configurable via the policy file: Anubis uses these environment variables for configuration: -| Environment Variable | Default value | Explanation | -| :----------------------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. | -| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` | -| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. | -| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.

Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. | -| `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_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. | -| `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 | -| `COOKIE_SAME_SITE` | `None` | Controls the cookie’s [`SameSite` attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value). Allowed: `None`, `Lax`, `Strict`, `Default`. `None` permits cross-site use but modern browsers require it to be **Secure**—so if `COOKIE_SECURE=false` or you serve over plain HTTP, use `Lax` (recommended) or `Strict` or the cookie will be rejected. `Default` uses the Go runtime’s `SameSiteDefaultMode`. `None` will be downgraded to `Lax` automatically if cookie is set NOT to be secure. | -| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | -| `DIFFICULTY_IN_JWT` | `false` | If set to `true`, adds the `difficulty` field into JWT claims, which indicates the difficulty the token has been generated. This may be useful for statistics and debugging. | -| `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. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. 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. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. | -| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. | -| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. | -| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. | -| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. | -| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. | -| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. | -| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. | -| `PUBLIC_URL` | unset | The externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for Traefik forwardAuth). | -| `REDIRECT_DOMAINS` | unset | If set, restrict the domains that Anubis can redirect to when passing a challenge.

If this is unset, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.

Note that if you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here. | -| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. | -| `SLOG_LEVEL` | `INFO` | The log level for structured logging. Valid values are `DEBUG`, `INFO`, `WARN`, and `ERROR`. Set to `DEBUG` to see all requests, evaluations, and detailed diagnostic information. | -| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. | -| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. | -| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. | -| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. | -| `USE_SIMPLIFIED_EXPLANATION` | false | If set to `true`, replaces the text when clicking "Why am I seeing this?" with a more simplified text for a non-tech-savvy audience. | -| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. | -| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. | +| Environment Variable | Default value | Explanation | +| :----------------------------- | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ASSET_LOOKUP_HEADER` | unset | If set, use the contents of this header in requests when looking up custom assets in `OVERLAY_FOLDER`. See [Header-based overlay dispatch](./botstopper.mdx#header-based-overlay-dispatch) for more details. | +| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. | +| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` | +| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. | +| `CHALLENGE_TITLE` | unset | If set, override the translation stack to show a custom title for challenge pages such as "Making sure your connection is secure!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. | +| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.

Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. | +| `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_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. | +| `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 | +| `COOKIE_SAME_SITE` | `None` | Controls the cookie’s [`SameSite` attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value). Allowed: `None`, `Lax`, `Strict`, `Default`. `None` permits cross-site use but modern browsers require it to be **Secure**—so if `COOKIE_SECURE=false` or you serve over plain HTTP, use `Lax` (recommended) or `Strict` or the cookie will be rejected. `Default` uses the Go runtime’s `SameSiteDefaultMode`. `None` will be downgraded to `Lax` automatically if cookie is set NOT to be secure. | +| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | +| `DIFFICULTY_IN_JWT` | `false` | If set to `true`, adds the `difficulty` field into JWT claims, which indicates the difficulty the token has been generated. This may be useful for statistics and debugging. | +| `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. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. 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. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. | +| `ERROR_TITLE` | unset | If set, override the translation stack to show a custom title for error pages such as "Something went wrong!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. | +| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. | +| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. | +| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. | +| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. | +| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. | +| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. | +| `OVERLAY_FOLDER` | unset | If set, treat the given path as an [overlay folder](./botstopper.mdx#custom-images-and-css), allowing you to customize CSS, fonts, images, and add other assets to BotStopper deployments. | +| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. | +| `PUBLIC_URL` | unset | The externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for Traefik forwardAuth). | +| `REDIRECT_DOMAINS` | unset | If set, restrict the domains that Anubis can redirect to when passing a challenge.

If this is unset, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.

Note that if you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here. | +| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. | +| `SLOG_LEVEL` | `INFO` | The log level for structured logging. Valid values are `DEBUG`, `INFO`, `WARN`, and `ERROR`. Set to `DEBUG` to see all requests, evaluations, and detailed diagnostic information. | +| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. | +| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. | +| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. | +| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. | +| `USE_SIMPLIFIED_EXPLANATION` | false | If set to `true`, replaces the text when clicking "Why am I seeing this?" with a more simplified text for a non-tech-savvy audience. | +| `USE_TEMPLATES` | false | If set to `true`, enable [custom HTML template support](./botstopper.mdx#custom-html-templates), allowing you to completely rewrite how BotStopper renders its HTML pages. | +| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. | +| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. |
Advanced configuration settings diff --git a/docs/src/components/EnterpriseOnly/index.jsx b/docs/src/components/EnterpriseOnly/index.jsx new file mode 100644 index 00000000..bfd9486c --- /dev/null +++ b/docs/src/components/EnterpriseOnly/index.jsx @@ -0,0 +1,11 @@ +import styles from './styles.module.css'; + +export default function EnterpriseOnly({ link }) { + return ( + +
+ BotStopper Only +
+
+ ); +} \ No newline at end of file diff --git a/docs/src/components/EnterpriseOnly/styles.module.css b/docs/src/components/EnterpriseOnly/styles.module.css new file mode 100644 index 00000000..f3ac7a15 --- /dev/null +++ b/docs/src/components/EnterpriseOnly/styles.module.css @@ -0,0 +1,18 @@ +.link { + text-decoration: none; +} + +.container { + background-color: #16a34a; /* green-500 */ + color: #ffffff; + font-weight: 700; + padding: 0.5rem 1rem; /* py-2 px-4 */ + border-radius: 9999px; /* rounded-full */ + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg approximation */ + display: inline-flex; /* flex */ + align-items: center; /* items-center */ +} + +.label { + line-height: 1; +} From d12993e31dc4d152828236dc8540d2e2272f6830 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Sun, 14 Sep 2025 21:41:45 -0400 Subject: [PATCH 08/14] feat(expressions): add contentLength to bot expressions (#1120) Signed-off-by: Xe Iaso --- docs/docs/CHANGELOG.md | 1 + docs/docs/admin/configuration/expressions.mdx | 1 + lib/policy/celchecker.go | 2 ++ lib/policy/expressions/environment.go | 1 + 4 files changed, 5 insertions(+) diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index c5a0da83..1322e4a3 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +- Add `contentLength` variable to bot expressions. - Add `COOKIE_SAME_SITE_MODE` to force anubis cookies SameSite value, and downgrade automatically from `None` to `Lax` if cookie is insecure. - Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)). - Fix lock convoy problem in bbolt by implementing the actor pattern ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)). diff --git a/docs/docs/admin/configuration/expressions.mdx b/docs/docs/admin/configuration/expressions.mdx index 6cb79546..6266743a 100644 --- a/docs/docs/admin/configuration/expressions.mdx +++ b/docs/docs/admin/configuration/expressions.mdx @@ -103,6 +103,7 @@ Anubis exposes the following variables to expressions: | :-------------- | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | | `headers` | `map[string, string]` | The [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) of the request being processed. | `{"User-Agent": "Mozilla/5.0 Gecko/20100101 Firefox/137.0"}` | | `host` | `string` | The [HTTP hostname](https://web.dev/articles/url-parts#host) the request is targeted to. | `anubis.techaro.lol` | +| `contentLength` | `int64` | The numerical value of the `Content-Length` header. | | `load_1m` | `double` | The current system load average over the last one minute. This is useful for making [load-based checks](#using-the-system-load-average). | | `load_5m` | `double` | The current system load average over the last five minutes. This is useful for making [load-based checks](#using-the-system-load-average). | | `load_15m` | `double` | The current system load average over the last fifteen minutes. This is useful for making [load-based checks](#using-the-system-load-average). | diff --git a/lib/policy/celchecker.go b/lib/policy/celchecker.go index a385398e..2b06392a 100644 --- a/lib/policy/celchecker.go +++ b/lib/policy/celchecker.go @@ -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": diff --git a/lib/policy/expressions/environment.go b/lib/policy/expressions/environment.go index 27f298cf..0d5022af 100644 --- a/lib/policy/expressions/environment.go +++ b/lib/policy/expressions/environment.go @@ -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), From f745d37d9006c3431bf3d435c61565250ab53a3e Mon Sep 17 00:00:00 2001 From: Anna Date: Mon, 15 Sep 2025 16:44:35 +0500 Subject: [PATCH 09/14] fix(run/openrc): truncate runtime directory before starting Anubis (#1122) If Anubis is not shut down correctly and there are leftover socket files, Anubis will refuse to start. As "checkpath -D" currently does not work as expected (https://github.com/OpenRC/openrc/issues/335), simply use "rm -rf" before starting Anubis. Signed-off-by: Anna @CyberTailor --- docs/docs/CHANGELOG.md | 1 + run/openrc/anubis.initd | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 1322e4a3..e503a788 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add the `DIFFICULTY_IN_JWT` option, which allows one to add the `difficulty` field in the JWT claims which indicates the difficulty of the token ([#1063](https://github.com/TecharoHQ/anubis/pull/1063)). - Ported the client-side JS to TypeScript to avoid egregious errors in the future. - Fixes concurrency problems with very old browsers ([#1082](https://github.com/TecharoHQ/anubis/issues/1082)). +- Update OpenRC service to truncate the runtime directory before starting Anubis. ### Bug Fixes diff --git a/run/openrc/anubis.initd b/run/openrc/anubis.initd index 3b749248..7c8c9abf 100644 --- a/run/openrc/anubis.initd +++ b/run/openrc/anubis.initd @@ -30,5 +30,6 @@ start_pre() { return 1 fi - checkpath -d -o "${command_user?}" "/run/anubis_${instance?}" + rm -rf "/run/anubis_${instance?}" + checkpath -D -o "${command_user?}" "/run/anubis_${instance?}" } From 03299024c5f3e16fc69f0e93b489ff15b170201b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:23:15 -0400 Subject: [PATCH 10/14] build(deps): bump the npm group with 2 updates (#1117) Bumps the npm group with 2 updates: [preact](https://github.com/preactjs/preact) and [postcss-import-url](https://github.com/unlight/postcss-import-url). Updates `preact` from 10.27.1 to 10.27.2 - [Release notes](https://github.com/preactjs/preact/releases) - [Commits](https://github.com/preactjs/preact/compare/10.27.1...10.27.2) Updates `postcss-import-url` from 1.0.0 to 7.2.0 - [Release notes](https://github.com/unlight/postcss-import-url/releases) - [Changelog](https://github.com/unlight/postcss-import-url/blob/master/CHANGELOG.md) - [Commits](https://github.com/unlight/postcss-import-url/commits/v7.2.0) --- updated-dependencies: - dependency-name: preact dependency-version: 10.27.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm - dependency-name: postcss-import-url dependency-version: 7.2.0 dependency-type: direct:development update-type: version-update:semver-major dependency-group: npm ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 675 +++++----------------------------------------- package.json | 4 +- 2 files changed, 64 insertions(+), 615 deletions(-) diff --git a/package-lock.json b/package-lock.json index f30877ca..5c241a9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", - "preact": "^10.27.1" + "preact": "^10.27.2" }, "devDependencies": { "cssnano": "^7.1.1", @@ -19,7 +19,7 @@ "playwright": "^1.52.0", "postcss-cli": "^11.0.1", "postcss-import": "^16.1.1", - "postcss-import-url": "^1.0.0", + "postcss-import-url": "^7.2.0", "postcss-url": "^10.1.3" } }, @@ -553,13 +553,6 @@ "node": ">=14.0.0" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC" - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -658,13 +651,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -788,18 +774,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/cli": { - "version": "0.4.4-2", - "resolved": "https://registry.npmjs.org/cli/-/cli-0.4.4-2.tgz", - "integrity": "sha512-zvFHTz+T8S4gejPHNVtdqc0mDnWmZcwd5juDF4ScZkPerNdl/9aiWcBv3l57v81jzq+n89eYLkRJdvc5aWJROA==", - "dev": true, - "dependencies": { - "glob": ">= 3.1.4" - }, - "engines": { - "node": ">=0.2.5" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -859,17 +833,6 @@ "dev": true, "license": "MIT" }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", @@ -1084,23 +1047,6 @@ "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/deep-equal": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", - "integrity": "sha512-rUCt39nKM7s6qUyYgp/reJmtXjgkOS/JbLO24DioMZaBNkD3b7C7cD3zJjSyjclEElNTpetAIRD6fMIbBIbX1Q==", - "dev": true, - "license": "MIT" - }, "node_modules/dependency-graph": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", @@ -1111,15 +1057,6 @@ "node": ">=4" } }, - "node_modules/diff": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz", - "integrity": "sha512-0bTLzyr1S59cPsgAD/lR+ivvHTbgPb+k/mUR6WGqma1J6QDU+kUegI8uQFuH/cMUNK7JGN3Tk1Y5Jf2MO85WrA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1258,16 +1195,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1295,15 +1222,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz", - "integrity": "sha512-ROG9M8tikYOuOJsvRBggh10WiQ/JebnldAwuCaQyFoiAUIE9XrYVnpznIjOQGZfCMzxzEBYHQr/LHJp3tcndzQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/fs-extra": { "version": "11.3.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", @@ -1319,13 +1237,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -1361,28 +1272,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1396,19 +1285,6 @@ "node": ">= 6" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1416,35 +1292,6 @@ "dev": true, "license": "ISC" }, - "node_modules/growl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz", - "integrity": "sha512-VWv7s1EI41AG2LiCr7uAuxWikLDN1SQOuEUc37d/P34NAIIYgkvWYngNw0d9d9iCrDFL0SYCE9UQpxhIjjtuLg==", - "dev": true - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1465,32 +1312,6 @@ "dev": true, "license": "ISC" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1570,64 +1391,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha512-mkk3vzUHFjzKjpCXeu+IjXeZD+QOTjUUdubgmHtHTDwvAO2ZTkMTTVrapts5CWz3JvJryh/4KWZpjeZrCepZ3A==", - "deprecated": "Jade has been renamed to pug, please install the latest version of pug instead of jade", - "dev": true, - "dependencies": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "bin": { - "jade": "bin/jade" - } - }, - "node_modules/jade/node_modules/commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha512-0fLycpl1UMTGX257hRsu/arL/cUbcvQM4zMKwvLvzXtfdezIV4yotPS2dYtknF+NmEfWSoCEF6+hj9XLm/6hEw==", - "dev": true, - "engines": { - "node": ">= 0.4.x" - } - }, - "node_modules/jade/node_modules/mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "license": "MIT/X11", - "engines": { - "node": "*" - } - }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/js-beautify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.4.2.tgz", - "integrity": "sha512-0o7oku1AcG66QoDIoSLCBENbyFgV6WHoqnZhC8oL4URTWYDzIXWo3tTGTLrLh6jR91miKS5YC+WBZeYC5iZMQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "config-chain": "~1.1.5", - "mkdirp": "0.3.5", - "nopt": "~2.1.1" - }, - "bin": { - "css-beautify": "js/bin/css-beautify.js", - "html-beautify": "js/bin/html-beautify.js", - "js-beautify": "js/bin/js-beautify.js" - } - }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -1654,6 +1417,13 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -1661,6 +1431,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.trim": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/lodash.trim/-/lodash.trim-4.5.1.tgz", + "integrity": "sha512-nJAlRl/K+eiOehWKDzoBVrSMhK0K3A3YQsUNXHQa5yIrKBAhsZgSu3KoAFoFT+mEgiyBHddZ0pRk1ITpIp90Wg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -1668,13 +1445,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==", - "dev": true, - "license": "ISC" - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1724,95 +1494,6 @@ "node": "*" } }, - "node_modules/mkdirp": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", - "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.0.tgz", - "integrity": "sha512-Bmjo5ZIr+RcxCKRLFpE7tpGiYemqCkWNVBx31seyUv+c45MahZcBBcoRN33yMhvOBmiq0ABhpENk19WtM3BcOw==", - "deprecated": "Mocha v1.x is no longer supported.", - "dev": true, - "dependencies": { - "commander": "2.0.0", - "debug": "*", - "diff": "1.0.7", - "glob": "3.2.3", - "growl": "1.7.x", - "jade": "0.26.3", - "mkdirp": "0.3.5" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 0.4.x" - } - }, - "node_modules/mocha/node_modules/commander": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz", - "integrity": "sha512-qebjpyeaA/nJ4w3EO2cV2++/zEkccPnjWogzA2rff+Lk8ILI75vULeTmyd4wPxWdKwtP3J+G39IXVZadh0UHyw==", - "dev": true, - "engines": { - "node": ">= 0.6.x" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", - "integrity": "sha512-WPaLsMHD1lYEqAmIQI6VOJSPwuBdGShDWnj1yUo0vQqEO809R8W3LM9OVU13CnnDhyv/EiNwOtxEW74SmrzS6w==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "BSD", - "dependencies": { - "graceful-fs": "~2.0.0", - "inherits": "2", - "minimatch": "~0.2.11" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/graceful-fs": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", - "integrity": "sha512-hcj/NTUWv+C3MbqrVb9F+aH6lvTwEHJdx2foBxlrVq5h6zE8Bfu4pv4CAAqbDcZrw/9Ak5lsRXlY9Ao8/F0Tuw==", - "deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js", - "dev": true, - "license": "BSD", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha512-zZ+Jy8lVWlvqqeM8iZB7w7KmQkoJn8djM585z88rywrEbzoqawVa9FR5p2hwD+y74nfuKOjmNvi9gtWJNLqHvA==", - "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", - "dev": true, - "license": "MIT", - "dependencies": { - "lru-cache": "2", - "sigmund": "~1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1840,19 +1521,6 @@ "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz", - "integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1886,26 +1554,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1913,83 +1561,6 @@ "dev": true, "license": "MIT" }, - "node_modules/phpfn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/phpfn/-/phpfn-1.0.0.tgz", - "integrity": "sha512-hwl2fXpXDOHCOlFDkNYXwD4FUKsddgXooF7Cb8eyynt82Ej9DLVfL6P/2d6L0uQghJq1X6DUnTd0rKM3yC8oOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "phpjs": "latest" - } - }, - "node_modules/phpjs": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/phpjs/-/phpjs-1.3.2.tgz", - "integrity": "sha512-S/V298ABWBDLsWgssVl91JmexMvTmmBR4oufeHvQU3W63+xOBluVtbVEoMyxv6ZdFuj/fx6BXe/WC6gWnO+lig==", - "deprecated": "phpjs is no longer maintained. Please use Locutus instead: https://locutus.io", - "dev": true, - "dependencies": { - "cli": "0.4.4-2", - "deep-equal": "0.1.2", - "glob": "3.2.1", - "js-beautify": "1.4.2", - "mocha": "1.17.0", - "send": "0.1.0", - "underscore": "1.5.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/phpjs/node_modules/glob": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.1.tgz", - "integrity": "sha512-wvxQZUqjkvW//FJMr/DCmPlAOFcrmf2ojnUddQTdgAQ5XkKL8ILfob0Rz+Ch/fSiols6EtiHRJS3i9W0kBRZmQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "BSD", - "dependencies": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - }, - "engines": { - "node": "*" - } - }, - "node_modules/phpjs/node_modules/graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha512-iiTUZ5vZ+2ZV+h71XAgwCSu6+NAizhFU3Yw8aC/hH5SQ3SnISqEqAek40imAFGtDcwJKNhXvSY+hzIolnLwcdQ==", - "deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js", - "dev": true, - "license": "BSD", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/phpjs/node_modules/inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha512-Al67oatbRSo3RV5hRqIoln6Y5yMVbJSIn4jEJNL7VCImzq/kLr7vvb6sFRJXqr8rpHc/2kJOM+y0sPKN47VdzA==", - "dev": true - }, - "node_modules/phpjs/node_modules/minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha512-zZ+Jy8lVWlvqqeM8iZB7w7KmQkoJn8djM585z88rywrEbzoqawVa9FR5p2hwD+y74nfuKOjmNvi9gtWJNLqHvA==", - "deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue", - "dev": true, - "license": "MIT", - "dependencies": { - "lru-cache": "2", - "sigmund": "~1.0.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2254,116 +1825,23 @@ } }, "node_modules/postcss-import-url": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-import-url/-/postcss-import-url-1.0.0.tgz", - "integrity": "sha512-sXZVBws7VJZDc3P60oTI/7hR5I5EZnjIrmm9QFQY6iwhdmRHi4o9deYoAcnV6jaKrPzzaqO8VGrxf6X2yxUfHQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/postcss-import-url/-/postcss-import-url-7.2.0.tgz", + "integrity": "sha512-El61K/5+Rv753G9mBiHyQlOIN2mBfN0YHPMXLlgIo/m1+tPDLM32wd97WoUjc8FHUnC6EyyfVA8RDuKoyuVl0Q==", "dev": true, - "license": "Beerware", + "license": "MIT", "dependencies": { - "bluebird": "^3.0.2", "http-https": "^1.0.0", - "is-url": "^1.2.1", - "phpfn": "^1.0.0", - "postcss": "^5.0.2" - } - }, - "node_modules/postcss-import-url/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-import-url/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-import-url/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "is-url": "^1.2.4", + "lodash.assign": "^4.2.0", + "lodash.trim": "^4.5.1", + "resolve-relative-url": "^1.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-import-url/node_modules/chalk/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/postcss-import-url/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-import-url/node_modules/postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "node": ">=10" }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/postcss-import-url/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss-import-url/node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^1.0.0" - }, - "engines": { - "node": ">=0.8.0" + "peerDependencies": { + "postcss": "^8.0.0" } }, "node_modules/postcss-load-config": { @@ -2850,9 +2328,9 @@ } }, "node_modules/preact": { - "version": "10.27.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.1.tgz", - "integrity": "sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==", + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", "license": "MIT", "funding": { "type": "opencollective", @@ -2869,20 +2347,21 @@ "node": ">= 0.8" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/range-parser": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz", - "integrity": "sha512-okJVEq9DbZyg+5lD8pr6ooQmeA0uu8DYIyAU7VK1WUUK7hctI1yw2ZHhKiKjB6RXaDrYRmTR4SsIHkyiQpaLMA==", + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", "dev": true, "engines": { - "node": "*" + "node": ">=0.4.x" } }, "node_modules/read-cache": { @@ -2939,6 +2418,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-relative-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-relative-url/-/resolve-relative-url-1.0.0.tgz", + "integrity": "sha512-zpcelQBAmrwckiyRmym9os1goECU3EzuTU/UrYkGzXV0i14n8FkyGUvwkOYA5klqVLq1Hz/EiFZMS7bZQdd+EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "url": "0.10.x" + } + }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", @@ -2956,34 +2445,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.1.0.tgz", - "integrity": "sha512-D/GaJQQYx7ICNq9Te5V4wHetfDQdFk3HJ4oBfDUBNW7XQmLbJ8sQDm/wFvVUUpKN8tluOnO1dFdM8KODn6D79w==", - "dev": true, - "dependencies": { - "debug": "*", - "fresh": "0.1.0", - "mime": "1.2.6", - "range-parser": "0.0.4" - } - }, - "node_modules/send/node_modules/mime": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.6.tgz", - "integrity": "sha512-S4yfg1ehMduQ5F3NeTUUWJesnut4RvymaRSatO4etOm68yZE98oCg2GtgG0coGYx03GCv240sezMvRwFk8DUKw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", - "dev": true, - "license": "ISC" - }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -2997,16 +2458,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3175,12 +2626,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/underscore": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", - "integrity": "sha512-yejOFsRnTJs0N9CK5Apzf6maDO2djxGoLLrlZlvGs2o9ZQuhIhDL18rtFyy4FBIbOkzA6+4hDgXbgz5EvDQCXQ==", - "dev": true - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -3222,6 +2667,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3247,13 +2703,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/xxhashjs": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", diff --git a/package.json b/package.json index c7d2ae0f..f9ede0ca 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,11 @@ "playwright": "^1.52.0", "postcss-cli": "^11.0.1", "postcss-import": "^16.1.1", - "postcss-import-url": "^1.0.0", + "postcss-import-url": "^7.2.0", "postcss-url": "^10.1.3" }, "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", - "preact": "^10.27.1" + "preact": "^10.27.2" } } \ No newline at end of file From 00679aed66f4eabb66847d47a0d016d2bb09a04d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:23:31 -0400 Subject: [PATCH 11/14] build(deps): bump the github-actions group with 3 updates (#1118) Bumps the github-actions group with 3 updates: [actions-hub/kubectl](https://github.com/actions-hub/kubectl), [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [github/codeql-action](https://github.com/github/codeql-action). Updates `actions-hub/kubectl` from 1.34.0 to 1.34.1 - [Release notes](https://github.com/actions-hub/kubectl/releases) - [Commits](https://github.com/actions-hub/kubectl/compare/af345ed727f0268738e65be48422e463cc67c220...f14933a23bc8c582b5aa7d108defd8e2cb9fa86d) Updates `astral-sh/setup-uv` from 6.6.1 to 6.7.0 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/557e51de59eb14aaaba2ed9621916900a91d50c6...b75a909f75acd358c2196fb9a5f1299a9a8868a4) Updates `github/codeql-action` from 3.30.1 to 3.30.3 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/f1f6e5f6af878fb37288ce1c627459e94dbf7d01...192325c86100d080feab897ff886c34abd4c83a3) --- updated-dependencies: - dependency-name: actions-hub/kubectl dependency-version: 1.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: astral-sh/setup-uv dependency-version: 6.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: github/codeql-action dependency-version: 3.30.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs-deploy.yml | 4 ++-- .github/workflows/zizmor.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 1d2fc878..812c087a 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -53,14 +53,14 @@ jobs: push: true - name: Apply k8s manifests to limsa lominsa - uses: actions-hub/kubectl@af345ed727f0268738e65be48422e463cc67c220 # v1.34.0 + uses: actions-hub/kubectl@f14933a23bc8c582b5aa7d108defd8e2cb9fa86d # v1.34.1 env: KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }} with: args: apply -k docs/manifest - name: Apply k8s manifests to limsa lominsa - uses: actions-hub/kubectl@af345ed727f0268738e65be48422e463cc67c220 # v1.34.0 + uses: actions-hub/kubectl@f14933a23bc8c582b5aa7d108defd8e2cb9fa86d # v1.34.1 env: KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }} with: diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 65527386..5f0dd142 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -21,7 +21,7 @@ jobs: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif @@ -29,7 +29,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 + uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 with: sarif_file: results.sarif category: zizmor From b2d525bba48393af24c41e88fd6b280b75248b58 Mon Sep 17 00:00:00 2001 From: Jan Pieter Waagmeester Date: Tue, 16 Sep 2025 13:53:30 +0200 Subject: [PATCH 12/14] Update nl.json removeing literal translated 'cookie' (koekje) with 'cookie' (#1126) Signed-off-by: Jan Pieter Waagmeester --- lib/localization/locales/nl.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/localization/locales/nl.json b/lib/localization/locales/nl.json index 990a0122..20710847 100644 --- a/lib/localization/locales/nl.json +++ b/lib/localization/locales/nl.json @@ -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 MDN voor meer informatie.", "js_calculating": "Berekenen...", From edb84f03b70d7730e6a091f93482fb78e463b40a Mon Sep 17 00:00:00 2001 From: NetSysFire <59517351+NetSysFire@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:14:10 +0200 Subject: [PATCH 13/14] convert issue templates into issue forms (#1115) --- .github/ISSUE_TEMPLATE/bug_report.md | 38 ------------- .github/ISSUE_TEMPLATE/bug_report.yaml | 61 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ------- .github/ISSUE_TEMPLATE/feature_request.yaml | 39 +++++++++++++ .github/ISSUE_TEMPLATE/security.md | 9 --- 6 files changed, 105 insertions(+), 67 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml delete mode 100644 .github/ISSUE_TEMPLATE/security.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 0e962a64..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: 'bug:' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000..f7f94316 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,61 @@ +name: Bug report +description: Create a report to help us improve + +body: + - type: textarea + id: description-of-bug + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: I can reliably get an error when... + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps to reproduce + description: | + Steps to reproduce the behavior. + placeholder: | + 1. Go to the following url... + 2. Click on... + 3. You get the following error: ... + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: | + A clear and concise description of what you expected to happen. + Ideally also describe *why* you expect it to happen. + placeholder: Instead of displaying an error, it would... + validations: + required: true + + - type: input + id: version-os + attributes: + label: Your operating system and its version. + description: Unsure? Visit https://whatsmyos.com/ + placeholder: Android 13 + validations: + required: true + + - type: input + id: version-browser + attributes: + label: Your browser and its version. + description: Unsure? Visit https://www.whatsmybrowser.org/ + placeholder: Firefox 142 + validations: + required: true + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context about the problem here. + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..d5395a3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security + url: https://techaro.lol/contact + about: Do not file security reports here. Email security@techaro.lol. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 42d78a25..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: 'feature:' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 00000000..64f710b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,39 @@ +name: Feature request +description: Suggest an idea for this project +title: '[Feature request] ' + +body: + - type: textarea + id: description-of-bug + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is that made you submit this report. + placeholder: I am always frustrated, when... + validations: + required: true + + - type: textarea + id: description-of-solution + attributes: + label: Solution you would like. + description: A clear and concise description of what you want to happen. + placeholder: Instead of behaving like this, there should be... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you have considered. + description: A clear and concise description of any alternative solutions or features you have considered. + placeholder: Another workaround that would work, is... + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context (such as mock-ups, proof of concepts or screenshots) about the feature request here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/security.md b/.github/ISSUE_TEMPLATE/security.md deleted file mode 100644 index 9b33a8f0..00000000 --- a/.github/ISSUE_TEMPLATE/security.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: Security report -about: Do not file security reports here. Email security@techaro.lol. -title: "security:" -labels: "" -assignees: Xe ---- - -Do not file security reports here. Email security@techaro.lol. From 26076b85205a15d8e516095db557a9b1f0087214 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:22:28 -0400 Subject: [PATCH 14/14] build(deps): bump github.com/docker/docker in /test (#1130) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 28.3.2+incompatible to 28.3.3+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v28.3.2...v28.3.3) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-version: 28.3.3+incompatible dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/go.mod | 2 +- test/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/go.mod b/test/go.mod index e61146d6..752b3280 100644 --- a/test/go.mod +++ b/test/go.mod @@ -6,7 +6,7 @@ replace github.com/TecharoHQ/anubis => .. require ( github.com/TecharoHQ/anubis v1.22.0 - github.com/docker/docker v28.3.2+incompatible + github.com/docker/docker v28.3.3+incompatible github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 github.com/google/uuid v1.6.0 ) diff --git a/test/go.sum b/test/go.sum index 3711a852..2a197d9b 100644 --- a/test/go.sum +++ b/test/go.sum @@ -82,8 +82,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA= -github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=