Compare commits

..

85 Commits

Author SHA1 Message Date
Jason Cameron
ff725d5cab Revert "build(deps): bump the gomod group with 6 updates (#873)"
This reverts commit 03758405d3.
2025-07-20 21:17:38 -04:00
dependabot[bot]
03758405d3 build(deps): bump the gomod group with 6 updates (#873)
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-21 00:57:00 +00:00
dependabot[bot]
eb78ccc30c build(deps-dev): bump the npm group with 3 updates (#872)
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-20 20:56:02 -04:00
dependabot[bot]
4156f84020 build(deps): bump the github-actions group with 2 updates (#871)
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-20 20:54:50 -04:00
Xe Iaso
76dcd21582 feat(expressions): add missingHeader function to bot environment (#870)
Also add tests to the bot expressions custom functions.
2025-07-20 19:09:29 -04:00
Josef Moravec
6b639cd911 feat(localization): Add Czech language translation (#849)
* feat(localization): Add Czech language translation

* feat(localization): Add record to CHANGELOG.md
2025-07-18 17:23:19 -04:00
Josef Moravec
a0aba2d74a chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-18 21:22:50 +00:00
hankskyjames777
b485499125 fix untranslated string (#850)
Signed-off-by: hankskyjames777 <54805804+hankskyjames777@users.noreply.github.com>
2025-07-18 13:52:38 -04:00
Nicholas Sherlock
300720f030 fix broken bbolt database cleanup process (#848) (#848)
Closes #820, was broken since #761
2025-07-18 13:51:32 -04:00
Xe Iaso
d6298adc6d chore: fix name of backoff-retry, expose in devcontainer
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-18 17:51:13 +00:00
Xe Iaso
1a9d8fb0cf test(ssh-ci): deflake SSH CI with exponential backoff (#859)
* test(ssh-ci): deflake SSH CI with exponential backoff

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(ssh-ci): re-disable in PRs

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-18 17:46:49 +00:00
Xe Iaso
36e25ff5f3 test: add i18n smoke test (#858)
* test: add i18n smoke test

Makes sure that all of the languages that Anubis supports show up when
the challenge page is sent to a client.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(i18n): build anubis so that the smoke test doesn't backoff timeout

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-18 13:30:46 -04:00
Emily Rowlands
c59b7179c3 fix(cmd/anubis): add signal handling to metrics server (#856)
This fixes a bug that was introduced in 68b653b0, in which the call
to metricsServer was passed a plain context.Background without
signal handling.

This commit adds back in the signal handling for the metrics server,
as well as for the Thoth client and storage backend.

Closes: #853

Signed-off-by: Emily Rowlands <emily@erowl.net>
2025-07-18 13:56:52 +00:00
Lothar Serra Mari
59515ed669 docs(known-instances): update list of known instances (#847)
Signed-off-by: Lothar Serra Mari <mail@serra.me>
2025-07-17 07:37:37 -04:00
Xe Iaso
4d6b578f93 chore: release v1.21.0 (#844)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-16 21:21:20 -04:00
Xe Iaso
2915c1d209 fix(docs/manifest): k8s typo
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-16 20:35:33 -04:00
Xe Iaso
68b653b099 feat(anubis): add /healthz route to metrics server (#843)
* feat(anubis): add /healthz route to metrics server

Also add health check test for Docker Compose and update documentation
for health checking Anubis with Docker Compose.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-17 00:31:18 +00:00
CXM
509a4f3ce8 fix(localization): fix missing string in template (#835)
* fix(localization): fix missing string in template

* chore: temp place locale
2025-07-16 19:40:27 -04:00
Marcel Bischoff
5c4d8480e6 Add services folder, add Uptime Robot policy definition (#838)
Uptime Robot is a commonly used service for tracking service
interruptions. Additional policy definitions may be beneficial for
services that do publish their IP addresses in use. The list is
additionally aggregated to slightly shorten it.

Signed-off-by: Marcel Bischoff <marcel@herrbischoff.com>
2025-07-16 09:17:48 -04:00
Xe Iaso
132b2ed853 feat(cmd/anubis): capture ja4h fingerprints (#834)
This is not used yet, but it will be part of a larger strategy around
adding/removing weight based on JA4H (and other) fingerprint matches
with Thoth.

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 23:31:33 -04:00
Xe Iaso
d28991ce8d fix: race conditions, cookie logic, and the try again button (#833)
* fix(lib): fix race condition when rendering multiple challenge pages at once

Closes #832

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(web): make try again button work

Looks like the intent of this was "try the solution again". This fix
makes the client try the challenge again.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(web): don't block a user if they have an invalid challenge cookie

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs: update CHANGELOG

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-15 00:54:08 +00:00
Xe Iaso
0fd4bb81b8 ci(docs): fix docs image tag names in the right file
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 21:28:22 +00:00
Xe Iaso
603c68fd54 ci(docs): fix docs image tag names
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 21:25:36 +00:00
Xe Iaso
c8f2eb1185 ci(docs): make a new docker image for the docs per commit sha (#831)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 17:23:23 -04:00
Xe Iaso
f6b94dca98 test: add git push smoke test (#830)
* test: add git push smoke test

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(git-push): add git config commands

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(git-push): set upstream

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(git-push): set remote branch name

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 10:42:35 -04:00
Xe Iaso
6d8b98eb3d Revert "test: add git push smoke test"
This reverts commit b9d8275234.
2025-07-14 10:26:47 -04:00
Xe Iaso
b9d8275234 test: add git push smoke test
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 10:25:40 -04:00
Xe Iaso
c2cc1df172 test: add smoke test for git clone (#828)
* test: add smoke test for git clone

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: exempt tests from spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test: rename this to git-clone

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test: pin ko setup reference

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test: don't persist credentials

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test: terminating newline

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 14:01:03 +00:00
Xe Iaso
735b2ceb14 fix(default-config): disable system load check by default (#827)
This was causing issues with git clone against highly loaded servers. I
thought that this would be pretty innocuous, but I guess I was wrong.
Oops!

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-14 13:06:56 +00:00
dependabot[bot]
2cb57fc247 build(deps-dev): bump esbuild from 0.25.5 to 0.25.6 in the npm group (#825)
Bumps the npm group with 1 update: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.25.5 to 0.25.6
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.5...v0.25.6)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-13 21:28:29 -04:00
dependabot[bot]
61ce581f36 build(deps): bump the gomod group with 3 updates (#823)
Bumps the gomod group with 3 updates: [github.com/gaissmai/bart](https://github.com/gaissmai/bart), [golang.org/x/net](https://github.com/golang/net) and [golang.org/x/text](https://github.com/golang/text).


Updates `github.com/gaissmai/bart` from 0.20.4 to 0.20.5
- [Release notes](https://github.com/gaissmai/bart/releases)
- [Commits](https://github.com/gaissmai/bart/compare/v0.20.4...v0.20.5)

Updates `golang.org/x/net` from 0.41.0 to 0.42.0
- [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0)

Updates `golang.org/x/text` from 0.26.0 to 0.27.0
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: github.com/gaissmai/bart
  dependency-version: 0.20.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gomod
- dependency-name: golang.org/x/net
  dependency-version: 0.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
- dependency-name: golang.org/x/text
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gomod
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-13 20:41:26 -04:00
Xe Iaso
3f6750ac7d chore(sponsors): add fabulous systems
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-12 23:08:30 +00:00
Xe Iaso
25d75b352a chore: release v1.21.0-pre3 2025-07-12 17:29:18 -04:00
Xe Iaso
de17823bc7 chore: release v1.21.0-pre2 (#816)
* chore: release v1.21.0-pre2

* chore: disable automated stable package builds for now

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-12 16:57:08 -04:00
Xe Iaso
29622e605d chore(docs): add link to status page in the footer (#814)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-12 13:32:24 -04:00
Jesús Martínez Novo
9fa1795db7 fix(index.templ) centered-div class usage typo (#812)
* Fix centered-div class usage in index.templ

There was a redundant <center> tag around a div with centered-div class. Well, not so redundant because a typo in the class attribute caused it to not apply.

Removed another <center> tag and replaced by a div.centered-div for consistency.

Signed-off-by: Jesús Martínez Novo <martineznovo@gmail.com>

* Fix centered-div class usage in index.templ (continuation)

Template needed to be compiled into go code...

---------

Signed-off-by: Jesús Martínez Novo <martineznovo@gmail.com>
2025-07-11 14:59:17 -04:00
Maxime Louet
fbf69680f5 chore(docs): fix typo in configuration/expressions (#811)
This example code block was missing a closing single quote.

Signed-off-by: Maxime Louet <maxime@saumon.io>
2025-07-11 13:30:27 +00:00
Lothar Serra Mari
c74de19532 docs(known-instances): add rpmfusion.org and wiki.freepascal.org to known instances (#807)
* docs(known-instances): add rpmfusion.org to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

* docs(known-instances): add wiki.freepascal.org to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

---------

Signed-off-by: Lothar Serra Mari <mail@serra.me>
2025-07-10 14:38:17 -04:00
Evgeni Golov
6dc726013a correct gitea.botPolicies extension to be yaml, not json (#800)
* correct gitea.botPolicies extension to be yaml, not json

while Anubis probably doesn't care about the extension, and would parse a JSON file just fine too, the rest of the page talks about `gitea.botPolicies.yaml`, so let's be consistent

Signed-off-by: Evgeni Golov <evgeni@golov.de>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Evgeni Golov <evgeni@golov.de>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-07-10 17:10:47 +00:00
Lothar Serra Mari
02304e8f3c docs(known-instances): update list of known instances (#801)
* docs(known-instances): add clew.se to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

* docs(known-instances): add tumfatig.net to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

---------

Signed-off-by: Lothar Serra Mari <mail@serra.me>
2025-07-10 12:56:46 -04:00
Xe Iaso
607c9791d8 chore(docs): add fly.toml file as a hail mary
Ref #799

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-10 06:05:17 -04:00
Xe Iaso
6b67be86a1 chore(docs/manifest): branded 404 page
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-09 17:06:23 -04:00
Xe Iaso
e02f017153 chore(docs/manifest): remove fastcgi from the nginx config
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-09 17:01:42 -04:00
Xe Iaso
66b39f64af docs: update CHANGELOG for language changes (#793)
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
2025-07-09 20:58:08 +00:00
Xe Iaso
944fd25924 chore: use nginx-micro to make the docs image 13 MB (#796)
* chore: use nginx-micro to make the docs image 13 MB

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: update spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-09 20:54:44 +00:00
Xe Iaso
fa3fbfb0a5 feat(blog): incident report for TI-20250709-0001 (#795)
* feat(blog): incident report for TI-20250709-0001

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

check-spelling run (pull_request) for Xe/TI-20250709-0001

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>

* fix(blog/TI-20250709-0001): add TecharoHQ/anubis#794

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(blog/TI-20250709-0001): amend grammar

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
2025-07-09 14:56:12 +00:00
Xe Iaso
3c739c1305 fix(internal/thoth): don't block Anubis starting if healthcheck fails (#794)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-09 13:31:28 +00:00
searingmoonlight
cc56baa5c7 feat(localization): Add Filipino language (#775)
* feat(localization): Add Filipino language

* Add tests

* remove duplicated string

* Minor fixes in translation

Signed-off-by: searingmoonlight <scripterrookie12@gmail.com>

---------

Signed-off-by: searingmoonlight <scripterrookie12@gmail.com>
2025-07-09 12:07:26 +00:00
giomba
053d29e0b6 feat(localization): Add Italian language translation (#778)
Signed-off-by: Giovan Battista Rolandi <giomba@glgprograms.it>
2025-07-09 11:49:19 +00:00
Sveinn í Felli
a668095c22 Create is.json (#780)
* Create is.json

Adding Icelandic translation

Signed-off-by: Sveinn í Felli <sv1@fellsnet.is>

* fix(localization): add Icelandic to manifest.json

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Sveinn í Felli <sv1@fellsnet.is>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-07-09 11:29:40 +00:00
Henri Vasserman
1c4a1aec4a feat(i18n): add Estonian locale (#783)
* feat(i18n): add et locale

* chore: update changelog

* wording

* "feature"
2025-07-09 11:18:11 +00:00
dai
5b8b6d1c94 feat(localization): add Japanese language translation (#772)
* feat(localization): add Japanese language translation

* fix(locales): add Japanese to the manifest

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(locales): fix manifest

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
Co-authored-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <xe.iaso@techaro.lol>
2025-07-09 07:08:34 -04:00
Joe Brockmeier
0cb6ef76e1 Update apache.mdx (#784)
Was missing the opening stanza to enable mod_proxy for Apache.

Signed-off-by: Joe Brockmeier <jzb@zonker.net>
2025-07-09 07:08:19 -04:00
Henri Vasserman
a900e98b8b fix(localization): HTML language header and forced-language (#787)
* fix: HTML language header and forced-language

* style(changelog): added a couple headers

* add test
2025-07-09 07:04:42 -04:00
Mahid Sheikh
e79cd93b61 docs(installation): Clarify information about private keys and multiple instances (#788)
Signed-off-by: Mahid Sheikh <mahid@standingpad.org>
2025-07-09 10:54:36 +00:00
CXM
d17fc6a174 feat(localization): add Simplified Chinese (#774) 2025-07-09 06:53:08 -04:00
Lothar Serra Mari
95768cb70f docs(known-instances): update list of known instances (#776)
* docs(known-instances): update list of known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

* Update metadata

check-spelling run (pull_request) for probably-daily-new-spotted-instances-update

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>

---------

Signed-off-by: Lothar Serra Mari <mail@serra.me>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
Co-authored-by: Lothar Serra Mari <lotharsm@users.noreply.github.com>
2025-07-07 22:01:14 +00:00
mihugo
ca61b8a05f Update apache.mdx replace nginx with Apache in place (#779)
Signed-off-by: mihugo <mike.github@m3h.com>
2025-07-07 17:17:24 -04:00
dependabot[bot]
1ea1157cd7 build(deps): bump github.com/shirou/gopsutil/v4 in the gomod group (#771)
Bumps the gomod group with 1 update: [github.com/shirou/gopsutil/v4](https://github.com/shirou/gopsutil).


Updates `github.com/shirou/gopsutil/v4` from 4.25.1 to 4.25.6
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v4.25.1...v4.25.6)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v4
  dependency-version: 4.25.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gomod
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-06 21:21:14 -04:00
dependabot[bot]
44ae5f2e2b build(deps): bump the github-actions group with 2 updates (#770)
Bumps the github-actions group with 2 updates: [dominikh/staticcheck-action](https://github.com/dominikh/staticcheck-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `dominikh/staticcheck-action` from 1.3.1 to 1.4.0
- [Release notes](https://github.com/dominikh/staticcheck-action/releases)
- [Changelog](https://github.com/dominikh/staticcheck-action/blob/master/CHANGES.md)
- [Commits](fe1dd0c365...024238d289)

Updates `github/codeql-action` from 3.29.1 to 3.29.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](39edc492db...181d5eefc2)

---
updated-dependencies:
- dependency-name: dominikh/staticcheck-action
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 3.29.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-07-06 20:51:24 -04:00
Xe Iaso
ea2e76c6ee chore: tag version 1.21.0-pre1
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-06 19:35:06 -04:00
Xe Iaso
4ea0add50d feat(lib/policy/expressions): add system load average to bot expression inputs (#766)
* feat(lib/policy/expressions): add system load average to bot expression inputs

This lets Anubis dynamically react to system load in order to
increase and decrease the required level of scrutiny. High load? More
scrutiny required. Low load? Less scrutiny required.

* docs: spell system correctly

Signed-off-by: Xe Iaso <me@xeiaso.net>

* Update metadata

check-spelling run (pull_request) for Xe/load-average

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>

* fix(default-config): don't enable low load average feature by default

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
2025-07-06 20:13:50 +00:00
XLion
289c802a0b feat(localization): Add Traditional Chinese language translation (#759)
* Add translation for Traditional Chinese

* Add translation for Traditional Chinese: test

* Add translation for Traditional Chinese: Add PR number to CHANGELOG

* Add translation for Traditional Chinese: test: remove empty lines

* Add translation for Traditional Chinese: test: remove empty lines
2025-07-06 19:59:00 +00:00
Lothar Serra Mari
543b942be1 docs(known-instances): update list of known instances (#767)
Signed-off-by: Lothar Serra Mari <mail@serra.me>
2025-07-06 19:45:18 +00:00
Lothar Serra Mari
edbe1dcfd6 feat(localization): Update German language translation (#764) 2025-07-06 11:25:05 -04:00
Xe Iaso
94db16c0df docs: add emma.pet sponsor
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-06 14:25:27 +00:00
Xe Iaso
c2f46907a1 docs: remove proof of work branding (#763)
* docs(index): start cleanup, remove proof of work from core branding

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs(index): rewrite copy, add CELPHASE illustrations

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-06 02:34:52 +00:00
Xe Iaso
6fa5b8e4e0 fix(lib/store/bbolt): run cleanup every hour instead of every 5 minutes (#762)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-06 01:19:44 +00:00
Xe Iaso
f98750b038 fix(lib/store/bbolt): use a multi-bucket flow instead of a single bucket flow (#761)
* fix(lib/store/bbolt): use a multi-bucket flow instead of a single bucket flow

Signed-off-by: Xe Iaso <me@xeiaso.net>

* Update metadata

check-spelling run (push) for Xe/optimize-bbolt

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>

* fix(lib/store/bbolt): gracefully handle the obsolete anubis bucket in cleanup

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
2025-07-06 01:16:11 +00:00
Xe Iaso
7d0c58d1a8 fix: make ogtags and dnsbl use the Store instead of memory (#760)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 16:17:46 -04:00
Xe Iaso
e870ede120 docs(known-instances): add git.aya.so
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 16:41:36 +00:00
Xe Iaso
592d1e3dfc docs(known-instances): add Pluralpedia
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 16:40:32 +00:00
Xe Iaso
f6254b4b98 docs(installation): clarify BASE_PREFIX matches the /.within.website endpoints
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 13:47:02 +00:00
Lothar Serra Mari
d19026d693 docs(known-instances): Add Duke University, coinhoards.org (and myself) to known instances (#757)
* docs(known-instances): add Duke University to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

* docs(known-instances): add fabulous.systems to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

* docs(known-instances): add coinhoards.org to known instances

Signed-off-by: Lothar Serra Mari <mail@serra.me>

* chore(spelling): exempt the known instances page

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Lothar Serra Mari <mail@serra.me>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-07-05 08:29:44 -04:00
Xe Iaso
7b72c790ab chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 12:29:19 +00:00
Xe Iaso
719a1409ca test(lib/store/bbolt): disable this test case for now
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 04:56:19 +00:00
Xe Iaso
890f21bf47 chore(devcontainer): move playwright to its own devcontainer service (#756)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-05 00:53:45 -04:00
Xe Iaso
93bfe910d8 docs(user/faq): clarify Anubis not being a cryptocurrency miner
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-04 22:49:39 +00:00
Xe Iaso
19d8de784b chore(docs/manifest): enable bbolt in an emptyDir
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-04 21:07:37 +00:00
Xe Iaso
dff2176beb feat(lib): use new challenge creation flow (#749)
* feat(decaymap): add Delete method

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(lib/challenge): refactor Validate to take ValidateInput

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(lib): implement store interface

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(lib/store): all metapackage to import all store implementations

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(policy): import all store backends

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(lib): use new challenge creation flow

Previously Anubis constructed challenge strings from request metadata.
This was a good idea in spirit, but has turned out to be a very bad idea
in practice. This new flow reuses the Store facility to dynamically
create challenge values with completely random data.

This is a fairly big rewrite of how Anubis processes challenges. Right
now it defaults to using the in-memory storage backend, but on-disk
(boltdb) and valkey-based adaptors will come soon.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(decaymap): fix documentation typo

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(lib): fix SA4004

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(lib/store): make generic storage interface test adaptor

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(decaymap): invert locking process for Delete

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(lib/store): add bbolt store implementation

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: go mod tidy

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(devcontainer): adapt to docker compose, add valkey service

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(lib): make challenges live for 30 minutes by default

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(lib/store): implement valkey backend

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(lib/store/valkey): disable tests if not using docker

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(lib/policy/config): ensure valkey stores can be loaded

Signed-off-by: Xe Iaso <me@xeiaso.net>

* Update metadata

check-spelling run (pull_request) for Xe/store-interface

Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>

* chore(devcontainer): remove port forwards because vs code handles that for you

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs(default-config): add a nudge to the storage backends section of the docs

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore(docs): listen on 0.0.0.0 for dev container support

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs(policy): document storage backends

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs: update CHANGELOG and internal links

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs(admin/policies): don't start a sentence with as

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: fixes found in review

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
2025-07-04 20:42:28 +00:00
SGHFan
506d8817d5 Update known-instances.md (#755)
Add eBird to instance list

Signed-off-by: SGHFan <62897482+SGHFan@users.noreply.github.com>
2025-07-04 19:54:26 +00:00
Duru Can Celasun
d0fae02d05 feat(localization): Add Turkish language translation (#751)
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-07-04 00:50:16 -04:00
Xe Iaso
845095c3f6 chore(robots.txt): don't block CCBot
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-04 00:23:21 +00:00
Xe Iaso
2f1e78cc6c chore(docs/manifest): allow common crawl to test with the team
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-04 00:22:43 +00:00
Xe Iaso
7c0996448a chore(default-config): allowlist common crawl (#753)
This may seem strange, but allowlisting common crawl means that scrapers
have less incentive to scrape because they can just grab the data from
common crawl instead of scraping it again.
2025-07-04 00:10:45 +00:00
143 changed files with 4925 additions and 1174 deletions

View File

@@ -3,10 +3,10 @@ FROM ghcr.io/xe/devcontainer-base/pre/go
WORKDIR /app
COPY go.mod go.sum package.json package-lock.json ./
RUN go install github.com/a-h/templ/cmd/templ \
&& npx --yes playwright@1.52.0 install --with-deps\
&& apt-get update \
&& apt-get -y install zstd brotli \
RUN apt-get update \
&& apt-get -y install zstd brotli redis \
&& mkdir -p /home/vscode/.local/share/fish \
&& chown -R vscode:vscode /home/vscode/.local/share/fish \
&& chown -R vscode:vscode /go
&& chown -R vscode:vscode /go
CMD ["/usr/bin/sleep", "infinity"]

View File

@@ -2,17 +2,15 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
"name": "Dev",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"build": {
"dockerfile": "./Dockerfile",
"context": "..",
"cacheFrom": [
"type=registry,ref=ghcr.io/techarohq/anubis/devcontainer"
]
},
"postStartCommand": "npm ci && go mod download",
"dockerComposeFile": [
"./docker-compose.yaml"
],
"service": "workspace",
"workspaceFolder": "/workspace/anubis",
"postStartCommand": "bash ./.devcontainer/poststart.sh",
"features": {
"ghcr.io/xe/devcontainer-features/ko:1.1.0": {}
"ghcr.io/xe/devcontainer-features/ko:1.1.0": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"initializeCommand": "mkdir -p ${localEnv:HOME}${localEnv:USERPROFILE}/.local/share/atuin",
"customizations": {
@@ -26,9 +24,5 @@
"redhat.vscode-yaml"
]
}
},
"forwardPorts": [
8923,
3000
]
}
}

View File

@@ -0,0 +1,26 @@
services:
playwright:
image: mcr.microsoft.com/playwright:v1.52.0-noble
init: true
network_mode: service:workspace
command:
- /bin/sh
- -c
- npx -y playwright@1.52.0 run-server --port 9001 --host 0.0.0.0
valkey:
image: valkey/valkey:8
pull_policy: always
# VS Code workspace service
workspace:
image: ghcr.io/techarohq/anubis/devcontainer
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ../:/workspace/anubis:cached
environment:
VALKEY_URL: redis://valkey:6379/0
#entrypoint: ["/usr/bin/sleep", "infinity"]
user: vscode

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
pwd
npm ci &
go mod download &
go install ./utils/cmd/... &
wait

View File

@@ -3,3 +3,4 @@ https
ssh
ubuntu
workarounds
rjack

View File

@@ -84,6 +84,7 @@
^\Q.github/workflows/spelling.yml\E$
^data/crawlers/
^docs/blog/tags\.yml$
^docs/docs/user/known-instances.md$
^docs/manifest/.*$
^docs/static/\.nojekyll$
^lib/policy/config/testdata/bad/unparseable\.json$
@@ -91,3 +92,4 @@ ignore$
robots.txt
^lib/localization/locales/.*\.json$
^lib/localization/.*_test.go$
^test/.*$

View File

@@ -1,12 +1,10 @@
acs
aeacus
Aibrew
alrest
amazonbot
anthro
anubis
anubistest
apk
Applebot
archlinux
asnc
@@ -16,12 +14,12 @@ aspirational
atuin
azuretools
badregexes
bbolt
bdba
berr
bingbot
bitcoin
Bitcoin
bitrate
blogging
Bluesky
blueskybot
boi
@@ -30,6 +28,7 @@ botstopper
BPort
Brightbot
broked
byteslice
Bytespider
cachebuster
cachediptoasn
@@ -46,11 +45,13 @@ cgr
chainguard
chall
challengemozilla
challengetest
checkpath
checkresult
chibi
cidranger
ckie
ckies
cloudflare
Codespaces
confd
@@ -58,7 +59,6 @@ connnection
containerbuild
coreutils
Cotoyogi
CRDs
Cromite
crt
Cscript
@@ -67,6 +67,7 @@ DDOS
Debian
debrpm
decaymap
devcontainers
Diffbot
discordapp
discordbot
@@ -116,6 +117,7 @@ goland
gomod
goodbot
googlebot
gopsutil
govulncheck
goyaml
GPG
@@ -127,7 +129,7 @@ Hashcash
hashrate
headermap
healthcheck
hebis
healthz
hec
hmc
hostable
@@ -143,6 +145,7 @@ Imagesift
imgproxy
impressum
inp
internets
IPTo
iptoasn
iss
@@ -156,7 +159,6 @@ jshelter
JWTs
kagi
kagibot
keikaku
Keyfunc
keypair
KHTML
@@ -170,13 +172,13 @@ lgbt
licend
licstart
lightpanda
LIMSA
limsa
Linting
linuxbrew
LLU
loadbalancer
lol
LOMINSA
lominsa
maintainership
malware
mcr
@@ -184,12 +186,13 @@ memes
metarefresh
metrix
mimi
minica
Minfilia
mistralai
Mojeek
mojeekbot
mozilla
nbf
nepeat
netsurf
nginx
nicksnyder
@@ -218,6 +221,7 @@ pipefail
pki
podkova
podman
poststart
prebaked
privkey
promauto
@@ -232,13 +236,12 @@ qwantbot
rac
rawler
rcvar
rdb
redhat
redir
redirectscheme
refactors
relayd
reputational
reqmeta
risc
ruleset
runlevels
@@ -246,23 +249,20 @@ RUnlock
runtimedir
sas
sasl
Scumm
searchbot
searx
sebest
secretplans
selfsigned
Semrush
Seo
setsebool
shellcheck
shirou
Sidetrade
simprint
sitemap
skopeo
sls
sni
Sourceware
Spambot
sparkline
spyderbot
@@ -270,6 +270,7 @@ srv
stackoverflow
startprecmd
stoppostcmd
storetest
subgrid
subr
subrequest
@@ -291,19 +292,21 @@ Tik
Timpibot
traefik
uberspace
Unbreak
unbreakdocker
unifiedjs
unixhttpd
unmarshal
unparseable
uuidgen
uvx
UXP
Valkey
valkey
Varis
Velen
vendored
vhosts
videotest
VKE
Vultr
waitloop
weblate
webmaster
@@ -317,7 +320,6 @@ wordpress
Workaround
workdir
wpbot
xcaddy
Xeact
xeiaso
xeserv
@@ -334,6 +336,7 @@ yeet
yeetfile
yourdomain
yoursite
yyz
Zenos
zizmor
zombocom

View File

@@ -1,47 +0,0 @@
name: Dev container prebuild
on:
push:
branches: ["main"]
tags: ["v*.*.*"]
jobs:
devcontainer:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: latest
- run: |
sudo apt-get update
sudo apt-get -y install skopeo
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with:
registry: ghcr.io
username: techarohq
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pre-build dev container image
uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417
with:
imageName: ghcr.io/techarohq/anubis/devcontainer
cacheFrom: ghcr.io/techarohq/anubis/devcontainer
push: always
platform: linux/amd64,linux/arm64

View File

@@ -36,6 +36,9 @@ jobs:
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ghcr.io/techarohq/anubis/docs
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
main
- name: Build and push
id: build
@@ -49,15 +52,15 @@ jobs:
platforms: linux/amd64
push: true
- name: Apply k8s manifests to aeacus
uses: actions-hub/kubectl@d50394b7d704525f93faefce1e65a6329ff67271 # v1.33.2
- name: Apply k8s manifests to limsa lominsa
uses: actions-hub/kubectl@b5b19eeb6a0ffde16637e398f8b96ef01eb8fdb7 # v1.33.3
env:
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
with:
args: apply -k docs/manifest
- name: Apply k8s manifests to aeacus
uses: actions-hub/kubectl@d50394b7d704525f93faefce1e65a6329ff67271 # v1.33.2
- name: Apply k8s manifests to limsa lominsa
uses: actions-hub/kubectl@b5b19eeb6a0ffde16637e398f8b96ef01eb8fdb7 # v1.33.3
env:
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
with:

View File

@@ -2,7 +2,7 @@ name: Docs test build
on:
pull_request:
branches: [ "main" ]
branches: ["main"]
permissions:
contents: read
@@ -24,7 +24,10 @@ jobs:
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ghcr.io/${{ github.repository }}/docs
images: ghcr.io/techarohq/anubis/docs
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
main
- name: Build and push
id: build

View File

@@ -82,7 +82,7 @@ jobs:
run: npm run test
- name: Lint with staticcheck
uses: dominikh/staticcheck-action@fe1dd0c3658873b46f8c9bb3291096a617310ca6 # v1.3.1
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
with:
version: "latest"

View File

@@ -1,8 +1,9 @@
name: Package builds (stable)
on:
release:
types: [published]
workflow_dispatch:
# release:
# types: [published]
permissions:
contents: write
@@ -13,67 +14,67 @@ jobs:
#runs-on: alrest-techarohq
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
fetch-tags: true
fetch-depth: 0
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
fetch-tags: true
fetch-depth: 0
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@main
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@main
- name: Setup Homebrew cellar cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
/home/linuxbrew/.linuxbrew/Cellar
/home/linuxbrew/.linuxbrew/bin
/home/linuxbrew/.linuxbrew/etc
/home/linuxbrew/.linuxbrew/include
/home/linuxbrew/.linuxbrew/lib
/home/linuxbrew/.linuxbrew/opt
/home/linuxbrew/.linuxbrew/sbin
/home/linuxbrew/.linuxbrew/share
/home/linuxbrew/.linuxbrew/var
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-homebrew-cellar-
- name: Setup Homebrew cellar cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
/home/linuxbrew/.linuxbrew/Cellar
/home/linuxbrew/.linuxbrew/bin
/home/linuxbrew/.linuxbrew/etc
/home/linuxbrew/.linuxbrew/include
/home/linuxbrew/.linuxbrew/lib
/home/linuxbrew/.linuxbrew/opt
/home/linuxbrew/.linuxbrew/sbin
/home/linuxbrew/.linuxbrew/share
/home/linuxbrew/.linuxbrew/var
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-homebrew-cellar-
- name: Install Brew dependencies
run: |
brew bundle
- name: Install Brew dependencies
run: |
brew bundle
- name: Setup Golang caches
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-golang-
- name: Setup Golang caches
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-golang-
- name: install node deps
run: |
npm ci
- name: install node deps
run: |
npm ci
- name: Build Packages
run: |
go tool yeet
- name: Build Packages
run: |
go tool yeet
- name: Upload released artifacts
env:
GITHUB_TOKEN: ${{ github.TOKEN }}
RELEASE_VERSION: ${{github.event.release.tag_name}}
shell: bash
run: |
RELEASE="${RELEASE_VERSION}"
cd var
for file in *; do
gh release upload $RELEASE $file
done
- name: Upload released artifacts
env:
GITHUB_TOKEN: ${{ github.TOKEN }}
RELEASE_VERSION: ${{github.event.release.tag_name}}
shell: bash
run: |
RELEASE="${RELEASE_VERSION}"
cd var
for file in *; do
gh release upload $RELEASE $file
done

45
.github/workflows/smoke-tests.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Smoke tests
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
jobs:
smoke-test:
strategy:
matrix:
test:
- git-clone
- git-push
- healthcheck
- i18n
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: latest
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: stable
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
- name: Install utils
run: |
go install ./utils/cmd/...
- name: Run test
run: |
cd test/${{ matrix.test }}
backoff-retry --try-count 10 ./test.sh

View File

@@ -25,13 +25,19 @@ jobs:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Install CI target SSH key
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0
with:
key: ${{ secrets.CI_SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: stable
- name: Run CI
run: bash test/ssh-ci/rigging.sh ${{ matrix.host }}
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
env:
GITHUB_RUN_ID: ${{ github.run_id }}

View File

@@ -21,7 +21,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@7edac99f961f18b581bbd960d59d049f04c0002f # v6.4.1
- 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@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
sarif_file: results.sarif
category: zizmor

View File

@@ -41,6 +41,20 @@ Anubis is brought to you by sponsors and donors like:
<a href="https://wildbase.xyz/">
<img src="./docs/static/img/sponsors/wildbase-logo.webp" alt="Wildbase" height="64">
</a>
<a href="https://emma.pet">
<img
src="./docs/static/img/sponsors/nepeat-logo.webp"
alt="Cat eyes over the word Emma in a serif font"
height="64"
/>
</a>
<a href="https://fabulous.systems/">
<img
src="./docs/static/img/sponsors/fabulous-systems.webp"
alt="Cat eyes over the word Emma in a serif font"
height="64"
/>
</a>
## Overview

View File

@@ -1 +1 @@
1.20.0
1.21.0

View File

@@ -38,6 +38,7 @@ import (
"github.com/facebookgo/flagenv"
_ "github.com/joho/godotenv/autoload"
"github.com/prometheus/client_golang/prometheus/promhttp"
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
)
var (
@@ -97,7 +98,7 @@ func keyFromHex(value string) (ed25519.PrivateKey, error) {
}
func doHealthCheck() error {
resp, err := http.Get("http://localhost" + *metricsBind + anubis.BasePrefix + "/metrics")
resp, err := http.Get("http://localhost" + *metricsBind + "/healthz")
if err != nil {
return fmt.Errorf("failed to fetch metrics: %w", err)
}
@@ -231,20 +232,6 @@ func makeReverseProxy(target string, targetSNI string, targetHost string, insecu
return rp, nil
}
func startDecayMapCleanup(ctx context.Context, s *libanubis.Server) {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.CleanupDecayMap()
case <-ctx.Done():
return
}
}
}
func main() {
flagenv.Parse()
flag.Parse()
@@ -255,6 +242,15 @@ func main() {
}
internal.InitSlog(*slogLevel)
internal.SetHealth("anubis", healthv1.HealthCheckResponse_NOT_SERVING)
if *healthcheck {
log.Println("running healthcheck")
if err := doHealthCheck(); err != nil {
log.Fatal(err)
}
return
}
if *extractResources != "" {
if err := extractEmbedFS(data.BotPolicies, ".", *extractResources); err != nil {
@@ -267,6 +263,17 @@ func main() {
return
}
// install signal handler
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
wg := new(sync.WaitGroup)
if *metricsBind != "" {
wg.Add(1)
go metricsServer(ctx, wg.Done)
}
var rp http.Handler
// when using anubis via Systemd and environment variables, then it is not possible to set targe to an empty string but only to space
if strings.TrimSpace(*target) != "" {
@@ -281,8 +288,6 @@ func main() {
log.Fatalf("you can't set COOKIE_DOMAIN and COOKIE_DYNAMIC_DOMAIN at the same time")
}
ctx := context.Background()
// Thoth configuration
switch {
case *thothURL != "" && *thothToken == "":
@@ -412,22 +417,12 @@ func main() {
log.Fatalf("can't construct libanubis.Server: %v", err)
}
wg := new(sync.WaitGroup)
// install signal handler
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if *metricsBind != "" {
wg.Add(1)
go metricsServer(ctx, wg.Done)
}
go startDecayMapCleanup(ctx, s)
var h http.Handler
h = s
h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h)
h = internal.XForwardedForToXRealIP(h)
h = internal.XForwardedForUpdate(*xffStripPrivate, h)
h = internal.JA4H(h)
srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()}
listener, listenerUrl := setupListener(*bindNetwork, *bind)
@@ -456,6 +451,8 @@ func main() {
}
}()
internal.SetHealth("anubis", healthv1.HealthCheckResponse_SERVING)
if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
@@ -466,20 +463,30 @@ func metricsServer(ctx context.Context, done func()) {
defer done()
mux := http.NewServeMux()
mux.Handle(anubis.BasePrefix+"/metrics", promhttp.Handler())
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
st, ok := internal.GetHealth("anubis")
if !ok {
slog.Error("health service anubis does not exist, file a bug")
}
switch st {
case healthv1.HealthCheckResponse_NOT_SERVING:
http.Error(w, "NOT OK", http.StatusInternalServerError)
return
case healthv1.HealthCheckResponse_SERVING:
fmt.Fprintln(w, "OK")
return
default:
http.Error(w, "UNKNOWN", http.StatusFailedDependency)
return
}
})
srv := http.Server{Handler: mux, ErrorLog: internal.GetFilteredHTTPLogger()}
listener, metricsUrl := setupListener(*metricsBindNetwork, *metricsBind)
slog.Debug("listening for metrics", "url", metricsUrl)
if *healthcheck {
log.Println("running healthcheck")
if err := doHealthCheck(); err != nil {
log.Fatal(err)
}
return
}
go func() {
<-ctx.Done()
c, cancel := context.WithTimeout(context.Background(), 5*time.Second)

View File

@@ -74,6 +74,25 @@ bots:
weight:
adjust: 10
# ## System load based checks.
# # If the system is under high load, add weight.
# - name: high-load-average
# action: WEIGH
# expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
# weight:
# adjust: 20
## If your backend service is running on the same operating system as Anubis,
## you can uncomment this rule to make the challenge easier when the system is
## under low load.
##
## If it is not, remove weight.
# - name: low-load-average
# action: WEIGH
# expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
# weight:
# adjust: -10
# Generic catchall rule
- name: generic-browser
user_agent_regex: >-
@@ -145,6 +164,14 @@ status_codes:
CHALLENGE: 200
DENY: 200
# Anubis can store temporary data in one of a few backends. See the storage
# backends section of the docs for more information:
#
# https://anubis.techaro.lol/docs/admin/policies#storage-backends
store:
backend: memory
parameters: {}
# The weight thresholds for when to trigger individual challenges. Any
# CHALLENGE will take precedence over this.
#

View File

@@ -0,0 +1,223 @@
- name: uptime-robot
user_agent_regex: UptimeRobot
action: ALLOW
# https://api.uptimerobot.com/meta/ips
remote_addresses: [
"3.12.251.153/32",
"3.20.63.178/32",
"3.77.67.4/32",
"3.79.134.69/32",
"3.105.133.239/32",
"3.105.190.221/32",
"3.133.226.214/32",
"3.149.57.90/32",
"3.212.128.62/32",
"5.161.61.238/32",
"5.161.73.160/32",
"5.161.75.7/32",
"5.161.113.195/32",
"5.161.117.52/32",
"5.161.177.47/32",
"5.161.194.92/32",
"5.161.215.244/32",
"5.223.43.32/32",
"5.223.53.147/32",
"5.223.57.22/32",
"18.116.205.62/32",
"18.180.208.214/32",
"18.192.166.72/32",
"18.193.252.127/32",
"24.144.78.39/32",
"24.144.78.185/32",
"34.198.201.66/32",
"45.55.123.175/32",
"45.55.127.146/32",
"49.13.24.81/32",
"49.13.130.29/32",
"49.13.134.145/32",
"49.13.164.148/32",
"49.13.167.123/32",
"52.15.147.27/32",
"52.22.236.30/32",
"52.28.162.93/32",
"52.59.43.236/32",
"52.87.72.16/32",
"54.64.67.106/32",
"54.79.28.129/32",
"54.87.112.51/32",
"54.167.223.174/32",
"54.249.170.27/32",
"63.178.84.147/32",
"64.225.81.248/32",
"64.225.82.147/32",
"69.162.124.227/32",
"69.162.124.235/32",
"69.162.124.238/32",
"78.46.190.63/32",
"78.46.215.1/32",
"78.47.98.55/32",
"78.47.173.76/32",
"88.99.80.227/32",
"91.99.101.207/32",
"128.140.41.193/32",
"128.140.106.114/32",
"129.212.132.140/32",
"134.199.240.137/32",
"138.197.53.117/32",
"138.197.53.138/32",
"138.197.54.143/32",
"138.197.54.247/32",
"138.197.63.92/32",
"139.59.50.44/32",
"142.132.180.39/32",
"143.198.249.237/32",
"143.198.250.89/32",
"143.244.196.21/32",
"143.244.196.211/32",
"143.244.221.177/32",
"144.126.251.21/32",
"146.190.9.187/32",
"152.42.149.135/32",
"157.90.155.240/32",
"157.90.156.63/32",
"159.69.158.189/32",
"159.223.243.219/32",
"161.35.247.201/32",
"167.99.18.52/32",
"167.235.143.113/32",
"168.119.53.160/32",
"168.119.96.239/32",
"168.119.123.75/32",
"170.64.250.64/32",
"170.64.250.132/32",
"170.64.250.235/32",
"178.156.181.172/32",
"178.156.184.20/32",
"178.156.185.127/32",
"178.156.185.231/32",
"178.156.187.238/32",
"178.156.189.113/32",
"178.156.189.249/32",
"188.166.201.79/32",
"206.189.241.133/32",
"209.38.49.1/32",
"209.38.49.206/32",
"209.38.49.226/32",
"209.38.51.43/32",
"209.38.53.7/32",
"209.38.124.252/32",
"216.144.248.18/31",
"216.144.248.21/32",
"216.144.248.22/31",
"216.144.248.24/30",
"216.144.248.28/31",
"216.144.248.30/32",
"216.245.221.83/32",
"2400:6180:10:200::56a0:b000/128",
"2400:6180:10:200::56a0:c000/128",
"2400:6180:10:200::56a0:e000/128",
"2400:6180:100:d0::94b6:4001/128",
"2400:6180:100:d0::94b6:5001/128",
"2400:6180:100:d0::94b6:7001/128",
"2406:da14:94d:8601:9d0d:7754:bedf:e4f5/128",
"2406:da14:94d:8601:b325:ff58:2bba:7934/128",
"2406:da14:94d:8601:db4b:c5ac:2cbe:9a79/128",
"2406:da1c:9c8:dc02:7ae1:f2ea:ab91:2fde/128",
"2406:da1c:9c8:dc02:7db9:f38b:7b9f:402e/128",
"2406:da1c:9c8:dc02:82b2:f0fd:ee96:579/128",
"2600:1f16:775:3a00:ac3:c5eb:7081:942e/128",
"2600:1f16:775:3a00:37bf:6026:e54a:f03a/128",
"2600:1f16:775:3a00:3f24:5bb0:95d7:5a6b/128",
"2600:1f16:775:3a00:8c2c:2ba6:778f:5be5/128",
"2600:1f16:775:3a00:91ac:3120:ff38:92b5/128",
"2600:1f16:775:3a00:dbbe:36b0:3c45:da32/128",
"2600:1f18:179:f900:71:af9a:ade7:d772/128",
"2600:1f18:179:f900:2406:9399:4ae6:c5d3/128",
"2600:1f18:179:f900:4696:7729:7bb3:f52f/128",
"2600:1f18:179:f900:4b7d:d1cc:2d10:211/128",
"2600:1f18:179:f900:5c68:91b6:5d75:5d7/128",
"2600:1f18:179:f900:e8dd:eed1:a6c:183b/128",
"2604:a880:800:14:0:1:68ba:d000/128",
"2604:a880:800:14:0:1:68ba:e000/128",
"2604:a880:800:14:0:1:68bb:0/128",
"2604:a880:800:14:0:1:68bb:1000/128",
"2604:a880:800:14:0:1:68bb:3000/128",
"2604:a880:800:14:0:1:68bb:4000/128",
"2604:a880:800:14:0:1:68bb:5000/128",
"2604:a880:800:14:0:1:68bb:6000/128",
"2604:a880:800:14:0:1:68bb:7000/128",
"2604:a880:800:14:0:1:68bb:a000/128",
"2604:a880:800:14:0:1:68bb:b000/128",
"2604:a880:800:14:0:1:68bb:c000/128",
"2604:a880:800:14:0:1:68bb:d000/128",
"2604:a880:800:14:0:1:68bb:e000/128",
"2604:a880:800:14:0:1:68bb:f000/128",
"2607:ff68:107::4/128",
"2607:ff68:107::14/128",
"2607:ff68:107::33/128",
"2607:ff68:107::48/127",
"2607:ff68:107::50/125",
"2607:ff68:107::58/127",
"2607:ff68:107::60/128",
"2a01:4f8:c0c:83fa::1/128",
"2a01:4f8:c17:42e4::1/128",
"2a01:4f8:c2c:9fc6::1/128",
"2a01:4f8:c2c:beae::1/128",
"2a01:4f8:1c1a:3d53::1/128",
"2a01:4f8:1c1b:4ef4::1/128",
"2a01:4f8:1c1b:5b5a::1/128",
"2a01:4f8:1c1b:7ecc::1/128",
"2a01:4f8:1c1c:11aa::1/128",
"2a01:4f8:1c1c:5353::1/128",
"2a01:4f8:1c1c:7240::1/128",
"2a01:4f8:1c1c:a98a::1/128",
"2a01:4f8:c012:c60e::1/128",
"2a01:4f8:c013:c18::1/128",
"2a01:4f8:c013:34c0::1/128",
"2a01:4f8:c013:3b0f::1/128",
"2a01:4f8:c013:3c52::1/128",
"2a01:4f8:c013:3c53::1/128",
"2a01:4f8:c013:3c54::1/128",
"2a01:4f8:c013:3c55::1/128",
"2a01:4f8:c013:3c56::1/128",
"2a01:4ff:f0:bfd::1/128",
"2a01:4ff:f0:2219::1/128",
"2a01:4ff:f0:3e03::1/128",
"2a01:4ff:f0:5f80::1/128",
"2a01:4ff:f0:7fad::1/128",
"2a01:4ff:f0:9c5f::1/128",
"2a01:4ff:f0:b2f2::1/128",
"2a01:4ff:f0:b6f1::1/128",
"2a01:4ff:f0:d283::1/128",
"2a01:4ff:f0:d3cd::1/128",
"2a01:4ff:f0:e516::1/128",
"2a01:4ff:f0:e9cf::1/128",
"2a01:4ff:f0:eccb::1/128",
"2a01:4ff:f0:efd1::1/128",
"2a01:4ff:f0:fdc7::1/128",
"2a01:4ff:2f0:193c::1/128",
"2a01:4ff:2f0:27de::1/128",
"2a01:4ff:2f0:3b3a::1/128",
"2a03:b0c0:2:f0::bd91:f001/128",
"2a03:b0c0:2:f0::bd92:1/128",
"2a03:b0c0:2:f0::bd92:1001/128",
"2a03:b0c0:2:f0::bd92:2001/128",
"2a03:b0c0:2:f0::bd92:4001/128",
"2a03:b0c0:2:f0::bd92:5001/128",
"2a03:b0c0:2:f0::bd92:6001/128",
"2a03:b0c0:2:f0::bd92:7001/128",
"2a03:b0c0:2:f0::bd92:8001/128",
"2a03:b0c0:2:f0::bd92:9001/128",
"2a03:b0c0:2:f0::bd92:a001/128",
"2a03:b0c0:2:f0::bd92:b001/128",
"2a03:b0c0:2:f0::bd92:c001/128",
"2a03:b0c0:2:f0::bd92:e001/128",
"2a03:b0c0:2:f0::bd92:f001/128",
"2a05:d014:1815:3400:6d:9235:c1c0:96ad/128",
"2a05:d014:1815:3400:654f:bd37:724c:212b/128",
"2a05:d014:1815:3400:90b4:4ef9:5631:b170/128",
"2a05:d014:1815:3400:9779:d8e9:100a:9642/128",
"2a05:d014:1815:3400:af29:e95e:64ff:df81/128",
"2a05:d014:1815:3400:c7d6:f7f3:6cc1:30d1/128",
"2a05:d014:1815:3400:d784:e5dd:8e0:67cb/128",
]

View File

@@ -48,6 +48,26 @@ func (m *Impl[K, V]) expire(key K) bool {
return true
}
// Delete a value from the DecayMap by key.
//
// 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
}
m.lock.Lock()
delete(m.data, key)
m.lock.Unlock()
return true
}
// Get gets a value from the DecayMap by key.
//
// If a value has expired, forcibly delete it if it was not updated.

View File

@@ -19,5 +19,3 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Kubernetes manifests
/manifest

View File

@@ -5,6 +5,7 @@ COPY . .
RUN npm ci && npm run build
FROM docker.io/library/nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
FROM ghcr.io/xe/nginx-micro
COPY --from=build /app/build /www
COPY ./manifest/cfg/nginx/nginx.conf /conf
LABEL org.opencontainers.image.source="https://github.com/TecharoHQ/anubis"

View File

@@ -0,0 +1,105 @@
---
slug: incident/TI-20250709-0001
title: "TI-20250709-0001: IPv4 traffic failures for Techaro services"
authors: [xe]
tags: [incident]
image: ./window-portal.jpg
---
![](./window-portal.jpg)
Techaro services were down for IPv4 traffic on July 9th, 2025. This blogpost is a report of what happened, what actions were taken to resolve the situation, and what actions are being done in the near future to prevent this problem. Enjoy this incident report!
{/* truncate */}
:::note
In other companies, this kind of documentation would be kept internal. At Techaro, we believe that you deserve radical candor and the truth. As such, we are proving our lofty words with actions by publishing details about how things go wrong publicly.
Everything past this point follows my standard incident root cause meeting template.
:::
This incident report will focus on the services affected, timeline of what happened at which stage of the incident, where we got lucky, the root cause analysis, and what action items are being planned or taken to prevent this from happening in the future.
## Timeline
All events take place on July 9th, 2025.
| Time (UTC) | Description |
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 12:32 | Uptime Kuma reports that another unrelated website on the same cluster was timing out. |
| 12:33 | Uptime Kuma reports that Thoth's production endpoint is failing gRPC health checks. |
| 12:35 | Investigation begins, [announcement made on Xe's Bluesky](https://bsky.app/profile/xeiaso.net/post/3ltjtdczpwc2x) due to the impact including their personal blog. |
| 12:39 | `nginx-ingress` logs on the production cluster show IPv6 traffic but an abrupt cutoff in IPv4 traffic around 12:32 UTC. Ticket is opened with the hosting provider. |
| 12:41 | IPv4 traffic resumes long enough for Uptime Kuma to report uptime, but then immediately fails again. |
| 12:46 | IPv4 traffic resumes long enough for Uptime Kuma to report uptime, but then immediately fails again. (repeat instances of this have been scrubbed, but it happened about every 5-10 minutes) |
| 12:48 | First reply from the hosting provider. |
| 12:57 | Reply to hosting provider, ask to reboot the load balancer. |
| 13:00 | Incident responder because busy due to a meeting under the belief that the downtime was out of their control and that uptime monitoring software would let them know if it came back up. |
| 13:20 | Incident responder ended meeting and went back to monitoring downtime and preparing this document. |
| 13:34 | IPv4 traffic starts to show up in the `ingress-nginx` logs. |
| 13:35 | All services start to report healthy. Incident status changes to monitoring. |
| 13:48 | Incident closed. |
| 14:07 | Incident re-opened. Issues seem to be manifesting as BGP issues in the upstream provider. |
| 14:10 | IPv4 traffic resumes and then stops. |
| 14:18 | IPv4 traffic resumes again. Incident status changes to monitoring. |
| 14:40 | Incident closed. |
## Services affected
| Service name | User impact |
| :-------------------------------------------------- | :----------------- |
| [Anubis Docs](https://anubis.techaro.lol) (IPv4) | Connection timeout |
| [Anubis Docs](https://anubis.techaro.lol) (IPv6) | None |
| [Thoth](/docs/admin/thoth/) (IPv4) | Connection timeout |
| [Thoth](/docs/admin/thoth/) (IPv6) | None |
| Other websites colocated on the same cluster (IPv4) | Connection timeout |
| Other websites colocated on the same cluster (IPv6) | None |
## Root cause analysis
In simplify server management, Techaro runs a [Kubernetes](https://kubernetes.io/) cluster on [Vultr VKE](https://www.vultr.com/kubernetes/) (Vultr Kubernetes Engine). When you do this, Vultr needs to provision a [load balancer](https://docs.vultr.com/how-to-use-a-vultr-load-balancer-with-vke) to bridge the gap between the outside world and the Kubernetes world, kinda like this:
```mermaid
---
title: Overall architecture
---
flowchart LR
UT(User Traffic)
subgraph Provider Infrastructure
LB[Load Balancer]
end
subgraph Kubernetes
IN(ingress-nginx)
TH(Thoth)
AN(Anubis Docs)
OS(Other sites)
IN --> TH
IN --> AN
IN --> OS
end
UT --> LB --> IN
```
Techaro controls everything inside the Kubernetes side of that diagram. Anything else is out of our control. That load balancer is routed to the public internet via [Border Gateway Protocol (BGP)](https://en.wikipedia.org/wiki/Border_Gateway_Protocol).
If there is an interruption with the BGP sessions in the upstream provider, this can manifest as things either not working or inconsistently working. This is made more difficult by the fact that the IPv4 and IPv6 internets are technically separate networks. With this in mind, it's very possible to have IPv4 traffic fail but not IPv6 traffic.
The root cause is that the hosting provider we use for production services had flapping IPv4 BGP sessions in its Toronto region. When this happens all we can do is open a ticket and wait for it to come back up.
## Where we got lucky
The Uptime Kuma instance that caught this incident runs on an IPv4-only network. If it was dual stack, this would not have been caught as quickly.
The `ingress-nginx` logs print IP addresses of remote clients to the log feed. If this was not the case, it would be much more difficult to find this error.
## Action items
- A single instance of downtime like this is not enough reason to move providers. Moving providers because of this is thus out of scope.
- Techaro needs a status page hosted on a different cloud provider than is used for the production cluster (`TecharoHQ/TODO#6`).
- Health checks for IPv4 and IPv6 traffic need to be created (`TecharoHQ/TODO#7`).
- Remove the requirement for [Anubis to pass Thoth health checks before it can start if Thoth is enabled](https://github.com/TecharoHQ/anubis/pull/794).

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -13,25 +13,129 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- This changes the project to: -->
- Expired records are now properly removed from bbolt databases ([#848](https://github.com/TecharoHQ/anubis/pull/848)).
- Fix hanging on service restart ([#853](https://github.com/TecharoHQ/anubis/issues/853))
### Added
Anubis now supports these new languages:
- [Czech](https://github.com/TecharoHQ/anubis/pull/849)
Anubis now supports the [`missingHeader`](./admin/configuration/expressions.mdx#missingHeader) to assert the absence of headers in requests.
## v1.21.0: Minfilia Warde
> Please, be at ease. You are among friends here.
In this release, Anubis becomes internationalized, gains the ability to use system load as input to issuing challenges, finally fixes the "invalid response" after "success" bug, and more! Please read these notes before upgrading as the changes are big enough that administrators should take action to ensure that the upgrade goes smoothly.
### Big ticket changes
The biggest change is that the ["invalid response" after "success" bug](https://github.com/TecharoHQ/anubis/issues/564) is now finally fixed for good by totally rewriting how Anubis' challenge issuance flow works. Instead of generating challenge strings from request metadata (under the assumption that the values being compared against are stable), Anubis now generates random data for each challenge. This data is stored in the active [storage backend](./admin/policies.mdx#storage-backends) for up to 30 minutes. This also fixes [#746](https://github.com/TecharoHQ/anubis/issues/746) and other similar instances of this issue.
In order to reduce confusion, the "Success" interstitial that shows up when you pass a proof of work challenge has been removed.
#### Storage
Anubis now is able to store things persistently [in memory](./admin/policies.mdx#memory), [on the disk](./admin/policies.mdx#bbolt), or [in Valkey](./admin/policies.mdx#valkey) (this includes other compatible software). By default Anubis uses the in-memory backend. If you have an environment with mutable storage (even if it is temporary), be sure to configure the [`bbolt`](./admin/policies.mdx#bbolt) storage backend.
#### Localization
Anubis now supports localized responses. Locales can be added in [lib/localization/locales/](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). This release includes support for the following languages:
- [Brazilian Portugese](https://github.com/TecharoHQ/anubis/pull/726)
- [Chinese (Simplified)](https://github.com/TecharoHQ/anubis/pull/774)
- [Chinese (Traditional)](https://github.com/TecharoHQ/anubis/pull/759)
- English
- [Estonian](https://github.com/TecharoHQ/anubis/pull/783)
- [Filipino](https://github.com/TecharoHQ/anubis/pull/775)
- [French](https://github.com/TecharoHQ/anubis/pull/716)
- [German](https://github.com/TecharoHQ/anubis/pull/741)
- [Icelandic](https://github.com/TecharoHQ/anubis/pull/780)
- [Italian](https://github.com/TecharoHQ/anubis/pull/778)
- [Japanese](https://github.com/TecharoHQ/anubis/pull/772)
- [Spanish](https://github.com/TecharoHQ/anubis/pull/716)
- [Turkish](https://github.com/TecharoHQ/anubis/pull/751)
If facts or local regulations demand, you can set Anubis default language with the `FORCED_LANGUAGE` environment variable or the `--forced-language` command line argument:
```sh
FORCED_LANGUAGE=de
```
#### Load average
Anubis can dynamically take action [based on the system load average](./admin/configuration/expressions.mdx#using-the-system-load-average), allowing you to write rules like this:
```yaml
## System load based checks.
# If the system is under high load for the last minute, add weight.
- name: high-load-average
action: WEIGH
expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
weight:
adjust: 20
# If it is not for the last 15 minutes, remove weight.
- name: low-load-average
action: WEIGH
expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
weight:
adjust: -10
```
Something to keep in mind about system load average is that it is not aware of the number of cores the system has. If you have a 16 core system that has 16 processes running but none of them is hogging the CPU, then you will get a load average below 16. If you are in doubt, make your "high load" metric at least two times the number of CPU cores and your "low load" metric at least half of the number of CPU cores. For example:
| Kind | Core count | Load threshold |
| --------: | :--------- | :------------- |
| high load | 4 | `8.0` |
| low load | 4 | `2.0` |
| high load | 16 | `32.0` |
| low load | 16 | `8` |
Also keep in mind that this does not account for other kinds of latency like I/O latency. A system can have its web applications unresponsive due to high latency from a MySQL server but still have that web application server report a load near or at zero.
### Other features and fixes
There are a bunch of other assorted features and fixes too:
- Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies)
- Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained)
- Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)).
- Implement localization system. Find locale files in lib/localization/locales/.
- Implement a [development container](https://containers.dev/) manifest to make contributions easier.
- Fix dynamic cookie domains functionality ([#731](https://github.com/TecharoHQ/anubis/pull/731))
- Add option for custom cookie prefix ([#732](https://github.com/TecharoHQ/anubis/pull/732))
- Add translation for German language ([#741](https://github.com/TecharoHQ/anubis/pull/741))
- Remove the "Success" interstitial after a proof of work challenge is concluded.
- Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742))
- Make the [Open Graph](./admin/configuration/open-graph.mdx) subsystem and DNSBL subsystem use [storage backends](./admin/policies.mdx#storage-backends) instead of storing everything in memory by default.
- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape
- The [bbolt storage backend](./admin/policies.mdx#bbolt) now runs its cleanup every hour instead of every five minutes.
- Don't block Anubis starting up if [Thoth](./admin/thoth.mdx) health checks fail.
- A race condition involving [opening two challenge pages at once in different tabs](https://github.com/TecharoHQ/anubis/issues/832) causing one of them to fail has been fixed.
- The "Try again" button on the error page has been fixed. Previously it meant "try the solution again" instead of "try the challenge again".
- In certain cases, a user could be stuck with a test cookie that is invalid, locking them out of the service for up to half an hour. This has been fixed with better validation of this case and clearing the cookie.
- Start exposing JA4H fingerprints for later use in CEL expressions.
- Add `/healthz` route for use in platform-based health checks.
### Potentially breaking changes
We try to introduce breaking changes as much as possible, but these are the changes that may be relevant for you as an administrator:
#### Challenge format change
Previously Anubis did no accounting for challenges that it issued. This means that if Anubis restarted during a client, the client would be able to proceed once Anubis came back online.
During the upgrade to v1.21.0 and when v1.21.0 (or later) restarts with the [in-memory storage backend](./admin/policies.mdx#memory), you may see a higher rate of failed challenges than normal. If this persists beyond a few minutes, [open an issue](https://github.com/TecharoHQ/anubis/issues/new).
If you are using the in-memory storage backend, please consider using [a different storage backend](./admin/policies.mdx#storage-backends).
#### Systemd service changes
The following potentially breaking change applies to native installs with systemd only:
Each instance of systemd service template now has a unique `RuntimeDirectory`, as opposed to each instance of the service sharing a `RuntimeDirectory`. This change was made to avoid [the `RuntimeDirectory` getting nuked any time one of the Anubis instances restarts](https://github.com/TecharoHQ/anubis/issues/748).
If you configured Anubis' unix sockets to listen on `/run/anubis/foo.sock` for instance `anubis@foo`, you will need to configure Anubis to listen on `/run/anubis/foo/sock` and additionally configure your HTTP load balancer as appropriate.
If you configured Anubis' unix sockets to listen on `/run/anubis/foo.sock` for instance `anubis@foo`, you will need to configure Anubis to listen on `/run/anubis/foo/foo.sock` and additionally configure your HTTP load balancer as appropriate.
If you need the legacy behaviour, install this [systemd unit dropin](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/):
@@ -41,6 +145,8 @@ If you need the legacy behaviour, install this [systemd unit dropin](https://www
RuntimeDirectory=anubis
```
Just keep in mind that this will cause problems when Anubis restarts.
## v1.20.0: Thancred Waters
The big ticket items are as follows:

View File

@@ -77,7 +77,7 @@ For example, consider this rule:
For this rule, if a request comes in from `8.8.8.8` or `1.1.1.1`, Anubis will deny the request and return an error page.
#### `all` blocks
### `all` blocks
An `all` block that contains a list of expressions. If all expressions in the list return `true`, then the action specified in the rule will be taken. If any of the expressions in the list returns `false`, Anubis will move on to the next rule.
@@ -99,15 +99,18 @@ For this rule, if a request comes in matching [the signature of the `go get` com
Anubis exposes the following variables to expressions:
| Name | Type | Explanation | Example |
| :-------------- | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
| `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` |
| `method` | `string` | The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) in the request being processed. | `GET`, `POST`, `DELETE`, etc. |
| `path` | `string` | The [path](https://web.dev/articles/url-parts#pathname) of the request being processed. | `/`, `/api/memes/create` |
| `query` | `map[string, string]` | The [query parameters](https://web.dev/articles/url-parts#query) of the request being processed. | `?foo=bar` -> `{"foo": "bar"}` |
| `remoteAddress` | `string` | The IP address of the client. | `1.1.1.1` |
| `userAgent` | `string` | The [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent) string in the request being processed. | `Mozilla/5.0 Gecko/20100101 Firefox/137.0` |
| Name | Type | Explanation | Example |
| :-------------- | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
| `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` |
| `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). |
| `method` | `string` | The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) in the request being processed. | `GET`, `POST`, `DELETE`, etc. |
| `path` | `string` | The [path](https://web.dev/articles/url-parts#pathname) of the request being processed. | `/`, `/api/memes/create` |
| `query` | `map[string, string]` | The [query parameters](https://web.dev/articles/url-parts#query) of the request being processed. | `?foo=bar` -> `{"foo": "bar"}` |
| `remoteAddress` | `string` | The IP address of the client. | `1.1.1.1` |
| `userAgent` | `string` | The [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent) string in the request being processed. | `Mozilla/5.0 Gecko/20100101 Firefox/137.0` |
Of note: in many languages when you look up a key in a map and there is nothing there, the language will return some "falsy" value like `undefined` in JavaScript, `None` in Python, or the zero value of the type in Go. In CEL, if you try to look up a value that does not exist, execution of the expression will fail and Anubis will return an error.
@@ -120,7 +123,7 @@ In order to avoid this, make sure the header or query parameter you are testing
- 'path == "/index.php"'
- '"title" in query'
- '"action" in query'
- 'query["action"] == "history"
- 'query["action"] == "history"'
```
This rule throws a challenge if and only if all of the following conditions are true:
@@ -141,12 +144,74 @@ X-Real-Ip: 8.8.8.8
Anubis would return a challenge because all of those conditions are true.
### Using the system load average
In Unix-like systems (such as Linux), every process on the system has to wait its turn to be able to run. This means that as more processes on the system are running, they need to wait longer to be able to execute. The [load average](<https://en.wikipedia.org/wiki/Load_(computing)>) represents the number of processes that want to be able to run but can't run yet. This metric isn't the most reliable to identify a cause, but is great at helping to identify symptoms.
Anubis lets you use the system load average as an input to expressions so that you can make dynamic rules like "when the system is under a low amount of load, dial back the protection, but when it's under a lot of load, crank it up to the mix". This lets you get all of the blocking features of Anubis in the background but only really expose Anubis to users when the system is actively being attacked.
This is best combined with the [weight](../policies.mdx#request-weight) and [threshold](./thresholds.mdx) systems so that you can have Anubis dynamically respond to attacks. Consider these rules in the default configuration file:
```yaml
## System load based checks.
# If the system is under high load for the last minute, add weight.
- name: high-load-average
action: WEIGH
expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
weight:
adjust: 20
# If it is not for the last 15 minutes, remove weight.
- name: low-load-average
action: WEIGH
expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
weight:
adjust: -10
```
This combination of rules makes Anubis dynamically react to the system load and only kick in when the system is under attack.
Something to keep in mind about system load average is that it is not aware of the number of cores the system has. If you have a 16 core system that has 16 processes running but none of them is hogging the CPU, then you will get a load average below 16. If you are in doubt, make your "high load" metric at least two times the number of CPU cores and your "low load" metric at least half of the number of CPU cores. For example:
| Kind | Core count | Load threshold |
| --------: | :--------- | :------------- |
| high load | 4 | `8.0` |
| low load | 4 | `2.0` |
| high load | 16 | `32.0` |
| low load | 16 | `8` |
Also keep in mind that this does not account for other kinds of latency like I/O latency. A system can have its web applications unresponsive due to high latency from a MySQL server but still have that web application server report a load near or at zero.
## Functions exposed to Anubis expressions
Anubis expressions can be augmented with the following functions:
### `missingHeader`
Available in `bot` expressions.
```ts
function missingHeader(headers: Record<string, string>, key: string) bool
```
`missingHeader` returns `true` if the request does not contain a header. This is useful when you are trying to assert behavior such as:
```yaml
# Adds weight to old versions of Chrome
- name: old-chrome
action: WEIGH
weight:
adjust: 10
expression:
all:
- userAgent.matches("Chrome/[1-9][0-9]?\\.0\\.0\\.0")
- missingHeader(headers, "Sec-Ch-Ua")
```
### `randInt`
Available in all expressions.
```ts
function randInt(n: int): int;
```

View File

@@ -30,7 +30,7 @@ Effectively you have one trip through Apache to do TLS termination, a detour thr
:::note
These examples assume that you are using a setup where your nginx configuration is made up of a bunch of files in `/etc/httpd/conf.d/*.conf`. This is not true for all deployments of Apache. If you are not in such an environment, append these snippets to your `/etc/httpd/conf/httpd.conf` file.
These examples assume that you are using a setup where your Apache configuration is made up of a bunch of files in `/etc/httpd/conf.d/*.conf`. This is not true for all deployments of Apache. If you are not in such an environment, append these snippets to your `/etc/httpd/conf/httpd.conf` file.
:::
@@ -56,6 +56,7 @@ Assuming you are protecting `anubistest.techaro.lol`, you need the following ser
</VirtualHost>
# HTTPS listener that forwards to Anubis
<IfModule mod_proxy.c>
<VirtualHost *:443>
ServerAdmin your@email.here
ServerName anubistest.techaro.lol

View File

@@ -4,7 +4,7 @@ Docker compose is typically used in concert with other load balancers such as [A
```yaml
services:
anubis-nginx:
anubis:
image: ghcr.io/techarohq/anubis:latest
environment:
BIND: ":8080"
@@ -15,10 +15,17 @@ services:
POLICY_FNAME: "/data/cfg/botPolicy.yaml"
OG_PASSTHROUGH: "true"
OG_EXPIRY_TIME: "24h"
healthcheck:
test: ["CMD", "anubis", "--healthcheck"]
interval: 5s
timeout: 30s
retries: 5
start_period: 500ms
ports:
- 8080:8080
volumes:
- "./botPolicy.yaml:/data/cfg/botPolicy.yaml:ro"
nginx:
image: nginx
volumes:

View File

@@ -60,16 +60,16 @@ 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. 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. |
| `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.<br/><br/>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_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_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. |
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. |
| `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. 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. |
| `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. |

View File

@@ -137,7 +137,7 @@ Test to make sure it's running with `curl`:
curl http://localhost:8240/metrics
```
Then set up your reverse proxy (Nginx, Caddy, etc.) to point to the Anubis port. Anubis will then reverse proxy all requests that meet the policies in `/etc/anubis/gitea.botPolicies.json` to the target service.
Then set up your reverse proxy (Nginx, Caddy, etc.) to point to the Anubis port. Anubis will then reverse proxy all requests that meet the policies in `/etc/anubis/gitea.botPolicies.yaml` to the target service.
For more details on particular reverse proxies, see here:

View File

@@ -237,6 +237,120 @@ remote_addresses:
Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information.
## Storage backends
Anubis needs to store temporary data in order to determine if a user is legitimate or not. Administrators should choose a storage backend based on their infrastructure needs. Each backend has its own advantages and disadvantages.
Anubis offers the following storage backends:
- [`memory`](#memory) -- A simple in-memory hashmap
- [`bbolt`](#bbolt) -- An on-disk key/value store backed by [bbolt](https://github.com/etcd-io/bbolt), an embedded key/value database for Go programs
- [`valkey`](#valkey) -- A remote in-memory key/value database backed by [Valkey](https://valkey.io/) (or another database compatible with the [RESP](https://redis.io/docs/latest/develop/reference/protocol-spec/) protocol)
If no storage backend is set in the policy file, Anubis will use the [`memory`](#memory) backend by default. This is equivalent to the following in the policy file:
```yaml
store:
backend: memory
parameters: {}
```
### `memory`
The memory backend is an in-memory cache. This backend works best if you don't use multiple instances of Anubis or don't have mutable storage in the environment you're running Anubis in.
| Should I use this backend? | Yes/no |
| :------------------------------------------------------------ | :----- |
| Are you running only one instance of Anubis for this service? | ✅ Yes |
| Does your service get a lot of traffic? | 🚫 No |
| Do you want to store data persistently when Anubis restarts? | 🚫 No |
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
The biggest downside is that there is not currently a limit to how much data can be stored in memory. This will be addressed at a later time.
:::warning
The in-memory backend exists mostly for validation, testing, and to ensure that the default configuration of Anubis works as expected. Do not use this persistently in production.
:::
#### Configuration
The memory backend does not require any configuration to use.
### `bbolt`
An on-disk storage layer powered by [bbolt](https://github.com/etcd-io/bbolt), a high performance embedded key/value database used by containerd, etcd, Kubernetes, and NATS. This backend works best if you're running Anubis on a single host and get a lot of traffic.
| Should I use this backend? | Yes/no |
| :------------------------------------------------------------ | :----- |
| Are you running only one instance of Anubis for this service? | ✅ Yes |
| Does your service get a lot of traffic? | ✅ Yes |
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
| Do you run Anubis without mutable filesystem storage? | 🚫 No |
When Anubis opens a bbolt database, it takes an exclusive lock on that database. Other instances of Anubis or other tools cannot view the bbolt database while it is locked by another instance of Anubis. If you run multiple instances of Anubis for different services, give each its own `bbolt` configuration.
#### Configuration
The `bbolt` backend takes the following configuration options:
| Name | Type | Example | Description |
| :----- | :--- | :----------------- | :--------------------------------------------------------------------------------------------------------------------------- |
| `path` | path | `/data/anubis.bdb` | The filesystem path for the Anubis bbolt database. Anubis requires write access to the folder containing the bbolt database. |
Example:
If you have persistent storage mounted to `/data`, then your store configuration could look like this:
```yaml
store:
backend: bbolt
parameters:
path: /data/anubis.bdb
```
### `valkey`
[Valkey](https://valkey.io/) is an in-memory key/value store that clients access over the network. This allows multiple instances of Anubis to share information and does not require each instance of Anubis to have persistent filesystem storage.
:::note
You can also use [Redis](http://redis.io/) with Anubis.
:::
This backend is ideal if you are running multiple instances of Anubis in a worker pool (eg: Kubernetes Deployments with a copy of Anubis in each Pod).
| Should I use this backend? | Yes/no |
| :------------------------------------------------------------ | :----- |
| Are you running only one instance of Anubis for this service? | 🚫 No |
| Does your service get a lot of traffic? | ✅ Yes |
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
| Do you have Redis or Valkey installed? | ✅ Yes |
#### Configuration
The `valkey` backend takes the following configuration options:
| Name | Type | Example | Description |
| :---- | :----- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
| `url` | string | `redis://valkey:6379/0` | The URL for the instance of Redis or Valkey that Anubis should store data in. This is in the same format as `REDIS_URL` in many cloud providers. |
Example:
If you have an instance of Valkey running with the hostname `valkey.int.techaro.lol`, then your store configuration could look like this:
```yaml
store:
backend: valkey
parameters:
url: "redis://valkey.int.techaro.lol:6379/0"
```
This would have the Valkey client connect to host `valkey.int.techaro.lol` on port `6379` with database `0` (the default database).
## Risk calculation for downstream services
In case your service needs it for risk calculation reasons, Anubis exposes information about the rules that any requests match using a few headers:

View File

@@ -2,7 +2,7 @@
title: Why does Anubis use Proof-of-Work?
---
Anubis uses a [proof of work](https://en.wikipedia.org/wiki/Proof_of_work) in order to validate that clients are genuine. The reason Anubis does this was inspired by [Hashcash](https://en.wikipedia.org/wiki/Hashcash), a suggestion from the early 2000's about extending the email protocol to avoid spam. The idea is that genuine people sending emails will have to do a small math problem that is expensive to compute, but easy to verify such as hashing a string with a given number of leading zeroes. This will have basically no impact on individuals sending a few emails a week, but the company churning out industrial quantities of advertising will be required to do prohibitively expensive computation. This is also how Bitcoin's consensus algorithm works.
Anubis uses [proof of work](https://en.wikipedia.org/wiki/Proof_of_work) in order to validate that clients are genuine. The reason Anubis does this was inspired by [Hashcash](https://en.wikipedia.org/wiki/Hashcash), a suggestion from the early 2000's about extending the email protocol to avoid spam. The idea is that genuine people sending emails will have to do a small math problem that is expensive to compute, but easy to verify such as hashing a string with a given number of leading zeroes. This will have basically no impact on individuals sending a few emails a week, but the company churning out industrial quantities of advertising will be required to do prohibitively expensive computation. This is also how Bitcoin's consensus algorithm works.
## How Anubis' proof of work scheme works
@@ -21,16 +21,3 @@ const hash = await sha256(`${challenge}${nonce}`);
In order to pass a challenge, the `hash` has to have the right number of leading zeros (the "difficulty"). When a client requests to pass the challenge, they include the nonce they used. The server then only has to do one sha256 operation: the one that confirms that the challenge (generated from request metadata) and the nonce (provided by the client) match the difficulty number of leading zeroes.
Ultimately, this is a hack whose real purpose is to give a "good enough" placeholder solution so that more time can be spent on fingerprinting and identifying headless browsers (EG via how they do font rendering) so that the challenge proof of work page doesn't need to be presented to known legitimate users.
## Challenge format
Anubis generates challenges based on browser metadata, including but not limited to the following:
- The contents of your [`Accept-Language` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language)
- The IP address of your client
- Your browser's [`User-Agent` string](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent)
- The date of the current week, rooted on Sundays
- Anubis' ed25519 public signing key for [JSON web tokens](https://jwt.io/) (JWTs)
- The challenge difficulty
This is intended to be a random value that is difficult for attackers to forge and guess, but also deterministic enough that it will naturally reset itself.

View File

@@ -58,6 +58,20 @@ Anubis is brought to you by sponsors and donors like:
<a href="https://wildbase.xyz/">
<img src="/img/sponsors/wildbase-logo.webp" alt="Wildbase" height="64" />
</a>
<a href="https://emma.pet">
<img
src="/img/sponsors/nepeat-logo.webp"
alt="Cat eyes over the word Emma in a serif font"
height="64"
/>
</a>
<a href="https://fabulous.systems/">
<img
src="/img/sponsors/fabulous-systems.webp"
alt="Cat eyes over the word Emma in a serif font"
height="64"
/>
</a>
## Overview

View File

@@ -21,8 +21,4 @@ If you use a browser extension such as [JShelter](https://jshelter.org/), you wi
## Does Anubis mine Bitcoin?
No. Anubis does not mine Bitcoin.
In order to mine bitcoin, you need to download a copy of the blockchain (so you have the state required to do mining) and also broadcast your mined blocks to the network should you reach a hash with the right number of leading zeroes. You also need to continuously read for newly broadcasted transactions so you can batch them into a block. This requires gigabytes of data to be transferred from the server to the client.
Anubis transfers two digit numbers of kilobytes from the server to the client (which you can independently verify with your browser's Developer Tools feature). This is orders of magnitude below what is required to mine Bitcoin.
No. Anubis does not mine Bitcoin or any other cryptocurrency.

View File

@@ -45,6 +45,19 @@ This page contains a non-exhaustive list with all websites using Anubis.
- https://gitlab.postmarketos.org/
- https://wiki.koha-community.org/
- https://extensions.typo3.org/
- https://ebird.org/
- https://fabulous.systems/
- https://coinhoards.org/
- https://pluralpedia.org/
- https://git.aya.so/
- https://marginalia-search.com/
- https://repositorio.ufrn.br/home/
- https://mozillazine.org/
- https://clew.se/
- https://tumfatig.net/
- https://rpmfusion.org/
- https://wiki.freepascal.org/
- https://azurlane.koumakan.jp/
- <details>
<summary>FreeCAD</summary>
- https://forum.freecad.org/
@@ -82,3 +95,21 @@ This page contains a non-exhaustive list with all websites using Anubis.
- https://karla.hds.hebis.de/
- and many more (see https://www.hebis.de/dienste/hebis-discovery-system/)
</details>
- <details>
<summary>Duke University</summary>
- https://repository.duke.edu/
- https://archives.lib.duke.edu/
- https://find.library.duke.edu/
- https://nicholas.duke.edu/
</details>
- <details>
<summary>Forschungszentrum Jülich</summary>
- https://juser.fz-juelich.de/
</details>
- <details>
<summary>archlinux32.org</summary>
- https://www.archlinux32.org/packages/
- https://bbs.archlinux32.org/
- https://bugs.archlinux32.org/
</details>

View File

@@ -6,7 +6,7 @@ import type * as Preset from '@docusaurus/preset-classic';
const config: Config = {
title: 'Anubis',
tagline: 'Weigh the soul of incoming HTTP requests using proof-of-work to stop AI crawlers',
tagline: 'Weigh the soul of incoming HTTP requests to protect your website!',
favicon: 'img/favicon.ico',
// Set the production url of your site here
@@ -40,27 +40,20 @@ const config: Config = {
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/TecharoHQ/anubis/tree/main/docs/',
},
blog: {
showReadingTime: true,
feedOptions: {
type: ['rss', 'atom', "json"],
xslt: true,
},
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
// Useful options to enforce blogging best practices
editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/',
onInlineTags: 'warn',
onInlineAuthors: 'warn',
onUntruncatedBlogPosts: 'warn',
onUntruncatedBlogPosts: 'throw',
},
docs: {
sidebarPath: './sidebars.ts',
editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/',
},
theme: {
customCss: './src/css/custom.css',
@@ -74,7 +67,7 @@ const config: Config = {
respectPrefersColorScheme: true,
},
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
image: 'img/social-card.jpg',
navbar: {
title: 'Anubis',
logo: {
@@ -82,23 +75,28 @@ const config: Config = {
src: 'img/favicon.webp',
},
items: [
{ to: '/blog', label: 'Blog', position: 'left' },
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Docs',
},
{ to: '/blog', label: 'Blog', position: 'left' },
{
href: 'https://github.com/sponsors/Xe',
label: "Sponsorship",
position: 'left'
to: '/docs/admin/botstopper',
label: "Unbranded Version",
position: "left"
},
{
href: 'https://github.com/TecharoHQ/anubis',
label: 'GitHub',
position: 'right',
},
{
href: 'https://github.com/sponsors/Xe',
label: "Sponsor the Project",
position: 'right'
},
],
},
footer: {
@@ -141,6 +139,10 @@ const config: Config = {
label: 'GitHub',
href: 'https://github.com/TecharoHQ/anubis',
},
{
label: 'Status',
href: 'https://techarohq.github.io/status/'
},
],
},
],

19
docs/fly.toml Normal file
View File

@@ -0,0 +1,19 @@
app = 'anubis-docs'
primary_region = 'yyz'
[build]
image = "ghcr.io/techarohq/anubis/docs:main"
[http_service]
internal_port = 80
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[vm]]
cpu_kind = 'shared'
cpus = 1
memory_mb = 256

View File

@@ -11,6 +11,7 @@
## /usr/share/docs/anubis/data or in the tarball you extracted Anubis from.
bots:
- import: (data)/crawlers/commoncrawl.yaml
# Pathological bots to deny
- # This correlates to data/bots/deny-pathological.yaml in the source tree
# https://github.com/TecharoHQ/anubis/blob/main/data/bots/deny-pathological.yaml
@@ -126,3 +127,8 @@ impressum:
status_codes:
CHALLENGE: 200
DENY: 200
store:
backend: bbolt
parameters:
path: /xe/data/anubis/data.bdb

View File

@@ -0,0 +1,99 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@@ -0,0 +1,31 @@
user nginx;
worker_processes 2;
error_log /dev/stdout warn;
pid /nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
access_log /dev/stdout;
sendfile on;
keepalive_timeout 65;
server {
listen 80 default_server;
server_name _;
error_page 404 /404.html;
root /www;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
}

View File

@@ -15,6 +15,11 @@ spec:
- name: anubis
configMap:
name: anubis-cfg
- name: nginx
configMap:
name: nginx-cfg
- name: temporary-data
emptyDir: {}
containers:
- name: anubis-docs
image: ghcr.io/techarohq/anubis/docs:main
@@ -26,8 +31,23 @@ spec:
requests:
cpu: 250m
memory: 128Mi
volumeMounts:
- name: nginx
mountPath: /conf
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 1
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 20
- name: anubis
image: ghcr.io/techarohq/anubis:main
imagePullPolicy: Always
@@ -51,6 +71,8 @@ spec:
volumeMounts:
- name: anubis
mountPath: /xe/cfg/anubis
- name: temporary-data
mountPath: /xe/data/anubis
resources:
limits:
cpu: 500m
@@ -71,3 +93,15 @@ spec:
envFrom:
- secretRef:
name: anubis-docs-thoth
readinessProbe:
httpGet:
path: /healthz
port: 9090
initialDelaySeconds: 1
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 9090
initialDelaySeconds: 10
periodSeconds: 20

View File

@@ -11,3 +11,8 @@ configMapGenerator:
behavior: create
files:
- ./cfg/anubis/botPolicies.yaml
- name: nginx-cfg
behavior: create
files:
- ./cfg/nginx/mime.types
- ./cfg/nginx/nginx.conf

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"start": "docusaurus start --host 0.0.0.0",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "echo 'use CI' && exit 1",
@@ -45,4 +45,4 @@
"engines": {
"node": ">=18.0"
}
}
}

View File

@@ -5,49 +5,50 @@ import styles from "./styles.module.css";
type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<"svg">>;
imageURL: string;
description: ReactNode;
};
const FeatureList: FeatureItem[] = [
{
title: "Easy to Use",
Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default,
imageURL: require("@site/static/img/anubis/happy.webp").default,
description: (
<>
Anubis is easy to set up, lightweight, and helps get rid of the lowest
hanging fruit so you can sleep at night.
Anubis sits in the background and weighs the risk of incoming requests.
If it asks a client to complete a challenge, no user interaction is
required.
</>
),
},
{
title: "Lightweight",
Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default,
imageURL: require("@site/static/img/anubis/pensive.webp").default,
description: (
<>
Anubis is efficient and as lightweight as possible, blocking the worst
of the bots on the internet and makes it easy to protect what you host
online.
Anubis is so lightweight you'll forget it's there until you look at your
hosting bill. On average it uses less than 128 MB of ram.
</>
),
},
{
title: "Multi-threaded",
Svg: require("@site/static/img/undraw_docusaurus_react.svg").default,
title: "Block the scrapers",
imageURL: require("@site/static/img/anubis/reject.webp").default,
description: (
<>
Anubis uses a multi-threaded proof of work check to ensure that users
browsers are up to date and support modern standards.
Anubis uses a combination of heuristics to identify and block bots
before they take your website down. You can customize the rules with{" "}
<a href="/docs/admin/policies">your own policies</a>.
</>
),
},
];
function Feature({ title, Svg, description }: FeatureItem) {
function Feature({ title, description, imageURL }: FeatureItem) {
return (
<div className={clsx("col col--4")}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
<img src={imageURL} className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<Heading as="h3">{title}</Heading>

View File

@@ -31,19 +31,12 @@ export default function Home(): ReactNode {
const { siteConfig } = useDocusaurusContext();
return (
<Layout
title={`Anubis: self hostable scraper defense software`}
description="Weigh the soul of incoming HTTP requests using proof-of-work to stop AI crawlers"
title={`Anubis: Web AI Firewall Utility`}
description="Weigh the soul of incoming HTTP requests to protect your website!"
>
<HomepageHeader />
<main>
<HomepageFeatures />
<center>
<p>
This is all placeholder text. It will be fixed. Give me time. I am
one person and my project has unexpectedly gone viral.
</p>
</center>
</main>
</Layout>
);

BIN
docs/static/img/anubis/happy.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/static/img/anubis/pensive.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/static/img/anubis/reject.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/static/img/social-card.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

66
go.mod
View File

@@ -7,18 +7,24 @@ require (
github.com/a-h/templ v0.3.906
github.com/cespare/xxhash/v2 v2.3.0
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
github.com/gaissmai/bart v0.20.4
github.com/gaissmai/bart v0.20.5
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/cel-go v0.25.0
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2
github.com/joho/godotenv v1.5.1
github.com/lum8rjack/go-ja4h v0.0.0-20250606032308-3a989c6635be
github.com/nicksnyder/go-i18n/v2 v2.6.0
github.com/playwright-community/playwright-go v0.5200.0
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.11.0
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
golang.org/x/net v0.41.0
golang.org/x/text v0.26.0
github.com/shirou/gopsutil/v4 v4.25.6
github.com/testcontainers/testcontainers-go v0.37.0
go.etcd.io/bbolt v1.4.2
golang.org/x/net v0.42.0
golang.org/x/text v0.27.0
google.golang.org/grpc v1.73.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.33.2
@@ -31,6 +37,7 @@ require (
cel.dev/expr v0.23.1 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
@@ -49,31 +56,45 @@ require (
github.com/cli/browser v1.3.0 // indirect
github.com/cli/go-gh v0.1.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/docker/docker v28.0.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.14.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-yaml v1.12.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-github/v70 v70.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/google/rpmpack v0.6.1-0.20250405124433-758cc6896cbc // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/goreleaser/chglog v0.7.0 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/goreleaser/nfpm/v2 v2.42.1 // indirect
@@ -83,14 +104,27 @@ require (
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/natefinch/atomic v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
@@ -102,30 +136,42 @@ require (
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/suzuki-shunsuke/logrus-error v0.1.4 // indirect
github.com/suzuki-shunsuke/pinact v1.6.0 // indirect
github.com/suzuki-shunsuke/urfave-cli-help-all v0.0.4 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
honnef.co/go/tools v0.6.1 // indirect

165
go.sum
View File

@@ -6,8 +6,12 @@ cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
@@ -50,6 +54,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
@@ -69,6 +77,12 @@ github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5
github.com/cli/shurcooL-graphql v0.0.1/go.mod h1:U7gCSuMZP/Qy7kbqkk5PrqXEeDgtfG5K+W+u8weorps=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
@@ -81,10 +95,22 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ=
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
github.com/docker/docker v28.0.1+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=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c h1:mxWGS0YyquJ/ikZOjSrRjjFIbUqIP9ojyYQ+QZTU3Rg=
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@@ -101,12 +127,14 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gaissmai/bart v0.20.4 h1:Ik47r1fy3jRVU+1eYzKSW3ho2UgBVTVnUS8O993584U=
github.com/gaissmai/bart v0.20.4/go.mod h1:cEed+ge8dalcbpi8wtS9x9m2hn/fNJH5suhdGQOHnYk=
github.com/gaissmai/bart v0.20.5 h1:ehoWZWQ7j//qt0K0Zs4i9hpoPpbgqsMQiR8W2QPJh+c=
github.com/gaissmai/bart v0.20.5/go.mod h1:cEed+ge8dalcbpi8wtS9x9m2hn/fNJH5suhdGQOHnYk=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -119,10 +147,13 @@ github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@@ -142,6 +173,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM=
github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
@@ -153,6 +186,7 @@ github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAy
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
@@ -182,6 +216,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
@@ -196,6 +232,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM=
github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -213,6 +251,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lum8rjack/go-ja4h v0.0.0-20250606032308-3a989c6635be h1:dVIND0nXGXPQnFZYrMXT6CxHhBYhTPMm0GFqcmfaIC4=
github.com/lum8rjack/go-ja4h v0.0.0-20250606032308-3a989c6635be/go.mod h1:q68TUR45WDa2r3yU4aO6WgxfCc0Vj1qtRaKaRE3yMLM=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -232,6 +276,20 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
@@ -241,6 +299,10 @@ github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpM
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -250,6 +312,8 @@ github.com/playwright-community/playwright-go v0.5200.0/go.mod h1:UnnyQZaqUOO5yw
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
@@ -258,6 +322,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
@@ -269,6 +335,8 @@ github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -289,6 +357,8 @@ github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8w
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -303,7 +373,13 @@ github.com/suzuki-shunsuke/pinact v1.6.0 h1:2QvSzREOquwLwKXhF9Hj0AInE/Rl63SZz9dK
github.com/suzuki-shunsuke/pinact v1.6.0/go.mod h1:FDUMck0mmL0mcnNZ23Vjh/aOR5cIdZhF1IIpGksT4dQ=
github.com/suzuki-shunsuke/urfave-cli-help-all v0.0.4 h1:YGHgrVjGTYHY98II6zijXUHP+OyvrzSCvd8m9iUcaK8=
github.com/suzuki-shunsuke/urfave-cli-help-all v0.0.4/go.mod h1:sSi6xaUaHfaqu32ECLeyE7NTMv+ZM5dW0JikhllaalY=
github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg=
github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM=
github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
@@ -314,66 +390,91 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -384,9 +485,10 @@ golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0=
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -394,8 +496,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -404,10 +506,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
@@ -415,15 +521,16 @@ golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk=
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
@@ -442,6 +549,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=

25
internal/health.go Normal file
View File

@@ -0,0 +1,25 @@
package internal
import (
"context"
"google.golang.org/grpc/health"
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
)
var HealthSrv = health.NewServer()
func SetHealth(svc string, status healthv1.HealthCheckResponse_ServingStatus) {
HealthSrv.SetServingStatus(svc, status)
}
func GetHealth(svc string) (healthv1.HealthCheckResponse_ServingStatus, bool) {
st, err := HealthSrv.Check(context.Background(), &healthv1.HealthCheckRequest{
Service: svc,
})
if err != nil {
return healthv1.HealthCheckResponse_UNKNOWN, false
}
return st.GetStatus(), true
}

14
internal/ja4h.go Normal file
View File

@@ -0,0 +1,14 @@
package internal
import (
"net/http"
"github.com/lum8rjack/go-ja4h"
)
func JA4H(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Header.Add("X-Http-Fingerprint-JA4H", ja4h.JA4H(r))
next.ServeHTTP(w, r)
})
}

View File

@@ -1,6 +1,7 @@
package ogtags
import (
"context"
"errors"
"log/slog"
"net/url"
@@ -8,7 +9,7 @@ import (
)
// GetOGTags is the main function that retrieves Open Graph tags for a URL
func (c *OGTagCache) GetOGTags(url *url.URL, originalHost string) (map[string]string, error) {
func (c *OGTagCache) GetOGTags(ctx context.Context, url *url.URL, originalHost string) (map[string]string, error) {
if url == nil {
return nil, errors.New("nil URL provided, cannot fetch OG tags")
}
@@ -21,12 +22,12 @@ func (c *OGTagCache) GetOGTags(url *url.URL, originalHost string) (map[string]st
cacheKey := c.generateCacheKey(target, originalHost)
// Check cache first
if cachedTags := c.checkCache(cacheKey); cachedTags != nil {
if cachedTags := c.checkCache(ctx, cacheKey); cachedTags != nil {
return cachedTags, nil
}
// Fetch HTML content, passing the original host
doc, err := c.fetchHTMLDocumentWithCache(target, originalHost, cacheKey)
doc, err := c.fetchHTMLDocumentWithCache(ctx, target, originalHost, cacheKey)
if errors.Is(err, syscall.ECONNREFUSED) {
slog.Debug("Connection refused, returning empty tags")
return nil, nil
@@ -42,7 +43,7 @@ func (c *OGTagCache) GetOGTags(url *url.URL, originalHost string) (map[string]st
ogTags := c.extractOGTags(doc)
// Store in cache
c.cache.Set(cacheKey, ogTags, c.ogTimeToLive)
c.cache.Set(ctx, cacheKey, ogTags, c.ogTimeToLive)
return ogTags, nil
}
@@ -59,8 +60,8 @@ func (c *OGTagCache) generateCacheKey(target string, originalHost string) string
}
// checkCache checks if we have the tags cached and returns them if so
func (c *OGTagCache) checkCache(cacheKey string) map[string]string {
if cachedTags, ok := c.cache.Get(cacheKey); ok {
func (c *OGTagCache) checkCache(ctx context.Context, cacheKey string) map[string]string {
if cachedTags, err := c.cache.Get(ctx, cacheKey); err == nil {
slog.Debug("cache hit", "tags", cachedTags)
return cachedTags
}

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
func TestCacheReturnsDefault(t *testing.T) {
@@ -21,14 +22,14 @@ func TestCacheReturnsDefault(t *testing.T) {
TimeToLive: time.Minute,
ConsiderHost: false,
Override: want,
})
}, memory.New(t.Context()))
u, err := url.Parse("https://anubis.techaro.lol")
if err != nil {
t.Fatal(err)
}
result, err := cache.GetOGTags(u, "anubis.techaro.lol")
result, err := cache.GetOGTags(t.Context(), u, "anubis.techaro.lol")
if err != nil {
t.Fatal(err)
}
@@ -49,7 +50,7 @@ func TestCheckCache(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
// Set up test data
urlStr := "http://example.com/page"
@@ -60,16 +61,16 @@ func TestCheckCache(t *testing.T) {
cacheKey := cache.generateCacheKey(urlStr, "example.com")
// Test cache miss
tags := cache.checkCache(cacheKey)
tags := cache.checkCache(t.Context(), cacheKey)
if tags != nil {
t.Errorf("expected nil tags on cache miss, got %v", tags)
}
// Manually add to cache
cache.cache.Set(cacheKey, expectedTags, time.Minute)
cache.cache.Set(t.Context(), cacheKey, expectedTags, time.Minute)
// Test cache hit
tags = cache.checkCache(cacheKey)
tags = cache.checkCache(t.Context(), cacheKey)
if tags == nil {
t.Fatal("expected non-nil tags on cache hit, got nil")
}
@@ -112,7 +113,7 @@ func TestGetOGTags(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
// Parse the test server URL
parsedURL, err := url.Parse(ts.URL)
@@ -122,7 +123,7 @@ func TestGetOGTags(t *testing.T) {
// Test fetching OG tags from the test server
// Pass the host from the parsed test server URL
ogTags, err := cache.GetOGTags(parsedURL, parsedURL.Host)
ogTags, err := cache.GetOGTags(t.Context(), parsedURL, parsedURL.Host)
if err != nil {
t.Fatalf("failed to get OG tags: %v", err)
}
@@ -142,14 +143,14 @@ func TestGetOGTags(t *testing.T) {
// Test fetching OG tags from the cache
// Pass the host from the parsed test server URL
ogTags, err = cache.GetOGTags(parsedURL, parsedURL.Host)
ogTags, err = cache.GetOGTags(t.Context(), parsedURL, parsedURL.Host)
if err != nil {
t.Fatalf("failed to get OG tags from cache: %v", err)
}
// Test fetching OG tags from the cache (3rd time)
// Pass the host from the parsed test server URL
newOgTags, err := cache.GetOGTags(parsedURL, parsedURL.Host)
newOgTags, err := cache.GetOGTags(t.Context(), parsedURL, parsedURL.Host)
if err != nil {
t.Fatalf("failed to get OG tags from cache: %v", err)
}
@@ -263,10 +264,10 @@ func TestGetOGTagsWithHostConsideration(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: tc.ogCacheConsiderHost,
})
}, memory.New(t.Context()))
for i, req := range tc.requests {
ogTags, err := cache.GetOGTags(parsedURL, req.host)
ogTags, err := cache.GetOGTags(t.Context(), parsedURL, req.host)
if err != nil {
t.Errorf("Request %d (host: %s): unexpected error: %v", i+1, req.host, err)
continue // Skip further checks for this request if error occurred

View File

@@ -20,8 +20,8 @@ var (
// fetchHTMLDocumentWithCache fetches the HTML document from the given URL string,
// preserving the original host header.
func (c *OGTagCache) fetchHTMLDocumentWithCache(urlStr string, originalHost string, cacheKey string) (*html.Node, error) {
req, err := http.NewRequestWithContext(context.Background(), "GET", urlStr, nil)
func (c *OGTagCache) fetchHTMLDocumentWithCache(ctx context.Context, urlStr string, originalHost string, cacheKey string) (*html.Node, error) {
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
if err != nil {
return nil, fmt.Errorf("failed to create http request: %w", err)
}
@@ -41,7 +41,7 @@ func (c *OGTagCache) fetchHTMLDocumentWithCache(urlStr string, originalHost stri
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
slog.Debug("og: request timed out", "url", urlStr)
c.cache.Set(cacheKey, emptyMap, c.ogTimeToLive/2) // Cache empty result for half the TTL to not spam the server
c.cache.Set(ctx, cacheKey, emptyMap, c.ogTimeToLive/2) // Cache empty result for half the TTL to not spam the server
}
return nil, fmt.Errorf("http get failed: %w", err)
}
@@ -56,7 +56,7 @@ func (c *OGTagCache) fetchHTMLDocumentWithCache(urlStr string, originalHost stri
if resp.StatusCode != http.StatusOK {
slog.Debug("og: received non-OK status code", "url", urlStr, "status", resp.StatusCode)
c.cache.Set(cacheKey, emptyMap, c.ogTimeToLive) // Cache empty result for non-successful status codes
c.cache.Set(ctx, cacheKey, emptyMap, c.ogTimeToLive) // Cache empty result for non-successful status codes
return nil, fmt.Errorf("%w: page not found", ErrOgHandled)
}

View File

@@ -1,6 +1,7 @@
package ogtags
import (
"context"
"fmt"
"io"
"net/http"
@@ -11,6 +12,7 @@ import (
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
"golang.org/x/net/html"
)
@@ -85,8 +87,8 @@ func TestFetchHTMLDocument(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
doc, err := cache.fetchHTMLDocument(ts.URL, "anything")
}, memory.New(t.Context()))
doc, err := cache.fetchHTMLDocument(t.Context(), ts.URL, "anything")
if tt.expectError {
if err == nil {
@@ -116,9 +118,9 @@ func TestFetchHTMLDocumentInvalidURL(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
doc, err := cache.fetchHTMLDocument("http://invalid.url.that.doesnt.exist.example", "anything")
doc, err := cache.fetchHTMLDocument(t.Context(), "http://invalid.url.that.doesnt.exist.example", "anything")
if err == nil {
t.Error("expected error for invalid URL, got nil")
@@ -130,7 +132,7 @@ func TestFetchHTMLDocumentInvalidURL(t *testing.T) {
}
// fetchHTMLDocument allows you to call fetchHTMLDocumentWithCache without a duplicate generateCacheKey call
func (c *OGTagCache) fetchHTMLDocument(urlStr string, originalHost string) (*html.Node, error) {
func (c *OGTagCache) fetchHTMLDocument(ctx context.Context, urlStr string, originalHost string) (*html.Node, error) {
cacheKey := c.generateCacheKey(urlStr, originalHost)
return c.fetchHTMLDocumentWithCache(urlStr, originalHost, cacheKey)
return c.fetchHTMLDocumentWithCache(ctx, urlStr, originalHost, cacheKey)
}

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
func TestIntegrationGetOGTags(t *testing.T) {
@@ -110,7 +111,7 @@ func TestIntegrationGetOGTags(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
// Create URL for test
testURL, _ := url.Parse(ts.URL)
@@ -119,7 +120,7 @@ func TestIntegrationGetOGTags(t *testing.T) {
// Get OG tags
// Pass the host from the test URL
ogTags, err := cache.GetOGTags(testURL, testURL.Host)
ogTags, err := cache.GetOGTags(t.Context(), testURL, testURL.Host)
// Check error expectation
if tc.expectError {
@@ -147,7 +148,7 @@ func TestIntegrationGetOGTags(t *testing.T) {
// Test cache retrieval
// Pass the host from the test URL
cachedOGTags, err := cache.GetOGTags(testURL, testURL.Host)
cachedOGTags, err := cache.GetOGTags(t.Context(), testURL, testURL.Host)
if err != nil {
t.Fatalf("failed to get OG tags from cache: %v", err)
}

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
"golang.org/x/net/html"
)
@@ -30,7 +31,7 @@ func BenchmarkGetTarget(b *testing.B) {
for _, tt := range tests {
b.Run(tt.name, func(b *testing.B) {
cache := NewOGTagCache(tt.target, config.OpenGraph{})
cache := NewOGTagCache(tt.target, config.OpenGraph{}, memory.New(b.Context()))
urls := make([]*url.URL, len(tt.paths))
for i, path := range tt.paths {
u, _ := url.Parse(path)
@@ -66,7 +67,7 @@ func BenchmarkExtractOGTags(b *testing.B) {
</head><body><div><p>Content</p></div></body></html>`,
}
cache := NewOGTagCache("http://example.com", config.OpenGraph{})
cache := NewOGTagCache("http://example.com", config.OpenGraph{}, memory.New(b.Context()))
docs := make([]*html.Node, len(htmlSamples))
for i, sample := range htmlSamples {
@@ -84,7 +85,7 @@ func BenchmarkExtractOGTags(b *testing.B) {
// Memory usage test
func TestMemoryUsage(t *testing.T) {
cache := NewOGTagCache("http://example.com", config.OpenGraph{})
cache := NewOGTagCache("http://example.com", config.OpenGraph{}, memory.New(t.Context()))
// Force GC and wait for it to complete
runtime.GC()

View File

@@ -9,8 +9,8 @@ import (
"strings"
"time"
"github.com/TecharoHQ/anubis/decaymap"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store"
)
const (
@@ -22,7 +22,7 @@ const (
)
type OGTagCache struct {
cache *decaymap.Impl[string, map[string]string]
cache store.JSON[map[string]string]
targetURL *url.URL
client *http.Client
@@ -36,7 +36,7 @@ type OGTagCache struct {
ogOverride map[string]string
}
func NewOGTagCache(target string, conf config.OpenGraph) *OGTagCache {
func NewOGTagCache(target string, conf config.OpenGraph, backend store.Interface) *OGTagCache {
// Predefined approved tags and prefixes
defaultApprovedTags := []string{"description", "keywords", "author"}
defaultApprovedPrefixes := []string{"og:", "twitter:", "fediverse:"}
@@ -77,7 +77,10 @@ func NewOGTagCache(target string, conf config.OpenGraph) *OGTagCache {
}
return &OGTagCache{
cache: decaymap.New[string, map[string]string](),
cache: store.JSON[map[string]string]{
Underlying: backend,
Prefix: "ogtags:",
},
targetURL: parsedTargetURL,
ogPassthrough: conf.Enabled,
ogTimeToLive: conf.TimeToLive,
@@ -124,9 +127,3 @@ func (c *OGTagCache) getTarget(u *url.URL) string {
return sb.String()
}
func (c *OGTagCache) Cleanup() {
if c.cache != nil {
c.cache.Cleanup()
}
}

View File

@@ -1,12 +1,14 @@
package ogtags
import (
"context"
"net/url"
"strings"
"testing"
"unicode/utf8"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
"golang.org/x/net/html"
)
@@ -46,7 +48,7 @@ func FuzzGetTarget(f *testing.F) {
}
// Create cache - should not panic
cache := NewOGTagCache(target, config.OpenGraph{})
cache := NewOGTagCache(target, config.OpenGraph{}, memory.New(context.Background()))
// Create URL
u := &url.URL{
@@ -130,7 +132,7 @@ func FuzzExtractOGTags(f *testing.F) {
return
}
cache := NewOGTagCache("http://example.com", config.OpenGraph{})
cache := NewOGTagCache("http://example.com", config.OpenGraph{}, memory.New(context.Background()))
// Should not panic
tags := cache.extractOGTags(doc)
@@ -186,7 +188,7 @@ func FuzzGetTargetRoundTrip(f *testing.F) {
t.Skip()
}
cache := NewOGTagCache(target, config.OpenGraph{})
cache := NewOGTagCache(target, config.OpenGraph{}, memory.New(context.Background()))
u := &url.URL{Path: path, RawQuery: query}
result := cache.getTarget(u)
@@ -243,7 +245,7 @@ func FuzzExtractMetaTagInfo(f *testing.F) {
},
}
cache := NewOGTagCache("http://example.com", config.OpenGraph{})
cache := NewOGTagCache("http://example.com", config.OpenGraph{}, memory.New(context.Background()))
// Should not panic
property, content := cache.extractMetaTagInfo(node)
@@ -296,7 +298,7 @@ func BenchmarkFuzzedGetTarget(b *testing.B) {
for _, input := range inputs {
b.Run(input.name, func(b *testing.B) {
cache := NewOGTagCache(input.target, config.OpenGraph{})
cache := NewOGTagCache(input.target, config.OpenGraph{}, memory.New(context.Background()))
u := &url.URL{Path: input.path, RawQuery: input.query}
b.ResetTimer()

View File

@@ -15,6 +15,7 @@ import (
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
func TestNewOGTagCache(t *testing.T) {
@@ -44,7 +45,7 @@ func TestNewOGTagCache(t *testing.T) {
Enabled: tt.ogPassthrough,
TimeToLive: tt.ogTimeToLive,
ConsiderHost: false,
})
}, memory.New(t.Context()))
if cache == nil {
t.Fatal("expected non-nil cache, got nil")
@@ -84,7 +85,7 @@ func TestNewOGTagCache_UnixSocket(t *testing.T) {
Enabled: true,
TimeToLive: 5 * time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
if cache == nil {
t.Fatal("expected non-nil cache, got nil")
@@ -169,7 +170,7 @@ func TestGetTarget(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
u := &url.URL{
Path: tt.path,
@@ -242,14 +243,14 @@ func TestIntegrationGetOGTags_UnixSocket(t *testing.T) {
Enabled: true,
TimeToLive: time.Minute,
ConsiderHost: false,
})
}, memory.New(t.Context()))
// Create a dummy URL for the request (path and query matter)
testReqURL, _ := url.Parse("/some/page?query=1")
// Get OG tags
// Pass an empty string for host, as it's irrelevant for unix sockets
ogTags, err := cache.GetOGTags(testReqURL, "")
ogTags, err := cache.GetOGTags(t.Context(), testReqURL, "")
if err != nil {
t.Fatalf("GetOGTags failed for unix socket: %v", err)
@@ -265,7 +266,7 @@ func TestIntegrationGetOGTags_UnixSocket(t *testing.T) {
// Test cache retrieval (should hit cache)
// Pass an empty string for host
cachedTags, err := cache.GetOGTags(testReqURL, "")
cachedTags, err := cache.GetOGTags(t.Context(), testReqURL, "")
if err != nil {
t.Fatalf("GetOGTags (cache hit) failed for unix socket: %v", err)
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
"golang.org/x/net/html"
)
@@ -17,7 +18,7 @@ func TestExtractOGTags(t *testing.T) {
Enabled: false,
ConsiderHost: false,
TimeToLive: time.Minute,
})
}, memory.New(t.Context()))
// Manually set approved tags/prefixes based on the user request for clarity
testCache.approvedTags = []string{"description"}
testCache.approvedPrefixes = []string{"og:"}
@@ -198,7 +199,7 @@ func TestExtractMetaTagInfo(t *testing.T) {
Enabled: false,
ConsiderHost: false,
TimeToLive: time.Minute,
})
}, memory.New(t.Context()))
testCache.approvedTags = []string{"description"}
testCache.approvedPrefixes = []string{"og:"}

View File

@@ -60,15 +60,6 @@ func New(ctx context.Context, thothURL, apiToken string, plaintext bool) (*Clien
hc := healthv1.NewHealthClient(conn)
resp, err := hc.Check(ctx, &healthv1.HealthCheckRequest{})
if err != nil {
return nil, fmt.Errorf("can't verify thoth health at %s: %w", thothURL, err)
}
if resp.Status != healthv1.HealthCheckResponse_SERVING {
return nil, fmt.Errorf("thoth is not healthy, wanted %s but got %s", healthv1.HealthCheckResponse_SERVING, resp.Status)
}
return &Client{
conn: conn,
health: hc,

22
internal/unbreakdocker.go Normal file
View File

@@ -0,0 +1,22 @@
package internal
import (
"os"
"os/exec"
)
func UnbreakDocker() {
// XXX(Xe): This is bad code. Do not do this.
//
// I have to do this because I'm running from inside the context of a dev
// container. This dev container runs in a different docker network than
// the valkey test container runs in. In order to let my dev container
// connect to the test container, they need to share a network in common.
// The easiest network to use for this is the default "bridge" network.
//
// This is a horrifying monstrosity, but the part that scares me the most
// is the fact that it works.
if hostname, err := os.Hostname(); err == nil {
exec.Command("docker", "network", "connect", "bridge", hostname).Run()
}
}

View File

@@ -1,8 +1,9 @@
package lib
import (
"context"
"crypto/ed25519"
"crypto/sha256"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
@@ -16,6 +17,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/google/cel-go/common/types"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
@@ -30,6 +32,7 @@ import (
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store"
// challenge implementations
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
@@ -67,11 +70,11 @@ type Server struct {
next http.Handler
mux *http.ServeMux
policy *policy.ParsedConfig
DNSBLCache *decaymap.Impl[string, dnsbl.DroneBLResponse]
OGTags *ogtags.OGTagCache
ed25519Priv ed25519.PrivateKey
hs512Secret []byte
opts Options
store store.Interface
}
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
@@ -87,23 +90,51 @@ func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
}
}
func (s *Server) challengeFor(r *http.Request, difficulty int) string {
var fp [32]byte
if len(s.hs512Secret) == 0 {
fp = sha256.Sum256(s.ed25519Priv.Public().(ed25519.PublicKey)[:])
} else {
fp = sha256.Sum256(s.hs512Secret)
func (s *Server) challengeFor(r *http.Request) (*challenge.Challenge, error) {
ckies := r.CookiesNamed(anubis.TestCookieName)
if len(ckies) == 0 {
return s.issueChallenge(r.Context(), r)
}
challengeData := fmt.Sprintf(
"X-Real-IP=%s,User-Agent=%s,WeekTime=%s,Fingerprint=%x,Difficulty=%d",
r.Header.Get("X-Real-Ip"),
r.UserAgent(),
time.Now().UTC().Round(24*7*time.Hour).Format(time.RFC3339),
fp,
difficulty,
)
return internal.FastHash(challengeData)
j := store.JSON[challenge.Challenge]{Underlying: s.store}
ckie := ckies[0]
chall, err := j.Get(r.Context(), "challenge:"+ckie.Value)
if err != nil {
return nil, err
}
return &chall, nil
}
func (s *Server) issueChallenge(ctx context.Context, r *http.Request) (*challenge.Challenge, error) {
id, err := uuid.NewV7()
if err != nil {
return nil, err
}
var randomData = make([]byte, 256)
if _, err := rand.Read(randomData); err != nil {
return nil, err
}
chall := challenge.Challenge{
ID: id.String(),
RandomData: fmt.Sprintf("%x", randomData),
IssuedAt: time.Now(),
Metadata: map[string]string{
"User-Agent": r.Header.Get("User-Agent"),
"X-Real-Ip": r.Header.Get("X-Real-Ip"),
},
}
j := store.JSON[challenge.Challenge]{Underlying: s.store}
if err := j.Set(ctx, "challenge:"+id.String(), chall, 30*time.Minute); err != nil {
return nil, err
}
return &chall, err
}
func (s *Server) maybeReverseProxyHttpStatusOnly(w http.ResponseWriter, r *http.Request) {
@@ -247,15 +278,16 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
}
func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string, lg *slog.Logger) bool {
db := &store.JSON[dnsbl.DroneBLResponse]{Underlying: s.store, Prefix: "dronebl:"}
if s.policy.DNSBL && ip != "" {
resp, ok := s.DNSBLCache.Get(ip)
if !ok {
resp, err := db.Get(r.Context(), ip)
if err != nil {
lg.Debug("looking up ip in dnsbl")
resp, err := dnsbl.Lookup(ip)
if err != nil {
lg.Error("can't look up ip in dnsbl", "err", err)
}
s.DNSBLCache.Set(ip, resp, 24*time.Hour)
db.Set(r.Context(), ip, resp, 24*time.Hour)
droneBLHits.WithLabelValues(resp.String()).Inc()
}
@@ -309,15 +341,30 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
return
}
lg = lg.With("check_result", cr)
chal := s.challengeFor(r, rule.Challenge.Difficulty)
s.SetCookie(w, CookieOpts{Host: r.Host, Name: anubis.TestCookieName, Value: chal})
chall, err := s.challengeFor(r)
if err != nil {
lg.Error("failed to fetch or issue challenge", "err", err)
w.WriteHeader(http.StatusInternalServerError)
err := encoder.Encode(struct {
Error string `json:"error"`
}{
Error: fmt.Sprintf("%s \"makeChallenge\"", localizer.T("internal_server_error")),
})
if err != nil {
lg.Error("failed to encode error response", "err", err)
w.WriteHeader(http.StatusInternalServerError)
}
return
}
s.SetCookie(w, CookieOpts{Host: r.Host, Name: anubis.TestCookieName, Value: chall.ID})
err = encoder.Encode(struct {
Rules *config.ChallengeRules `json:"rules"`
Challenge string `json:"challenge"`
}{
Challenge: chal,
Challenge: chall.RandomData,
Rules: rule.Challenge,
})
if err != nil {
@@ -325,7 +372,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
return
}
lg.Debug("made challenge", "challenge", chal, "rules", rule.Challenge, "cr", cr)
lg.Debug("made challenge", "challenge", chall, "rules", rule.Challenge, "cr", cr)
challengesIssued.WithLabelValues("api").Inc()
}
@@ -347,8 +394,6 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
return
}
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
redir := r.FormValue("redir")
redirURL, err := url.ParseRequestURI(redir)
if err != nil {
@@ -384,9 +429,20 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
return
}
challengeStr := s.challengeFor(r, rule.Challenge.Difficulty)
chall, err := s.challengeFor(r)
if err != nil {
lg.Error("check failed", "err", err)
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
return
}
if err := impl.Validate(r, lg, rule, challengeStr); err != nil {
in := &challenge.ValidateInput{
Challenge: chall,
Rule: rule,
Store: s.store,
}
if err := impl.Validate(r, lg, in); err != nil {
failedValidations.WithLabelValues(rule.Challenge.Algorithm).Inc()
var cerr *challenge.Error
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
@@ -405,7 +461,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
// generate JWT cookie
tokenString, err := s.signJWT(jwt.MapClaims{
"challenge": challengeStr,
"challenge": chall.ID,
"method": rule.Challenge.Algorithm,
"policyRule": rule.Hash(),
"action": string(cr.Rule),
@@ -493,8 +549,3 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error)
Rules: &checker.List{},
}, nil
}
func (s *Server) CleanupDecayMap() {
s.DNSBLCache.Cleanup()
s.OGTags.Cleanup()
}

View File

@@ -1,60 +1,11 @@
package challenge
import (
"log/slog"
"net/http"
"sort"
"sync"
import "time"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/a-h/templ"
)
var (
registry map[string]Impl = map[string]Impl{}
regLock sync.RWMutex
)
func Register(name string, impl Impl) {
regLock.Lock()
defer regLock.Unlock()
registry[name] = impl
}
func Get(name string) (Impl, bool) {
regLock.RLock()
defer regLock.RUnlock()
result, ok := registry[name]
return result, ok
}
func Methods() []string {
regLock.RLock()
defer regLock.RUnlock()
var result []string
for method := range registry {
result = append(result, method)
}
sort.Strings(result)
return result
}
type IssueInput struct {
Impressum *config.Impressum
Rule *policy.Bot
Challenge string
OGTags map[string]string
}
type Impl interface {
// Setup registers any additional routes with the Impl for assets or API routes.
Setup(mux *http.ServeMux)
// Issue a new challenge to the user, called by the Anubis.
Issue(r *http.Request, lg *slog.Logger, in *IssueInput) (templ.Component, error)
// Validate a challenge, making sure that it passes muster.
Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string) error
// Challenge is the metadata about a single challenge issuance.
type Challenge struct {
ID string `json:"id"` // UUID identifying the challenge
RandomData string `json:"randomData"` // The random data the client processes
IssuedAt time.Time `json:"issuedAt"` // When the challenge was issued
Metadata map[string]string `json:"metadata"` // Challenge metadata such as IP address and user agent
}

View File

@@ -0,0 +1,23 @@
package challengetest
import (
"testing"
"time"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/google/uuid"
)
func New(t *testing.T) *challenge.Challenge {
t.Helper()
id := uuid.Must(uuid.NewV7())
randomData := internal.SHA256sum(time.Now().String())
return &challenge.Challenge{
ID: id.String(),
RandomData: randomData,
IssuedAt: time.Now(),
}
}

View File

@@ -0,0 +1,7 @@
package challengetest
import "testing"
func TestNew(t *testing.T) {
_ = New(t)
}

View File

@@ -0,0 +1,68 @@
package challenge
import (
"log/slog"
"net/http"
"sort"
"sync"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store"
"github.com/a-h/templ"
)
var (
registry map[string]Impl = map[string]Impl{}
regLock sync.RWMutex
)
func Register(name string, impl Impl) {
regLock.Lock()
defer regLock.Unlock()
registry[name] = impl
}
func Get(name string) (Impl, bool) {
regLock.RLock()
defer regLock.RUnlock()
result, ok := registry[name]
return result, ok
}
func Methods() []string {
regLock.RLock()
defer regLock.RUnlock()
var result []string
for method := range registry {
result = append(result, method)
}
sort.Strings(result)
return result
}
type IssueInput struct {
Impressum *config.Impressum
Rule *policy.Bot
Challenge *Challenge
OGTags map[string]string
Store store.Interface
}
type ValidateInput struct {
Rule *policy.Bot
Challenge *Challenge
Store store.Interface
}
type Impl interface {
// Setup registers any additional routes with the Impl for assets or API routes.
Setup(mux *http.ServeMux)
// Issue a new challenge to the user, called by the Anubis.
Issue(r *http.Request, lg *slog.Logger, in *IssueInput) (templ.Component, error)
// Validate a challenge, making sure that it passes muster.
Validate(r *http.Request, lg *slog.Logger, in *ValidateInput) error
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/localization"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/web"
"github.com/a-h/templ"
)
@@ -32,11 +31,11 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput)
q := u.Query()
q.Set("redir", r.URL.String())
q.Set("challenge", in.Challenge)
q.Set("challenge", in.Challenge.RandomData)
u.RawQuery = q.Encode()
loc := localization.GetLocalizer(r)
component, err := web.BaseWithChallengeAndOGTags(loc.T("making_sure_not_bot"), page(in.Challenge, u.String(), in.Rule.Challenge.Difficulty, loc), in.Impressum, in.Challenge, in.Rule.Challenge, in.OGTags, loc)
component, err := web.BaseWithChallengeAndOGTags(loc.T("making_sure_not_bot"), page(u.String(), in.Rule.Challenge.Difficulty, loc), in.Impressum, in.Challenge.RandomData, in.Rule.Challenge, in.OGTags, loc)
if err != nil {
return nil, fmt.Errorf("can't render page: %w", err)
@@ -45,11 +44,11 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput)
return component, nil
}
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, wantChallenge string) error {
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
gotChallenge := r.FormValue("challenge")
if subtle.ConstantTimeCompare([]byte(wantChallenge), []byte(gotChallenge)) != 1 {
return challenge.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", challenge.ErrFailed, wantChallenge, gotChallenge))
if subtle.ConstantTimeCompare([]byte(in.Challenge.RandomData), []byte(gotChallenge)) != 1 {
return challenge.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", challenge.ErrFailed, in.Challenge.RandomData, gotChallenge))
}
return nil

View File

@@ -7,7 +7,7 @@ import (
"github.com/TecharoHQ/anubis/lib/localization"
)
templ page(challenge, redir string, difficulty int, loc *localization.SimpleLocalizer) {
templ page(redir string, difficulty int, loc *localization.SimpleLocalizer) {
<div class="centered-div">
<img id="image" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>

View File

@@ -15,7 +15,7 @@ import (
"github.com/TecharoHQ/anubis/lib/localization"
)
func page(challenge, redir string, difficulty int, loc *localization.SimpleLocalizer) templ.Component {
func page(redir string, difficulty int, loc *localization.SimpleLocalizer) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {

View File

@@ -11,7 +11,6 @@ import (
"github.com/TecharoHQ/anubis/internal"
chall "github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/localization"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/web"
"github.com/a-h/templ"
)
@@ -31,7 +30,7 @@ func (i *Impl) Setup(mux *http.ServeMux) {
func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
loc := localization.GetLocalizer(r)
component, err := web.BaseWithChallengeAndOGTags(loc.T("making_sure_not_bot"), web.Index(loc), in.Impressum, in.Challenge, in.Rule.Challenge, in.OGTags, loc)
component, err := web.BaseWithChallengeAndOGTags(loc.T("making_sure_not_bot"), web.Index(loc), in.Impressum, in.Challenge.RandomData, in.Rule.Challenge, in.OGTags, loc)
if err != nil {
return nil, fmt.Errorf("can't render page: %w", err)
}
@@ -39,7 +38,10 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *chall.IssueInput) (te
return component, nil
}
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string) error {
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
rule := in.Rule
challenge := in.Challenge.RandomData
nonceStr := r.FormValue("nonce")
if nonceStr == "" {
return chall.NewError("validate", "invalid response", fmt.Errorf("%w nonce", chall.ErrMissingField))

View File

@@ -124,16 +124,25 @@ func TestBasic(t *testing.T) {
t.Run(cs.name, func(t *testing.T) {
lg := slog.With()
i.Setup(http.NewServeMux())
inp := &challenge.IssueInput{
Rule: bot,
Challenge: cs.challengeStr,
Rule: bot,
Challenge: &challenge.Challenge{
RandomData: cs.challengeStr,
},
}
if _, err := i.Issue(cs.req, lg, inp); err != nil {
t.Errorf("can't issue challenge: %v", err)
}
if err := i.Validate(cs.req, lg, bot, cs.challengeStr); !errors.Is(err, cs.err) {
if err := i.Validate(cs.req, lg, &challenge.ValidateInput{
Rule: bot,
Challenge: &challenge.Challenge{
RandomData: cs.challengeStr,
},
}); !errors.Is(err, cs.err) {
t.Errorf("got wrong error from Validate, got %v but wanted %v", err, cs.err)
}
})

View File

@@ -15,9 +15,7 @@ import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/data"
"github.com/TecharoHQ/anubis/decaymap"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/internal/dnsbl"
"github.com/TecharoHQ/anubis/internal/ogtags"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/localization"
@@ -108,8 +106,8 @@ func New(opts Options) (*Server, error) {
hs512Secret: opts.HS512Secret,
policy: opts.Policy,
opts: opts,
DNSBLCache: decaymap.New[string, dnsbl.DroneBLResponse](),
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph),
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store),
store: opts.Policy.Store,
}
mux := http.NewServeMux()

View File

@@ -128,19 +128,25 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
}
challengesIssued.WithLabelValues("embedded").Add(1)
challengeStr := s.challengeFor(r, rule.Challenge.Difficulty)
chall, err := s.challengeFor(r)
if err != nil {
lg.Error("can't get challenge", "err", "err")
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
return
}
var ogTags map[string]string = nil
if s.opts.OpenGraph.Enabled {
var err error
ogTags, err = s.OGTags.GetOGTags(r.URL, r.Host)
ogTags, err = s.OGTags.GetOGTags(r.Context(), r.URL, r.Host)
if err != nil {
lg.Error("failed to get OG tags", "err", err)
}
}
s.SetCookie(w, CookieOpts{
Value: challengeStr,
Value: chall.ID,
Host: r.Host,
Path: "/",
Name: anubis.TestCookieName,
@@ -150,6 +156,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
impl, ok := challenge.Get(rule.Challenge.Algorithm)
if !ok {
lg.Error("check failed", "err", "can't get algorithm", "algorithm", rule.Challenge.Algorithm)
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
return
}
@@ -157,8 +164,9 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
in := &challenge.IssueInput{
Impressum: s.policy.Impressum,
Rule: rule,
Challenge: challengeStr,
Challenge: chall,
OGTags: ogTags,
Store: s.store,
}
component, err := impl.Issue(r, lg, in)
@@ -190,7 +198,7 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg string, status int) {
localizer := localization.GetLocalizer(r)
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, r.FormValue("redir"), localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {

View File

@@ -0,0 +1,64 @@
{
"loading": "Načítám...",
"why_am_i_seeing": "Proč to vidím?",
"protected_by": "Chráněno pomocí",
"protected_from": "Od",
"made_with": "Vytvořeno s ❤️ v 🇨🇦",
"mascot_design": "Design maskota od",
"ai_companies_explanation": "Vidíte to proto, že správce této webové stránky nastavil Anubis na ochranu serveru před pohromou AI společností agresivně stahujících webové stránky. To může, a také způsobuje výpadky webových stránek, což je činí nepřístupnými pro všechny.",
"anubis_compromise": "Anubis je kompromis. Anubis používá schéma Proof-of-Work v duchu Hashcash, návrhu schématu proof-of-work pro snížení e-mailového spamu. Myšlenka je, že na individuálních úrovních je dodatečná zátěž zanedbatelná, ale na úrovni masového použití se sčítá a činí stahování mnohem dražším.",
"hack_purpose": "Ve výsledku je to hack, jehož skutečným účelem je poskytnout \"dostatečně dobré\" prozatímní řešení, které nám poskytuje čas pracovat na ověřování a identifikaci robotů (např. prostřednictvím toho, jak vykreslují fonty), tak aby stránka s výzvou proof of work nemusela být prezentována uživatelům, kteří jsou legitimní.",
"jshelter_note": "Upozorňujeme, že Anubis vyžaduje použití moderních funkcí JavaScriptu, které rozšíření jako JShelter omezují. Prosím zakažte JShelter nebo jiná podobná rozšíření pro tuto doménu.",
"version_info": "Tato webová stránka běží na Anubis verzi",
"try_again": "Zkusit znovu",
"go_home": "Přejít na úvodní stránku",
"contact_webmaster": "nebo pokud si myslíte, že byste neměli být blokováni, kontaktujte správce na",
"connection_security": "Prosím počkejte chvilku, zatímco zajišťujeme bezpečnost vašeho připojení.",
"javascript_required": "Bohužel musíte povolit JavaScript, abyste prošli touto výzvou. To je vyžadováno proto, že AI společnosti změnily společenskou smlouvu ohledně toho, jak funguje hosting webových stránek. Řešení bez JavaScriptu je ve vývoji.",
"benchmark_requires_js": "Spuštění testovacího nástroje vyžaduje povolení JavaScriptu.",
"difficulty": "Obtížnost:",
"algorithm": "Algoritmus:",
"compare": "Porovnat:",
"time": "Čas",
"iters": "Iterace",
"time_a": "Čas A",
"iters_a": "Iterace A",
"time_b": "Čas B",
"iters_b": "Iterace B",
"static_check_endpoint": "Toto je pouze kontrolní bod pro přístup na tuto stránku.",
"authorization_required": "Vyžadována autorizace",
"cookies_disabled": "Váš prohlížeč je nakonfigurován tak, aby zakázal cookies. Anubis vyžaduje cookies pro legitimní zájem zajistit, že jste legitimní uživatel. Prosím povolte cookies pro tuto doménu",
"access_denied": "Přístup zamítnut: kód chyby",
"dronebl_entry": "DroneBL nahlásil záznam",
"see_dronebl_lookup": "viz",
"internal_server_error": "Interní chyba serveru: správce špatně nakonfiguroval Anubis. Kontaktujte správce a požádejte ho, aby se do systémových záznamů",
"invalid_redirect": "Neplatné přesměrování",
"redirect_not_parseable": "URL přesměrování nelze analyzovat",
"redirect_domain_not_allowed": "Doména přesměrování není povolena",
"failed_to_sign_jwt": "nepodařilo se podepsat JWT",
"invalid_invocation": "Neplatné vyvolání MakeChallenge",
"client_error_browser": "Chyba prohlížeče: Ujistěte se, že váš prohlížeč je aktuální a zkuste to později.",
"oh_noes": "Ale ne!",
"benchmarking_anubis": "Testování Anubise!",
"you_are_not_a_bot": "Nejste robot!",
"making_sure_not_bot": "Ujišťujeme se, že nejste robot!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Váš prohlížeč nepodporuje funkci web.crypto. Používáte zabezpečené připojení?",
"js_web_workers_error": "Váš prohlížeč nepodporuje web workers (Anubis je používá, aby zabránil zamrznutí vašeho prohlížeče). Máte nainstalovano rozšíření JShelter nebo podobné?",
"js_cookies_error": "Váš prohlížeč neukládá cookies. Anubis používá cookies k určení, kteří klienti prošli výzvami uložením podepsaného tokenu v cookie. Prosím povolte ukládání cookies pro tuto doménu. Názvy cookies, které Anubis ukládá, se mohou měnit bez upozornění. Názvy a hodnoty cookies nejsou součástí veřejného API.",
"js_context_not_secure": "Váše připojení není bezpečné!",
"js_context_not_secure_msg": "Zkuste se připojit přes HTTPS nebo informujte správce o nastavení HTTPS. Pro více informací viz <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Počítám...",
"js_missing_feature": "Chybějící funkce",
"js_challenge_error": "Chyba výzvy!",
"js_challenge_error_msg": "Nepodařilo se vyřešit kontrolní algoritmus. Možná budete chtít obnovit stránku.",
"js_calculating_difficulty": "Počítám...<br/>Obtížnost:",
"js_speed": "Rychlost:",
"js_verification_longer": "Ověřování trvá déle, než se očekávalo. Prosím neobnovujte stránku.",
"js_success": "Úspěch!",
"js_done_took": "Hotovo! Trvalo to",
"js_iterations": "iterací",
"js_finished_reading": "Dokončil jsem čtení, pokračovat →",
"js_calculation_error": "Chyba výpočtu!",
"js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:"
}

View File

@@ -2,19 +2,20 @@
"loading": "Ladevorgang...",
"why_am_i_seeing": "Warum sehe ich diese Seite?",
"protected_by": "Geschützt durch",
"protected_from": "From",
"made_with": "Mit ❤️ gemacht in 🇨🇦",
"mascot_design": "Maskottchen erstellt von",
"ai_companies_explanation": "Diese Seite wird angezeigt, da der Betreiber der Webseite Anubis eingerichtet hat, um sie vor aggressiven KI-Website-Scrapern zu schützen. Diese können Ausfälle der Webseite verursachen, wodurch die Webseite für jeden nicht erreichbar ist.",
"anubis_compromise": "Anubis ist eine Art Kompromiss. Es verwendet die sogenannte Proof-of-Work Methode nach Hashcash, ein Mechanismus, der ursprünglich zur E-Mail-Spam-Bekämpfung entwickelt wurde. Die Idee dahinter ist, dass ein einziger User nur eine kleine Verzögerung hat, auf die Webseite zu gelangen; bei Scrapern kann das allerdings große Auswirkungen haben.",
"hack_purpose": "Man könnte dies als eine Lösung bezeichnen, die gut genug ist, einem etwas Zeit zu verschaffen für Fingerprinting und dem Identifizieren von Headless Browsern, sodass im besten Fall normale User diese Seite garnicht erst zu sehen bekommen.",
"jshelter_note": "Anubis benötigt moderne JavaScript-Features, welche von Plugins wie zB JShelter deaktiviert werden. Bitte deaktiviere also JShelter oder ähnliche Plugins für diese Domain.",
"version_info": "Diese Webseite läuft mit Anubis version",
"try_again": "Nochmal probieren",
"ai_companies_explanation": "Diese Seite wird angezeigt, da der Betreiber der Webseite Anubis eingerichtet hat, um sie vor aggressiven KI-Website-Scrapern zu schützen. Diese können Ausfälle der Webseite verursachen, wodurch die Webseite für niemanden erreichbar ist.",
"anubis_compromise": "Anubis stellt einen Kompromiss dar. Es verwendet eine Proof-of-Work-Methode nach Hashcash, die ursprünglich zur Bekämpfung von E-Mail-Spam entwickelt wurde. Die Idee dahinter ist, dass ein legitimer Besucher die Webseite mit einer vernachlässigbaren Verzögerung erreichen kann. Massenhaftes Scraping wird dadurch jedoch aufwändig und teuer.",
"hack_purpose": "Man könnte dies als eine Lösung bezeichnen, die einem etwas Zeit für Fingerprinting und dem Identifizieren von Headless-Browsern verschafft. Besucher, die mit sehr hoher Wahrscheinlichkeit legitim sind, bekommen diese Seite nicht zu sehen.",
"jshelter_note": "Anubis benötigt moderne JavaScript-Features, welche von Plugins wie JShelter deaktiviert werden. Bitte deaktiviere JShelter oder ähnliche Plugins für diese Domain.",
"version_info": "Diese Webseite läuft mit der Anubis-Version",
"try_again": "Erneut versuchen",
"go_home": "Zur Hauptseite",
"contact_webmaster": "oder wenn es sich hier um einen Fehler handelt, kontaktiere bitte den Administrator der Webseite unter",
"connection_security": "Bitte warte einen Moment während wir sicherstellen, dass eine sichere Verbindung verwendet wird.",
"javascript_required": "Es muss leider JavaScript aktiviert werden, um den Check durchführen zu können. Dies ist leider notwendig weil Firmen im KI-Sektor die sozialen Verhältnisse geändert haben, wie Website-Hosting funktioniert. Eine Lösung ohne JavaScript ist in Entwicklung.",
"benchmark_requires_js": "Das Benchmark-Tool benötigt das Aktivieren von JavaScript.",
"contact_webmaster": "oder wenn Du glaubst, dass es sich hierbei um einen Fehler handelt, kontaktiere bitte den Administrator der Webseite unter",
"connection_security": "Bitte warte einen Moment, während wir sicherstellen, dass eine sichere Verbindung verwendet wird.",
"javascript_required": "Du musst JavaScript aktivieren, um diese Prüfung durchführen zu können. Dies ist notwendig, da KI-Unternehmen den sozialen Vertrag bezüglich des Hostings von Webseiten gebrochen haben. Eine Lösung ohne JavaScript ist in Entwicklung.",
"benchmark_requires_js": "Für die Nutzung des Benchmark-Tools muss JavaScript aktiviert werden.",
"difficulty": "Schwierigkeit:",
"algorithm": "Algorithmus:",
"compare": "Vergleich:",
@@ -24,40 +25,40 @@
"iters_a": "Iterationen A",
"time_b": "Zeit B",
"iters_b": "Iterationen B",
"static_check_endpoint": "Dies ist nur ein Check-Endpunkt, der von beispielsweise einem Reverse-Proxy geprüft werden kann.",
"static_check_endpoint": "Dies ist nur ein Endpunkt, der von einem Reverse-Proxy geprüft werden kann.",
"authorization_required": "Zugriffserlaubnis benötigt",
"cookies_disabled": "Cookies sind in Ihrem Browser deaktiviert. Anubis benötigt Cookies um sicherzustellen, dass es sich hierbei um einen validen Zugriff handelt. Bitte aktiviere Cookies für diese Domain.",
"cookies_disabled": "Cookies sind in deinem Browser deaktiviert. Anubis benötigt Cookies, um sicherzustellen, dass es sich hierbei um einen legitimen Zugriff handelt. Bitte aktiviere Cookies für diese Domain.",
"access_denied": "Zugriff verweigert: Fehlercode",
"dronebl_entry": "Eintrag in DroneBL",
"see_dronebl_lookup": "anzeigen",
"internal_server_error": "Interner Server Error: Misskonfiguration von Anubis. Bitte kontatkiere den Administrator damit dieser die Logs prüfen kann.",
"internal_server_error": "Interner Server-Fehler: Der Administrator hat Anubis fehlerhaft konfiguriert. Bitte kontaktiere den Administrator und bitte ihn, die Logs zu prüfen.",
"invalid_redirect": "Ungültige Weiterleitung",
"redirect_not_parseable": "URL der Weiterleitung kann nicht verarbeitet werden",
"redirect_domain_not_allowed": "Domain der Weiterleitung nicht erlaubt",
"failed_to_sign_jwt": "Signierung des JWT fehlgeschlagen",
"invalid_invocation": "Aufrufen von MakeChallenge ungültig",
"client_error_browser": "Client Error: Bitte stelle sicher, dass der Browser aktuell ist und probiere es später erneut.",
"oh_noes": "Vermaledeit!",
"failed_to_sign_jwt": "JWT konnte nicht signiert werden",
"invalid_invocation": "Ungültiger Aufruf von MakeChallenge",
"client_error_browser": "Client-Fehler: Bitte stelle sicher, dass dein Browser aktuell ist und versuche es später erneut.",
"oh_noes": "Oh nein!",
"benchmarking_anubis": "Benchmark wird durchgeführt!",
"you_are_not_a_bot": "Sie sind kein Bot!",
"making_sure_not_bot": "Ihr Browser wird geprüft!",
"you_are_not_a_bot": "Du bist kein Bot!",
"making_sure_not_bot": "Dein Browser wird geprüft!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Ihr Browser hat leider kein funktionierendes web.crypto Element. Wird eine sichere Verbindung verwendet?",
"js_web_workers_error": "Ihr Browser unterstützt keine Web-Worker (Anubis verwendet diese, damit der Browser nicht unresponsive wird). Ist eventuell ein Plugin wie zB JShelter installiert?",
"js_cookies_error": "Ihr Browser speichert keine Cookies. Anubis verwendet Cookies um ein gültiges Token zu speichern damit es wissen kann, welche Browser bereits geprüft wurden. Bitte aktiviere Cookies für diese Domain. Die Cookie-Namen von Anubis könnten sich jederzeit ändern. Cookie-Namen sind kein Teil der öffentlichen API.",
"js_web_crypto_error": "Dein Browser hat kein funktionierendes web.crypto Element. Wird eine sichere Verbindung verwendet?",
"js_web_workers_error": "Dein Browser unterstützt keine Web-Worker (Anubis verwendet diese, damit der Browser nicht einfriert). Ist ein Plugin wie JShelter installiert?",
"js_cookies_error": "Dein Browser speichert keine Cookies. Anubis verwendet Cookies, um nach bestandener Prüfung ein signiertes Token abzulegen. Bitte aktiviere Cookies für diese Domain. Die Cookie-Namen von Anubis könnten sich jederzeit ändern. Cookie-Namen und die gespeicherten Werte sind kein Teil der öffentlichen API.",
"js_context_not_secure": "Diese Verbindung ist nicht sicher!",
"js_context_not_secure_msg": "Bitte probiere, dich via HTTPS zu verbinden und lass den Webseiten-Administrator wissen, sauber HTTPS einzurichten. Mehr Informationen unter: <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_context_not_secure_msg": "Bitte versuche, dich via HTTPS zu verbinden oder weise den Administrator darauf hin, HTTPS einzurichten. Mehr Informationen unter: <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Berechnung wird durchgeführt...",
"js_missing_feature": "Fehlendes Feature",
"js_challenge_error": "Fehler während des Checks!",
"js_challenge_error_msg": "Der Check-Algorithmus konnte nicht geladen werden. Bitte lade diese Seite erneut.",
"js_challenge_error": "Prüfung fehlgeschlagen!",
"js_challenge_error_msg": "Der Prüf-Algorithmus konnte nicht geladen werden. Bitte lade diese Seite erneut.",
"js_calculating_difficulty": "Berechnung wird durchgeführt...<br/>Schwierigkeit:",
"js_speed": "Geschwindigkeit:",
"js_verification_longer": "Der Check benötigt länger als erwartet. Bitte bleibe auf der Seite.",
"js_verification_longer": "Die Prüfung benötigt länger als erwartet. Bitte bleibe auf der Seite und lade diese nicht neu.",
"js_success": "Erfolgreich!",
"js_done_took": "Fertig! Dauer:",
"js_iterations": "Iterationen",
"js_finished_reading": "Fertig gelesen, weiter zur Seite →",
"js_calculation_error": "Fehler bei der Berechnung!",
"js_calculation_error_msg": "Fehler bei der Berechnung des Checks:"
"js_calculation_error": "Berechnung fehlgeschlagen!",
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:"
}

View File

@@ -2,6 +2,7 @@
"loading": "Loading...",
"why_am_i_seeing": "Why am I seeing this?",
"protected_by": "Protected by",
"protected_from": "From",
"made_with": "Made with ❤️ in 🇨🇦",
"mascot_design": "Mascot design by",
"ai_companies_explanation": "You are seeing this because the administrator of this website has set up Anubis to protect the server against the scourge of AI companies aggressively scraping websites. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.",

View File

@@ -2,6 +2,7 @@
"loading": "Cargando...",
"why_am_i_seeing": "¿Por qué veo esto?",
"protected_by": "Protegido por",
"protected_from": "From",
"made_with": "Hecho con ❤️ en 🇨🇦",
"mascot_design": "Diseño de la mascota por",
"ai_companies_explanation": "Ves esto porque el administrador de este sitio web ha configurado Anubis para proteger el servidor contra la plaga de empresas de IA que rastrean agresivamente los sitios web. Esto puede y causa tiempo de inactividad para los sitios web, haciendo que sus recursos sean inaccesibles para todos.",

View File

@@ -0,0 +1,64 @@
{
"loading": "Laadin...",
"why_am_i_seeing": "Miks ma pean seda nägema?",
"protected_by": "Kaitseb",
"protected_from": "From",
"made_with": "Tehtud ❤ga 🇨🇦s",
"mascot_design": "Maskoti disainis",
"ai_companies_explanation": "Seda näidatakse selle pärast, et selle lehe administraator on paigaldanud Anubise, et kaitsta serverit selle nuhtluse eest, mida kujutab endast AI firmade agressiivne veebikraapimine. Selle tagajärjeks võib olla ja tihti ongi see, et veebilehed lakkavad töötamast ja keegi ei saa nendele ligi.",
"anubis_compromise": "Anubis on kompromisslahendus. Anubis kasutab nö. töötõendi skeemi, mille sarnane oli <em>Hashcash</em>, mis oli mõeldud spämmikaitseks. Põhimõte on selles, et üksiku kasutaja tasemel on lisakoormus tajumatu, aga massiivse kraapimise tasemel see koormus läheb kõik arvesse ja muudab andmete töötluse palju kallimaks.",
"hack_purpose": "Lõppudelõpuks on see siiski rutuga tehtud lahendus, mille tegelik eesmärk on olla \"piisavalt hea\", et oleks rohkem aega leida viise, kuidas saaks tuvastada brauseriautomaate (näiteks sellest, kuidas nad fonte joonistavad), et saavutada olukord, kus kontrollekraani ei olegi vaja näidata kasutajatele, kes on suurema tõenäosusega päriselt inimesed.",
"jshelter_note": "NB! Anubis vajab töötamiseks kaasaegseid JavaScripti võimalusi, mida teatud pluginad nagu JShelter ära keelavad. Palun lülita JShelter või teised sellised veebilehitseja laiendused välja.",
"version_info": "Sellel lehel jookseb Anubis, versioon",
"try_again": "Proovi uuesti",
"go_home": "Mine koju",
"contact_webmaster": "või kui sa arvad, et sa ei peaks olema blokeeritud, võta ühendust veebimeistriga aadressil",
"connection_security": "Oota korraks, me kontrollime ühenduse turvalisust.",
"javascript_required": "Kahjuks tuleb JavaScript sisse lülitada, et sellest kontrollist mööda pääseda. See on kohustuslik, sest AI ettevõtted on muutnud ühiskondlikke norme veebimajutuse suhtes. Ilma JavaScriptita töötav versioon on alles arendamisel.",
"benchmark_requires_js": "Kiirustesti jaoks on vajalik JavaScript sisse lülitada.",
"difficulty": "Raskus:",
"algorithm": "Algoritm:",
"compare": "Võrdle:",
"time": "Aega",
"iters": "Korda",
"time_a": "A aega",
"iters_a": "A korda",
"time_b": "B aega",
"iters_b": "B korda",
"static_check_endpoint": "Seda lehte vaatab ainult sinu vaheserver.",
"authorization_required": "Ligipääs puudub",
"cookies_disabled": "Sinu brauseris on küpsised keelatud. Anubis vajab küpsiseid töötamiseks, et aru saada, kas sa oled päris kasutaja või mitte. Palun luba küpsised sellel domeenil",
"access_denied": "Ligipääs keelatud: veakood",
"dronebl_entry": "DroneBL tagastas sissekande",
"see_dronebl_lookup": "vaata",
"internal_server_error": "Programmi sisemine viga: administraator on Anubise valesti seadistanud. Võta temaga ühendust ja palu tal otsida logidest märksõna",
"invalid_redirect": "Vigane ümbersuunamine",
"redirect_not_parseable": "Ümbersuunamise URL on vigane",
"redirect_domain_not_allowed": "Ümbersuunamise domeen pole lubatud",
"failed_to_sign_jwt": "JWT allkirjastamine ebaõnnestus",
"invalid_invocation": "MakeChallenge väljakutsumine on vigane",
"client_error_browser": "Kliendipoolne viga: palun kontrolli, et su brauser oleks uuendatud ja proovi uuesti.",
"oh_noes": "Oi ei!",
"benchmarking_anubis": "Anubise kiirustest!",
"you_are_not_a_bot": "Sina ei ole bott!",
"making_sure_not_bot": "Kontrollime, et sa ei ole bott!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Sinu brauseris ei ole töötavat web.crypto elementi. Kas sa avasid selle turvakontekstis?",
"js_web_workers_error": "Sinu brauser ei toeta veebi taustaprotsesse (Anubis kasutab neid, et su veebilehitseja ei hanguks). Kas sul on installitud mingi laiendus nagu JShelter?",
"js_cookies_error": "Sinu brauser ei salvesta küpsiseid. Anubis kirjutab küpsise, milles on allkirjastatud sedel, et vahet teha, millised kliendid on kontrolli läbinud ja millised mitte. Palun luba küpsiste salvestamine sellel domeenil. Küpsiste nimed, mida Anubis kasutab, võivad muutuda ette teatamata. Küpsiste nimed ja väärtused ei ole avaliku liidese osa.",
"js_context_not_secure": "Sinu brauserikontekst ei ole turvaline!",
"js_context_not_secure_msg": "Proovi ühendada HTTPS aadressiga või anna administraatorile teada, et HTTPS on vajalik seadistada. Lisainfot vaata <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDNist</a>.",
"js_calculating": "Arvutan...",
"js_missing_feature": "Puuduv brauseri omadus",
"js_challenge_error": "Kontrolli viga!",
"js_challenge_error_msg": "Ei suutnud tuvastada kontrollalgoritmi. Võiksid proovida lehe uuesti laadida.",
"js_calculating_difficulty": "Arvutan...<br/>Raskus:",
"js_speed": "Kiirus:",
"js_verification_longer": "Kontrollimine võtab kauem kui tavaliselt. Palun ära lae lehte uuesti.",
"js_success": "Õnnestus!",
"js_done_took": "Tehtud! Võttis",
"js_iterations": "kordust",
"js_finished_reading": "Lugesin ära, edasi →",
"js_calculation_error": "Arvutamise viga!",
"js_calculation_error_msg": "Ei suutnud kontrolli arvutada:"
}

View File

@@ -0,0 +1,65 @@
{
"loading": "Naglo-load...",
"why_am_i_seeing": "Bakit nakikita ko ito?",
"protected_by": "Pinoprotekta ng",
"protected_from": "mula sa",
"made_with": "Ginawa na may ❤️ sa 🇨🇦",
"mascot_design": "Disenyo ng Maskot ni/ng",
"ai_companies_explanation": "Nakikita mo ito dahil ang tagapangasiwa ng website na ito ay nag-set up ng Anubis upang protektahan ang server laban sa salot ng mga kumpanya ng AI na aggresibong nagse-scrape ng mga website. Maaari nitong magdulot ng downtime para sa mga website, na gagawing hindi naa-access ang kanilang mga resource para sa lahat.",
"anubis_compromise": "Isang kompromiso ang Anubis. Gumagamit ang Anubis ng isang Proof-of-Work na scheme sa ugat ng Hashcash, isang iminungkahing proof-of-work scheme upang mabawasan ang email spam. Ang ideya ay sa indibidwal na scale hindi napapansin ang karagdagang load, ngunit sa malaking antas ng pag-scrape nagkararagdag ito at ginagawang mas mahal ang pag-scrape.",
"hack_purpose": "Sa huli, ito ay isang hack na ang totoong layunin ay magbigay ng \"sapat na mabuti\" na placeholder na solusyon upang mas maraming oras ang magugugol sa pag-fingerprint at pagtukoy ng mga walang ulo na browser (hal: sa pamamagitan ng kung paano nila ginagawa ang pag-render ng font) nang sa gayon ay hindi na kailangang iharap sa mga user na mas malamang na maging lehitimo ang patunay ng hamon sa pahina ng trabaho.",
"jshelter_note": "Pakitandaan na kinakailangan ng Anubis ang paggamit ng modernong JavaScript na feature na idi-disable ng mga plugin tulad ng JShelter. Mangyaring i-disable ang JShelter o ibang mga plugin para sa domain na ito.",
"version_info": "Ang website na ito ay tumatakbo ng Anubis bersyon",
"try_again": "Subukan muli",
"go_home": "Bumalik sa panimula",
"contact_webmaster": "o kung naniniwala ka na hindi ka dapat na-block, mangyaring makipag-ugnayan sa mga webmaster sa",
"connection_security": "Mangyaring maghintay nang ilang sandali habang sinisigurado namin ang seguridad ng iyong koneksyon.",
"javascript_required": "Nakalulungkot, ngunit kailangan mong paganahin ang JavaScript upang malampasan ang hamong ito. Ito ay kinakailangan dahil binago ng mga kumpanya ng AI ang social contract tungkol sa kung paano gumagana ang pagho-host ng website. Ang isang walang-JS na solusyon ay isang work-in-progress.",
"benchmark_requires_js": "Kinakailangang naka-enable ang JavaScript upang patakbuhin ang benchmark tool.",
"difficulty": "Kahirapan:",
"algorithm": "Algoritmo:",
"compare": "Kumpara:",
"time": "Oras",
"iters": "Mga Iterasyon",
"time_a": "Time A",
"iters_a": "Iters A",
"time_b": "Time B",
"iters_b": "Iters B",
"static_check_endpoint": "Isa lang itong check endpoint para magamit ng iyong reverse proxy.",
"authorization_required": "Kinakailangan ang pagpapatunay",
"cookies_disabled": "Ang iyong browser ay na-configure upang hindi paganahin ang cookies. Kinakailangan ng Anubis ang cookies para sa lehitimong interes ng pagtiyak na ikaw ay isang wastong kliyente. Mangyaring paganahin ang cookies para sa domain na ito",
"access_denied": "Tinanggihan ang Access: error code",
"dronebl_entry": "Nag-ulat ang DroneBL ng entry",
"see_dronebl_lookup": "tignan ang",
"internal_server_error": "Internal Server Error: hindi na-configure nang mabuti ng tagapangasiwa ang Anubis. Makipag-ugnayan sa tagapangasiwa at sabihin sa kanila na tumingin sa mga log sa paligid ng",
"invalid_redirect": "Hindi wastong redirect",
"redirect_not_parseable": "Hindi ma-parse ang redirect URL",
"redirect_domain_not_allowed": "Hindi pinapayagan ang redirect domain",
"failed_to_sign_jwt": "nabigong ilagda ang JWT",
"invalid_invocation": "Hindi wastong panawagan para sa MakeChallenge",
"client_error_browser": "Error sa Kliyente: Pakitiyak na napapanahon ang iyong browser at subukang muli sa ibang pagkakataon.",
"oh_noes": "Ay, naku!",
"benchmarking_anubis": "Binebenchmark ang Anubis!",
"you_are_not_a_bot": "Hindi ka isang bot!",
"making_sure_not_bot": "Sinisigurado na hindi ka isang bot!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Ang iyong browser ay walang gumaganang web.crypto element. Tinitingnan mo ba ito sa isang secure na konteksto?",
"js_web_workers_error": "Hindi sinusuportahan ng iyong browser ang mga web worker (ginagamit ito ng Anubis upang maiwasan ang pag-freeze ng iyong browser). Mayroon ka bang naka-install na plugin tulad ng JShelter?",
"js_cookies_error": "Your browser doesn't store cookies. Anubis uses cookies to determine which clients have passed challenges by storing a signed token in a cookie. Please enable storing cookies for this domain. The names of the cookies Anubis stores may vary w",
"js_cookies_error": "Ang iyong browser ay hindi nag-iimbak ng cookies. Gumagamit ang Anubis ng cookies upang matukoy kung aling mga kliyente ang nakapasa sa mga hamon sa pamamagitan ng pag-iimbak ng isang nilagdaang token sa isang cookie. Mangyaring paganahin ang pag-iimbak ng cookies para sa domain na ito. Ang mga pangalan ng cookies na Anubis store ay maaaring mag-iba nang walang abiso. Ang mga pangalan at value ng cookie ay hindi bahagi ng pampublikong API.",
"js_context_not_secure": "Hindi secure ang iyong konteksto!",
"js_context_not_secure_msg": "Subukang kumonekta sa pamamagitan ng HTTPS o sabihin sa admin na i-set up ang HTTPS. Para sa karagdagang impormasyon, tignan ang <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Kinakalkula...",
"js_missing_feature": "Nawawalang feature",
"js_challenge_error": "Error sa hamon!",
"js_challenge_error_msg": "Nabigong iresolba ang algoritmo ng pagsusuri. Baka gusto mong i-reload ang pahina.",
"js_calculating_difficulty": "Kinakalkula...<br/>Kahirapan:",
"js_speed": "Bilis:",
"js_verification_longer": "Mas tumatagal ang pag-verify kaysa sa inaasahan. Mangyaring huwag i-refresh ang pahina.",
"js_success": "Matagumpay!",
"js_done_took": "Tapos na! Nagtagal nang",
"js_iterations": "mga iterasyon",
"js_finished_reading": "Tapos na akong magbasa, magpatuloy →",
"js_calculation_error": "Error sa pagkalkula!",
"js_calculation_error_msg": "Nabigong ikalkula ang hamon:"
}

View File

@@ -2,6 +2,7 @@
"loading": "Chargement...",
"why_am_i_seeing": "Pourquoi je vois ceci ?",
"protected_by": "Protégé par",
"protected_from": "From",
"made_with": "Fait avec ❤️ au 🇨🇦",
"mascot_design": "Design de la mascotte par",
"ai_companies_explanation": "Vous voyez ceci car l'administrateur de ce site web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui scrapent agressivement les sites web. Cela peut et cause des temps d'arrêt pour les sites web, ce qui rend leurs ressources inaccessibles pour tout le monde.",

View File

@@ -0,0 +1,64 @@
{
"loading": "Hleður...",
"why_am_i_seeing": "Af hverju er ég að sjá þetta?",
"protected_by": "Verndað með",
"protected_from": "From",
"made_with": "Gert í 🇨🇦 með ❤️",
"mascot_design": "Lukkudýrið hannað af",
"ai_companies_explanation": "Þú ert að sjá þetta vegna þess að kerfisstjóri þessa vefsvæðis hefur sett upp Anubis til að vernda vefþjóninn fyrir holskeflu beiðna frá svokölluðum gervigreindarfyrirtækjum sem samviskulaust eru að skrapa upplýsingar af vefsvæðum annarra. Þetta getur valdið og veldur töfum og truflunum á þessum vefsvæðum, sem aftur veldur því að efni þeirra verður öllum óaðgengilegt.",
"anubis_compromise": "Anubis er millivegur. Anubis notar sönnun-á-vinnu (Proof-of-Work) skema í líkingu við Hashcash, sem er viðlíka skema til að minnka ruslpóst. Hugmyndin er að fyrir almennar heimsóknir verði viðbótarálagið vegna þessa ásættanlegt og valdi litlum truflunum, en við massaskröpun verði samlegðaráhrifin veruleg og geri slíka skröpun upplýsinga of dýra hvað varðar afköst og reiknigetu.",
"hack_purpose": "Hafa verður í huga að þetta er \"redding\" sem hefur þann tilgang að gefa \"nógu góða\" bráðabirgðalausn við þessu vandamáli, þannig að hægt sé á meðan að finna betri aðferðir við að auðkenna og gera fingraför til að þekkja viðmótslausa vafra (headless browsers - til dæmis með því hvernig þeir meðhöndla letur) þannig að í framtíðinni þurfi ekki að birta síðuna með sönnun-á-vinnu áskoruninni fyrir notendur sem líklegir eru til að vera löglegir/mennskir notendur.",
"jshelter_note": "Athugaðu að Anubis krefst notkunar á ýmsum nútímalegum eiginleikum JavaScript sem viðbætur á borð við JShelter munu gera ávirka. Endilega gerðu JShelter eða álíka viðbætur óvirkar fyrir þetta lén.",
"version_info": "Þetta vefsvæði er að keyra Anubis útgáfu",
"try_again": "Prófaðu aftur",
"go_home": "Farðu aftur heim til þín",
"contact_webmaster": "eða ef þú heldur að ekki ætti að loka á þig, þá ættirðu að hafa samband við vefstjórann á",
"connection_security": "Hinkraðu augnablik á meðan við tryggjum öryggi tengingarinnar þinnar.",
"javascript_required": "Það er leiðinlegt, en þú verður að virkja JavaScript til að komast í gegnum þessa áskorun. Þetta er nauðsynlegt vegna þess að AI-fyrirtækin neita að fara eftir þeim samfélagslegu viðmiðum sem hafa mótað það hvernig vefhýsing virkar. Lausn sem ekki reiðir sig á JS er í vinnslu.",
"benchmark_requires_js": "JavaScript þarf að vera virkt til að keyra afkastaprófunarkerfið.",
"difficulty": "Erfiðleikastig:",
"algorithm": "Reiknirit:",
"compare": "Bera saman:",
"time": "Tími",
"iters": "Umferðir",
"time_a": "Tími A",
"iters_a": "Umferðir A",
"time_b": "Tími B",
"iters_b": "Umferðir B",
"static_check_endpoint": "Þetta er bara endapunktur prófunar til notkunar fyrir öfuga milliþjóninn (reverse proxy) þinn.",
"authorization_required": "Auðkenning nauðsynleg",
"cookies_disabled": "Vafrinn þinn er stilltur á að gera vefkökur óvirkar. Anubis þarf að nota vefkökur í þeim tilgangi að tryggja að þú sért með leyfilegt forrit. Vinsamlega virkjaðu vefkökur fyrir þetta lén",
"access_denied": "Aðgangi hafnað: villukóði",
"dronebl_entry": "DroneBL tilkynnti færslu",
"see_dronebl_lookup": "skoðaðu",
"internal_server_error": "Innri villa á netþjóni: Kerfisstjóri hefur stillt Anubis rangt. Hafðu samband við kerfisstjóra og biddu þá um að skoða atvikaskrár sem tengjast þessu",
"invalid_redirect": "Ógild endurbeining",
"redirect_not_parseable": "Slóð endurbeiningar er ekki túlkanleg",
"redirect_domain_not_allowed": "Lén endurbeiningar er ekki leyft",
"failed_to_sign_jwt": "mistókst að undirrita JWT",
"invalid_invocation": "Ógild kvaðning á MakeChallenge",
"client_error_browser": "Villa í forriti: Gakktu úr skugga um að vafrinn þinn sé uppfærður í nýjustu útgáfu og prófaðu aftur síðar.",
"oh_noes": "Æi nei!",
"benchmarking_anubis": "Afkastaprófun Anubis!",
"you_are_not_a_bot": "Þú ert ekki botti!",
"making_sure_not_bot": "Geng úr skugga um að þú sért ekki botti!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Vafrinn þinn er ekki með web.crypto einindi sem virkar. Ertu að skoða þetta í gegnum öruggt umhverfi?",
"js_web_workers_error": "Vafrinn þinn styður ekki vefvaktara (web workers - Anubis notar þetta til að koma í veg fyrir að vafrinn frjósi). Ertu með viðbót á borð við JShelter uppsetta?",
"js_cookies_error": "Vafrinn þinn geymir ekki vefkökur. Anubis notar vefkökur til að ákvarða hvaða biðlaraforrit hafi leyst áskoranir og geymir þá undirritað teikn í vefköku. Vinsamlega virkjaðu geymslu á vefkökum fyrir þetta lén. Nöfnin á þeim vefkökum sem Anubis geymir geta breyst fyrirvaralaust. Heiti vefkakna og gildi þeirra eru ekki hluti opinbera API-kerfisviðmótsins.",
"js_context_not_secure": "Umhverfið þitt er ekki öruggt!",
"js_context_not_secure_msg": "Prófaðu að tengjast í gegnum HTTPS eða láttu kerfisstjórann vita að hann þurfi að setja upp HTTPS. Fyrir nánari upplýsingar er hægt að skoða <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Reikna...",
"js_missing_feature": "Eiginleika vantar",
"js_challenge_error": "Villa í áskorun!",
"js_challenge_error_msg": "Mistókst að leysa reiknirit á prófunar. Þú gætir viljað endurlesa síðuna.",
"js_calculating_difficulty": "Reikna...<br/>Erfiðleikastig:",
"js_speed": "Hraði:",
"js_verification_longer": "Sannvottun tók lengri tíma en búast má við. Ekki endurlesa síðuna.",
"js_success": "Tókst!",
"js_done_took": "Klárt! Tók",
"js_iterations": "umferðir",
"js_finished_reading": "Ég hef lokið lestrinum, höldum áfram →",
"js_calculation_error": "Reiknivilla!",
"js_calculation_error_msg": "Mistókst að reikna áskorun:"
}

View File

@@ -0,0 +1,64 @@
{
"loading": "Caricamento...",
"why_am_i_seeing": "Perché vedo questa schermata?",
"protected_by": "Protetto da",
"protected_from": "From",
"made_with": "Realizzato con ❤️ in 🇨🇦",
"mascot_design": "Mascotte disegnata da",
"ai_companies_explanation": "Vedi questa schermata perché l'amministratore di questo sito web ha installato Anubis per proteggere il server dalla piaga delle aziende di AI generativa che estraggono, senza freno, dati dai siti web. Questo comportamento causa disservizi per i siti web, rendendoli inaccessibili a tutti.",
"anubis_compromise": "Anubis è un compromesso. Anubis utilizza un meccanismo di proof-of-work in stile Hashcash, un meccanismo per ridurre le email di spam. L'idea è che, a livello individuale, il lavoro aggiuntivo necessario per la proof-of-work sia trascurabile, ma, a livello di grandi reti di bot, il lavoro si somma e diventa molto più costoso.",
"hack_purpose": "In sostanza, questo è un espediente il cui scopo è fornire una soluzione temporanea e ragionevolmente funzionante, nel mentre che vengono ricercate soluzioni migliori nel riconoscimento e nell'identificazione di programmi automatici per la navigazione web (per esempio: osservando il modo in cui disegnano i caratteri), così che la pagina della proof-of-work non debba essere sottoposta agli utenti che hanno maggiore probabilità di essere legittimi.",
"jshelter_note": "Si noti che Anubis richiede l'utilizzo di caratteristiche moderne di JavaScript che alcuni plugin, come JShelter, disabilitano. Per accedere, disabilita JShelter (o altri plugin simili) per questo dominio.",
"version_info": "Questo sito sta usando Anubis versione",
"try_again": "Riprova",
"go_home": "Vai alla home",
"contact_webmaster": "o, se pensi di non dover essere bloccato, contatta l'amministratore a",
"connection_security": "Un momento: stiamo controllando la sicurezza della tua connessione.",
"javascript_required": "Purtroppo, devi abilitare Javascript per riuscire a superare questa pagina. Questa misura è necessaria perché alcune compagnie di AI hanno unilateralmente deciso di violare il contratto sociale sulla fornitura di siti web. Stiamo lavorando ad una soluzione che non richieda Javascript.",
"benchmark_requires_js": "Per eseguire lo strumento di test, è necessario abilitare Javascript.",
"difficulty": "Difficoltà:",
"algorithm": "Algoritmo:",
"compare": "Test:",
"time": "Tempo",
"iters": "Iterazioni",
"time_a": "Tempo A",
"iters_a": "Iterazioni A",
"time_b": "Tempo B",
"iters_b": "Iterazioni B",
"static_check_endpoint": "Questo è solo un endpoint di test da utilizzare col reverse proxy.",
"authorization_required": "Autorizzazione necessaria",
"cookies_disabled": "Il tuo browser è configurato per disabilitare i cookies. Anubis richiede i cookie per accertarsi che tu sia un visitatore umano, ed è un legittimo interesse. Per favore, abilita i cookie per questo dominio.",
"access_denied": "Accesso negato: errore",
"dronebl_entry": "DroneBL ha riportato un record",
"see_dronebl_lookup": "vedi",
"internal_server_error": "Internal Server Error: Anubis non è configurato correttamente. Contattare l'amministratore e chiedergli di controllare i log attorno a",
"invalid_redirect": "Reindirizzamento non valido",
"redirect_not_parseable": "Errore di sintassi nel reindirizzamento",
"redirect_domain_not_allowed": "Dominio non permesso per il reindirizzamento",
"failed_to_sign_jwt": "Impossibile firmare JWT",
"invalid_invocation": "Chiamata non valida a MakeChallenge",
"client_error_browser": "Client Error: assicurati che il tuo browser sia aggiornato e riprova.",
"oh_noes": "Oh no!",
"benchmarking_anubis": "Testando Anubis!",
"you_are_not_a_bot": "Non sei un robot!",
"making_sure_not_bot": "Controllo se sei un robot...",
"celphase": "CELPHASE",
"js_web_crypto_error": "Il tuo browser non ha un elemento web.crypto funzionante. Stai utilizzando una connessione sicura?",
"js_web_workers_error": "Il tuo browser non supporta web workers (Anubis li utilizza per evitare di rallentare il tuo browser). Hai installato un plugin come JShelter?",
"js_cookies_error": "Il tuo browser non salva i cookie. Anubis utilizza i cookie per determinare quali client hanno superato la prova, salvando un identificativo firmato digitalmente in un cookie. Abilita il salvataggio dei cookie per questo dominio. Il nome del cookie salvato da Anubis potrebbe cambiare senza preavviso. I nomi dei cookie e il loro contenuto non fanno parte dell'API pubblica.",
"js_context_not_secure": "La tua connessione non è sicura!",
"js_context_not_secure_msg": "Prova a connetterti tramite HTTPS, o fallo abilitare dall'amministratore del sito. Per maggiori informazioni, vedi <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Calcolo in corso...",
"js_missing_feature": "Funzionalità mancante",
"js_challenge_error": "Errore nel test!",
"js_challenge_error_msg": "Impossibile trovare l'algoritmo di controllo. Ricarica la pagina.",
"js_calculating_difficulty": "Calcolo in corso...<br/>Difficoltà:",
"js_speed": "Velocità:",
"js_verification_longer": "La verifica sta richiedendo più tempo del previsto. Non aggiornare la pagina: attendere.",
"js_success": "Successo!",
"js_done_took": "Fatto! Sono state necessarie",
"js_iterations": "iterazioni.",
"js_finished_reading": "Ho finito di leggere, continua →",
"js_calculation_error": "Errore nel calcolo!",
"js_calculation_error_msg": "Impossibile superare il test:"
}

View File

@@ -0,0 +1,64 @@
{
"loading": "ロード中...",
"why_am_i_seeing": "なぜこれが表示されるのですか?",
"protected_by": "Protected by",
"protected_from": "From",
"made_with": "Made with ❤️ 🇨🇦",
"mascot_design": "Mascot design by",
"ai_companies_explanation": "このメッセージが表示されているのは、このウェブサイトの管理者が、AI企業による過剰なウェブスクレイピングからサーバーを守るためにAnubisを導入しているためです。これにより、ウェブサイトがダウンし、すべての利用者がリソースにアクセスできなくなる事態が発生することがあります。",
"anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。",
"hack_purpose": "最終的には、これは「十分に使える」仮の解決策を提供するためのハックです。本来の目的は、フォントレンダリングの方法などを通じてヘッドレスブラウザの指紋付けや識別により多くの時間を割けるようにし、正当なユーザーに対してはProof-of-Workのチャレンジページを表示しなくて済むようにすることです。",
"jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。",
"version_info": "このウェブサイトはAnubisバージョンで動作しています",
"try_again": "再試行",
"go_home": "ホームに戻る",
"contact_webmaster": "もしブロックされるべきでないと思われる場合は、ウェブマスターにご連絡ください:",
"connection_security": "接続の安全性を確認しています。しばらくお待ちください。",
"javascript_required": "申し訳ありませんが、このチャレンジを通過するにはJavaScriptを有効にする必要があります。これはAI企業がウェブホスティングの社会的契約を変えてしまったためです。JavaScriptなしの解決策は現在開発中です。",
"benchmark_requires_js": "ベンチマークツールを実行するにはJavaScriptを有効にする必要があります。",
"difficulty": "難易度:",
"algorithm": "アルゴリズム:",
"compare": "比較:",
"time": "時間",
"iters": "イテレーション数",
"time_a": "時間A",
"iters_a": "イテレーションA",
"time_b": "時間B",
"iters_b": "イテレーションB",
"static_check_endpoint": "これはリバースプロキシ用のチェックエンドポイントです。",
"authorization_required": "認証が必要です",
"cookies_disabled": "お使いのブラウザはCookieを無効にしています。Anubisは、あなたが正当なクライアントであることを確認するためにCookieを必要とします。このドメインでCookieを有効にしてください。",
"access_denied": "アクセス拒否: エラーコード",
"dronebl_entry": "DroneBLにエントリーが報告されました",
"see_dronebl_lookup": "参照",
"internal_server_error": "内部サーバーエラー: 管理者がAnubisの設定を誤っています。管理者に連絡し、次のログを確認するよう依頼してください:",
"invalid_redirect": "無効なリダイレクト",
"redirect_not_parseable": "リダイレクトURLを解析できません",
"redirect_domain_not_allowed": "リダイレクトドメインは許可されていません",
"failed_to_sign_jwt": "JWTの署名に失敗しました",
"invalid_invocation": "MakeChallengeの無効な呼び出し",
"client_error_browser": "クライアントエラー: ブラウザが最新であることを確認し、後でもう一度お試しください。",
"oh_noes": "Oh noes!",
"benchmarking_anubis": "Anubisのベンチマーク中",
"you_are_not_a_bot": "あなたはボットではありません!",
"making_sure_not_bot": "あなたがボットでないことを確認しています!",
"celphase": "CELPHASE",
"js_web_crypto_error": "お使いのブラウザには正常に動作するweb.crypto要素がありません。安全なコンテキストで閲覧していますか",
"js_web_workers_error": "お使いのブラウザはWebワーカーをサポートしていませんAnubisはこれでブラウザのフリーズを防ぎます。JShelterのようなプラグインを使用していませんか",
"js_cookies_error": "お使いのブラウザはCookieを保存しません。Anubisは、チャレンジを通過したクライアントを判別するために署名付きトークンをCookieに保存します。このドメインでCookieの保存を有効にしてください。Anubisが保存するCookie名は予告なく変更される場合があります。Cookie名や値は公開APIの一部ではありません。",
"js_context_not_secure": "お使いのコンテキストは安全ではありません!",
"js_context_not_secure_msg": "HTTPSで接続するか、管理者にHTTPSの設定を依頼してください。詳細は<a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>をご覧ください。",
"js_calculating": "計算中...",
"js_missing_feature": "機能がありません",
"js_challenge_error": "チャレンジエラー!",
"js_challenge_error_msg": "チェックアルゴリズムの解決に失敗しました。ページを再読み込みしてください。",
"js_calculating_difficulty": "計算中...<br/>難易度:",
"js_speed": "速度:",
"js_verification_longer": "検証に予想以上の時間がかかっています。ページをリフレッシュしないでください。",
"js_success": "成功!",
"js_done_took": "完了!所要時間",
"js_iterations": "イテレーション数",
"js_finished_reading": "読み終わりました。続行 →",
"js_calculation_error": "計算エラー!",
"js_calculation_error_msg": "チャレンジの計算に失敗しました:"
}

View File

@@ -1,3 +1,18 @@
{
"supportedLanguages": ["en", "fr", "es", "pt-BR", "de"]
}
"supportedLanguages": [
"cs",
"de",
"en",
"es",
"et",
"fil",
"fr",
"is",
"it",
"ja",
"pt-BR",
"tr",
"zh-CN",
"zh-TW"
]
}

View File

@@ -2,6 +2,7 @@
"loading": "Carregando...",
"why_am_i_seeing": "Por que estou vendo isso?",
"protected_by": "Protegido por",
"protected_from": "From",
"made_with": "Feito com ❤️ no 🇨🇦",
"mascot_design": "Design do mascote por",
"ai_companies_explanation": "Você está vendo isso porque o administrador deste site configurou Anubis para proteger o servidor contra a praga de empresas de IA que realizam scraping agressivo em sites. Isso pode causar, e de fato causa, inoperância nos sites, o que torna seus recursos inacessíveis para todos.",

View File

@@ -0,0 +1,64 @@
{
"loading": "Yükleniyor...",
"why_am_i_seeing": "Bunu neden görüyorum?",
"protected_by": "Koruma sağlayan:",
"protected_from": "From",
"made_with": "🇨🇦'da ❤️ ile yapıldı",
"mascot_design": "Maskot tasarımı:",
"ai_companies_explanation": "Bunu görüyorsunuz çünkü bu web sitesinin yöneticisi, yapay zeka şirketlerinin web sitelerini agresif şekilde kazımasına karşı sunucuyu korumak için Anubis'i kurdu. Bu tarz kazımalar sitelerin erişilemez olmasına ve kesintilere neden olabiliyor.",
"anubis_compromise": "Anubis bir uzlaşmadır. Anubis, spam e-postaları azaltmak için önerilen bir iş kanıtı sistemi olan Hashcash benzeri bir sistemi kullanır. Bireysel kullanımda bu ek yük göz ardı edilebilir olsa da, büyük ölçekli kazıyıcılarda birikerek kazımayı oldukça maliyetli hale getirir.",
"hack_purpose": "Bu geçici bir çözümdür. Esas amacı, başsız tarayıcıları parmak iziyle tanımlamak için daha fazla zaman kazandırmak, ve bu sayede meşru kullanıcıların bu zorluk sayfasını görmesini önlemektir.",
"jshelter_note": "Lütfen dikkat: Anubis, JShelter gibi eklentilerin devre dışı bıraktığı modern JavaScript özelliklerini gerektirir. Lütfen bu alan adı için JShelter veya benzeri eklentileri devre dışı bırakın.",
"version_info": "Bu web sitesi şu Anubis sürümünü çalıştırıyor:",
"try_again": "Tekrar dene",
"go_home": "Ana sayfaya dön",
"contact_webmaster": "ya da engellenmemeniz gerektiğini düşünüyorsanız, lütfen şu adrese e-posta gönderin:",
"connection_security": "Bağlantınızın güvenliği sağlanırken lütfen bekleyin.",
"javascript_required": "Ne yazık ki bu aşamayı geçebilmek için JavaScripti etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zeka şirketlerinin web barındırma konusundaki sosyal sözleşmeyi değiştirmiş olmasıdır. JavaScriptsiz bir çözüm geliştirilmektedir.",
"benchmark_requires_js": "Kıyaslama aracının çalıştırılması için JavaScriptin etkin olması gereklidir.",
"difficulty": "Zorluk:",
"algorithm": "Algoritma:",
"compare": "Karşılaştır:",
"time": "Süre",
"iters": "Tekrar",
"time_a": "Süre A",
"iters_a": "Tekrar A",
"time_b": "Süre B",
"iters_b": "Tekrar B",
"static_check_endpoint": "Bu sadece ters proxy'nizin kullanması için bir kontrol adresidir.",
"authorization_required": "Yetkilendirme gerekli",
"cookies_disabled": "Tarayıcınız çerezleri devre dışı bırakacak şekilde yapılandırılmış. Anubis, gerçek bir kullanıcı olduğunuzu doğrulamak için çerezlere ihtiyaç duyar. Lütfen bu alan adı için çerezleri etkinleştirin.",
"access_denied": "Erişim Reddedildi: hata kodu",
"dronebl_entry": "DroneBL bir giriş bildirdi",
"see_dronebl_lookup": "bakınız",
"internal_server_error": "Sunucu Hatası: Yönetici Anubis'i yanlış yapılandırmış. Lütfen yöneticinizle iletişime geçin ve şunun civarındaki kayıtlara bakmasını isteyin:",
"invalid_redirect": "Geçersiz yönlendirme",
"redirect_not_parseable": "Yönlendirme URL'si çözümlenemiyor",
"redirect_domain_not_allowed": "Yönlendirme alan adına izin verilmiyor",
"failed_to_sign_jwt": "JWT imzalanamadı",
"invalid_invocation": "Geçersiz MakeChallenge çağrısı",
"client_error_browser": "İstemci Hatası: Lütfen tarayıcınızın güncel olduğundan emin olun ve daha sonra tekrar deneyin.",
"oh_noes": "Ah hayır!",
"benchmarking_anubis": "Anubis kıyaslanıyor!",
"you_are_not_a_bot": "Bot değilsiniz!",
"making_sure_not_bot": "Bot olmadığınızdan emin oluyoruz!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Tarayıcınızda çalışan bir web.crypto öğesi yok. Bu sayfayı güvenli bir bağlantı üzerinden mi görüntülüyorsunuz?",
"js_web_workers_error": "Tarayıcınız web işçilerini desteklemiyor (Anubis, tarayıcınızın donmaması için bunları kullanır). JShelter gibi bir eklenti mi kurulu?",
"js_cookies_error": "Tarayıcınız çerezleri kaydetmiyor. Anubis, kullanıcıların zorlukları geçtiğini belirlemek için imzalı bir belirteci çerezde saklar. Lütfen bu alan adı için çerezleri etkinleştirin. Anubisin kullandığı çerez adları önceden bildirilmeksizin değişebilir. Çerez adları ve değerleri resmi APInin bir parçası değildir.",
"js_context_not_secure": "Bağlantınız güvenli değil!",
"js_context_not_secure_msg": "HTTPS üzerinden bağlanmayı deneyin veya yöneticiden HTTPS kurulumu yapmasını isteyin. Daha fazla bilgi için bkz. <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Hesaplanıyor...",
"js_missing_feature": "Eksik özellik",
"js_challenge_error": "Hesaplama hatası!",
"js_challenge_error_msg": "Algoritma çözümlemesi başarısız oldu. Sayfayı yeniden yüklemeyi deneyebilirsiniz.",
"js_calculating_difficulty": "Hesaplanıyor...<br/>Zorluk:",
"js_speed": "Hız:",
"js_verification_longer": "Doğrulama beklenenden uzun sürüyor. Lütfen sayfayı yenilemeyin.",
"js_success": "Başarılı!",
"js_done_took": "Tamamlandı! Süre:",
"js_iterations": "tekrar",
"js_finished_reading": "Okumayı bitirdim, devam et →",
"js_calculation_error": "Hesaplama hatası!",
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:"
}

View File

@@ -0,0 +1,64 @@
{
"loading": "加载中...",
"why_am_i_seeing": "为什么我会看到这个?",
"protected_by": "本网站由",
"protected_from": "保护,来自",
"made_with": "在 🇨🇦 用 ❤️ 制作",
"mascot_design": "吉祥物由",
"ai_companies_explanation": "您会看到这个画面,是因为网站管理员启用了 Anubis 来保护服务器,避免 AI 公司大量爬取网站内容。这类行为会导致网站崩溃,让所有用户都无法正常访问资源。",
"anubis_compromise": "Anubis 是一种折中做法。它采用了类似 Hashcash 的工作量证明机制Proof-of-Work该机制最初是为了减少垃圾邮件而提出。其核心概念是对个别用户而言额外的计算负担可以忽略但对大规模爬虫来说累积起来的成本将大幅增加从而让爬取行为变得更困难。",
"hack_purpose": "本质上,这是一种权宜的解法,目的是提供一个“够用”的暂时性防护措施,好让开发者有更多时间针对无头浏览器进行指纹特征识别(例如:分析其字体渲染方式),以便未来不再需要对可能为合法用户的访客展示工作量证明页面。",
"jshelter_note": "请注意Anubis 需要使用现代 JavaScript 功能,而像 JShelter 这类插件可能会阻挡这些功能。请为此域名停用 JShelter 或类似的插件。",
"version_info": "这个网站正在运行的 Anubis 版本为",
"try_again": "再试一次",
"go_home": "返回首页",
"contact_webmaster": "或者您觉得您不应该被封锁,请联系网站管理员于",
"connection_security": "请稍等,我们需要在继续之前检查您的连接安全性。",
"javascript_required": "很遗憾,您必须启用 JavaScript 才能通过这项验证。这是因为 AI 公司已经改变了网站托管的社会契约,因此我们必须采取这样的保护机制。无需 JavaScript 的解决方案仍在开发中。",
"benchmark_requires_js": "运行基准测试工具需要启用 JavaScript。",
"difficulty": "难度:",
"algorithm": "算法:",
"compare": "比较:",
"time": "时间",
"iters": "迭代",
"time_a": "时间 A",
"iters_a": "迭代 A",
"time_b": "时间 B",
"iters_b": "迭代 B",
"static_check_endpoint": "这是提供给您的反向代理服务器使用的检查端点。",
"authorization_required": "需要认证",
"cookies_disabled": "您的浏览器目前已禁用 Cookie为了确认您是合法用户Anubis 需要启用 Cookie。 请您为此域名启用 Cookie",
"access_denied": "拒绝访问:错误代码",
"dronebl_entry": "DroneBL 报告了一条记录",
"see_dronebl_lookup": "见",
"internal_server_error": "内部服务器错误:管理员错误地配置了 Anubis。 请联系管理员要求他们检查日志",
"invalid_redirect": "无效的重定向",
"redirect_not_parseable": "重定向 URL 无法解析",
"redirect_domain_not_allowed": "重定向的域名并不允许",
"failed_to_sign_jwt": "签署 JWT 失败",
"invalid_invocation": "无效的 MakeChallenge 调用",
"client_error_browser": "客户端错误:请确保您的浏览器是最新版本并稍候再试。",
"oh_noes": "哎呀糟糕了!",
"benchmarking_anubis": "正在进行 Anubis 性能测试!",
"you_are_not_a_bot": "你不是机器人!",
"making_sure_not_bot": "正在确认你是不是机器人!",
"celphase": "CELPHASE 设计",
"js_web_crypto_error": "您的浏览器无法正常使用 web.crypto 组件。您是否通过安全连接HTTPS查看此网站",
"js_web_workers_error": "您的浏览器并不支持 Web workers Anubis 使用这个来避免冻结您的浏览器 )您有安装像是 JShelter 之类的插件吗?",
"js_cookies_error": "您的浏览器无法存储 Cookie。 Anubis 会使用 Cookie 存储签署的凭证,以判断用户是否已通过验证。请为此域名启用 Cookie 存储功能。 请注意Anubis 存储的 Cookie 名称可能会变动,且其名称与内容不属于公开 API 的一部分。",
"js_context_not_secure": "您的内容并不安全",
"js_context_not_secure_msg": "请尝试使用 HTTPS 连接,或联系网站管理员设置 HTTPS。更多信息请参见 <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>。",
"js_calculating": "计算中...",
"js_missing_feature": "缺少功能",
"js_challenge_error": "挑战错误!",
"js_challenge_error_msg": "解决检查算法失败。 您可能会想要刷新页面。",
"js_calculating_difficulty": "计算中...<br/>难度:",
"js_speed": "速度:",
"js_verification_longer": "验证所花的时间高于预期。 请不要刷新页面。",
"js_success": "成功!",
"js_done_took": "完成! 花费",
"js_iterations": "迭代",
"js_finished_reading": "我读完了,继续 →",
"js_calculation_error": "计算错误!",
"js_calculation_error_msg": "计算挑战失败:"
}

View File

@@ -0,0 +1,64 @@
{
"loading": "載入中...",
"why_am_i_seeing": "為什麼我看到這個?",
"protected_by": "本網站由",
"protected_from": "保護,來自",
"made_with": "在 🇨🇦 用 ❤️ 製作",
"mascot_design": "吉祥物由",
"ai_companies_explanation": "您會看到這個畫面,是因為網站管理員啟用了 Anubis 來保護伺服器,避免 AI 公司大量爬取網站內容。這類行為會導致網站當機,讓所有使用者都無法正常存取資源。",
"anubis_compromise": "Anubis 是一種折衷做法。它採用了類似 Hashcash 的工作量證明機制Proof-of-Work該機制最初是為了減少垃圾郵件而提出。其核心概念是對個別使用者而言額外的運算負擔可以忽略但對大規模爬蟲來說累積起來的成本將大幅增加從而讓爬取行為變得更困難。",
"hack_purpose": "本質上,這是一種權宜的解法,目的是提供一個「夠用」的暫時性防護措施,好讓開發者有更多時間針對無頭瀏覽器進行指紋特徵辨識(例如:分析其字型渲染方式),以便未來不再需要對可能為合法使用者的訪客展示工作量證明頁面。",
"jshelter_note": "請注意Anubis 需要使用現代 JavaScript 功能,而像 JShelter 這類外掛可能會阻擋這些功能。請為此網域停用 JShelter 或類似的插件。",
"version_info": "這個網站正在運行 Anubis 版本",
"try_again": "再試一次",
"go_home": "回首頁",
"contact_webmaster": "或者您覺得您不應該被封鎖,請聯絡站點管理員於",
"connection_security": "請稍等,我們需要在繼續之前檢閱您的連線安全性。",
"javascript_required": "很遺憾,您必須啟用 JavaScript 才能通過這項驗證。這是因為 AI 公司已經改變了網站託管的社會契約,因此我們必須採取這樣的保護機制。無需 JavaScript 的解法仍在開發中。",
"benchmark_requires_js": "執行基準測試工具需要啟用 JavaScript。",
"difficulty": "難度:",
"algorithm": "演算法:",
"compare": "比較:",
"time": "時間",
"iters": "迭代",
"time_a": "時間 A",
"iters_a": "迭代 A",
"time_b": "時間 B",
"iters_b": "迭代 B",
"static_check_endpoint": "這是提供給您的反向代理伺服器使用的檢查端點。",
"authorization_required": "需要認證",
"cookies_disabled": "您的瀏覽器目前已停用 Cookie為了確認您是合法使用者Anubis 需要啟用 Cookie。 請為此網域啟用 Cookie",
"access_denied": "拒絕存取:錯誤代碼",
"dronebl_entry": "DroneBL 回報了一筆紀錄",
"see_dronebl_lookup": "見",
"internal_server_error": "內部伺服器錯誤:管理員錯誤地配置了 Anubis。 請聯絡管理員要求他們檢閱日誌",
"invalid_redirect": "無效的重新導向",
"redirect_not_parseable": "重新導向 URL 無法解析",
"redirect_domain_not_allowed": "重新導向的網域並不允許",
"failed_to_sign_jwt": "簽署 JWT 失敗",
"invalid_invocation": "無效的 MakeChallenge 呼叫",
"client_error_browser": "客戶端錯誤:請確保您的瀏覽器是最新版本並稍候再試。",
"oh_noes": "哎呀糟糕了!",
"benchmarking_anubis": "正在進行 Anubis 效能測試!",
"you_are_not_a_bot": "你不是機器人!",
"making_sure_not_bot": "正在確認你是不是機器人!",
"celphase": "CELPHASE 設計",
"js_web_crypto_error": "您的瀏覽器無法正常使用 web.crypto 元件。您是否透過安全連線HTTPS檢視此網站",
"js_web_workers_error": "您的瀏覽器並不支援 Web workers Anubis 使用這個來避免凍結您的瀏覽器 )您有安裝像是 JShelter 之類的插件嗎?",
"js_cookies_error": "您的瀏覽器無法儲存 Cookie。 Anubis 會使用 Cookie 儲存簽署的憑證,以判斷使用者是否已通過驗證。請為此網域啟用 Cookie 儲存功能。 請注意Anubis 儲存的 Cookie 名稱可能會變動,且其名稱與內容不屬於公開 API 的一部分。",
"js_context_not_secure": "您的內容並不安全",
"js_context_not_secure_msg": "請嘗試使用 HTTPS 連線,或聯繫網站管理員設定 HTTPS。更多資訊請參見 <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>。",
"js_calculating": "計算中...",
"js_missing_feature": "缺少功能",
"js_challenge_error": "挑戰錯誤!",
"js_challenge_error_msg": "解決檢查演算法失敗。 您可能會想要重整頁面。",
"js_calculating_difficulty": "計算中...<br/>難度:",
"js_speed": "速度:",
"js_verification_longer": "驗證所花的時間高於預期。 請不要重整頁面。",
"js_success": "成功!",
"js_done_took": "完成! 花費",
"js_iterations": "迭代",
"js_finished_reading": "我讀完了,繼續 →",
"js_calculation_error": "計算錯誤!",
"js_calculation_error_msg": "計算挑戰失敗:"
}

View File

@@ -3,11 +3,11 @@ package localization
import (
"embed"
"encoding/json"
"github.com/TecharoHQ/anubis"
"net/http"
"strings"
"sync"
"github.com/TecharoHQ/anubis"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
@@ -94,6 +94,15 @@ func (sl *SimpleLocalizer) T(messageID string) string {
return sl.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
}
// Get the language that is used by the localizer by retrieving a well-known string that is required to be present
func (sl *SimpleLocalizer) GetLang() string {
_, tag, err := sl.Localizer.LocalizeWithTag(&i18n.LocalizeConfig{MessageID: "loading"})
if err != nil {
return "en"
}
return tag.String()
}
// GetLocalizer creates a localizer based on the request's Accept-Language header or forcedLanguage option
func GetLocalizer(r *http.Request) *SimpleLocalizer {
var localizer *i18n.Localizer

View File

@@ -2,6 +2,7 @@ package localization
import (
"encoding/json"
"fmt"
"sort"
"testing"
@@ -11,59 +12,48 @@ import (
func TestLocalizationService(t *testing.T) {
service := NewLocalizationService()
t.Run("English localization", func(t *testing.T) {
localizer := service.GetLocalizer("en")
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
if result != "Loading..." {
t.Errorf("Expected 'Loading...', got '%s'", result)
}
})
loadingStrMap := map[string]string{
"de": "Ladevorgang...",
"en": "Loading...",
"es": "Cargando...",
"et": "Laadin...",
"fil": "Naglo-load...",
"fr": "Chargement...",
"ja": "ロード中...",
"is": "Hleður...",
"pt-BR": "Carregando...",
"tr": "Yükleniyor...",
"zh-CN": "加载中...",
"zh-TW": "載入中...",
}
t.Run("French localization", func(t *testing.T) {
localizer := service.GetLocalizer("fr")
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
if result != "Chargement..." {
t.Errorf("Expected 'Chargement...', got '%s'", result)
}
})
t.Run("German localization", func(t *testing.T) {
localizer := service.GetLocalizer("de")
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
if result != "Ladevorgang..." {
t.Errorf("Expected 'Ladevorgang...', got '%s'", result)
}
})
t.Run("All required keys exist in English", func(t *testing.T) {
localizer := service.GetLocalizer("en")
requiredKeys := []string{
"loading", "why_am_i_seeing", "protected_by", "made_with",
"mascot_design", "try_again", "go_home", "javascript_required",
}
for _, key := range requiredKeys {
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
if result == "" {
t.Errorf("Key '%s' returned empty string", key)
for lang, expected := range loadingStrMap {
t.Run(fmt.Sprintf("%s localization", lang), func(t *testing.T) {
localizer := service.GetLocalizer(lang)
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
if result != expected {
t.Errorf("Expected '%s', got '%s'", expected, result)
}
}
})
})
}
t.Run("All required keys exist in French", func(t *testing.T) {
localizer := service.GetLocalizer("fr")
requiredKeys := []string{
"loading", "why_am_i_seeing", "protected_by", "made_with",
"mascot_design", "try_again", "go_home", "javascript_required",
}
// Test for requiredKeys localization
requiredKeys := []string{
"loading", "why_am_i_seeing", "protected_by", "protected_from", "made_with",
"mascot_design", "try_again", "go_home", "javascript_required",
}
for _, key := range requiredKeys {
result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
if result == "" {
t.Errorf("Key '%s' returned empty string", key)
for lang := range loadingStrMap {
t.Run(fmt.Sprintf("All required keys exist in %s", lang), func(t *testing.T) {
loc := service.GetLocalizer(lang)
for _, key := range requiredKeys {
result := loc.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
if result == "" {
t.Errorf("Key '%s' returned empty string", key)
}
}
}
})
})
}
}
type manifest struct {
@@ -112,6 +102,10 @@ func TestComprehensiveTranslations(t *testing.T) {
t.Run(lang, func(t *testing.T) {
loc := service.GetLocalizer(lang)
sl := SimpleLocalizer{Localizer: loc}
service_lang := sl.GetLang()
if service_lang != lang {
t.Error("Localizer language not same as specified")
}
for _, key := range keys {
t.Run(key, func(t *testing.T) {
if result := sl.T(key); result == "" {

View File

@@ -73,6 +73,12 @@ func (cr *CELRequest) ResolveName(name string) (any, bool) {
return expressions.URLValues{Values: cr.URL.Query()}, true
case "headers":
return expressions.HTTPHeaders{Header: cr.Header}, true
case "load_1m":
return expressions.Load1(), true
case "load_5m":
return expressions.Load5(), true
case "load_15m":
return expressions.Load15(), true
default:
return nil, false
}

View File

@@ -329,6 +329,7 @@ type fileConfig struct {
OpenGraph openGraphFileConfig `json:"openGraph,omitempty"`
Impressum *Impressum `json:"impressum,omitempty"`
StatusCodes StatusCodes `json:"status_codes"`
Store *Store `json:"store"`
Thresholds []Threshold `json:"thresholds"`
}
@@ -361,6 +362,12 @@ func (c *fileConfig) Valid() error {
}
}
if c.Store != nil {
if err := c.Store.Valid(); err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 {
return fmt.Errorf("config is not valid:\n%w", errors.Join(errs...))
}
@@ -374,6 +381,9 @@ func Load(fin io.Reader, fname string) (*Config, error) {
Challenge: http.StatusOK,
Deny: http.StatusOK,
},
Store: &Store{
Backend: "memory",
},
}
if err := yaml.NewYAMLToJSONDecoder(fin).Decode(&c); err != nil {
@@ -392,6 +402,7 @@ func Load(fin io.Reader, fname string) (*Config, error) {
Override: c.OpenGraph.Override,
},
StatusCodes: c.StatusCodes,
Store: c.Store,
}
if c.OpenGraph.TimeToLive != "" {
@@ -457,6 +468,7 @@ type Config struct {
Impressum *Impressum
OpenGraph OpenGraph
StatusCodes StatusCodes
Store *Store
}
func (c Config) Valid() error {

Some files were not shown because too many files have changed in this diff Show More