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. 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/.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/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/.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 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/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 bfb645bb..8b2c9aa6 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,15 +13,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 -- 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 `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)). +- 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)). - Randomly use the Refresh header instead of the meta refresh tag in the metarefresh challenge. +- Update OpenRC service to truncate the runtime directory before starting Anubis. ### Bug Fixes 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/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. 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/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx index efb0fce3..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,39 +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 | -| `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; +} 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/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 5ecb6d12..1332035a 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, 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...", 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), 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 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 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?}" } 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= diff --git a/test/ssh-ci/rigging.sh b/test/ssh-ci/rigging.sh index 9aebdb59..2e35af7e 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}" uname -av >/dev/null ssh "${Target}" mkdir -p "${RunFolder}" git archive HEAD | ssh "${Target}" tar xC "${RunFolder}" -ssh "${Target}" << EOF +ssh "${Target}" <