Compare commits

...

42 Commits

Author SHA1 Message Date
Xe Iaso
a0fb2d7c43 chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-20 22:33:57 +00:00
Xe Iaso
5ba6b3f477 fix(default-config): block Huawei Cloud
Closes #978

Huawei Cloud has been egregious about its scraping. All attempts to
contact their abuse team have failed. If you work for Huawei Cloud,
please raise this issue internally and get the scraping to just stop.
2025-08-20 22:30:09 +00:00
Xe Iaso
6e4e471792 fix(lib): ensure issued challenges don't get double-spent (#1003)
* fix(lib): ensure issued challenges don't get double-spent

Closes #1002

TL;DR: challenge IDs were not validated at time of token issuance. A
dedicated attacker could solve a challenge once and reuse it across
multiple sessons in order to mint additional tokens.

With the advent of store based challenge issuance in #749, this means
that these challenge IDs are only good for 30 minutes. Websites using
the most recent version of Anubis have limited exposure to this problem.

Websites using older versions of Anubis have a much more increased
exposure to this problem and are encouraged to keep this software
updated as often and as frequently as possible.

* docs: update CHANGELOG

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

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-20 12:33:32 -04:00
Xe Iaso
e8dfff6350 feat(blog): add short funding update post (#994)
* feat(blog): add short funding update post

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-08-18 08:42:27 -04:00
Dryusdan
237a6a98e2 Bump ai.robots.txt to v1.39 (#982) 2025-08-18 06:52:23 -04:00
Xe Iaso
e43999f30c chore: add libreapay
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-16 03:01:59 +00:00
Martin
29d038835f feat(web): Add option for customizable explanation text (#747)
* Add option for customizable explanation text

* Add changes to CHANGELOG.md

* Replace custom explanation text in favor of static simplified text

Also includes translations for the simple_explanation using Google
Translate as a placeholder so tests pass.

---------

Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
Co-authored-by: Xe Iaso <xe.iaso@techaro.lol>
2025-08-14 11:12:55 -04:00
Xe Iaso
39215457e4 fix(locales): remove the word "hack" from the description of Anubis (#973)
This was causing confusion and less technical users were thinking that
websites had been intruded upon, causing them to send me horrible things
over email.

All non-English strings were amended using Google Translate. Please fix
the localization as appropriate.
2025-08-14 01:15:28 +00:00
Martin
ff691dfee8 feat(lib): Add optional restrictions for JWT based on a specific header value (#697)
* Add JWTRestrictionHeader funktionality

* Add JWTRestrictionHeader to docs

* Move JWT_RESTRICTION_HEADER from advanced section to normal one

* Add rull request URL to Changelog

* Set default value of JWT_RESTRICTION_HEADER to X-Real-IP
2025-08-13 23:27:42 +00:00
Mathieu Lu
83503525f2 Update known-instances.md: add lab.civicrm.org (#971)
Signed-off-by: Mathieu Lu <mathieu@civicrm.org>
2025-08-13 19:32:29 +00:00
phoval
a8b7b2ad7b feat: support HTTP redirect for forward authentication middleware in Traefik (#368)
* feat: support HTTP redirect for forward authentication middleware in Traefik

* fix(docs): fix my terrible merge 

Signed-off-by: Jason Cameron <jasoncameron.all@gmail.com>

* chore: fix typo in docs

Signed-off-by: Jason Cameron <jasoncameron.all@gmail.com>

* fix(ci): add forwardauth

Signed-off-by: Jason Cameron <jasoncameron.all@gmail.com>

* chore: improve doc, target must be a space

* chore: changelog

* fix: validate X-Forwarded headers and check redirect domain

* chore: refactor error handling

* fix(doc): cookie traefik

* fix: tests merge

* Update docs/docs/admin/environments/traefik.mdx

Co-authored-by: Henri Vasserman <henv@hot.ee>
Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Jason Cameron <git@jasoncameron.dev>
Signed-off-by: Jason Cameron <jasoncameron.all@gmail.com>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
Co-authored-by: Jason Cameron <jasoncameron.all@gmail.com>
Co-authored-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Henri Vasserman <henv@hot.ee>
2025-08-12 20:59:45 -04:00
Elliot Speck
87651f9506 default pattern fixes (#963)
* feat(checker): allow png/gif/jpg/jpeg/svg favicons as well as ico

* changelog: add updates to keep-internet-working.yaml

* fix(checker): tighten default regex patterns for well-known files

* changelog: add updates to regular expression patterns in keep-internet-working.yaml

---------

Signed-off-by: Elliot Speck <11192354+arcayr@users.noreply.github.com>
2025-08-09 07:40:33 -04:00
Elliot Speck
100005ce70 feat(checker): allow png/gif/jpg/jpeg/svg favicons as well as ico (#961)
* feat(checker): allow png/gif/jpg/jpeg/svg favicons as well as ico

* changelog: add updates to keep-internet-working.yaml

---------

Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
Co-authored-by: Xe Iaso <xe.iaso@techaro.lol>
2025-08-08 16:53:23 +00:00
Medvidek77
0a68415c2e fix(localization): Improve Czech language translation (#895)
* fix(localization): Improve Czech language translation

Improved naturalness and flow of several phrases. Corrected typos and punctuation. Completed one previously unfinished sentence.

Signed-off-by: Medvidek77 <medvidek77@centrum.cz>

* Update cs.json

Signed-off-by: Medvidek77 <medvidek77@centrum.cz>

---------

Signed-off-by: Medvidek77 <medvidek77@centrum.cz>
2025-08-08 12:50:23 -04:00
SecularSteve
b3886752a1 Added Dutch translation (#937)
* Added Dutch translation

Signed-off-by: SecularSteve <33793273+SecularSteve@users.noreply.github.com>

* Added Dutch translation

Signed-off-by: SecularSteve <33793273+SecularSteve@users.noreply.github.com>

* Added Dutch translation

Signed-off-by: SecularSteve <33793273+SecularSteve@users.noreply.github.com>

* Update lib/localization/locales/nl.json

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

---------

Signed-off-by: SecularSteve <33793273+SecularSteve@users.noreply.github.com>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-08-08 12:49:49 -04:00
Sunniva Løvstad
0e9f831201 chore: fix capitalisation in bokmål and nynorsk (#959) 2025-08-08 12:48:07 -04:00
Xe Iaso
22ee227f20 fix(anubis): use global cookie prefix variable
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-07 13:51:18 +00:00
Jason Cameron
adda60c163 Revert "build(deps): bump the github-actions group with 2 updates (#952)" (#962) 2025-08-06 03:01:25 +00:00
dependabot[bot]
e0a15bf4dc build(deps): bump the github-actions group with 2 updates (#952)
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-08-05 22:45:07 -04:00
Xe Iaso
f6481b81a2 fix(web): embed challenge ID in pass-challenge invocations (#944)
* refactor: make challenge pages return the challenge component

This means that challenge pages will return only the little bit that
actually matters, not the entire component.

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

* fix(web): move Anubis version info to be implicitly in the footer

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

* fix(web): embed challenge ID into generated pages

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

* fix(lib): make tests pass

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

* test(lib/policy/config): amend tests

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

* test(lib): fix tests again

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>
2025-08-04 18:49:19 +00:00
Xe Iaso
790bcbe773 fix(internal): silence unsolicited response log lines (#950)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-03 19:08:23 +00:00
Xe Iaso
7c80c23e90 docs: remove JSON examples from policy file docs (#945)
* docs: remove JSON examples from policy file docs

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

* fix(lib): remove mentions of botPolicies.json in the tests

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

* docs: update link to challenge methods

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

* docs: unbreak links to the challenges category

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

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-03 18:09:26 +00:00
axell
2d8e942377 Add swedish local (#913)
* add swedish local

* added to changelog

* add to TestLocalizationService

* build(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /docs (#909)

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* add local (signed this time hopefully)

* Update sv.json

Co-authored-by: David Marby <david@dmarby.se>
Signed-off-by: axel <mail@axell.me>

* Update sv.json

Co-authored-by: David Marby <david@dmarby.se>
Signed-off-by: axel <mail@axell.me>

* Update localization_test.go

Co-authored-by: Jonathan Herlin <Jonte@jherlin.se>
Signed-off-by: axel <mail@axell.me>

* Update sv.json

Co-authored-by: Jonathan Herlin <Jonte@jherlin.se>
Signed-off-by: axel <mail@axell.me>

* Update sv.json

Co-authored-by: Jonathan Herlin <Jonte@jherlin.se>
Signed-off-by: axel <mail@axell.me>

* Update sv.json

Co-authored-by: Jonathan Herlin <Jonte@jherlin.se>
Signed-off-by: axel <mail@axell.me>

* Update sv.json

Co-authored-by: Jonathan Herlin <Jonte@jherlin.se>
Signed-off-by: axel <mail@axell.me>

* Update sv.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: axel <mail@axell.me>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: David Marby <david@dmarby.se>
Co-authored-by: Jonathan Herlin <Jonte@jherlin.se>
2025-08-02 22:17:31 -04:00
Xe Iaso
d5f01dbdb9 fix(web/sha256-browserjs): fix function name (#943)
* fix(web/sha256-browserjs): fix function name

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-08-02 16:05:48 +00:00
lillian-b
70bf58cc63 Add HackLab.TO to known instances (#936)
* Add HackLab.TO to known instances

Signed-off-by: lillian-b <146143737+lillian-b@users.noreply.github.com>

* fix?

Signed-off-by: lillian-b <146143737+lillian-b@users.noreply.github.com>

---------

Signed-off-by: lillian-b <146143737+lillian-b@users.noreply.github.com>
2025-08-02 15:30:34 +00:00
Xe Iaso
0dccf2e009 refactor(web): redo proof of work web worker logic (#941)
* chore(web/js): delete proof-of-work-slow.mjs

This code has served its purpose and now needs to be retired to the
great beyond. There is no replacement for this, the fast implementation
will be used instead.

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

* chore(web): handle building multiple JS entrypoints and web workers

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

* feat(web): rewrite frontend worker handling

This completely rewrites how the proof of work challenge works based on
feedback from browser engine developers and starts the process of making
the proof of work function easier to change out.

- Import @aws-crypto/sha256-js to use in Firefox as its implementation
  of WebCrypto doesn't jump directly from highly optimized browser
  internals to JIT-ed JavaScript like Chrome's seems to.
- Move the worker code to `web/js/worker/*` with each worker named after
  the hashing method and hash method implementation it uses.
- Update bench.mjs to import algorithms the new way.
- Delete video.mjs, it was part of a legacy experiment that I never had
  time to finish.
- Update LibreJS comment to add info about the use of
  @aws-crypto/sha256-js.
- Also update my email to my @techaro.lol address.

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

* fix(web): don't hard dep webcrypto anymore

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

* chore(lib/policy): start the deprecation process for slow

This mostly adds a warning, but the "slow" method is in the process of
being removed. Warn admins with slog.Warn.

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

* docs: update CHANGELOG

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

* feat(web/js): allow running Anubis in non-secure contexts

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

* Update metadata

check-spelling run (pull_request) for Xe/purge-slow

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: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
2025-08-02 11:27:26 -04:00
Xe Iaso
8d08de6d9c fix: allow social preview images (#934)
* feat(ogtags): when encountering opengraph URLs, add them to an allow cache

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

* feat(lib): automatically allow any urls in the ogtags allow cache

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

* docs: update CHANGELOG

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

* chore: spelling

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

* docs(changelog): remove this bit to make it its own PR

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

* test(palemoon): add 180 second timeout

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

* test(palemoon): actually invoke timeout

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

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-31 08:44:49 -04:00
Xe Iaso
1f7fcf938b fix(lib): add the ability to set a custom slog Logger (#915)
* fix(lib): add the ability to set a custom slog Logger

Closes #864

* test(lib): amend s.check usage

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>
2025-07-31 08:06:35 -04:00
Emir SARI
6ae386a11a fix: polish Turkish translations (#897)
* Polish Turkish translations

* Update tr.json

Co-authored-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Emir SARI <emir_sari@icloud.com>

* Update tr.json

Co-authored-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Emir SARI <emir_sari@icloud.com>

* Update tr.json

Co-authored-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Emir SARI <emir_sari@icloud.com>

* Try to make “From” sound better

Signed-off-by: Emir SARI <emir_sari@icloud.com>

---------

Signed-off-by: Emir SARI <emir_sari@icloud.com>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-07-31 07:33:16 -04:00
Sveinn í Felli
963527fb60 Update is.json (#935)
Just one new string.

Signed-off-by: Sveinn í Felli <sv1@fellsnet.is>
2025-07-30 12:08:27 -04:00
Xe Iaso
b81c577106 chore(docs/anubis-cfg): update contact email
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-29 15:38:08 +00:00
Xe Iaso
987c1d7410 chore(go.mod): depend on at least go 1.24.2
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-29 04:06:16 +00:00
Saterfield990
826433e8be build(deps): bump the gomod group (#931)
* build(deps): bump the gomod group

* chore: spelling

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

* chore: npm run assets

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

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
2025-07-28 23:47:18 -04:00
Xe Iaso
4a4031450c fix(anubis): store the challenge method in the store (#924)
* fix(lib): reduce challenge string size

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

* fix(internal): add host, method, and path to request logs

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

* fix(anubis): log when challenges explicitly fail

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

* fix(lib): make challenge validation fully deterministic

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

* fix(anubis): nuke challengeFor function

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-28 10:57:50 -04:00
dependabot[bot]
8feacc78fc build(deps): bump the github-actions group with 2 updates (#929)
Bumps the github-actions group with 2 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `astral-sh/setup-uv` from 6.4.1 to 6.4.3
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](7edac99f96...e92bafb625)

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

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 6.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 3.29.4
  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-27 22:47:21 -04:00
Xe Iaso
bca2e87e80 feat(default-rules): add weight to Custom-AsyncHttpClient (#914)
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
2025-07-27 00:41:43 +00:00
Xe Iaso
a735770c93 feat(expressions): add segments function to break path into segments (#916)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-25 16:21:08 -04:00
Xe Iaso
bf42014ac3 test: add automated Pale Moon tests (#903)
* test: start work on Pale Moon tests

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

* test(palemoon): rewrite to use ci-images

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

* ci: enable palemoon tests

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

* test(palemoon): add some variables

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

* fix: disable tmate

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

* test(palemoon): disable i386 for now

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-25 11:42:08 -04:00
dependabot[bot]
0ef3461816 build(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /docs (#909)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 11:18:27 -04:00
Xe Iaso
7d7028d25c test(lib): add a test for the X-Forwarded-For middleware (#912)
Previously the X-Forwarded-For middleware could return two commas in a
row. This is a regression test to make sure that doesn't happen again.

Imports a patch previously exclusive to Botstopper.

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-25 10:58:41 -04:00
Xe Iaso
9affd2edf4 chore: expose thoth in lib (#911)
Imports a patch previously exclusive to Botstopper.

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-25 10:58:30 -04:00
dependabot[bot]
26b6d8a91a build(deps): bump on-headers and compression in /docs (#910)
Bumps [on-headers](https://github.com/jshttp/on-headers) and [compression](https://github.com/expressjs/compression). These dependencies needed to be updated together.

Updates `on-headers` from 1.0.2 to 1.1.0
- [Release notes](https://github.com/jshttp/on-headers/releases)
- [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0)

Updates `compression` from 1.8.0 to 1.8.1
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: on-headers
  dependency-version: 1.1.0
  dependency-type: indirect
- dependency-name: compression
  dependency-version: 1.8.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-25 10:53:28 -04:00
129 changed files with 3649 additions and 1757 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,3 @@
patreon: cadey
github: xe
github: xe
liberapay: Xe

View File

@@ -52,7 +52,6 @@ checkresult
chibi
cidranger
ckie
ckies
cloudflare
Codespaces
confd
@@ -64,6 +63,7 @@ Cromite
crt
Cscript
daemonizing
dayjob
DDOS
Debian
debrpm
@@ -99,11 +99,13 @@ Factset
fastcgi
fediverse
ffprobe
financials
finfos
Firecrawl
flagenv
Fordola
forgejo
forwardauth
fsys
fullchain
gaissmai
@@ -111,6 +113,7 @@ Galvus
geoip
geoipchecker
gha
GHSA
gipc
gitea
godotenv
@@ -138,8 +141,10 @@ htmlc
htmx
httpdebug
Huawei
huawei
hypertext
iaskspider
iaso
iat
ifm
Imagesift
@@ -155,6 +160,7 @@ isset
ivh
Jenomis
JGit
jhjj
joho
journalctl
jshelter
@@ -202,7 +208,7 @@ nobots
NONINFRINGEMENT
nosleep
OCOB
ogtags
ogtag
omgili
omgilibot
openai
@@ -230,6 +236,7 @@ promauto
promhttp
proofofwork
publicsuffix
purejs
pwcmd
pwuser
qualys
@@ -281,6 +288,7 @@ SVCNAME
tagline
tarballs
tarrif
taviso
tbn
tbr
techaro
@@ -293,6 +301,7 @@ thoth
thothmock
Tik
Timpibot
TLog
traefik
uberspace
Unbreak
@@ -307,10 +316,8 @@ Varis
Velen
vendored
vhosts
videotest
VKE
Vultr
waitloop
weblate
webmaster
webpage

View File

@@ -18,7 +18,9 @@ jobs:
- git-push
- healthcheck
- i18n
runs-on: ubuntu-24.04
- palemoon/amd64
#- palemoon/i386
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@@ -43,3 +45,14 @@ jobs:
run: |
cd test/${{ matrix.test }}
backoff-retry --try-count 10 ./test.sh
- name: Sanitize artifact name
if: always()
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
if: always()
with:
name: ${{ env.ARTIFACT_NAME }}
path: test/${{ matrix.test }}/var

View File

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

View File

@@ -36,3 +36,6 @@ const DefaultDifficulty = 4
// ForcedLanguage is the language being used instead of the one of the request's Accept-Language header
// if being set.
var ForcedLanguage = ""
// UseSimplifiedExplanation can be set to true for using the simplified explanation
var UseSimplifiedExplanation = false

View File

@@ -30,10 +30,10 @@ import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/data"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/internal/thoth"
libanubis "github.com/TecharoHQ/anubis/lib"
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/thoth"
"github.com/TecharoHQ/anubis/web"
"github.com/facebookgo/flagenv"
_ "github.com/joho/godotenv/autoload"
@@ -49,8 +49,9 @@ var (
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
cookieDynamicDomain = flag.Bool("cookie-dynamic-domain", false, "if set, automatically set the cookie Domain value based on the request domain")
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis")
cookiePrefix = flag.String("cookie-prefix", anubis.CookieName, "prefix for browser cookies created by Anubis")
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
useSimplifiedExplanation = flag.Bool("use-simplified-explanation", false, "if true, replaces the text when clicking \"Why am I seeing this?\" with a more simplified text for a non-tech-savvy audience.")
forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header")
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies")
@@ -77,11 +78,13 @@ var (
extractResources = flag.String("extract-resources", "", "if set, extract the static resources to the specified folder")
webmasterEmail = flag.String("webmaster-email", "", "if set, displays webmaster's email on the reject page for appeals")
versionFlag = flag.Bool("version", false, "print Anubis version")
publicUrl = flag.String("public-url", "", "the externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for forwardAuth).")
xffStripPrivate = flag.Bool("xff-strip-private", true, "if set, strip private addresses from X-Forwarded-For")
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
jwtRestrictionHeader = flag.String("jwt-restriction-header", "X-Real-IP", "If set, the JWT is only valid if the current value of this header matched the value when the JWT was created")
)
func keyFromHex(value string) (ed25519.PrivateKey, error) {
@@ -385,6 +388,7 @@ func main() {
anubis.CookieName = *cookiePrefix + "-auth"
anubis.TestCookieName = *cookiePrefix + "-cookie-verification"
anubis.ForcedLanguage = *forcedLanguage
anubis.UseSimplifiedExplanation = *useSimplifiedExplanation
// If OpenGraph configuration values are not set in the config file, use the
// values from flags / envvars.
@@ -396,22 +400,24 @@ func main() {
}
s, err := libanubis.New(libanubis.Options{
BasePrefix: *basePrefix,
StripBasePrefix: *stripBasePrefix,
Next: rp,
Policy: policy,
ServeRobotsTXT: *robotsTxt,
ED25519PrivateKey: ed25519Priv,
HS512Secret: []byte(*hs512Secret),
CookieDomain: *cookieDomain,
CookieDynamicDomain: *cookieDynamicDomain,
CookieExpiration: *cookieExpiration,
CookiePartitioned: *cookiePartitioned,
RedirectDomains: redirectDomainsList,
Target: *target,
WebmasterEmail: *webmasterEmail,
OpenGraph: policy.OpenGraph,
CookieSecure: *cookieSecure,
BasePrefix: *basePrefix,
StripBasePrefix: *stripBasePrefix,
Next: rp,
Policy: policy,
ServeRobotsTXT: *robotsTxt,
ED25519PrivateKey: ed25519Priv,
HS512Secret: []byte(*hs512Secret),
CookieDomain: *cookieDomain,
CookieDynamicDomain: *cookieDynamicDomain,
CookieExpiration: *cookieExpiration,
CookiePartitioned: *cookiePartitioned,
RedirectDomains: redirectDomainsList,
Target: *target,
WebmasterEmail: *webmasterEmail,
OpenGraph: policy.OpenGraph,
CookieSecure: *cookieSecure,
PublicUrl: *publicUrl,
JWTRestrictionHeader: *jwtRestrictionHeader,
})
if err != nil {
log.Fatalf("can't construct libanubis.Server: %v", err)
@@ -440,6 +446,7 @@ func main() {
"base-prefix", *basePrefix,
"cookie-expiration-time", *cookieExpiration,
"rule-error-ids", ruleErrorIDs,
"public-url", *publicUrl,
)
go func() {

View File

@@ -1,29 +0,0 @@
{
"bots": [
{
"import": "(data)/bots/_deny-pathological.yaml"
},
{
"import": "(data)/meta/ai-block-aggressive.yaml"
},
{
"import": "(data)/crawlers/_allow-good.yaml"
},
{
"import": "(data)/bots/aggressive-brazilian-scrapers.yaml"
},
{
"import": "(data)/common/keep-internet-working.yaml"
},
{
"name": "generic-browser",
"user_agent_regex": "Mozilla|Opera",
"action": "CHALLENGE"
}
],
"dnsbl": false,
"status_codes": {
"CHALLENGE": 200,
"DENY": 200
}
}

View File

@@ -12,8 +12,8 @@
bots:
# 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
- # This correlates to data/bots/_deny-pathological.yaml in the source tree
# https://github.com/TecharoHQ/anubis/blob/main/data/bots/_deny-pathological.yaml
import: (data)/bots/_deny-pathological.yaml
- import: (data)/bots/aggressive-brazilian-scrapers.yaml

View File

@@ -1,3 +1,5 @@
- import: (data)/bots/cloudflare-workers.yaml
- import: (data)/bots/headless-browsers.yaml
- import: (data)/bots/us-ai-scraper.yaml
- import: (data)/bots/us-ai-scraper.yaml
- import: (data)/bots/custom-async-http-client.yaml
- import: (data)/crawlers/huawei-cloud.yaml

View File

@@ -4,5 +4,5 @@
# CCBot is allowed because if Common Crawl is allowed, then scrapers don't need to scrape to get the data.
- name: "ai-robots-txt"
user_agent_regex: >-
AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|bedrockbot|Brightbot 1.0|Bytespider|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User/1.0|MyCentralAIScraperBot|NovaAct|OAI-SearchBot|omgili|omgilibot|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot|SemrushBot-BA|SemrushBot-CT|SemrushBot-OCOB|SemrushBot-SI|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YandexAdditional|YandexAdditionalBot|YouBot
AddSearchBot|AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|Awario|bedrockbot|bigsur.ai|Brightbot 1.0|Bytespider|CCBot|ChatGPT Agent|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|CloudVertexBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Datenbank Crawler|Devin|Diffbot|DuckAssistBot|Echobot Bot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Gemini-Deep-Research|Google-CloudVertexBot|Google-Extended|GoogleAgent-Mariner|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|LinerBot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User|MistralAI-User/1.0|MyCentralAIScraperBot|netEstate Imprint Crawler|NovaAct|OAI-SearchBot|omgili|omgilibot|OpenAI|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|Thinkbot|TikTokSpider|Timpibot|VelenPublicWebCrawler|WARDBot|Webzio-Extended|wpbot|YaK|YandexAdditional|YandexAdditionalBot|YouBot
action: DENY

View File

@@ -0,0 +1,5 @@
- name: "custom-async-http-client"
user_agent_regex: "Custom-AsyncHttpClient"
action: WEIGH
weight:
adjust: 10

View File

@@ -1,13 +1,13 @@
# Common "keeping the internet working" routes
- name: well-known
path_regex: ^/.well-known/.*$
path_regex: ^/\.well-known/.*$
action: ALLOW
- name: favicon
path_regex: ^/favicon.ico$
path_regex: ^/favicon\.(?:ico|png|gif|jpg|jpeg|svg)$
action: ALLOW
- name: robots-txt
path_regex: ^/robots.txt$
path_regex: ^/robots\.txt$
action: ALLOW
- name: sitemap
path_regex: ^/sitemap.xml$
action: ALLOW
path_regex: ^/sitemap\.xml$
action: ALLOW

View File

@@ -0,0 +1,617 @@
- name: huawei-cloud
action: DENY
# Updated 2025-08-20 from IP addresses for AS136907
remote_addresses:
- 1.178.32.0/20
- 1.178.48.0/20
- 101.44.0.0/20
- 101.44.144.0/20
- 101.44.16.0/20
- 101.44.160.0/20
- 101.44.173.0/24
- 101.44.176.0/20
- 101.44.192.0/20
- 101.44.208.0/22
- 101.44.212.0/22
- 101.44.216.0/22
- 101.44.220.0/22
- 101.44.224.0/22
- 101.44.228.0/22
- 101.44.232.0/22
- 101.44.236.0/22
- 101.44.240.0/22
- 101.44.244.0/22
- 101.44.248.0/22
- 101.44.252.0/24
- 101.44.253.0/24
- 101.44.254.0/24
- 101.44.255.0/24
- 101.44.32.0/20
- 101.44.48.0/20
- 101.44.64.0/20
- 101.44.80.0/20
- 101.44.96.0/20
- 101.46.0.0/20
- 101.46.128.0/21
- 101.46.136.0/21
- 101.46.144.0/21
- 101.46.152.0/21
- 101.46.160.0/21
- 101.46.168.0/21
- 101.46.176.0/21
- 101.46.184.0/21
- 101.46.192.0/21
- 101.46.200.0/21
- 101.46.208.0/21
- 101.46.216.0/21
- 101.46.224.0/22
- 101.46.232.0/22
- 101.46.236.0/22
- 101.46.240.0/22
- 101.46.244.0/22
- 101.46.248.0/22
- 101.46.252.0/24
- 101.46.253.0/24
- 101.46.254.0/24
- 101.46.255.0/24
- 101.46.32.0/20
- 101.46.48.0/20
- 101.46.64.0/20
- 101.46.80.0/20
- 103.198.203.0/24
- 103.215.0.0/24
- 103.215.1.0/24
- 103.215.3.0/24
- 103.240.156.0/22
- 103.240.157.0/24
- 103.255.60.0/22
- 103.255.60.0/24
- 103.255.61.0/24
- 103.255.62.0/24
- 103.255.63.0/24
- 103.40.100.0/23
- 103.84.110.0/24
- 110.238.100.0/22
- 110.238.104.0/21
- 110.238.112.0/21
- 110.238.120.0/22
- 110.238.124.0/22
- 110.238.64.0/21
- 110.238.72.0/21
- 110.238.80.0/20
- 110.238.96.0/24
- 110.238.98.0/24
- 110.238.99.0/24
- 110.239.127.0/24
- 110.239.184.0/22
- 110.239.188.0/23
- 110.239.190.0/23
- 110.239.64.0/19
- 110.239.96.0/19
- 110.41.208.0/24
- 110.41.209.0/24
- 110.41.210.0/24
- 111.119.192.0/20
- 111.119.208.0/20
- 111.119.224.0/20
- 111.119.240.0/20
- 111.91.0.0/20
- 111.91.112.0/20
- 111.91.16.0/20
- 111.91.32.0/20
- 111.91.48.0/20
- 111.91.64.0/20
- 111.91.80.0/20
- 111.91.96.0/20
- 114.119.128.0/19
- 114.119.160.0/21
- 114.119.168.0/24
- 114.119.169.0/24
- 114.119.170.0/24
- 114.119.171.0/24
- 114.119.172.0/22
- 114.119.176.0/20
- 115.30.32.0/20
- 115.30.48.0/20
- 119.12.160.0/20
- 119.13.112.0/20
- 119.13.160.0/24
- 119.13.161.0/24
- 119.13.162.0/23
- 119.13.163.0/24
- 119.13.164.0/22
- 119.13.168.0/21
- 119.13.168.0/24
- 119.13.169.0/24
- 119.13.170.0/24
- 119.13.172.0/24
- 119.13.173.0/24
- 119.13.32.0/22
- 119.13.36.0/22
- 119.13.64.0/24
- 119.13.65.0/24
- 119.13.66.0/23
- 119.13.68.0/22
- 119.13.72.0/22
- 119.13.76.0/22
- 119.13.80.0/21
- 119.13.88.0/22
- 119.13.92.0/22
- 119.13.96.0/20
- 119.8.0.0/21
- 119.8.128.0/24
- 119.8.129.0/24
- 119.8.130.0/23
- 119.8.132.0/22
- 119.8.136.0/21
- 119.8.144.0/20
- 119.8.160.0/19
- 119.8.18.0/24
- 119.8.192.0/20
- 119.8.192.0/21
- 119.8.200.0/21
- 119.8.208.0/20
- 119.8.21.0/24
- 119.8.22.0/24
- 119.8.224.0/24
- 119.8.227.0/24
- 119.8.228.0/22
- 119.8.23.0/24
- 119.8.232.0/21
- 119.8.24.0/21
- 119.8.240.0/23
- 119.8.242.0/23
- 119.8.244.0/24
- 119.8.245.0/24
- 119.8.246.0/24
- 119.8.247.0/24
- 119.8.248.0/24
- 119.8.249.0/24
- 119.8.250.0/24
- 119.8.253.0/24
- 119.8.254.0/23
- 119.8.32.0/19
- 119.8.4.0/24
- 119.8.64.0/22
- 119.8.68.0/24
- 119.8.69.0/24
- 119.8.70.0/24
- 119.8.71.0/24
- 119.8.72.0/21
- 119.8.8.0/21
- 119.8.80.0/20
- 119.8.96.0/19
- 121.91.152.0/21
- 121.91.168.0/21
- 121.91.200.0/21
- 121.91.200.0/24
- 121.91.201.0/24
- 121.91.204.0/24
- 121.91.205.0/24
- 122.8.128.0/20
- 122.8.144.0/20
- 122.8.160.0/20
- 122.8.176.0/21
- 122.8.184.0/22
- 122.8.188.0/22
- 124.243.128.0/18
- 124.243.156.0/24
- 124.243.157.0/24
- 124.243.158.0/24
- 124.243.159.0/24
- 124.71.248.0/24
- 124.71.249.0/24
- 124.71.250.0/24
- 124.71.252.0/24
- 124.71.253.0/24
- 124.81.0.0/20
- 124.81.112.0/20
- 124.81.128.0/20
- 124.81.144.0/20
- 124.81.16.0/20
- 124.81.160.0/20
- 124.81.176.0/20
- 124.81.192.0/20
- 124.81.208.0/20
- 124.81.224.0/20
- 124.81.240.0/20
- 124.81.32.0/20
- 124.81.48.0/20
- 124.81.64.0/20
- 124.81.80.0/20
- 124.81.96.0/20
- 139.9.98.0/24
- 139.9.99.0/24
- 14.137.132.0/22
- 14.137.136.0/22
- 14.137.140.0/22
- 14.137.152.0/24
- 14.137.153.0/24
- 14.137.154.0/24
- 14.137.155.0/24
- 14.137.156.0/24
- 14.137.157.0/24
- 14.137.161.0/24
- 14.137.163.0/24
- 14.137.169.0/24
- 14.137.170.0/23
- 14.137.172.0/22
- 146.174.128.0/20
- 146.174.144.0/20
- 146.174.160.0/20
- 146.174.176.0/20
- 148.145.160.0/20
- 148.145.192.0/20
- 148.145.208.0/20
- 148.145.224.0/23
- 148.145.234.0/23
- 148.145.236.0/23
- 148.145.238.0/23
- 149.232.128.0/20
- 149.232.144.0/20
- 150.40.128.0/20
- 150.40.144.0/20
- 150.40.160.0/20
- 150.40.176.0/20
- 150.40.182.0/24
- 150.40.192.0/20
- 150.40.208.0/20
- 150.40.224.0/20
- 150.40.240.0/20
- 154.220.192.0/19
- 154.81.16.0/20
- 154.83.0.0/23
- 154.86.32.0/20
- 154.86.48.0/20
- 154.93.100.0/23
- 154.93.104.0/23
- 156.227.22.0/23
- 156.230.32.0/21
- 156.230.40.0/21
- 156.230.64.0/18
- 156.232.16.0/20
- 156.240.128.0/18
- 156.249.32.0/20
- 156.253.16.0/20
- 157.254.211.0/24
- 157.254.212.0/24
- 159.138.0.0/20
- 159.138.112.0/21
- 159.138.114.0/24
- 159.138.120.0/22
- 159.138.124.0/24
- 159.138.125.0/24
- 159.138.126.0/23
- 159.138.128.0/20
- 159.138.144.0/20
- 159.138.152.0/21
- 159.138.16.0/22
- 159.138.160.0/20
- 159.138.176.0/23
- 159.138.178.0/24
- 159.138.179.0/24
- 159.138.180.0/24
- 159.138.181.0/24
- 159.138.182.0/23
- 159.138.188.0/23
- 159.138.190.0/23
- 159.138.192.0/20
- 159.138.20.0/22
- 159.138.208.0/21
- 159.138.216.0/22
- 159.138.220.0/23
- 159.138.224.0/20
- 159.138.24.0/21
- 159.138.240.0/20
- 159.138.32.0/20
- 159.138.48.0/20
- 159.138.64.0/21
- 159.138.67.0/24
- 159.138.76.0/24
- 159.138.77.0/24
- 159.138.78.0/24
- 159.138.79.0/24
- 159.138.80.0/20
- 159.138.96.0/20
- 166.108.192.0/20
- 166.108.208.0/20
- 166.108.224.0/20
- 166.108.240.0/20
- 176.52.128.0/20
- 176.52.144.0/20
- 180.87.192.0/20
- 180.87.208.0/20
- 180.87.224.0/20
- 180.87.240.0/20
- 182.160.0.0/20
- 182.160.16.0/24
- 182.160.17.0/24
- 182.160.18.0/23
- 182.160.20.0/22
- 182.160.20.0/24
- 182.160.24.0/21
- 182.160.36.0/22
- 182.160.49.0/24
- 182.160.52.0/22
- 182.160.56.0/21
- 182.160.56.0/24
- 182.160.57.0/24
- 182.160.58.0/24
- 182.160.59.0/24
- 182.160.60.0/24
- 182.160.61.0/24
- 182.160.62.0/24
- 183.87.112.0/20
- 183.87.128.0/20
- 183.87.144.0/20
- 183.87.32.0/20
- 183.87.48.0/20
- 183.87.64.0/20
- 183.87.80.0/20
- 183.87.96.0/20
- 188.119.192.0/20
- 188.119.208.0/20
- 188.119.224.0/20
- 188.119.240.0/20
- 188.239.0.0/20
- 188.239.16.0/20
- 188.239.32.0/20
- 188.239.48.0/20
- 189.1.192.0/20
- 189.1.208.0/20
- 189.1.224.0/20
- 189.1.240.0/20
- 189.28.112.0/20
- 189.28.96.0/20
- 190.92.192.0/19
- 190.92.224.0/19
- 190.92.248.0/24
- 190.92.252.0/24
- 190.92.253.0/24
- 190.92.254.0/24
- 201.77.32.0/20
- 202.170.88.0/21
- 202.76.128.0/20
- 202.76.144.0/20
- 202.76.160.0/20
- 202.76.176.0/20
- 203.123.80.0/20
- 203.167.20.0/23
- 203.167.22.0/24
- 212.34.192.0/20
- 212.34.208.0/20
- 213.250.128.0/20
- 213.250.144.0/20
- 213.250.160.0/20
- 213.250.176.0/21
- 213.250.184.0/21
- 219.83.0.0/20
- 219.83.112.0/22
- 219.83.116.0/23
- 219.83.118.0/23
- 219.83.121.0/24
- 219.83.122.0/24
- 219.83.123.0/24
- 219.83.124.0/24
- 219.83.16.0/20
- 219.83.32.0/20
- 219.83.76.0/23
- 2404:a140:43::/48
- 2405:f080::/39
- 2405:f080:1::/48
- 2405:f080:1000::/39
- 2405:f080:1200::/39
- 2405:f080:1400::/48
- 2405:f080:1401::/48
- 2405:f080:1402::/48
- 2405:f080:1403::/48
- 2405:f080:1500::/40
- 2405:f080:1600::/48
- 2405:f080:1602::/48
- 2405:f080:1603::/48
- 2405:f080:1800::/39
- 2405:f080:1800::/44
- 2405:f080:1810::/48
- 2405:f080:1811::/48
- 2405:f080:1812::/48
- 2405:f080:1813::/48
- 2405:f080:1814::/48
- 2405:f080:1815::/48
- 2405:f080:1900::/40
- 2405:f080:1e02::/47
- 2405:f080:1e04::/47
- 2405:f080:1e06::/47
- 2405:f080:1e1e::/47
- 2405:f080:1e20::/47
- 2405:f080:200::/48
- 2405:f080:2000::/39
- 2405:f080:201::/48
- 2405:f080:202::/48
- 2405:f080:2040::/48
- 2405:f080:2200::/39
- 2405:f080:2280::/48
- 2405:f080:2281::/48
- 2405:f080:2282::/48
- 2405:f080:2283::/48
- 2405:f080:2284::/48
- 2405:f080:2285::/48
- 2405:f080:2286::/48
- 2405:f080:2287::/48
- 2405:f080:2288::/48
- 2405:f080:2289::/48
- 2405:f080:228a::/48
- 2405:f080:228b::/48
- 2405:f080:228c::/48
- 2405:f080:228d::/48
- 2405:f080:228e::/48
- 2405:f080:228f::/48
- 2405:f080:2400::/39
- 2405:f080:2600::/39
- 2405:f080:2800::/48
- 2405:f080:2a00::/48
- 2405:f080:2e00::/47
- 2405:f080:3000::/38
- 2405:f080:3000::/40
- 2405:f080:3100::/40
- 2405:f080:3200::/48
- 2405:f080:3201::/48
- 2405:f080:3202::/48
- 2405:f080:3203::/48
- 2405:f080:3204::/48
- 2405:f080:3205::/48
- 2405:f080:3400::/38
- 2405:f080:3400::/40
- 2405:f080:3500::/40
- 2405:f080:3600::/48
- 2405:f080:3601::/48
- 2405:f080:3602::/48
- 2405:f080:3603::/48
- 2405:f080:3604::/48
- 2405:f080:3605::/48
- 2405:f080:400::/39
- 2405:f080:4000::/40
- 2405:f080:4100::/48
- 2405:f080:4102::/48
- 2405:f080:4103::/48
- 2405:f080:4104::/48
- 2405:f080:4200::/40
- 2405:f080:4300::/40
- 2405:f080:600::/48
- 2405:f080:800::/40
- 2405:f080:810::/44
- 2405:f080:a00::/39
- 2405:f080:a11::/48
- 2405:f080:e02::/48
- 2405:f080:e03::/48
- 2405:f080:e04::/47
- 2405:f080:e05::/48
- 2405:f080:e06::/48
- 2405:f080:e07::/48
- 2405:f080:e0e::/47
- 2405:f080:e10::/47
- 2405:f080:edff::/48
- 27.106.0.0/20
- 27.106.112.0/20
- 27.106.16.0/20
- 27.106.32.0/20
- 27.106.48.0/20
- 27.106.64.0/20
- 27.106.80.0/20
- 27.106.96.0/20
- 27.255.0.0/23
- 27.255.10.0/23
- 27.255.12.0/23
- 27.255.14.0/23
- 27.255.16.0/23
- 27.255.18.0/23
- 27.255.2.0/23
- 27.255.20.0/23
- 27.255.22.0/23
- 27.255.26.0/23
- 27.255.28.0/23
- 27.255.30.0/23
- 27.255.32.0/23
- 27.255.34.0/23
- 27.255.36.0/23
- 27.255.38.0/23
- 27.255.4.0/23
- 27.255.40.0/23
- 27.255.42.0/23
- 27.255.44.0/23
- 27.255.46.0/23
- 27.255.48.0/23
- 27.255.50.0/23
- 27.255.52.0/23
- 27.255.54.0/23
- 27.255.58.0/23
- 27.255.6.0/23
- 27.255.60.0/23
- 27.255.62.0/23
- 27.255.8.0/23
- 42.201.128.0/20
- 42.201.144.0/20
- 42.201.160.0/20
- 42.201.176.0/20
- 42.201.192.0/20
- 42.201.208.0/20
- 42.201.224.0/20
- 42.201.240.0/20
- 43.225.140.0/22
- 43.255.104.0/22
- 45.194.104.0/21
- 45.199.144.0/22
- 45.202.128.0/19
- 45.202.160.0/20
- 45.202.176.0/21
- 45.202.184.0/21
- 45.203.40.0/21
- 46.250.160.0/20
- 46.250.176.0/20
- 49.0.192.0/21
- 49.0.200.0/21
- 49.0.224.0/22
- 49.0.228.0/22
- 49.0.232.0/21
- 49.0.240.0/20
- 62.245.0.0/20
- 62.245.16.0/20
- 80.238.128.0/22
- 80.238.132.0/22
- 80.238.136.0/22
- 80.238.140.0/22
- 80.238.144.0/22
- 80.238.148.0/22
- 80.238.152.0/22
- 80.238.156.0/22
- 80.238.164.0/22
- 80.238.164.0/24
- 80.238.165.0/24
- 80.238.168.0/22
- 80.238.168.0/24
- 80.238.169.0/24
- 80.238.170.0/24
- 80.238.171.0/24
- 80.238.172.0/22
- 80.238.176.0/22
- 80.238.180.0/24
- 80.238.181.0/24
- 80.238.183.0/24
- 80.238.184.0/24
- 80.238.185.0/24
- 80.238.186.0/24
- 80.238.190.0/24
- 80.238.192.0/20
- 80.238.208.0/20
- 80.238.224.0/20
- 80.238.240.0/20
- 83.101.0.0/21
- 83.101.104.0/21
- 83.101.16.0/21
- 83.101.24.0/21
- 83.101.32.0/21
- 83.101.48.0/21
- 83.101.56.0/23
- 83.101.58.0/23
- 83.101.64.0/21
- 83.101.72.0/21
- 83.101.8.0/23
- 83.101.80.0/21
- 83.101.88.0/24
- 83.101.89.0/24
- 83.101.96.0/21
- 87.119.12.0/24
- 89.150.192.0/20
- 89.150.208.0/20
- 94.244.128.0/20
- 94.244.144.0/20
- 94.244.160.0/20
- 94.244.176.0/20
- 94.45.160.0/19
- 94.45.160.0/24
- 94.45.161.0/24
- 94.45.163.0/24
- 94.74.112.0/21
- 94.74.120.0/21
- 94.74.64.0/20
- 94.74.80.0/20
- 94.74.96.0/20

View File

@@ -3,6 +3,6 @@ package data
import "embed"
var (
//go:embed botPolicies.yaml botPolicies.json all:apps all:bots all:clients all:common all:crawlers all:meta
//go:embed botPolicies.yaml all:apps all:bots all:clients all:common all:crawlers all:meta
BotPolicies embed.FS
)

View File

@@ -161,7 +161,7 @@ One of the first issues in Anubis before it was moved to the [TecharoHQ org](htt
When Anubis decides it needs to send a challenge to your browser, it sends a challenge page. Historically, this challenge page is [an HTML template](https://github.com/TecharoHQ/anubis/blob/main/web/index.templ) that kicks off some JavaScript, reads the challenge information out of the page body, and then solves it as fast as possible in order to let users see the website they want to visit.
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/category/challenges). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/admin/configuration/challenges/). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
In my testing, this has worked with every browser I have thrown it at (including CLI browsers, the browser embedded in emacs, etc.). The default configuration of Anubis does use the [meta refresh challenge](/docs/admin/configuration/challenges/metarefresh) for [clients with a very low suspicion](/docs/admin/configuration/thresholds), but by default clients will be sent an [easy proof of work challenge](/docs/admin/configuration/challenges/proof-of-work).

View File

@@ -213,7 +213,7 @@ When combined with [weight thresholds](/docs/admin/configuration/thresholds), th
## Challenge flow v2
The main goal of Anubis is to weigh the risks of incoming requests in order to protect upstream resources against abusive clients like badly written scrapers. In order to separate "good" clients (like users wanting to learn from a website's content) from "bad" clients, Anubis issues [challenges](/docs/category/challenges).
The main goal of Anubis is to weigh the risks of incoming requests in order to protect upstream resources against abusive clients like badly written scrapers. In order to separate "good" clients (like users wanting to learn from a website's content) from "bad" clients, Anubis issues [challenges](/docs/admin/configuration/challenges/).
Previously the Anubis challenge flow looked like this:

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,60 @@
---
slug: 2025/funding-update
title: Funding update
authors: [xe]
tags: [funding]
image: around-the-bend.webp
---
![](./around-the-bend.webp)
As we finish up work on [all of the features in the next release of Anubis](/docs/CHANGELOG#unreleased), I took a moment to add up the financials and here's an update on the recurring revenue of the project. Once I reach the [$5000 per month](https://github.com/TecharoHQ/anubis/discussions/278) mark, I can start reducing hours at my dayjob and start to make working on Anubis my full time job.
{/* truncate */}
Note that this only counts _recurring_ revenue (subscriptions to [BotStopper](/docs/admin/botstopper) and monthly repeating donations). Every one of the one-time donations I get is a gift and I am grateful for them, but I cannot make critically important financial decisions off of sporadic one-time donations.
:::note
All currency figures in this article are USD (United States Dollars) unless denoted otherwise.
:::
Here's the funding breakdown by income stream:
```mermaid
pie title Funding update August 2025
"GitHub Sponsors" : 3500
"Patreon" : 1500
"Liberapay" : 100
"Remaining" : 4800
```
Assuming that some of my private support contracts and other sales effort go through, this will slightly change the shapes of this (a new pie chart segment will emerge for "Manual invoices"), but I am halfway there. This is a huge bar to pass and as it stands right now this is just enough income to pay for my monthly rent (not accounting for tax).
As a reminder, here's the rough plan for the phases I want to hit based on the _recurring_ donation totals:
| Monthly donations | Details |
| :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| $0-5,000 per month | Anubis is a nights and weekends project based on how much spare time and energy I have. |
| $5,000-10,000 per month | Anubis gets 1-2 days per week of my time put into it consistently and I go part-time at my dayjob. |
| $10,000-15,000 per month | Anubis becomes my full time job. Features that are currently exclusive to [BotStopper](/docs/admin/botstopper/) start to trickle down to the open source version of Anubis. |
| $15,000 per month and above | I start planning hiring for Techaro. |
If your organization benefits from Anubis, please consider donating to the project in order to make this sustainable. The fewer financial problems I have means the more that Anubis can become better.
## New funding platform: Liberapay
After many comments about the funding options, I have set up [Liberapay](https://liberapay.com/Xe/) as an option to receive donations. Additional funding targets will be added to Liberapay as soon as I hear back from my accountant with more information. All money received via Liberapay goes directly towards supporting the project.
## Next goals
Here's my short term goals for the immediate future:
1. Finish [Thoth](/docs/admin/thoth/) and run a backfill to mass issue API keys.
2. Document and publish the writeup for the multi-region Google Cloud spot instance setup that Thoth is built upon.
3. Release v1.22.0 of Anubis with Traefik support and other important fixes.
4. Continue growing the project into a sustainable business.
5. Work through the [blog backlog](https://github.com/TecharoHQ/anubis/issues?q=is%3Aissue%20state%3Aopen%20label%3Ablog) to document the thoughts behind Anubis and how parts of it work.
Thank you for supporting Anubis! It's only going to get better from here.

View File

@@ -13,8 +13,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- This changes the project to: -->
- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
- The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
- [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)).
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.
- When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917))
- When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work.
- Proof of work solving has had a complete overhaul and rethink based on feedback from browser engine developers, frontend experts, and overall performance profiling.
- One of the biggest sources of lag in Firefox has been eliminated: the use of WebCrypto. Now whenever Anubis detects the client is using Firefox (or Pale Moon), it will swap over to a pure-JS implementation of SHA-256 for speed.
- Optimize the performance of the pure-JS Anubis solver.
- Web Workers are stored as dedicated JavaScript files in `static/js/workers/*.mjs`.
- Pave the way for non-SHA256 solver methods and eventually one that uses WebAssembly (or WebAssembly code compiled to JS for those that disable WebAssembly).
- Legacy JavaScript code has been eliminated.
- Add option for replacing the default explanation text with a custom one ([#747](https://github.com/TecharoHQ/anubis/pull/747))
- The contact email in the LibreJS header has been changed.
- The hard dependency on WebCrypto has been removed, allowing a proof of work challenge to work over plain (unencrypted) HTTP.
- Firefox for Android support has been fixed by embedding the challenge ID into the pass-challenge route. This also fixes some inconsistent issues with other mobile browsers.
- The Anubis version number is put in the footer of every page.
- The legacy JSON based policy file example has been removed and all documentation for how to write a policy file in JSON has been deleted. JSON based policy files will still work, but YAML is the superior option for Anubis configuration.
- A standard library HTTP server log message about HTTP pipelining not working has been filtered out of Anubis' logs. There is no action that can be taken about it.
- The default `favicon` pattern in `data/common/keep-internet-working.yaml` has been updated to permit requests for png/gif/jpg/svg files as well as ico.
- The `--cookie-prefix` flag has been fixed so that it is fully respected.
- The default patterns in `data/common/keep-internet-working.yaml` have been updated to appropriately escape the '.' character in the regular expression patterns.
- Add optional restrictions for JWT based on the value of a header ([#697](https://github.com/TecharoHQ/anubis/pull/697))
- The word "hack" has been removed from the translation strings for Anubis due to incidents involving people misunderstanding that word and sending particularly horrible things to the project lead over email.
- Bump AI-robots.txt to version 1.39
- Add a default block rule for Huawei Cloud.
### Security-relevant changes
#### Fix potential double-spend for challenges
Anubis operates by issuing a challenge and having the client present a solution for that challenge. Challenges are identified by a unique UUID, which is tored in the database.
The problem is that a challenge could potentially be used twice by a dedicated attacker making a targeted attack against Anubis. Challenge records did not have a "spent" or "used" field. In total, a dedicated attacker could solve a challenge once and reuse that solution across multiple sessions in order to mint additional tokens.
This was fixed by adding a "spent" field to challenges in the data store. When a challenge is solved, that "spent" field gets set to `true`. If a future attempt to solve this challenge is observed, it gets rejected.
With the advent of store based challenge issuance in [#749](https://github.com/TecharoHQ/anubis/pull/749), this means that these challenge IDs are [only good for 30 minutes](https://github.com/TecharoHQ/anubis/blob/e8dfff635015d6c906dddd49cb0eaf591326092a/lib/anubis.go#L130-L135d). Websites using the most recent version of Anubis have limited exposure to this problem.
Websites using older versions of Anubis have a much more increased exposure to this problem and are encouraged to keep this software updated as often and as frequently as possible.
Thanks to [@taviso](https://github.com/taviso) for reporting this issue.
### Breaking changes
- The "slow" frontend solver has been removed in order to reduce maintenance burden. Any existing uses of it will still work, but issue a warning upon startup asking administrators to upgrade to the "fast" frontend solver.
## v1.21.3: Minfilia Warde - Echo 3
### Added
#### New locales
Anubis now supports these new languages:
- [Swedish](https://github.com/TecharoHQ/anubis/pull/913)
### Fixes
#### Fixes a problem with nonstandard URLs and redirects
@@ -227,6 +282,7 @@ And some cleanups/refactors were added:
- Bump AI-robots.txt to version 1.37
- Make progress bar styling more compatible (UXP, etc)
- Add `--strip-base-prefix` flag/envvar to strip the base prefix from request paths when forwarding to target servers
- Added support to use Traefik forwardAuth middleware
- Fix an off-by-one in the default threshold config
- Add functionality for HS512 JWT algorithm
- Add support for dynamic cookie domains with the `--cookie-dynamic-domain`/`COOKIE_DYNAMIC_DOMAIN` flag/envvar

View File

@@ -1,12 +0,0 @@
---
title: Proof-of-Work Algorithm Selection
---
Anubis offers two proof-of-work algorithms:
- `"fast"`: highly optimized JavaScript that will run as fast as your computer lets it
- `"slow"`: intentionally slow JavaScript that will waste time and memory
The fast algorithm is used by default to limit impacts on users' computers. Administrators may configure individual bot policy rules to use the slow algorithm in order to make known malicious clients waitloop and do nothing useful.
Generally, you should use the fast algorithm unless you have a good reason not to.

View File

@@ -1,8 +1,5 @@
{
"label": "Challenges",
"position": 10,
"link": {
"type": "generated-index",
"description": "The different challenge methods that Anubis supports."
}
"link": null
}

View File

@@ -0,0 +1,8 @@
# Challenge Methods
Anubis supports multiple challenge methods:
- [Meta Refresh](./metarefresh.mdx)
- [Proof of Work](./proof-of-work.mdx)
Read the documentation to know which method is best for you.

View File

@@ -232,6 +232,39 @@ This is best applied when doing explicit block rules, eg:
It seems counter-intuitive to allow known bad clients through sometimes, but this allows you to confuse attackers by making Anubis' behavior random. Adjust the thresholds and numbers as facts and circumstances demand.
### `segments`
Available in `bot` expressions.
```ts
function segments(path: string): string[];
```
`segments` returns the number of slash-separated path segments, ignoring the leading slash. Here is what it will return with some common paths:
| Input | Output |
| :----------------------- | :--------------------- |
| `segments("/")` | `[""]` |
| `segments("/foo/bar")` | `["foo", "bar"] ` |
| `segments("/users/xe/")` | `["users", "xe", ""] ` |
:::note
If the path ends with a `/`, then the last element of the result will be an empty string. This is because `/users/xe` and `/users/xe/` are semantically different paths.
:::
This is useful if you want to write rules that allow requests that have no query parameters only if they have less than two path segments:
```yaml
- name: two-path-segments-no-query
action: ALLOW
expression:
all:
- size(query) == 0
- size(segments(path)) < 2
```
## Life advice
Expressions are very powerful. This is a benefit and a burden. If you are not careful with your expression targeting, you will be liable to get yourself into trouble. If you are at all in doubt, throw a `CHALLENGE` over a `DENY`. Legitimate users can easily work around a `CHALLENGE` result with a [proof of work challenge](../../design/why-proof-of-work.mdx). Bots are less likely to be able to do this.

View File

@@ -7,25 +7,6 @@ Anubis has the ability to let you import snippets of configuration into the main
EG:
<Tabs>
<TabItem value="json" label="JSON">
```json
{
"bots": [
{
"import": "(data)/bots/ai-catchall.yaml"
},
{
"import": "(data)/bots/cloudflare-workers.yaml"
}
]
}
```
</TabItem>
<TabItem value="yaml" label="YAML" default>
```yaml
bots:
# Pathological bots to deny
@@ -34,30 +15,8 @@ bots:
- import: (data)/bots/cloudflare-workers.yaml
```
</TabItem>
</Tabs>
Of note, a bot rule can either have inline bot configuration or import a bot config snippet. You cannot do both in a single bot rule.
<Tabs>
<TabItem value="json" label="JSON">
```json
{
"bots": [
{
"import": "(data)/bots/ai-catchall.yaml",
"name": "generic-browser",
"user_agent_regex": "Mozilla|Opera\n",
"action": "CHALLENGE"
}
]
}
```
</TabItem>
<TabItem value="yaml" label="YAML" default>
```yaml
bots:
- import: (data)/bots/ai-catchall.yaml
@@ -67,9 +26,6 @@ bots:
action: CHALLENGE
```
</TabItem>
</Tabs>
This will return an error like this:
```text
@@ -83,30 +39,11 @@ Paths can either be prefixed with `(data)` to import from the [the data folder i
You can also import from an imported file in case you want to import an entire folder of rules at once.
<Tabs>
<TabItem value="json" label="JSON">
```json
{
"bots": [
{
"import": "(data)/bots/_deny-pathological.yaml"
}
]
}
```
</TabItem>
<TabItem value="yaml" label="YAML" default>
```yaml
bots:
- import: (data)/bots/_deny-pathological.yaml
```
</TabItem>
</Tabs>
This lets you import an entire ruleset at once:
```yaml
@@ -124,22 +61,6 @@ Snippets can be written in either JSON or YAML, with a preference for YAML. When
Here is an example snippet that allows [IPv6 Unique Local Addresses](https://en.wikipedia.org/wiki/Unique_local_address) through Anubis:
<Tabs>
<TabItem value="json" label="JSON">
```json
[
{
"name": "ipv6-ula",
"action": "ALLOW",
"remote_addresses": ["fc00::/7"]
}
]
```
</TabItem>
<TabItem value="yaml" label="YAML" default>
```yaml
- name: ipv6-ula
action: ALLOW
@@ -147,9 +68,6 @@ Here is an example snippet that allows [IPv6 Unique Local Addresses](https://en.
- fc00::/7
```
</TabItem>
</Tabs>
## Extracting Anubis' embedded filesystem
You can always extract the list of rules embedded into the Anubis binary with this command:

View File

@@ -10,10 +10,6 @@ but it also applies to docker cli options.
:::
Currently, Anubis doesn't have any Traefik middleware,
so you need to manually route it between Traefik and your target service.
This routing is done per labels in Traefik.
In this example, we will use 4 Containers:
- `traefik` - the Traefik instance
@@ -21,12 +17,6 @@ In this example, we will use 4 Containers:
- `target` - our service to protect (`traefik/whoami` in this case)
- `target2` - a second service that isn't supposed to be protected (`traefik/whoami` in this case)
There are 3 steps we need to follow:
1. Create a new exclusive Traefik endpoint for Anubis
2. Pass all unspecified requests to Anubis
3. Let Anubis pass all verified requests back to Traefik on its exclusive endpoint
## Diagram of Flow
This is a small diagram depicting the flow.
@@ -40,74 +30,16 @@ anubis[Anubis]
target[Target]
user-->|:443 - Requesting Service|traefik
traefik-->|:8080 - Passing to Anubis|anubis
anubis-->|:3923 - Passing back to Traefik|traefik
traefik-->|:8080 - Check authorization to Anubis|anubis
anubis-->|redirect if failed|traefik
user-->|:8080 - make the challenge|traefik
anubis-->|redirect back to target|traefik
traefik-->|:80 - Passing to the target|target
```
## Create an Exclusive Anubis Endpoint in Traefik
There are 2 ways of registering a new endpoint in Traefik.
Which one to use depends on how you configured your Traefik so far.
**CLI Options:**
```yml
--entrypoints.anubis.address=:3923
```
**traefik.yml:**
```yml
entryPoints:
anubis:
address: ":3923"
```
It is important that the specified port isn't actually reachable from the outside,
but only exposed in the Docker network.
Exposing the Anubis port on Traefik directly will allow direct unprotected access to all containers behind it.
## Passing all unspecified Web Requests to Anubis
There are cases where you want Traefik to still route some requests without protection, just like before.
To achieve this, we can register Anubis as the default handler for non-protected requests.
We also don't want users to get SSL Errors during the checking phase,
thus we also need to let Traefik provide SSL Certs for our endpoint.
This example expects an TLS cert resolver called `le`.
We also expect there to be an endpoint called `websecure` for HTTPS in this example.
This is an example of the required labels to configure Traefik on the Anubis container:
```yml
labels:
- traefik.enable=true # Enabling Traefik
- traefik.docker.network=traefik # Telling Traefik which network to use
- traefik.http.routers.anubis.priority=1 # Setting Anubis to the lowest priority, so it only takes the slack
- traefik.http.routers.anubis.rule=PathRegexp(`.*`) # Wildcard match every path
- traefik.http.routers.anubis.entrypoints=websecure # Listen on HTTPS
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik to which port it should route requests
- traefik.http.routers.anubis.service=anubis # Telling Traefik to use the above specified port
- traefik.http.routers.anubis.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
```
## Passing all Verified Requests Back Correctly to Traefik
To pass verified requests back to Traefik,
we only need to configure Anubis using its environment variables:
```yml
environment:
- BIND=:8080
- TARGET=http://traefik:3923
```
## Full Example Config
Now that we know how to pass all requests back and forth, here is the example.
This example contains 2 services: one that is protected and the other one that is not.
This example contains 3 services: anubis, one that is protected and the other one that is not.
**compose.yml**
@@ -128,6 +60,8 @@ services:
# Enable Traefik
- traefik.enable=true
- traefik.docker.network=traefik
# Anubis middleware
- traefik.http.middlewares.anubis.forwardauth.address=http://anubis:8080/.within.website/x/cmd/anubis/api/check
# Redirect any HTTP to HTTPS
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
- traefik.http.routers.web.rule=PathPrefix(`/`)
@@ -140,17 +74,22 @@ services:
environment:
# Telling Anubis, where to listen for Traefik
- BIND=:8080
# Telling Anubis to point to Traefik via the Docker network
- TARGET=http://traefik:3923
# Telling Anubis to do redirect — ensure there is a space after '='
- 'TARGET= '
# Specifies which domains Anubis is allowed to redirect to.
- REDIRECT_DOMAINS=example.com
# Should be the full external URL for Anubis (including scheme)
- PUBLIC_URL=https://anubis.example.com
# Should match your domain for proper cookie scoping
- COOKIE_DOMAIN=example.com
networks:
- traefik
labels:
- traefik.enable=true # Enabling Traefik
- traefik.docker.network=traefik # Telling Traefik which network to use
- traefik.http.routers.anubis.priority=1 # Setting Anubis to the lowest priority, so it only takes the slack
- traefik.http.routers.anubis.rule=PathRegexp(`.*`) # wildcard match anything
- traefik.http.routers.anubis.rule=Host(`anubis.example.com`) # Only Matching Requests for example.com
- traefik.http.routers.anubis.entrypoints=websecure # Listen on HTTPS
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik to which port it should route requests
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik where to receive requests
- traefik.http.routers.anubis.service=anubis # Telling Traefik to use the above specified port
- traefik.http.routers.anubis.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
@@ -163,9 +102,11 @@ services:
- traefik.enable=true # Enabling Traefik
- traefik.docker.network=traefik # Telling Traefik which network to use
- traefik.http.routers.target.rule=Host(`example.com`) # Only Matching Requests for example.com
- traefik.http.routers.target.entrypoints=anubis # Listening on the exclusive Anubis Network
- traefik.http.routers.target.entrypoints=websecure # Listening on the exclusive Anubis Network
- traefik.http.services.target.loadbalancer.server.port=80 # Telling Traefik where to receive requests
- traefik.http.routers.target.service=target # Telling Traefik to use the above specified port
- traefik.http.routers.target.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
- traefik.http.routers.target.middlewares=anubis@docker # Use the Anubis middleware
# Not Protected by Anubis
target2:
@@ -175,7 +116,7 @@ services:
labels:
- traefik.enable=true # Enabling Traefik
- traefik.docker.network=traefik # Telling Traefik which network to use
- traefik.http.routers.target2.rule=Host(`another.com`) # Only Matching Requests for example.com
- traefik.http.routers.target2.rule=Host(`another.example.com`) # Only Matching Requests for example.com
- traefik.http.routers.target2.entrypoints=websecure # Listening on the exclusive Anubis Network
- traefik.http.services.target2.loadbalancer.server.port=80 # Telling Traefik where to receive requests
- traefik.http.routers.target2.service=target2 # Telling Traefik to use the above specified port
@@ -198,9 +139,6 @@ entryPoints:
address: ":80"
websecure:
address: ":443"
# Anubis
anubis:
address: ":3923"
certificatesResolvers:
le:

View File

@@ -7,27 +7,6 @@ import TabItem from "@theme/TabItem";
To work around this, you can make a custom [expression](../configuration/expressions.mdx) rule that allows HTMX requests if the user has passed a challenge in the past:
<Tabs>
<TabItem value="json" label="JSON">
```json
{
"name": "allow-htmx-iff-already-passed-challenge",
"action": "ALLOW",
"expression": {
"all": [
"\"Cookie\" in headers",
"headers[\"Cookie\"].contains(\"anubis-auth\")",
"\"Hx-Request\" in headers",
"headers[\"Hx-Request\"] == \"true\""
]
}
}
```
</TabItem>
<TabItem value="yaml" label="YAML" default>
```yaml
- name: allow-htmx-iff-already-passed-challenge
action: ALLOW
@@ -39,7 +18,4 @@ To work around this, you can make a custom [expression](../configuration/express
- 'headers["Hx-Request"] == "true"'
```
</TabItem>
</Tabs>
This will reduce some security because it does not assert the validity of the Anubis auth cookie, however in trade it improves the experience for existing users.

View File

@@ -69,20 +69,23 @@ Anubis uses these environment variables for configuration:
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
| `COOKIE_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. 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` | 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. |
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
| `PUBLIC_URL` | unset | The externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for Traefik forwardAuth). |
| `REDIRECT_DOMAINS` | unset | If set, restrict the domains that Anubis can redirect to when passing a challenge.<br/><br/>If this is unset, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.<br/><br/>Note that if you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here. |
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. |
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
| `USE_SIMPLIFIED_EXPLANATION` | false | If set to `true`, replaces the text when clicking "Why am I seeing this?" with a more simplified text for a non-tech-savvy audience. |
| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. |

View File

@@ -7,6 +7,10 @@ import TabItem from "@theme/TabItem";
Out of the box, Anubis is pretty heavy-handed. It will aggressively challenge everything that might be a browser (usually indicated by having `Mozilla` in its user agent). However, some bots are smart enough to get past the challenge. Some things that look like bots may actually be fine (IE: RSS readers). Some resources need to be visible no matter what. Some resources and remotes are fine to begin with.
Anubis lets you customize its configuration with a Policy File. This is a YAML document that spells out what actions Anubis should take when evaluating requests. The [default configuration](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml) explains everything, but this page contains an overview of everything you can do with it.
## Bot Policies
Bot policies let you customize the rules that Anubis uses to allow, deny, or challenge incoming requests. Currently you can set policies by the following matches:
- Request path
@@ -18,75 +22,18 @@ As of version v1.17.0 or later, configuration can be written in either JSON or Y
Here's an example rule that denies [Amazonbot](https://developer.amazon.com/en/amazonbot):
<Tabs>
<TabItem value="json" label="JSON" default>
```json
{
"name": "amazonbot",
"user_agent_regex": "Amazonbot",
"action": "DENY"
}
```
</TabItem>
<TabItem value="yaml" label="YAML">
```yaml
- name: amazonbot
user_agent_regex: Amazonbot
action: DENY
```
</TabItem>
</Tabs>
When this rule is evaluated, Anubis will check the `User-Agent` string of the request. If it contains `Amazonbot`, Anubis will send an error page to the user saying that access is denied, but in such a way that makes scrapers think they have correctly loaded the webpage.
Right now the only kinds of policies you can write are bot policies. Other forms of policies will be added in the future.
Here is a minimal policy file that will protect against most scraper bots:
<Tabs>
<TabItem value="json" label="JSON" default>
```json
{
"bots": [
{
"name": "cloudflare-workers",
"headers_regex": {
"CF-Worker": ".*"
},
"action": "DENY"
},
{
"name": "well-known",
"path_regex": "^/.well-known/.*$",
"action": "ALLOW"
},
{
"name": "favicon",
"path_regex": "^/favicon.ico$",
"action": "ALLOW"
},
{
"name": "robots-txt",
"path_regex": "^/robots.txt$",
"action": "ALLOW"
},
{
"name": "generic-browser",
"user_agent_regex": "Mozilla",
"action": "CHALLENGE"
}
]
}
```
</TabItem>
<TabItem value="yaml" label="YAML">
```yaml
bots:
- name: cloudflare-workers
@@ -107,22 +54,20 @@ bots:
action: CHALLENGE
```
</TabItem>
</Tabs>
This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.json) is a bit more cohesive, but this should be more than enough for most users.
If no rules match the request, it is allowed through. For more details on this default behavior and its implications, see [Default allow behavior](./default-allow-behavior.mdx).
## Writing your own rules
### Writing your own rules
There are three actions that can be returned from a rule:
There are four actions that can be returned from a rule:
| Action | Effects |
| :---------- | :-------------------------------------------------------------------------------- |
| `ALLOW` | Bypass all further checks and send the request to the backend. |
| `DENY` | Deny the request and send back an error message that scrapers think is a success. |
| `CHALLENGE` | Show a challenge page and/or validate that clients have passed a challenge. |
| Action | Effects |
| :---------- | :---------------------------------------------------------------------------------------------------------------------------------- |
| `ALLOW` | Bypass all further checks and send the request to the backend. |
| `DENY` | Deny the request and send back an error message that scrapers think is a success. |
| `CHALLENGE` | Show a challenge page and/or validate that clients have passed a challenge. |
| `WEIGH` | Change the [request weight](#request-weight) for this request. See the [request weight](#request-weight) docs for more information. |
Name your rules in lower case using kebab-case. Rule names will be exposed in Prometheus metrics.
@@ -130,27 +75,6 @@ Name your rules in lower case using kebab-case. Rule names will be exposed in Pr
Rules can also have their own challenge settings. These are customized using the `"challenge"` key. For example, here is a rule that makes challenges artificially hard for connections with the substring "bot" in their user agent:
<Tabs>
<TabItem value="json" label="JSON" default>
This rule has been known to have a high false positive rate in testing. Please use this with care.
```json
{
"name": "generic-bot-catchall",
"user_agent_regex": "(?i:bot|crawler)",
"action": "CHALLENGE",
"challenge": {
"difficulty": 16,
"report_as": 4,
"algorithm": "slow"
}
}
```
</TabItem>
<TabItem value="yaml" label="YAML">
This rule has been known to have a high false positive rate in testing. Please use this with care.
```yaml
@@ -164,16 +88,13 @@ This rule has been known to have a high false positive rate in testing. Please u
algorithm: slow # intentionally waste CPU cycles and time
```
</TabItem>
</Tabs>
Challenges can be configured with these settings:
| Key | Example | Description |
| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `difficulty` | `4` | The challenge difficulty (number of leading zeros) for proof-of-work. See [Why does Anubis use Proof-of-Work?](/docs/design/why-proof-of-work) for more details. |
| `report_as` | `4` | What difficulty the UI should report to the user. Useful for messing with industrial-scale scraping efforts. |
| `algorithm` | `"fast"` | The algorithm used on the client to run proof-of-work calculations. This must be set to `"fast"` or `"slow"`. See [Proof-of-Work Algorithm Selection](./algorithm-selection) for more details. |
| Key | Example | Description |
| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `difficulty` | `4` | The challenge difficulty (number of leading zeros) for proof-of-work. See [Why does Anubis use Proof-of-Work?](/docs/design/why-proof-of-work) for more details. |
| `report_as` | `4` | What difficulty the UI should report to the user. Useful for messing with industrial-scale scraping efforts. |
| `algorithm` | `"fast"` | The challenge method to use. See [the list of challenge methods](./configuration/challenges/) for more information. |
### Remote IP based filtering
@@ -181,21 +102,6 @@ The `remote_addresses` field of a Bot rule allows you to set the IP range that t
For example, you can allow a search engine to connect if and only if its IP address matches the ones they published:
<Tabs>
<TabItem value="json" label="JSON" default>
```json
{
"name": "qwantbot",
"user_agent_regex": "\\+https\\:\\/\\/help\\.qwant\\.com/bot/",
"action": "ALLOW",
"remote_addresses": ["91.242.162.0/24"]
}
```
</TabItem>
<TabItem value="yaml" label="YAML">
```yaml
- name: qwantbot
user_agent_regex: \+https\://help\.qwant\.com/bot/
@@ -204,25 +110,8 @@ For example, you can allow a search engine to connect if and only if its IP addr
remote_addresses: ["91.242.162.0/24"]
```
</TabItem>
</Tabs>
This also works at an IP range level without any other checks:
<Tabs>
<TabItem value="json" label="JSON" default>
```json
{
"name": "internal-network",
"action": "ALLOW",
"remote_addresses": ["100.64.0.0/10"]
}
```
</TabItem>
<TabItem value="yaml" label="YAML">
```yaml
name: internal-network
action: ALLOW
@@ -230,9 +119,6 @@ remote_addresses:
- 100.64.0.0/10
```
</TabItem>
</Tabs>
## Imprint / Impressum support
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.

View File

@@ -102,18 +102,6 @@ When a client passes a challenge, Anubis sets an HTTP cookie named `"techaro.lol
This ensures that the token has enough metadata to prove that the token is valid (due to the token's signature), but also so that the server can independently prove the token is valid. This cookie is allowed to be set without triggering an EU cookie banner notification; but depending on facts and circumstances, you may wish to disclose this to your users.
## Challenge format
Challenges are formed by taking some user request metadata and using that to generate a SHA-256 checksum. The following request headers are used:
- `Accept-Encoding`: The content encodings that the requestor supports, such as gzip.
- `X-Real-Ip`: The IP address of the requestor, as set by a reverse proxy server.
- `User-Agent`: The user agent string of the requestor.
- The current time in UTC rounded to the nearest week.
- The fingerprint (checksum) of Anubis' private ED25519 key.
This forms a fingerprint of the requestor using metadata that any requestor already is sending. It also uses time as an input, which is known to both the server and requestor due to the nature of linear timelines. Depending on facts and circumstances, you may wish to disclose this to your users.
## JWT signing
Anubis uses an ed25519 keypair to sign the JWTs issued when challenges are passed. Anubis will generate a new ed25519 keypair every time it starts. At this time, there is no way to share this keypair between instance of Anubis, but that will be addressed in future versions.

View File

@@ -58,6 +58,7 @@ This page contains a non-exhaustive list with all websites using Anubis.
- https://rpmfusion.org/
- https://wiki.freepascal.org/
- https://azurlane.koumakan.jp/
- https://lab.civicrm.org/
- <details>
<summary>FreeCAD</summary>
- https://forum.freecad.org/
@@ -112,4 +113,8 @@ This page contains a non-exhaustive list with all websites using Anubis.
- https://bbs.archlinux32.org/
- https://bugs.archlinux32.org/
</details>
- <details>
<summary>HackLab.TO</summary>
- https://hacklab.to/
- https://knowledge.hacklab.to/
</details>

View File

@@ -73,7 +73,7 @@ dnsbl: false
impressum:
footer: |
This website is hosted by Techaro. If you have any complaints or notes about the service, please contact <a href="mailto:contact@techaro.lol">contact@techaro.lol</a> and we will assist you as soon as possible.
This website is hosted by Techaro. If you have any complaints or notes about the service, please contact <a href="mailto:support@techaro.lol">support@techaro.lol</a> and we will assist you as soon as possible.
page:
title: Privacy Policy

20
docs/package-lock.json generated
View File

@@ -5908,9 +5908,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -6496,16 +6496,16 @@
}
},
"node_modules/compression": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.0.2",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
@@ -13562,9 +13562,9 @@
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"

126
go.mod
View File

@@ -4,12 +4,12 @@ go 1.24.2
require (
github.com/TecharoHQ/thoth-proto v0.4.0
github.com/a-h/templ v0.3.906
github.com/a-h/templ v0.3.924
github.com/cespare/xxhash/v2 v2.3.0
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
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/gaissmai/bart v0.23.0
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/cel-go v0.26.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
@@ -21,162 +21,164 @@ require (
github.com/redis/go-redis/v9 v9.11.0
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/shirou/gopsutil/v4 v4.25.6
github.com/testcontainers/testcontainers-go v0.37.0
github.com/testcontainers/testcontainers-go v0.38.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
google.golang.org/grpc v1.74.2
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.33.2
sigs.k8s.io/yaml v1.5.0
k8s.io/apimachinery v0.33.3
sigs.k8s.io/yaml v1.6.0
)
require (
al.essio.dev/pkg/shellescape v1.6.0 // indirect
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 // indirect
cel.dev/expr v0.23.1 // indirect
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717185734-6c6e0d3c608e.1 // indirect
cel.dev/expr v0.24.0 // 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/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // 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
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.2.0 // indirect
github.com/Songmu/gitconfig v0.2.0 // indirect
github.com/TecharoHQ/yeet v0.6.0 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/Songmu/gitconfig v0.2.1 // indirect
github.com/TecharoHQ/yeet v0.6.3 // indirect
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
github.com/cli/go-gh v0.1.0 // indirect
github.com/cli/go-gh/v2 v2.12.1 // indirect
github.com/cli/safeexec v1.0.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // 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/cpuguy83/go-md2man/v2 v2.0.7 // 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/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/docker v28.3.2+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/dop251/goja v0.0.0-20250630131328-58d95d85e994 // 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/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fsnotify/fsnotify v1.9.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-git/go-git/v5 v5.16.2 // 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-ole/go-ole v1.3.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+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/goccy/go-yaml v1.18.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/goreleaser/chglog v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/rpmpack v0.7.1 // indirect
github.com/goreleaser/chglog v0.7.3 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/goreleaser/nfpm/v2 v2.42.1 // indirect
github.com/goreleaser/nfpm/v2 v2.43.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
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/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // 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/go-archive v0.1.0 // 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/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/moby/term v0.5.2 // 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/pjbgf/sha1cd v0.4.0 // 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
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
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/spf13/cast v1.9.2 // indirect
github.com/stoewer/go-strcase v1.3.1 // 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/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // 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/contrib/instrumentation/net/http/otelhttp v0.62.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
go.yaml.in/yaml/v3 v3.0.4 // 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/exp v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/exp/typeparams v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/oauth2 v0.30.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/telemetry v0.0.0-20250721140356-96f361d9aaf7 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/tools v0.35.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-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // 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
mvdan.cc/sh/v3 v3.11.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
mvdan.cc/sh/v3 v3.12.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
)
tool (

314
go.sum
View File

@@ -1,53 +1,54 @@
al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717185734-6c6e0d3c608e.1 h1:Lg6klmCi3v7VvpqeeLEER9/m5S8y9e9DjhqQnSCNy4k=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250717185734-6c6e0d3c608e.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/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/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/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/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/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=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
github.com/Songmu/gitconfig v0.2.0 h1:pX2++u4KUq+K2k/ZCzGXLtkD3ceCqIdi0tDyb+IbSyo=
github.com/Songmu/gitconfig v0.2.0/go.mod h1:cB5bYJer+pl7W8g6RHFwL/0X6aJROVrYuHlvc7PT+hE=
github.com/ProtonMail/gopenpgp/v3 v3.3.0 h1:N6rHCH5PWwB6zSRMgRj1EbAMQHUAAHxH3Oo4KibsPwY=
github.com/ProtonMail/gopenpgp/v3 v3.3.0/go.mod h1:J+iNPt0/5EO9wRt7Eit9dRUlzyu3hiGX3zId6iuaKOk=
github.com/Songmu/gitconfig v0.2.1 h1:cZsqELfMtxWVI8ovq17gbvsR4qLfoYLAiXy5GwtJWbk=
github.com/Songmu/gitconfig v0.2.1/go.mod h1:XM4O3SoXFnli9Ql2G7qXK2Fg7LJwf7Hs8GLFEOJlzmM=
github.com/TecharoHQ/thoth-proto v0.4.0 h1:UbkvfgCku0Dm1R6O4ug3HOsJNnE6F3wB8x+Dpw2lzFI=
github.com/TecharoHQ/thoth-proto v0.4.0/go.mod h1:IcGnZt3iYUZQVEa0Lwk5l4ix0hCeXlWUV1TJMZvbWx0=
github.com/TecharoHQ/yeet v0.6.0 h1:RCBAjr7wIlllsgy0tpvWpLX7jsZgu2tiuBY3RrprcR0=
github.com/TecharoHQ/yeet v0.6.0/go.mod h1:bj2V4Fg8qKQXoiuPZa3HuawrE8g+LsOQv/9q2WyGSsA=
github.com/TecharoHQ/yeet v0.6.3 h1:Iev6TYt/tpFYU73kbkNIYjCObYTvlihtby+htGF4Us8=
github.com/TecharoHQ/yeet v0.6.3/go.mod h1:ltt+PWPjnvmQJxEHsdJ5K9u3GoWK83vSLWCCp8XbxqI=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
github.com/a-h/templ v0.3.906 h1:ZUThc8Q9n04UATaCwaG60pB1AqbulLmYEAMnWV63svg=
github.com/a-h/templ v0.3.906/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/a-h/templ v0.3.924 h1:t5gZqTneXqvehpNZsgtnlOscnBboNh9aASBH2MgV/0k=
github.com/a-h/templ v0.3.924/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -68,23 +69,26 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI=
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
github.com/cli/go-gh v0.1.0 h1:kMqFmC3ECBrV2UKzlOHjNOTTchExVc5tjNHtCqk/zYk=
github.com/cli/go-gh v0.1.0/go.mod h1:eTGWl99EMZ+3Iau5C6dHyGAJRRia65MtdBtuhWc+84o=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/cli/shurcooL-graphql v0.0.1/go.mod h1:U7gCSuMZP/Qy7kbqkk5PrqXEeDgtfG5K+W+u8weorps=
github.com/cli/go-gh/v2 v2.12.1 h1:SVt1/afj5FRAythyMV3WJKaUfDNsxXTIe7arZbwTWKA=
github.com/cli/go-gh/v2 v2.12.1/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw=
github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
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/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
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/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
@@ -99,16 +103,16 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/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/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/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/dop251/goja v0.0.0-20250630131328-58d95d85e994 h1:aQYWswi+hRL2zJqGacdCZx32XjKYV8ApXFGntw79XAM=
github.com/dop251/goja v0.0.0-20250630131328-58d95d85e994/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=
@@ -123,18 +127,16 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
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.5 h1:ehoWZWQ7j//qt0K0Zs4i9hpoPpbgqsMQiR8W2QPJh+c=
github.com/gaissmai/bart v0.20.5/go.mod h1:cEed+ge8dalcbpi8wtS9x9m2hn/fNJH5suhdGQOHnYk=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gaissmai/bart v0.23.0 h1:ct+78nySK5MaO+citQAUeef7QZ0ApXM3b+AYuCZYGIk=
github.com/gaissmai/bart v0.23.0/go.mod h1:RpLtt3lWq1BoRz3AAyDAJ7jhLWBkYhVCfi+ximB2t68=
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=
@@ -143,8 +145,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -152,41 +154,32 @@ 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=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
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/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
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-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
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=
@@ -194,34 +187,32 @@ github.com/google/go-github/v70 v70.0.0 h1:/tqCp5KPrcvqCc7vIvYyFYTiCGrYvaWoYMGHS
github.com/google/go-github/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20250405124433-758cc6896cbc h1:qES+d3PvR9CN+zARQQH/bNXH0ybzmdjNMHICrBwXD28=
github.com/google/rpmpack v0.6.1-0.20250405124433-758cc6896cbc/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/rpmpack v0.7.1 h1:YdWh1IpzOjBz60Wvdw0TU0A5NWP+JTVHA5poDqwMO2o=
github.com/google/rpmpack v0.7.1/go.mod h1:h1JL16sUTWCLI/c39ox1rDaTBo3BXUQGjczVJyK4toU=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/goreleaser/chglog v0.7.0 h1:/KzXWAeg4DrEz4r3OI6K2Yb8RAsVGeInCUfLWFXL9C0=
github.com/goreleaser/chglog v0.7.0/go.mod h1:2h/yyq9xvTUeM9tOoucBP+jri8Dj28splx+SjlYkklc=
github.com/goreleaser/chglog v0.7.3 h1:eCKJrvsDgG+F1F2fhwM6qX+S5yMiZgsQ4VNTPFl9qEM=
github.com/goreleaser/chglog v0.7.3/go.mod h1:HXPf4avc1kTD00a46LuTEH0i1dZctLq8Xs2BxUfROnY=
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
github.com/goreleaser/nfpm/v2 v2.42.1 h1:xu2pLRgQuz2ab+YZFoeIzwU/M5jjjCKDGwv1lRbVGvk=
github.com/goreleaser/nfpm/v2 v2.42.1/go.mod h1:dY53KWYKebkOocxgkmpM7SRX0Nv5hU+jEu2kIaM4/LI=
github.com/goreleaser/nfpm/v2 v2.43.0 h1:o5oureuZkhu55RK0M9WSN8JLW7hu6MymtMh7LypInlk=
github.com/goreleaser/nfpm/v2 v2.43.0/go.mod h1:f//PE8PjNHjaPCbd7Jkok+aPKdLTrzM+fuIWg3PfVRg=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
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=
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -249,27 +240,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
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/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
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=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
@@ -278,23 +260,26 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
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/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
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/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
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/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
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=
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
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=
@@ -303,8 +288,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
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/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/playwright-community/playwright-go v0.5200.0 h1:z/5LGuX2tBrg3ug1HupMXLjIG93f1d2MWdDsNhkMQ9c=
@@ -312,19 +297,18 @@ 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/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=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
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/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
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=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -333,8 +317,8 @@ github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtC
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
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/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/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=
@@ -350,10 +334,10 @@ github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sS
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
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=
@@ -373,23 +357,24 @@ 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/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
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=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
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/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
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=
@@ -401,8 +386,8 @@ 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/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/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
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=
@@ -413,16 +398,16 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd
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/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
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=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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=
@@ -431,16 +416,16 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
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/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/exp/typeparams v0.0.0-20250718183923-645b1fa84792 h1:54/e+WfmhvjR2Zuz8Q7dzLGxIBM+s5WZpvo1QfVDGB8=
golang.org/x/exp/typeparams v0.0.0-20250718183923-645b1fa84792/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
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/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
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=
@@ -452,8 +437,8 @@ 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.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/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
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=
@@ -465,32 +450,25 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
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=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.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/telemetry v0.0.0-20250721140356-96f361d9aaf7 h1:Z53b3vgJH20Us6ljHm4MNVLnJzJEjD3KrU+sNcT4vfs=
golang.org/x/telemetry v0.0.0-20250721140356-96f361d9aaf7/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -499,7 +477,6 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
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=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -516,31 +493,30 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
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=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
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-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/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 h1:mVXdvnmR3S3BQOqHECm9NGMjYiRtEvDYcqAqedTXY6s=
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:vYFwMYFbmA8vl6Z/krj/h7+U/AqpHknwJX4Uqgfyc7I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -549,19 +525,19 @@ 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=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
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=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
pault.ag/go/debian v0.18.0 h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ=
pault.ag/go/debian v0.18.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE=
pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -153,7 +153,7 @@ func computeXFFHeader(remoteAddr string, origXFFHeader string, pref XFFComputePr
// generally they'd be expected to do these two things on
// their own end to find the first non-spoofed IP
for i := len(origForwardedList) - 1; i >= 0; i-- {
segmentIP, err := netip.ParseAddr(origForwardedList[i])
segmentIP, err := netip.ParseAddr(strings.TrimSpace(origForwardedList[i]))
if err != nil {
// can't assess this element, so the remainder of the chain
// can't be trusted. not a fatal error, since anyone can

View File

@@ -26,8 +26,11 @@ func InitSlog(level string) {
slog.SetDefault(slog.New(h))
}
func GetRequestLogger(r *http.Request) *slog.Logger {
return slog.With(
func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger {
return base.With(
"host", r.Host,
"method", r.Method,
"path", r.URL.Path,
"user_agent", r.UserAgent(),
"accept_language", r.Header.Get("Accept-Language"),
"priority", r.Header.Get("Priority"),
@@ -47,6 +50,9 @@ func (elf *ErrorLogFilter) Write(p []byte) (n int, err error) {
if strings.Contains(logMessage, "context canceled") {
return len(p), nil // Suppress the log by doing nothing
}
if strings.Contains(logMessage, "Unsolicited response received on idle HTTP channel") {
return len(p), nil
}
if elf.Unwrap != nil {
return elf.Unwrap.Writer().Write(p)
}

View File

@@ -5,7 +5,9 @@ import (
"errors"
"log/slog"
"net/url"
"strings"
"syscall"
"time"
)
// GetOGTags is the main function that retrieves Open Graph tags for a URL
@@ -45,6 +47,18 @@ func (c *OGTagCache) GetOGTags(ctx context.Context, url *url.URL, originalHost s
// Store in cache
c.cache.Set(ctx, cacheKey, ogTags, c.ogTimeToLive)
for k, v := range ogTags {
switch {
case strings.HasSuffix(k, "image"), strings.HasSuffix(k, "audio"), strings.HasSuffix(k, "secure_url"), strings.HasSuffix(k, "video"):
v, _ = strings.CutPrefix(v, "http://")
v, _ = strings.CutPrefix(v, "https://")
slog.Debug("setting ogtags allow for", "url", k)
if err := c.cache.Underlying.Set(ctx, "ogtags:allow:"+v, []byte(k), time.Hour); err != nil {
slog.Debug("can't set ogtag allow cache", "err", err)
}
}
}
return ogTags, nil
}

View File

@@ -1,6 +1,7 @@
package ogtags
import (
"errors"
"net/http"
"net/http/httptest"
"net/url"
@@ -9,6 +10,7 @@ import (
"time"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
@@ -166,8 +168,13 @@ func TestGetOGTags(t *testing.T) {
if !ok || initialValue != cachedValue {
t.Errorf("Cache does not line up: expected %s: %s, got: %s", key, initialValue, cachedValue)
}
}
t.Run("ensure image is cached as allow", func(t *testing.T) {
if _, err := cache.cache.Underlying.Get(t.Context(), "ogtags:allow:example.com/image.jpg"); errors.Is(err, store.ErrNotFound) {
t.Fatal("ogtags allow caching for example.com/image.jpg did not work")
}
})
}
// TestGetOGTagsWithHostConsideration tests the behavior of the cache with and without host consideration and for multiple hosts in a theoretical setup.

View File

@@ -75,6 +75,7 @@ type Server struct {
hs512Secret []byte
opts Options
store store.Interface
logger *slog.Logger
}
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
@@ -90,41 +91,34 @@ func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
}
}
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)
}
func (s *Server) getChallenge(r *http.Request) (*challenge.Challenge, error) {
id := r.FormValue("id")
j := store.JSON[challenge.Challenge]{Underlying: s.store}
ckie := ckies[0]
chall, err := j.Get(r.Context(), "challenge:"+ckie.Value)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return s.issueChallenge(r.Context(), r)
}
chall, err := j.Get(r.Context(), "challenge:"+id)
return nil, err
}
return &chall, nil
return &chall, err
}
func (s *Server) issueChallenge(ctx context.Context, r *http.Request) (*challenge.Challenge, error) {
func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.Logger, cr policy.CheckResult, rule *policy.Bot) (*challenge.Challenge, error) {
if cr.Rule != config.RuleChallenge {
slog.Error("this should be impossible, asked to issue a challenge but the rule is not a challenge rule", "cr", cr, "rule", rule)
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
}
id, err := uuid.NewV7()
if err != nil {
return nil, err
}
var randomData = make([]byte, 256)
var randomData = make([]byte, 64)
if _, err := rand.Read(randomData); err != nil {
return nil, err
}
chall := challenge.Challenge{
ID: id.String(),
Method: rule.Challenge.Algorithm,
RandomData: fmt.Sprintf("%x", randomData),
IssuedAt: time.Now(),
Metadata: map[string]string{
@@ -138,6 +132,8 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request) (*challeng
return nil, err
}
lg.Info("new challenge issued", "challenge", id.String())
return &chall, err
}
@@ -150,7 +146,13 @@ func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request)
}
func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
lg := internal.GetRequestLogger(r)
lg := internal.GetRequestLogger(s.logger, r)
if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil {
lg.Debug("serving opengraph tag asset")
s.ServeHTTPNext(w, r)
return
}
// Adjust cookie path if base prefix is not empty
cookiePath := "/"
@@ -158,7 +160,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
}
cr, rule, err := s.check(r)
cr, rule, err := s.check(r, lg)
if err != nil {
lg.Error("check failed", "err", err)
localizer := localization.GetLocalizer(r)
@@ -185,21 +187,21 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
if err != nil {
lg.Debug("cookie not found", "path", r.URL.Path)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
if err := ckie.Valid(); err != nil {
lg.Debug("cookie is invalid", "err", err)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
lg.Debug("cookie expired", "path", r.URL.Path)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
@@ -208,7 +210,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
if err != nil || !token.Valid {
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
@@ -216,7 +218,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
if !ok {
lg.Debug("invalid token claims type", "path", r.URL.Path)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
@@ -224,14 +226,21 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
if !ok {
lg.Debug("policyRule claim is not a string")
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
if policyRule != rule.Hash() {
lg.Debug("user originally passed with a different rule, issuing new challenge", "old", policyRule, "new", rule.Name)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, rule, httpStatusOnly)
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
if s.opts.JWTRestrictionHeader != "" && claims["restriction"] != internal.SHA256sum(r.Header.Get(s.opts.JWTRestrictionHeader)) {
lg.Debug("JWT restriction header is invalid")
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.RenderIndex(w, r, cr, rule, httpStatusOnly)
return
}
@@ -274,7 +283,7 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
return true
default:
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
lg.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.Rules\"", localizer.T("internal_server_error")))
return true
}
@@ -310,7 +319,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
}
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
lg := internal.GetRequestLogger(r)
lg := internal.GetRequestLogger(s.logger, r)
localizer := localization.GetLocalizer(r)
redir := r.FormValue("redir")
@@ -329,7 +338,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
r.URL.Path = redir
encoder := json.NewEncoder(w)
cr, rule, err := s.check(r)
cr, rule, err := s.check(r, lg)
if err != nil {
lg.Error("check failed", "err", err)
w.WriteHeader(http.StatusInternalServerError)
@@ -346,7 +355,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
}
lg = lg.With("check_result", cr)
chall, err := s.challengeFor(r)
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
if err != nil {
lg.Error("failed to fetch or issue challenge", "err", err)
w.WriteHeader(http.StatusInternalServerError)
@@ -367,9 +376,11 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
err = encoder.Encode(struct {
Rules *config.ChallengeRules `json:"rules"`
Challenge string `json:"challenge"`
ID string `json:"id"`
}{
Challenge: chall.RandomData,
Rules: rule.Challenge,
Challenge: chall.RandomData,
ID: chall.ID,
})
if err != nil {
lg.Error("failed to encode challenge", "err", err)
@@ -381,7 +392,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
lg := internal.GetRequestLogger(r)
lg := internal.GetRequestLogger(s.logger, r)
localizer := localization.GetLocalizer(r)
redir := r.FormValue("redir")
@@ -428,7 +439,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
return
}
cr, rule, err := s.check(r)
cr, rule, err := s.check(r, lg)
if err != nil {
lg.Error("check failed", "err", err)
s.respondWithError(w, r, fmt.Sprintf("%s \"passChallenge\"", localizer.T("internal_server_error")))
@@ -436,19 +447,27 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
}
lg = lg.With("check_result", cr)
impl, ok := challenge.Get(rule.Challenge.Algorithm)
chall, err := s.getChallenge(r)
if err != nil {
lg.Error("getChallenge failed", "err", err)
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
return
}
if chall.Spent {
lg.Error("double spend prevented", "reason", "double_spend")
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), "double_spend"))
return
}
impl, ok := challenge.Get(chall.Method)
if !ok {
lg.Error("check failed", "err", err)
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm))
return
}
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
}
lg = lg.With("challenge", chall.ID)
in := &challenge.ValidateInput{
Challenge: chall,
@@ -466,20 +485,45 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
case errors.As(err, &cerr):
switch {
case errors.Is(err, challenge.ErrFailed):
lg.Error("challenge failed", "err", err)
s.respondWithStatus(w, r, cerr.PublicReason, cerr.StatusCode)
return
case errors.Is(err, challenge.ErrInvalidFormat), errors.Is(err, challenge.ErrMissingField):
lg.Error("invalid challenge format", "err", err)
s.respondWithError(w, r, cerr.PublicReason)
return
}
}
}
// generate JWT cookie
tokenString, err := s.signJWT(jwt.MapClaims{
"challenge": chall.ID,
"method": rule.Challenge.Algorithm,
"policyRule": rule.Hash(),
"action": string(cr.Rule),
})
var tokenString string
// check if JWTRestrictionHeader is set and header is in request
if s.opts.JWTRestrictionHeader != "" {
if r.Header.Get(s.opts.JWTRestrictionHeader) == "" {
lg.Error("JWTRestrictionHeader is set in config but not found in request, please check your reverse proxy config.")
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
s.respondWithError(w, r, "failed to sign JWT")
return
} else {
tokenString, err = s.signJWT(jwt.MapClaims{
"challenge": chall.ID,
"method": rule.Challenge.Algorithm,
"policyRule": rule.Hash(),
"action": string(cr.Rule),
"restriction": internal.SHA256sum(r.Header.Get(s.opts.JWTRestrictionHeader)),
})
}
} else {
tokenString, err = s.signJWT(jwt.MapClaims{
"challenge": chall.ID,
"method": rule.Challenge.Algorithm,
"policyRule": rule.Hash(),
"action": string(cr.Rule),
})
}
if err != nil {
lg.Error("failed to sign JWT", "err", err)
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
@@ -489,6 +533,12 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
s.SetCookie(w, CookieOpts{Path: cookiePath, Host: r.Host, Value: tokenString})
chall.Spent = true
j := store.JSON[challenge.Challenge]{Underlying: s.store}
if err := j.Set(r.Context(), "challenge:"+chall.ID, *chall, 30*time.Minute); err != nil {
lg.Debug("can't update information about challenge", "err", err)
}
challengesValidated.WithLabelValues(rule.Challenge.Algorithm).Inc()
lg.Debug("challenge passed, redirecting to app")
http.Redirect(w, r, redir, http.StatusFound)
@@ -503,7 +553,7 @@ func cr(name string, rule config.Rule, weight int) policy.CheckResult {
}
// Check evaluates the list of rules, and returns the result
func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) {
func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *policy.Bot, error) {
host := r.Header.Get("X-Real-Ip")
if host == "" {
return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] X-Real-Ip header is not set")
@@ -527,7 +577,7 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error)
case config.RuleDeny, config.RuleAllow, config.RuleBenchmark, config.RuleChallenge:
return cr("bot/"+b.Name, b.Action, weight), &b, nil
case config.RuleWeigh:
slog.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
weight += b.Weight.Adjust
}
}
@@ -536,7 +586,7 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error)
for _, t := range s.policy.Thresholds {
result, _, err := t.Program.ContextEval(r.Context(), &policy.ThresholdRequest{Weight: weight})
if err != nil {
slog.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err)
lg.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err)
continue
}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
@@ -17,13 +18,30 @@ import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/data"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/internal/thoth/thothmock"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/thoth/thothmock"
)
func init() {
internal.InitSlog("debug")
// TLogWriter implements io.Writer by logging each line to t.Log.
type TLogWriter struct {
t *testing.T
}
// NewTLogWriter returns an io.Writer that sends output to t.Log.
func NewTLogWriter(t *testing.T) io.Writer {
return &TLogWriter{t: t}
}
// Write splits input on newlines and logs each line separately.
func (w *TLogWriter) Write(p []byte) (n int, err error) {
lines := strings.Split(string(p), "\n")
for _, line := range lines {
if line != "" {
w.t.Log(line)
}
}
return len(p), nil
}
func loadPolicies(t *testing.T, fname string, difficulty int) *policy.ParsedConfig {
@@ -35,6 +53,8 @@ func loadPolicies(t *testing.T, fname string, difficulty int) *policy.ParsedConf
fname = "./testdata/test_config.yaml"
}
t.Logf("loading policy file: %s", fname)
anubisPolicy, err := LoadPoliciesOrDefault(ctx, fname, difficulty)
if err != nil {
t.Fatal(err)
@@ -55,10 +75,16 @@ func spawnAnubis(t *testing.T, opts Options) *Server {
t.Fatalf("can't construct libanubis.Server: %v", err)
}
s.logger = slog.New(slog.NewJSONHandler(&TLogWriter{t: t}, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
}))
return s
}
type challengeResp struct {
ID string `json:"id"`
Challenge string `json:"challenge"`
}
@@ -91,6 +117,8 @@ func makeChallenge(t *testing.T, ts *httptest.Server, cli *http.Client) challeng
func handleChallengeZeroDifficulty(t *testing.T, ts *httptest.Server, cli *http.Client, chall challengeResp) *http.Response {
t.Helper()
t.Logf("%#v", chall)
nonce := 0
elapsedTime := 420
redir := "/"
@@ -108,8 +136,11 @@ func handleChallengeZeroDifficulty(t *testing.T, ts *httptest.Server, cli *http.
q.Set("nonce", fmt.Sprint(nonce))
q.Set("redir", redir)
q.Set("elapsedTime", fmt.Sprint(elapsedTime))
q.Set("id", chall.ID)
req.URL.RawQuery = q.Encode()
t.Log(q.Encode())
resp, err := cli.Do(req)
if err != nil {
t.Fatalf("can't do request: %v", err)
@@ -155,6 +186,17 @@ func (lcj *loggingCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
lcj.cookies[u.Host] = append(lcj.cookies[u.Host], cookies...)
}
type userAgentRoundTripper struct {
rt http.RoundTripper
}
func (u *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Only set if not already present
req = req.Clone(req.Context()) // avoid mutating original request
req.Header.Set("User-Agent", "Mozilla/5.0")
return u.rt.RoundTrip(req)
}
func httpClient(t *testing.T) *http.Client {
t.Helper()
@@ -163,13 +205,16 @@ func httpClient(t *testing.T) *http.Client {
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &userAgentRoundTripper{
rt: http.DefaultTransport,
},
}
return cli
}
func TestLoadPolicies(t *testing.T) {
for _, fname := range []string{"botPolicies.json", "botPolicies.yaml"} {
for _, fname := range []string{"botPolicies.yaml"} {
t.Run(fname, func(t *testing.T) {
fin, err := data.BotPolicies.Open(fname)
if err != nil {
@@ -207,7 +252,7 @@ func TestCVE2025_24369(t *testing.T) {
}
func TestCookieCustomExpiration(t *testing.T) {
pol := loadPolicies(t, "", 0)
pol := loadPolicies(t, "testdata/zero_difficulty.yaml", 0)
ckieExpiration := 10 * time.Minute
srv := spawnAnubis(t, Options{
@@ -223,9 +268,7 @@ func TestCookieCustomExpiration(t *testing.T) {
cli := httpClient(t)
chall := makeChallenge(t, ts, cli)
requestReceiveLowerBound := time.Now().Add(-1 * time.Minute)
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
requestReceiveUpperBound := time.Now()
if resp.StatusCode != http.StatusFound {
resp.Write(os.Stderr)
@@ -244,19 +287,10 @@ func TestCookieCustomExpiration(t *testing.T) {
t.Errorf("Cookie %q not found", anubis.CookieName)
return
}
expirationLowerBound := requestReceiveLowerBound.Add(ckieExpiration)
expirationUpperBound := requestReceiveUpperBound.Add(ckieExpiration)
// Since the cookie expiration precision is only to the second due to the Unix() call, we can
// lower the level of expected precision.
if ckie.Expires.Unix() < expirationLowerBound.Unix() || ckie.Expires.Unix() > expirationUpperBound.Unix() {
t.Errorf("cookie expiration is not within the expected range. expected between: %v and %v. got: %v", expirationLowerBound, expirationUpperBound, ckie.Expires)
return
}
}
func TestCookieSettings(t *testing.T) {
pol := loadPolicies(t, "", 0)
pol := loadPolicies(t, "testdata/zero_difficulty.yaml", 0)
srv := spawnAnubis(t, Options{
Next: http.NewServeMux(),
@@ -268,7 +302,6 @@ func TestCookieSettings(t *testing.T) {
CookieExpiration: anubis.CookieDefaultExpirationTime,
})
requestReceiveLowerBound := time.Now()
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
defer ts.Close()
@@ -276,7 +309,6 @@ func TestCookieSettings(t *testing.T) {
chall := makeChallenge(t, ts, cli)
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
requestReceiveUpperBound := time.Now()
if resp.StatusCode != http.StatusFound {
resp.Write(os.Stderr)
@@ -300,15 +332,6 @@ func TestCookieSettings(t *testing.T) {
t.Errorf("cookie domain is wrong, wanted 127.0.0.1, got: %s", ckie.Domain)
}
expirationLowerBound := requestReceiveLowerBound.Add(anubis.CookieDefaultExpirationTime)
expirationUpperBound := requestReceiveUpperBound.Add(anubis.CookieDefaultExpirationTime)
// Since the cookie expiration precision is only to the second due to the Unix() call, we can
// lower the level of expected precision.
if ckie.Expires.Unix() < expirationLowerBound.Unix() || ckie.Expires.Unix() > expirationUpperBound.Unix() {
t.Errorf("cookie expiration is not within the expected range. expected between: %v and %v. got: %v", expirationLowerBound, expirationUpperBound, ckie.Expires)
return
}
if ckie.Partitioned != srv.opts.CookiePartitioned {
t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned)
}
@@ -325,7 +348,7 @@ func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
for i := 1; i < 10; i++ {
t.Run(fmt.Sprint(i), func(t *testing.T) {
anubisPolicy := loadPolicies(t, "", i)
anubisPolicy := loadPolicies(t, "testdata/test_config_no_thresholds.yaml", i)
s, err := New(Options{
Next: h,
@@ -343,7 +366,7 @@ func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
req.Header.Add("X-Real-Ip", "127.0.0.1")
cr, bot, err := s.check(req)
cr, bot, err := s.check(req, s.logger)
if err != nil {
t.Fatal(err)
}
@@ -476,8 +499,11 @@ func TestBasePrefix(t *testing.T) {
q.Set("nonce", fmt.Sprint(nonce))
q.Set("redir", redir)
q.Set("elapsedTime", fmt.Sprint(elapsedTime))
q.Set("id", chall.ID)
req.URL.RawQuery = q.Encode()
t.Log(req.URL.String())
resp, err = cli.Do(req)
if err != nil {
t.Fatalf("can't do challenge passing: %v", err)
@@ -583,7 +609,7 @@ func TestCloudflareWorkersRule(t *testing.T) {
req.Header.Add("X-Real-Ip", "127.0.0.1")
req.Header.Add("Cf-Worker", "true")
cr, _, err := s.check(req)
cr, _, err := s.check(req, s.logger)
if err != nil {
t.Fatal(err)
}
@@ -601,7 +627,7 @@ func TestCloudflareWorkersRule(t *testing.T) {
req.Header.Add("X-Real-Ip", "127.0.0.1")
cr, _, err := s.check(req)
cr, _, err := s.check(req, s.logger)
if err != nil {
t.Fatal(err)
}
@@ -926,3 +952,42 @@ func TestPassChallengeXSS(t *testing.T) {
}
})
}
func TestXForwardedForNoDoubleComma(t *testing.T) {
var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Forwarded-For", r.Header.Get("X-Forwarded-For"))
fmt.Fprintln(w, "OK")
})
h = internal.XForwardedForToXRealIP(h)
h = internal.XForwardedForUpdate(false, h)
pol := loadPolicies(t, "testdata/permissive.yaml", 4)
srv := spawnAnubis(t, Options{
Next: h,
Policy: pol,
})
ts := httptest.NewServer(srv)
t.Cleanup(ts.Close)
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("X-Real-Ip", "10.0.0.1")
resp, err := ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("response status is wrong, wanted %d but got: %s", http.StatusOK, resp.Status)
}
if xff := resp.Header.Get("X-Forwarded-For"); strings.HasPrefix(xff, ",,") {
t.Errorf("X-Forwarded-For has two leading commas: %q", xff)
}
}

View File

@@ -5,7 +5,9 @@ import "time"
// Challenge is the metadata about a single challenge issuance.
type Challenge struct {
ID string `json:"id"` // UUID identifying the challenge
Method string `json:"method"` // Challenge method
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
Spent bool `json:"spent"` // Has the challenge already been solved?
}

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/web"
"github.com/a-h/templ"
)
@@ -32,16 +31,14 @@ 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.RandomData)
q.Set("id", in.Challenge.ID)
u.RawQuery = q.Encode()
loc := localization.GetLocalizer(r)
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)
}
result := page(u.String(), in.Rule.Challenge.Difficulty, loc)
return component, nil
return result, nil
}
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.906
// templ: version: v0.3.924
package metarefresh
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View File

@@ -11,10 +11,11 @@ import (
"github.com/TecharoHQ/anubis/internal"
chall "github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/localization"
"github.com/TecharoHQ/anubis/web"
"github.com/a-h/templ"
)
//go:generate go tool github.com/a-h/templ/cmd/templ generate
func init() {
chall.Register("fast", &Impl{Algorithm: "fast"})
chall.Register("slow", &Impl{Algorithm: "slow"})
@@ -24,18 +25,11 @@ type Impl struct {
Algorithm string
}
func (i *Impl) Setup(mux *http.ServeMux) {
/* no implementation required */
}
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.RandomData, in.Rule.Challenge, in.OGTags, loc)
if err != nil {
return nil, fmt.Errorf("can't render page: %w", err)
}
return component, nil
return page(loc), nil
}
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {

View File

@@ -0,0 +1,44 @@
package proofofwork
import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/lib/localization"
)
templ page(localizer *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 }/>
<p id="status">{ localizer.T("loading") }</p>
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version }></script>
<div id="progress" role="progressbar" aria-labelledby="status">
<div class="bar-inner"></div>
</div>
<details>
if anubis.UseSimplifiedExplanation {
<p>
{ localizer.T("simplified_explanation") }
</p>
} else {
<p>
{ localizer.T("ai_companies_explanation") }
</p>
<p>
{ localizer.T("anubis_compromise") }
</p>
<p>
{ localizer.T("hack_purpose") }
</p>
<p>
{ localizer.T("jshelter_note") }
</p>
}
</details>
<noscript>
<p>
{ localizer.T("javascript_required") }
</p>
</noscript>
<div id="testarea"></div>
</div>
}

View File

@@ -0,0 +1,190 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924
package proofofwork
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/lib/localization"
)
func page(localizer *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 {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 10, Col: 165}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"> <img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 11, Col: 174}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p id=\"status\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("loading"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 12, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><script async type=\"module\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 13, Col: 136}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"></script><div id=\"progress\" role=\"progressbar\" aria-labelledby=\"status\"><div class=\"bar-inner\"></div></div><details>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if anubis.UseSimplifiedExplanation {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("simplified_explanation"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 20, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("ai_companies_explanation"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 24, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("anubis_compromise"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 27, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("hack_purpose"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 30, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</p><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("jshelter_note"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 33, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</details><noscript><p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("javascript_required"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `proofofwork.templ`, Line: 39, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p></noscript><div id=\"testarea\"></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -27,22 +27,25 @@ import (
)
type Options struct {
Next http.Handler
Policy *policy.ParsedConfig
Target string
CookieDynamicDomain bool
CookieDomain string
CookieExpiration time.Duration
CookiePartitioned bool
BasePrefix string
WebmasterEmail string
RedirectDomains []string
ED25519PrivateKey ed25519.PrivateKey
HS512Secret []byte
StripBasePrefix bool
OpenGraph config.OpenGraph
ServeRobotsTXT bool
CookieSecure bool
Next http.Handler
Policy *policy.ParsedConfig
Target string
CookieDynamicDomain bool
CookieDomain string
CookieExpiration time.Duration
CookiePartitioned bool
BasePrefix string
WebmasterEmail string
RedirectDomains []string
ED25519PrivateKey ed25519.PrivateKey
HS512Secret []byte
StripBasePrefix bool
OpenGraph config.OpenGraph
ServeRobotsTXT bool
CookieSecure bool
Logger *slog.Logger
PublicUrl string
JWTRestrictionHeader string
}
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
@@ -89,8 +92,12 @@ func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty
}
func New(opts Options) (*Server, error) {
if opts.Logger == nil {
opts.Logger = slog.With("subsystem", "anubis")
}
if opts.ED25519PrivateKey == nil && opts.HS512Secret == nil {
slog.Debug("opts.PrivateKey not set, generating a new one")
opts.Logger.Debug("opts.PrivateKey not set, generating a new one")
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("lib: can't generate private key: %v", err)
@@ -108,6 +115,7 @@ func New(opts Options) (*Server, error) {
opts: opts,
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store),
store: opts.Policy.Store,
logger: opts.Logger,
}
mux := http.NewServeMux()

View File

@@ -7,8 +7,8 @@ import (
"testing"
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/internal/thoth/thothmock"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/thoth/thothmock"
)
func TestInvalidChallengeMethod(t *testing.T) {

View File

@@ -1,9 +1,11 @@
package lib
import (
"errors"
"fmt"
"math/rand"
"net/http"
"net/url"
"regexp"
"slices"
"strings"
@@ -111,31 +113,43 @@ func randomChance(n int) bool {
return rand.Intn(n) == 0
}
func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *policy.Bot, returnHTTPStatusOnly bool) {
func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.CheckResult, rule *policy.Bot, returnHTTPStatusOnly bool) {
localizer := localization.GetLocalizer(r)
if returnHTTPStatusOnly {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(localizer.T("authorization_required")))
if s.opts.PublicUrl == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(localizer.T("authorization_required")))
} else {
redirectURL, err := s.constructRedirectURL(r)
if err != nil {
s.respondWithStatus(w, r, err.Error(), http.StatusBadRequest)
return
}
http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
}
return
}
lg := internal.GetRequestLogger(r)
lg := internal.GetRequestLogger(s.logger, r)
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) {
lg.Error("client was given a challenge but does not in fact support gzip compression")
s.respondWithError(w, r, localizer.T("client_error_browser"))
return
}
challengesIssued.WithLabelValues("embedded").Add(1)
chall, err := s.challengeFor(r)
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
if err != nil {
lg.Error("can't get challenge", "err", "err")
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
}
lg = lg.With("challenge", chall.ID)
var ogTags map[string]string = nil
if s.opts.OpenGraph.Enabled {
var err error
@@ -153,7 +167,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
Expiry: 30 * time.Minute,
})
impl, ok := challenge.Get(rule.Challenge.Algorithm)
impl, ok := challenge.Get(chall.Method)
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})
@@ -171,18 +185,46 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *polic
component, err := impl.Issue(r, lg, in)
if err != nil {
lg.Error("[unexpected] render failed, please open an issue", "err", err) // This is likely a bug in the template. Should never be triggered as CI tests for this.
lg.Error("[unexpected] challenge component render failed, please open an issue", "err", err) // This is likely a bug in the template. Should never be triggered as CI tests for this.
s.respondWithError(w, r, fmt.Sprintf("%s \"RenderIndex\"", localizer.T("internal_server_error")))
return
}
handler := internal.GzipMiddleware(1, internal.NoStoreCache(templ.Handler(
page := web.BaseWithChallengeAndOGTags(
localizer.T("making_sure_not_bot"),
component,
s.policy.Impressum,
chall,
in.Rule.Challenge,
in.OGTags,
localizer,
)
handler := internal.GzipMiddleware(1, internal.NoStoreCache(templ.Handler(
page,
templ.WithStatus(s.opts.Policy.StatusCodes.Challenge),
)))
handler.ServeHTTP(w, r)
}
func (s *Server) constructRedirectURL(r *http.Request) (string, error) {
proto := r.Header.Get("X-Forwarded-Proto")
host := r.Header.Get("X-Forwarded-Host")
uri := r.Header.Get("X-Forwarded-Uri")
if proto == "" || host == "" || uri == "" {
return "", errors.New("missing required X-Forwarded-* headers")
}
// Check if host is allowed in RedirectDomains
if len(s.opts.RedirectDomains) > 0 && !slices.Contains(s.opts.RedirectDomains, host) {
return "", errors.New("redirect domain not allowed")
}
redir := proto + "://" + host + uri
escapedURL := url.QueryEscape(redir)
return fmt.Sprintf("%s/.within.website/?redir=%s", s.opts.PublicUrl, escapedURL), nil
}
func (s *Server) RenderBench(w http.ResponseWriter, r *http.Request) {
localizer := localization.GetLocalizer(r)
@@ -242,7 +284,12 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
return
}
if (len(urlParsed.Host) > 0 && len(s.opts.RedirectDomains) != 0 && !slices.Contains(s.opts.RedirectDomains, urlParsed.Host)) || urlParsed.Host != r.URL.Host {
hostNotAllowed := len(urlParsed.Host) > 0 &&
len(s.opts.RedirectDomains) != 0 &&
!slices.Contains(s.opts.RedirectDomains, urlParsed.Host)
hostMismatch := r.URL.Host != "" && urlParsed.Host != r.URL.Host
if hostNotAllowed || hostMismatch {
s.respondWithStatus(w, r, localizer.T("redirect_domain_not_allowed"), http.StatusBadRequest)
return
}

View File

@@ -1,10 +1,13 @@
package lib
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/lib/policy"
)
func TestSetCookie(t *testing.T) {
@@ -129,3 +132,62 @@ func TestClearCookieWithDynamicDomain(t *testing.T) {
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
}
}
func TestRenderIndexRedirect(t *testing.T) {
s := &Server{
opts: Options{
PublicUrl: "https://anubis.example.com",
},
}
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("X-Forwarded-Proto", "https")
req.Header.Set("X-Forwarded-Host", "example.com")
req.Header.Set("X-Forwarded-Uri", "/foo")
rr := httptest.NewRecorder()
s.RenderIndex(rr, req, policy.CheckResult{}, nil, true)
if rr.Code != http.StatusTemporaryRedirect {
t.Errorf("expected status %d, got %d", http.StatusTemporaryRedirect, rr.Code)
}
location := rr.Header().Get("Location")
parsedURL, err := url.Parse(location)
if err != nil {
t.Fatalf("failed to parse location URL %q: %v", location, err)
}
scheme := "https"
if parsedURL.Scheme != scheme {
t.Errorf("expected scheme to be %q, got %q", scheme, parsedURL.Scheme)
}
host := "anubis.example.com"
if parsedURL.Host != host {
t.Errorf("expected url to be %q, got %q", host, parsedURL.Host)
}
redir := parsedURL.Query().Get("redir")
expectedRedir := "https://example.com/foo"
if redir != expectedRedir {
t.Errorf("expected redir param to be %q, got %q", expectedRedir, redir)
}
}
func TestRenderIndexUnauthorized(t *testing.T) {
s := &Server{
opts: Options{
PublicUrl: "",
},
}
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
s.RenderIndex(rr, req, policy.CheckResult{}, nil, true)
if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}
if body := rr.Body.String(); body != "Authorization required" {
t.Errorf("expected body %q, got %q", "Authorization required", body)
}
}

View File

@@ -5,9 +5,10 @@
"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.",
"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í, které agresivně stahují webové stránky. To může a skutečně způsobuje výpadky webových stránek, čímž se jejich zdroje stávají pro všechny nedostupnými.",
"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í.",
"hack_purpose": "V konečném důsledku se jedná o zástupné řešení, aby bylo možné věnovat více času otiskům prstů a identifikaci bezhlavých prohlížečů (např. podle toho, jak vykreslují písma), aby se stránka s důkazem práce nemusela zobrazovat uživatelům, kteří jsou mnohem pravděpodobněji legitimní.",
"simplified_explanation": "Jedná se o opatření proti botům a škodlivým požadavkům podobné CAPTCHA. Místo toho, abyste museli pracovat sami, váš prohlížeč dostane výpočetní úkol, který musí vyřešit, aby se zajistilo, že je platným klientem. Tento koncept se nazývá <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Úkol je vypočítán během několika sekund a získáte přístup na webovou stránku. Děkujeme za pochopení a trpělivost.",
"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",
@@ -25,28 +26,28 @@
"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.",
"static_check_endpoint": "Toto je pouze kontrolní koncový 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",
"cookies_disabled": "Váš prohlížeč je nakonfigurován tak, aby zakázal cookies. Anubis vyžaduje cookies pro zajištění, že jste platný klient. 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ů",
"internal_server_error": "Interní chyba serveru: správce špatně nakonfiguroval Anubis. Kontaktujte správce a požádejte ho, aby zkontroloval systémové záznamy.",
"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!",
"oh_noes": "Jejda!",
"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_web_workers_error": "Váš prohlížeč nepodporuje web workers (Anubis je používá, aby zabránil zamrznutí vašeho prohlížeče). Máte nainstalováno 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": "Vaš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",
@@ -58,7 +59,7 @@
"js_success": "Úspěch!",
"js_done_took": "Hotovo! Trvalo to",
"js_iterations": "iterací",
"js_finished_reading": "Dokončil jsem čtení, pokračovat →",
"js_finished_reading": "Čtení dokončeno, pokračovat →",
"js_calculation_error": "Chyba výpočtu!",
"js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:"
}

View File

@@ -7,7 +7,7 @@
"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 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.",
"hack_purpose": "Letztendlich ist dies eine Platzhalterlösung, damit mehr Zeit für die Fingerabdruckerkennung und Identifizierung von Headless-Browsern (z. B. anhand ihrer Schriftwiedergabe) aufgewendet werden kann, sodass die Proof-of-Work-Seite nicht Benutzern präsentiert werden muss, die viel wahrscheinlicher legitim sind.",
"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",
@@ -60,5 +60,6 @@
"js_iterations": "Iterationen",
"js_finished_reading": "Fertig gelesen, weiter zur Seite →",
"js_calculation_error": "Berechnung fehlgeschlagen!",
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:"
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:",
"simplified_explanation": "Dies ist eine Maßnahme gegen Bots und bösartige Anfragen, ähnlich einem CAPTCHA. Anstatt jedoch selbst arbeiten zu müssen, erhält Ihr Browser eine Berechnungsaufgabe, die er lösen muss, um sicherzustellen, dass es sich um einen gültigen Client handelt. Dieses Konzept wird als <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a> bezeichnet. Die Aufgabe wird in wenigen Sekunden berechnet und Sie erhalten Zugriff auf die Website. Vielen Dank für Ihr Verständnis und Ihre Geduld."
}

View File

@@ -7,7 +7,8 @@
"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.",
"anubis_compromise": "Anubis is a compromise. Anubis uses a Proof-of-Work scheme in the vein of Hashcash, a proposed proof-of-work scheme for reducing email spam. The idea is that at individual scales the additional load is ignorable, but at mass scraper levels it adds up and makes scraping much more expensive.",
"hack_purpose": "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 users that are much more likely to be legitimate.",
"hack_purpose": "Ultimately, this is a 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 users that are much more likely to be legitimate.",
"simplified_explanation": "This is a measure against bots and malicious requests similar to a CAPTCHA. However, instead of having to do work yourself, your browser is given a calculation task that it has to solve to ensure that it is a valid client. This concept is called <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. The task is calculated in a few seconds and you are granted access to the website. Thank you for your understanding and patience.",
"jshelter_note": "Please note that Anubis requires the use of modern JavaScript features that plugins like JShelter will disable. Please disable JShelter or other such plugins for this domain.",
"version_info": "This website is running Anubis version",
"try_again": "Try again",

View File

@@ -7,7 +7,7 @@
"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.",
"anubis_compromise": "Anubis es un compromiso. Anubis utiliza un esquema de Prueba de Trabajo en la línea de Hashcash, un esquema de prueba de trabajo propuesto para reducir el spam por correo electrónico. La idea es que a escala individual, la carga adicional es insignificante, pero a escala de raspadores masivos, se acumula y hace que el raspado sea mucho más costoso.",
"hack_purpose": "En última instancia, esto es un hack cuyo verdadero propósito es dar una solución alternativa \"suficientemente buena\" para que se pueda dedicar más tiempo a la huella digital e identificación de navegadores sin cabeza (por ejemplo: a través de cómo renderizan las fuentes) para que la página de desafío de prueba de trabajo no necesite ser presentada a usuarios que son mucho más propensos a ser legítimos.",
"hack_purpose": "En última instancia, esta es una solución provisional para que se pueda dedicar más tiempo a la identificación y el reconocimiento de navegadores sin cabeza (por ejemplo, a través de cómo renderizan las fuentes) de modo que la página de prueba de trabajo del desafío no tenga que presentarse a usuarios que son mucho más propensos a ser legítimos.",
"jshelter_note": "Ten en cuenta que Anubis requiere el uso de características modernas de JavaScript que plugins como JShelter deshabilitarán. Por favor, deshabilita JShelter u otros plugins similares para este dominio.",
"version_info": "Este sitio web utiliza Anubis versión",
"try_again": "Intentar de nuevo",
@@ -60,5 +60,6 @@
"js_iterations": "iteraciones",
"js_finished_reading": "He terminado de leer, continuar →",
"js_calculation_error": "¡Error de cálculo!",
"js_calculation_error_msg": "Falló al calcular el desafío:"
"js_calculation_error_msg": "Falló al calcular el desafío:",
"simplified_explanation": "Esta es una medida contra bots y solicitudes maliciosas similar a un CAPTCHA. Sin embargo, en lugar de tener que hacer el trabajo usted mismo, a su navegador se le asigna una tarea de cálculo que debe resolver para garantizar que es un cliente válido. Este concepto se llama <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Prueba de trabajo</a>. La tarea se calcula en unos segundos y se le concede acceso al sitio web. Gracias por su comprensión y paciencia."
}

View File

@@ -7,7 +7,7 @@
"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.",
"hack_purpose": "Lõppkokkuvõttes on see ajutine lahendus, et saaks rohkem aega kulutada peata brauserite (nt nende fondi renderdamise viisi kaudu) sõrmejälgede võtmisele ja tuvastamisele, nii et töö tõendamise lehte ei peaks esitama kasutajatele, kes on palju tõenäolisemalt legitiimsed.",
"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",
@@ -60,5 +60,6 @@
"js_iterations": "kordust",
"js_finished_reading": "Lugesin ära, edasi →",
"js_calculation_error": "Arvutamise viga!",
"js_calculation_error_msg": "Ei suutnud kontrolli arvutada:"
"js_calculation_error_msg": "Ei suutnud kontrolli arvutada:",
"simplified_explanation": "See on meede robotite ja pahatahtlike päringute vastu, mis sarnaneb CAPTCHA-le. Kuid selle asemel, et peaksite ise tööd tegema, antakse teie brauserile arvutusülesanne, mille see peab lahendama, et tagada selle kehtivus kliendina. Seda kontseptsiooni nimetatakse <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Töötõendiks</a>. Ülesanne arvutatakse mõne sekundiga ja teile antakse juurdepääs veebisaidile. Täname teid mõistva suhtumise ja kannatlikkuse eest."
}

View File

@@ -7,7 +7,7 @@
"mascot_design": "Maskotin suunnitellut",
"ai_companies_explanation": "Sivustolla on käytössä Anubis. Anubis estää robotteja lataamasta sivustoa ylettömästi. Tämä voi aiheuttaa palvelimen ylikuormituksen, joka estää ketään pääsemästä sivustolle.",
"anubis_compromise": "Anubis on kompromissi. Anubis käyttää roskapostin vähentämiseen ehdotettua, Hashcash-järjestelmän mukaista työnnäytettä. Yksittäiselle käyttäjälle kuormitus on mitätön, mutta kasvattaa sivuston ylettömän lataamisen kuluja huomattavasti.",
"hack_purpose": "Tämä on \"riittävän hyvä\" väliaikainen ratkaisu, joka antaa aikaa tunnistaa robotit, ettei työnnäyte sivua tarvitsisi näyttää todellisille käyttäjille.",
"hack_purpose": "Viime kädessä tämä on paikkamerkkiratkaisu, jotta enemmän aikaa voidaan käyttää päättömien selainten sormenjälkien ottamiseen ja tunnistamiseen (esim. niiden fonttien renderöintitavan perusteella), jotta työnäytesivua ei tarvitse esittää käyttäjille, jotka ovat paljon todennäköisemmin laillisia.",
"jshelter_note": "Anubis tarvitsee toimiakseen JavaScript-ominaisuuksia, jotka liitännäiset kuten jShelter estää. Otathan tällaiset liitännäiset pois käytöstä tälle verkkotunnukselle.",
"version_info": "Sivusto käyttää Anubis versiota",
"try_again": "Yritä uudelleen",
@@ -60,5 +60,6 @@
"js_iterations": "toistot",
"js_finished_reading": "Luettu, jatka →",
"js_calculation_error": "Laskentavirhe!",
"js_calculation_error_msg": "Haasteen laskenta ei onnistunut:"
"js_calculation_error_msg": "Haasteen laskenta ei onnistunut:",
"simplified_explanation": "Tämä on toimenpide botteja ja haitallisia pyyntöjä vastaan, joka on samanlainen kuin CAPTCHA. Sen sijaan, että joutuisit tekemään työtä itse, selaimesi saa laskentatehtävän, joka sen on ratkaistava varmistaakseen, että se on kelvollinen asiakas. Tätä käsitettä kutsutaan nimellä <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Työtodistus</a>. Tehtävä lasketaan muutamassa sekunnissa ja saat pääsyn verkkosivustolle. Kiitos ymmärryksestäsi ja kärsivällisyydestäsi."
}

View File

@@ -7,7 +7,7 @@
"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.",
"hack_purpose": "Sa huli, ito ay isang placeholder na solusyon upang mas maraming oras ang magugol sa pag-fingerprint at pagtukoy ng mga headless browser (hal: sa pamamagitan ng kung paano nila ginagawa ang pag-render ng font) upang hindi na kailangang iharap ang pahina ng patunay ng trabaho sa mga user na mas malamang na lehitimo.",
"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",
@@ -60,5 +60,6 @@
"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:"
"js_calculation_error_msg": "Nabigong ikalkula ang hamon:",
"simplified_explanation": "Ito ay isang panukala laban sa mga bot at malisyosong mga kahilingan na katulad ng isang CAPTCHA. Gayunpaman, sa halip na ikaw mismo ang gumawa ng trabaho, binibigyan ang iyong browser ng isang gawain sa pagkalkula na kailangan nitong lutasin upang matiyak na ito ay isang wastong kliyente. Ang konseptong ito ay tinatawag na <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Ang gawain ay kinakalkula sa loob ng ilang segundo at binibigyan ka ng access sa website. Salamat sa iyong pag-unawa at pasensya."
}

View File

@@ -7,7 +7,7 @@
"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.",
"anubis_compromise": "Anubis est un compromis. Anubis utilise un schéma de Preuve de Travail dans la veine de Hashcash, un schéma de preuve de travail proposé pour réduire le spam par email. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, cela s'accumule et rend le scraping beaucoup plus coûteux.",
"hack_purpose": "En fin de compte, c'est un hack dont le véritable objectif est de donner une solution de substitution \"assez bonne\" pour que plus de temps puisse être consacré à l'empreinte digitale et à l'identification des navigateurs sans tête (par exemple : via la façon dont ils font le rendu des polices) afin que la page de défi de preuve de travail n'ait pas besoin d'être présentée aux utilisateurs qui sont beaucoup plus susceptibles d'être légitimes.",
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution afin de consacrer plus de temps à l'identification et à l'empreinte digitale des navigateurs sans tête (par exemple, via leur rendu de police) afin que la page de preuve de travail du défi n'ait pas besoin d'être présentée aux utilisateurs qui sont beaucoup plus susceptibles d'être légitimes.",
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes que des plugins comme JShelter désactiveront. Veuillez désactiver JShelter ou d'autres plugins similaires pour ce domaine.",
"version_info": "Ce site web utilise Anubis version",
"try_again": "Réessayer",
@@ -60,5 +60,6 @@
"js_iterations": "itérations",
"js_finished_reading": "J'ai fini de lire, continuer →",
"js_calculation_error": "Erreur de calcul !",
"js_calculation_error_msg": "Échec du calcul du défi :"
"js_calculation_error_msg": "Échec du calcul du défi :",
"simplified_explanation": "Il s'agit d'une mesure contre les robots et les requêtes malveillantes similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour s'assurer qu'il est un client valide. Ce concept s'appelle <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche est calculée en quelques secondes et vous avez accès au site Web. Merci de votre compréhension et de votre patience."
}

View File

@@ -2,12 +2,12 @@
"loading": "Hleður...",
"why_am_i_seeing": "Af hverju er ég að sjá þetta?",
"protected_by": "Verndað með",
"protected_from": "From",
"protected_from": "Frá",
"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 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.",
"hack_purpose": "Að lokum er þetta staðgengilslausn svo hægt sé að eyða meiri tíma í fingraför og auðkenningu höfuðlausra vafra (t.d. með því hvernig þeir birta leturgerðir) svo að áskorunarprófunarsíðan þurfi ekki að birtast notendum sem eru mun líklegri til að vera lögmætir.",
"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",
@@ -60,5 +60,6 @@
"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:"
"js_calculation_error_msg": "Mistókst að reikna áskorun:",
"simplified_explanation": "Þetta er ráðstöfun gegn vélmennum og illgjarnum beiðnum svipað og CAPTCHA. Hins vegar, í stað þess að þurfa að vinna sjálfur, fær vafrinn þinn útreikningsverkefni sem hann þarf að leysa til að tryggja að hann sé gildur biðlari. Þetta hugtak er kallað <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Sönnun-á-vinnu</a>. Verkefnið er reiknað á nokkrum sekúndum og þú færð aðgang að vefsíðunni. Takk fyrir skilninginn og þolinmæðina."
}

View File

@@ -7,7 +7,7 @@
"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.",
"hack_purpose": "In definitiva, questa è una soluzione provvisoria in modo che si possa dedicare più tempo all'identificazione e al rilevamento dei browser headless (ad esempio, tramite il modo in cui rendono i caratteri) in modo che la pagina di prova del lavoro non debba essere presentata agli utenti che sono molto più propensi a 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",
@@ -60,5 +60,6 @@
"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:"
"js_calculation_error_msg": "Impossibile superare il test:",
"simplified_explanation": "Questa è una misura contro bot e richieste dannose simile a un CAPTCHA. Tuttavia, invece di dover lavorare tu stesso, al tuo browser viene assegnato un compito di calcolo che deve risolvere per garantire che sia un client valido. Questo concetto è chiamato <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Il compito viene calcolato in pochi secondi e ti viene concesso l'accesso al sito web. Grazie per la tua comprensione e pazienza."
}

View File

@@ -7,7 +7,7 @@
"mascot_design": "Mascot design by",
"ai_companies_explanation": "このメッセージが表示されているのは、このウェブサイトの管理者が、AI企業による過剰なウェブスクレイピングからサーバーを守るためにAnubisを導入しているためです。これにより、ウェブサイトがダウンし、すべての利用者がリソースにアクセスできなくなる事態が発生することがあります。",
"anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。",
"hack_purpose": "最終的に、これは「十分に使える」仮の解決策を提供するためのハックです。本来の目的は、フォントレンダリングの方法などを通じてヘッドレスブラウザの指紋付けや識別により多くの時間を割けるようにし、正当なユーザーに対してはProof-of-Workのチャレンジページを表示しなくて済むようにすることです。",
"hack_purpose": "最終的に、これはヘッドレスブラウザのフィンガープリントと識別に時間を費やすためのプレースホルダーソリューションです(例:フォントレンダリングの方法による)。これにより、正当なユーザーにはチャレンジのプルーフオブワークページを提示する必要がなくなります。",
"jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。",
"version_info": "このウェブサイトはAnubisバージョンで動作しています",
"try_again": "再試行",
@@ -60,5 +60,6 @@
"js_iterations": "イテレーション数",
"js_finished_reading": "読み終わりました。続行 →",
"js_calculation_error": "計算エラー!",
"js_calculation_error_msg": "チャレンジの計算に失敗しました:"
"js_calculation_error_msg": "チャレンジの計算に失敗しました:",
"simplified_explanation": "これは、CAPTCHAと同様の、ボットや悪意のあるリクエストに対する対策です。ただし、自分で作業する代わりに、ブラウザに計算タスクが与えられ、それを解決して有効なクライアントであることを確認する必要があります。この概念は<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>と呼ばれます。タスクは数秒で計算され、ウェブサイトへのアクセスが許可されます。ご理解とご協力をお願いいたします。"
}

View File

@@ -12,11 +12,13 @@
"it",
"ja",
"nb",
"nl",
"nn",
"pt-BR",
"ru",
"tr",
"zh-CN",
"zh-TW"
"zh-TW",
"sv"
]
}
}

View File

@@ -2,12 +2,12 @@
"loading": "Laster inn...",
"why_am_i_seeing": "Hvorfor ser jeg dette?",
"protected_by": "Beskyttet av",
"protected_from": "Fra",
"protected_from": "fra",
"made_with": "Laget med ❤️ i 🇨🇦",
"mascot_design": "Maskotdesign av",
"ai_companies_explanation": "Du ser dette fordi administratoren av dette nettstedet har satt opp Anubis for å beskytte sørveren mot plagen av KI-selskaper som aggressivt skraper nettsteder. Dette kan, og fortsetter med å, forårsake driftstans for nettstedene, som gjør ressursene deres utilgjengelige for alle.",
"anubis_compromise": "Anubis er et kompromiss. Anubis bruker et «Proof-of-Work»-skjema som ligner på Hashcash, et lignende skjema for å redusere søppel-e-post. Idéen er at ved småstilte tilfeller er den ytterligere belastningen ignorerbar, men ved storstilt skraping samler den på seg fart og gjør det å skrape mye mer dyrt.",
"hack_purpose": "Til syvende og sist er dette en hack som har som formål å gi en «god nok» plassholderløsning slik at mer tid kan brukes på å fingeravtrykke og gjenkjenne hodeløse nettlesere (f.eks. hvordan de gjengir skrifttyper) slik at utfordringssiden ikke må bli vist til brukere som er mer sannsynligvis ekte.",
"hack_purpose": "Til syvende og sist er dette en plassholderløsning slik at mer tid kan brukes på fingeravtrykk og identifisering av hodeløse nettlesere (f.eks. via hvordan de gjengir skrifttyper) slik at utfordringssiden for arbeidsprosessen ikke trenger å presenteres for brukere som er mye mer sannsynlig å være legitime.",
"jshelter_note": "NB: Anubis krever bruk av moderne JavaScript-funksjoner som tillegg som JShelter slår av. Vennligst slå av JShelter eller lignende tillegg for dette domenet.",
"version_info": "Dette nettstedet kjører Anubis-utgave",
"try_again": "Prøv igjen",
@@ -53,12 +53,13 @@
"js_challenge_error": "Utfordringsfeil!",
"js_challenge_error_msg": "Mislyktes i å tolke sjekkalgoritmen. Du burde laste inn denne siden på nytt.",
"js_calculating_difficulty": "Beregner…<br/>Vanskelighetsnivå:",
"js_speed": "Hastighet:",
"js_speed": "hastighet:",
"js_verification_longer": "Verifisering tar lengre enn forventet. Vennligst ikke last inn denne siden på nytt.",
"js_success": "Vellykket!",
"js_done_took": "Ferdig! Tok",
"js_iterations": "gjentakelser",
"js_finished_reading": "Jeg har sluttet å lese, fortsett →",
"js_calculation_error": "Beregningsfeil!",
"js_calculation_error_msg": "Mislyktes i å beregne utfordring:"
"js_calculation_error_msg": "Mislyktes i å beregne utfordring:",
"simplified_explanation": "Dette er et tiltak mot roboter og ondsinnede forespørsler som ligner på en CAPTCHA. Men i stedet for å måtte gjøre arbeidet selv, får nettleseren din en beregningsoppgave som den må løse for å sikre at den er en gyldig klient. Dette konseptet kalles <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Oppgaven beregnes på noen få sekunder, og du får tilgang til nettstedet. Takk for din forståelse og tålmodighet."
}

View File

@@ -0,0 +1,65 @@
{
"loading": "Laden...",
"why_am_i_seeing": "Waarom zie ik dit?",
"protected_by": "Beschermd door",
"protected_from": "Van",
"made_with": "Gemaakt met ❤️ in 🇨🇦",
"mascot_design": "Mascotte ontwerp door",
"ai_companies_explanation": "Je ziet dit omdat de beheerder van deze website Anubis heeft ingesteld om de server te beschermen tegen de plaag van AI-bedrijven die agressief websites scrapen. Dit kan downtime veroorzaken voor de websites, waardoor hun bronnen voor iedereen ontoegankelijk worden.",
"anubis_compromise": "Anubis is een compromis. Anubis gebruikt een Proef-van-Taak schema in de geest van Hashcash, een voorgesteld proef-van-taak schema voor het verminderen van e-mailspam. Het idee is dat op individuele schalen de extra belasting niet waarneembaar is, maar op het niveau van massascrapers telt het op en maakt het scrapen veel duurder.",
"hack_purpose": "Uiteindelijk is dit een tijdelijke oplossing, zodat er meer tijd kan worden besteed aan het identificeren en herkennen van headless browsers (bijv. via de manier waarop ze lettertypen renderen), zodat de proof-of-work-pagina niet hoeft te worden gepresenteerd aan gebruikers die veel waarschijnlijker legitiem zijn.",
"jshelter_note": "Anubis vereist het gebruik van moderne JavaScript-functies die worden uitgeschakeld door plugins zoals JShelter. Schakel JShelter of andere dergelijke plugins uit voor dit domein.",
"version_info": "Deze website draait op de Anubis-versie",
"try_again": "Probeer opnieuw",
"go_home": "Naar de thuispagina",
"contact_webmaster": "of als u denkt dat u niet geblokkeerd zou moeten worden, neem dan contact op met de webmaster op",
"connection_security": "Wacht even terwijl we de veiligheid van uw verbinding waarborgen.",
"javascript_required": "Helaas moet je JavaScript inschakelen om voorbij deze uitdaging te komen. Dit is nodig omdat AI-bedrijven het sociale contract rond de werking van websitehosting hebben veranderd. Een oplossing zonder JavaScript is nog in ontwikkeling.",
"benchmark_requires_js": "Voor het uitvoeren van de vergelijkingsinstrument moet JavaScript zijn ingeschakeld.",
"difficulty": "Moeilijkheidsgraad:",
"algorithm": "Algoritme:",
"compare": "Vergelijken:",
"time": "Tijd",
"iters": "Iters",
"time_a": "Tijd A",
"iters_a": "Iters A",
"time_b": "Tijd B",
"iters_b": "Iters B",
"static_check_endpoint": "Dit is gewoon een controle-eindpunt voor uw reverse proxy om te gebruiken.",
"authorization_required": "Autorisatie vereist",
"cookies_disabled": "Uw browser is geconfigureerd om koekjes uit te schakelen. Anubis heeft koekjes nodig om er zeker van te zijn dat u een geldige klant bent. Schakel cookies in voor dit domein",
"access_denied": "Toegang geweigerd: foutcode",
"dronebl_entry": "DroneBL meldde een item",
"see_dronebl_lookup": "zie",
"internal_server_error": "Interne Serverfout: beheerder heeft Anubis verkeerd geconfigureerd. Neem contact op met de beheerder en vraag of hij/zij de logs rond kan kijken",
"invalid_redirect": "Ongeldige omleiding",
"redirect_not_parseable": "Redirect URL niet parseerbaar",
"redirect_domain_not_allowed": "Redirect-domein niet toegestaan",
"failed_to_sign_jwt": "jWT niet ondertekend",
"invalid_invocation": "Ongeldige aanroep van MakeChallenge",
"client_error_browser": "Fout bij klant: Controleer of uw browser bijgewerkt is en probeer het later opnieuw.",
"oh_noes": "Oh nee-tjes!",
"benchmarking_anubis": "Anubis benchmarken!",
"you_are_not_a_bot": "Je bent geen bot!",
"making_sure_not_bot": "Ervoor zorgen dat je geen bot bent!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Uw browser heeft geen werkend web.crypto-element. Bekijkt u dit via een beveiligde context?",
"js_web_workers_error": "Je browser ondersteunt geen web-takers (Anubis gebruikt dit om te voorkomen dat je browser bevriest). Heb je een plugin zoals JShelter geïnstalleerd?",
"js_cookies_error": "Uw browser slaat geen cookies op. Anubis gebruikt cookies om te bepalen welke klanten geslaagd zijn voor uitdagingen door een ondertekend token in een koekje op te slaan. Schakel het opslaan van koekjes voor dit domein in. De namen van de koekjes die Anubis opslaat, kunnen zonder voorafgaande kennisgeving variëren. Koekjesnamen en -waarden maken geen deel uit van de openbare API.",
"js_context_not_secure": "Je context is niet veilig!",
"js_context_not_secure_msg": "Probeer verbinding te maken via HTTPS of laat de beheerder weten dat HTTPS moet worden ingesteld. Zie <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a> voor meer informatie.",
"js_calculating": "Berekenen...",
"js_missing_feature": "Ontbrekende functie",
"js_challenge_error": "Uitdagingsfout!",
"js_challenge_error_msg": "Mislukt bij het oplossen van het controlealgoritme. Misschien wilt u de pagina opnieuw laden.",
"js_calculating_difficulty": "Rekenen...<br/>Moeilijkheidsgraad:",
"js_speed": "Snelheid:",
"js_verification_longer": "Verificatie duurt langer dan verwacht. Vernieuw de pagina niet a.u.b.",
"js_success": "Gelukt!",
"js_done_took": "Klaar! Nam",
"js_iterations": "iteraties",
"js_finished_reading": "Ik ben klaar met lezen, ga verder →",
"js_calculation_error": "Rekenfout!",
"js_calculation_error_msg": "Uitdaging niet berekend:",
"simplified_explanation": "Dit is een maatregel tegen bots en kwaadwillende verzoeken, vergelijkbaar met een CAPTCHA. In plaats van dat u zelf werk moet verrichten, krijgt uw browser een rekentaak die hij moet oplossen om ervoor te zorgen dat het een geldige client is. Dit concept wordt <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a> genoemd. De taak wordt in een paar seconden berekend en u krijgt toegang tot de website. Bedankt voor uw begrip en geduld."
}

View File

@@ -2,12 +2,12 @@
"loading": "Lastar inn...",
"why_am_i_seeing": "Kvifor ser eg dette?",
"protected_by": "Verna av",
"protected_from": "Frå",
"protected_from": "frå",
"made_with": "Laga med ❤️ i 🇨🇦",
"mascot_design": "Maskotdesign av",
"ai_companies_explanation": "Du ser dette av di administratoren av denne nettstaden har sett opp Anubis for å verne sørvaren mot plaga av KI-selskap som aggressivt skrapar nettstader. Dette kan, og held frem med å, forårsake driftstans for nettstadene, som gjer ressursane deira utilgjengelege for alle.",
"anubis_compromise": "Anubis er eit kompromiss. Anubis brukar eit «Proof-of-Work»-skjema som liknar på Hashcash, eit liknande skjema for å redusere søppel-e-post. Idéen er at ved småstilte tilfelle er den ytterlegare lastinga ignorerbar, men ved storstilt skraping samlar ho på seg fart og gjer det å skrapa mykje meir dyrt.",
"hack_purpose": "Til sjuande og sist er dette ein hack som har som formål å gje ei «god nok» plasshaldarløysing slik at meir tid kan brukast på å fingeravtrykkje og attkjenne hovudlause nettlesarar (f.eks. korleis dei attgjev skrifttypar) slik at utfordringssida ikkje må verte synt til brukarar som er meir sannsynlegvis ekte.",
"hack_purpose": "Til sjuande og sist er dette ei plasshaldarløysing slik at meir tid kan brukast på fingeravtrykk og identifisering av hovudlause nettlesarar (t.d. via korleis dei attgjev skrifttypar) slik at utfordringssida for arbeidsprosessen ikkje treng å presenterast for brukarar som er mykje meir sannsynleg å vera legitime.",
"jshelter_note": "NB: Anubis krev bruk av moderne JavaScript-funksjonar som tillegg som JShelter slår av. Venlegast slå av JShelter eller liknande tillegg for dette domenet.",
"version_info": "Denne nettstaden køyrer Anubis-utgåve",
"try_again": "Prøv att",
@@ -53,12 +53,13 @@
"js_challenge_error": "Utfordringsfeil!",
"js_challenge_error_msg": "Mislukkast i å tolke sjekkalgoritmen. Du burde laste inn denne sida på nytt.",
"js_calculating_difficulty": "Reknar…<br/>Vanskenivå:",
"js_speed": "Fart:",
"js_speed": "fart:",
"js_verification_longer": "Verifisering tek lengre enn forventa. Venlegast ikkje last inn denne sida på nytt.",
"js_success": "Vellykka!",
"js_done_took": "Ferdig! Tok",
"js_iterations": "oppattakingar",
"js_finished_reading": "Eg har slutta å lesa, hald fram →",
"js_calculation_error": "Rekningsfeil!",
"js_calculation_error_msg": "Mislukkast i å rekne utfordring:"
"js_calculation_error_msg": "Mislukkast i å rekne utfordring:",
"simplified_explanation": "Dette er eit tiltak mot robotar og vondsinna førespurnader som liknar på ein CAPTCHA. Men i staden for å måtte gjere arbeidet sjølv, får nettlesaren din ei utrekningsoppgåve som han må løyse for å sikre at han er ein gyldig klient. Dette konseptet blir kalla <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Arbeidsbevis</a>. Oppgåva blir rekna ut på nokre få sekund, og du får tilgang til nettstaden. Takk for di forståing og tålmod."
}

View File

@@ -7,7 +7,7 @@
"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 captura agressiva dos dados de páginas da Web. Isso pode causar, e de fato causa, indisponibilidade nos sites, o que os torna inacessíveis para todos.",
"anubis_compromise": "O Anubis é um meio-termo. Ele utiliza um mecanismo de prova de validação semelhante ao Hashcash, proposto para reduzir spam de e-mail. A ideia é que, para acessos individuais, a carga adicional seja insignificante, mas acessos para captura em massa, ela se acumula e torna esse processo muito mais oneroso.",
"hack_purpose": "Por fim, essa é uma solução \"boa o suficiente\" cujo propósito real é gastar mais tempo na identificação de navegadores sem interface (por exemplo: como eles renderizam fontes), para que a página de validação não precise ser apresentada a usuários que têm muito mais probabilidade de serem legítimos.",
"hack_purpose": "Em última análise, esta é uma solução provisória para que mais tempo possa ser gasto na identificação e reconhecimento de navegadores sem interface (por exemplo, através de como eles renderizam fontes), para que a página de prova de trabalho do desafio não precise ser apresentada a usuários que são muito mais propensos a serem legítimos.",
"jshelter_note": "Lembrando que o Anubis requer o uso de recursos JavaScript modernos, que plugins como o JShelter desabilitarão. Desabilite o JShelter ou similares para este domínio.",
"version_info": "Este site está usando o Anubis versão",
"try_again": "Tente novamente",
@@ -60,5 +60,6 @@
"js_iterations": "iterações",
"js_finished_reading": "Terminei de ler, continue →",
"js_calculation_error": "Erro de cálculo!",
"js_calculation_error_msg": "Falha ao calcular a validação:"
"js_calculation_error_msg": "Falha ao calcular a validação:",
"simplified_explanation": "Esta é uma medida contra bots e solicitações maliciosas, semelhante a um CAPTCHA. No entanto, em vez de você mesmo ter que fazer o trabalho, seu navegador recebe uma tarefa de cálculo que ele deve resolver para garantir que seja um cliente válido. Esse conceito é chamado de <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Prova de Trabalho</a>. A tarefa é calculada em poucos segundos e você tem acesso ao site. Obrigado pela sua compreensão e paciência."
}

View File

@@ -7,7 +7,7 @@
"mascot_design": "Дизайн маскота от",
"ai_companies_explanation": "Вы это видите, потому что администратор этого сайта настроил Anubis для защиты сервера от атак, использующих ИИ, которые агрессивно копируют данные с сайтов. Это может привести к зависанию сайтов и делает их ресурсы недоступными для всех.",
"anubis_compromise": "Anubis - это компромисс. Anubis использует Proof-of-Work, похожую на Hashcash, для борьбы со спамом в электронной почте. Идея в том, что на отдельных уровнях дополнительная нагрузка не влиятельна, но на уровне массового парсинга она накапливается и значительно удорожает сбор данных.",
"hack_purpose": "В реальности, это хак, у которого настоящая цель - предоставить «достаточно хорошее» решение, чтобы можно было потратить больше времени на идентификацию headless-браузеров (например, по тому, как они выполняют отрисовку шрифтов), чтобы не отображать страницу с подтверждением пользователям, которые с гораздо большей вероятностью являются настоящими.",
"hack_purpose": "В конечном итоге, это временное решение, чтобы можно было уделить больше времени снятию отпечатков и идентификации безголовых браузеров (например, по тому, как они отрисовывают шрифты), чтобы страница с доказательством работы не требовалась для пользователей, которые с гораздо большей вероятностью являются легитимными.",
"jshelter_note": "Anubis требует использования современных функций JavaScript, которые плагины, по типу JShelter, отключают. Пожалуйста, отключите JShelter и другие подобные плагины для этого домена.",
"version_info": "На сайте запущен Anubis версии",
"try_again": "Попробуйте снова",
@@ -60,5 +60,6 @@
"js_iterations": "итераций",
"js_finished_reading": "Я дочитал, продолжить →",
"js_calculation_error": "Ошибка расчёта!",
"js_calculation_error_msg": "Не удалось рассчитать задачу:"
"js_calculation_error_msg": "Не удалось рассчитать задачу:",
"simplified_explanation": "Это мера против ботов и вредоносных запросов, аналогичная CAPTCHA. Однако вместо того, чтобы вам приходилось работать самостоятельно, вашему браузеру дается задача вычисления, которую он должен решить, чтобы убедиться, что он является действительным клиентом. Эта концепция называется <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Доказательство выполнения работы</a>. Задача рассчитывается за несколько секунд, и вам предоставляется доступ к веб-сайту. Спасибо за понимание и терпение."
}

View File

@@ -0,0 +1,65 @@
{
"loading": "Laddar...",
"why_am_i_seeing": "Varför ser jag detta?",
"protected_by": "Skyddat av",
"protected_from": "Från",
"made_with": "Gjort med ❤️ i 🇨🇦",
"mascot_design": "Maskotdesign av",
"ai_companies_explanation": "Du ser detta eftersom att administratören av denna webbsida har upprättat Anubis-systemet för att skydda servern mot plågan av att AI-företag aggressivt skrapar webbsidor. Detta kan orsaka driftstopp för webbsidor, vilket gör deras resurser otillgängliga för alla.",
"anubis_compromise": "Anubis är en kompromiss. Anubis använder sig av ett arbetsbevissystem på samma sätt som Hashcash, ett förslag om arbetsbevissystem för att minska epostspam. Idén är att den extra belastningen är obetydlig på en individuell skala, men att den på massskrapningsnivåer adderas upp och gör processen mycket dyrare.",
"hack_purpose": "I slutändan är detta en platshållarlösning så att mer tid kan ägnas åt fingeravtryck och identifiering av huvudlösa webbläsare (t.ex. via hur de renderar teckensnitt) så att utmaningens bevis på arbete-sida inte behöver presenteras för användare som är mycket mer sannolikt att vara legitima.",
"jshelter_note": "Notera att Anubis kräver användningen av moderna JavaScript-funktioner som tillägg såsom JShelter kommer att avaktivera. Var vänlig och avaktivera JShelter eller andra liknande tillägg för denna domän.",
"version_info": "Den här webbsidan kör Anubis version",
"try_again": "Försök igen",
"go_home": "Gå hem",
"contact_webmaster": "eller om du tycker att du inte borde bli blockerad, kontakta den webbansvarige på",
"connection_security": "Var vänlig och vänta en stund medan vi säkerställer din anslutnings säkerhet.",
"javascript_required": "Tyvärr måste du slå igång JavaScript för att komma förbi denna utmaning. Detta eftersom AI-företag har ändrat samhällskontraktet gällande webbhosting. En lösning som icke kräver JavaScript ett pågående arbete.",
"benchmark_requires_js": "För att köra prestandamätningsverktyget krävs det att JavaScript är igång.",
"difficulty": "Svårighetsgrad:",
"algorithm": "Algoritm:",
"compare": "Jämför:",
"time": "Tid",
"iters": "Iterationer",
"time_a": "Tid A",
"iters_a": "Iterationer A",
"time_b": "Tid B",
"iters_b": "Iterationer B",
"static_check_endpoint": "Detta är bara en kontrollendpunkt för användning av din reverse-proxy.",
"authorization_required": "Tillstånd krävs",
"cookies_disabled": "Din webbläsare är konfigurerad för att inaktivera cookies. Anubis kräver cookies för att säkerställa att du är en giltig klient. Var vänlig och aktivera cookies för den här domänen",
"access_denied": "Tillstånd nekat: felkod",
"dronebl_entry": "DroneBL rapporterade en post",
"see_dronebl_lookup": "visa",
"internal_server_error": "Internt serverfel: administratören har felkonfigurerat Anubis. Kontakta administratören och be dem att leta efter loggarna.",
"invalid_redirect": "Ogiltig omdirigering",
"redirect_not_parseable": "Omdirigeringsurl icke tolkbar",
"redirect_domain_not_allowed": "Omdirigeringsdomän icke tillåten",
"failed_to_sign_jwt": "misslyckades att signera JWT",
"invalid_invocation": "Ogiltigt anrop av MakeChallenge",
"client_error_browser": "Klientfel: Dubbelkolla att din webbläsare är uppdaterad och försök igen senare.",
"oh_noes": "Aj då!",
"benchmarking_anubis": "Prestandamäter Anubis!",
"you_are_not_a_bot": "Du är inte en bot!",
"making_sure_not_bot": "Kollar så att du inte är en bot!",
"celphase": "CELPHASE",
"js_web_crypto_error": "Din webbläsare har inte ett fungerande web.crypto-element. Ser du denna sida över en säker webbläsarkontext?",
"js_web_workers_error": "Din webbläsare stödjer inte webbworkers-teknik (Anubis använder sig av detta för att undvika att din webbläsare fryser). Har du ett tillägg såsom JShelter installerat?",
"js_cookies_error": "Din webbläsare lagrar inte cookies. Anubis använder sig av cookies för att avgöra vilka klienter som har klarat utmaningar genom att lagra en signerad token i en cookie. Vänligen aktivera lagring av cookies för den här domänen. Namnen på de cookies som Anubis lagrar kan variera utan varsel då cookienamn och värden inte ingår i det publika API:et.",
"js_context_not_secure": "Din webbläsarkontext är ej säker!",
"js_context_not_secure_msg": "Försök att ansluta via HTTPS eller kontakta administratören och be dem att konfigurera HTTPS. För mer information, se <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Beräknar...",
"js_missing_feature": "Funktion saknas",
"js_challenge_error": "Utmaningsfel!",
"js_challenge_error_msg": "Misslyckades att lösa kontrollalgoritm. Du bör ladda om sidan.",
"js_calculating_difficulty": "Beräknar...<br/>Svårighetsgrad:",
"js_speed": "Hastighet:",
"js_verification_longer": "Verifikation tar längre än förväntat. Ladda ej om sidan.",
"js_success": "Lyckades!",
"js_done_took": "Klart! tog",
"js_iterations": "iterationer",
"js_finished_reading": "Jag har läst klart, fortsätt →",
"js_calculation_error": "Beräkningsfel!",
"js_calculation_error_msg": "Misslyckades att kalkylera utmaning:",
"simplified_explanation": "Detta är en åtgärd mot botar och skadliga förfrågningar som liknar en CAPTCHA. Men i stället för att du själv måste göra jobbet får din webbläsare en beräkningsuppgift som den måste lösa för att säkerställa att den är en giltig klient. Detta koncept kallas <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Arbetsbevis</a>. Uppgiften beräknas på några sekunder och du beviljas tillgång till webbplatsen. Tack för din förståelse och ditt tålamod."
}

View File

@@ -2,19 +2,19 @@
"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ı",
"protected_from": "Yapan:",
"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.",
"ai_companies_explanation": "Bunu görüyorsunuz; çünkü bu web sitesinin yöneticisi, yapay zekâ şirketlerinin web sitelerini agresif şekilde kazımasına karşı sunucuyu korumak için Anubisi kurdu. Bu tarz kazımalar sitelerin erişilemez olmasına ve kesintilere neden olabiliyor.",
"anubis_compromise": "Anubis bir uzlaşmadır. Anubis, istenmeyen 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": "Nihayetinde, bu bir yer tutucu çözümdür, böylece başsız tarayıcıların parmak izi alma ve tanımlama (örneğin, yazı tipi oluşturma şekilleri aracılığıyla) için daha fazla zaman harcanabilir, böylece iş kanıtı sayfası meşru olma olasılığı çok daha yüksek olan kullanıcılara sunulmak zorunda kalmaz.",
"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:",
"contact_webmaster": "veya 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.",
"javascript_required": "Ne yazık ki bu aşamayı geçebilmek için JavaScripti etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zekâ ş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:",
@@ -25,19 +25,19 @@
"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.",
"static_check_endpoint": "Bu sadece, ters vekil sunucunuzun 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",
"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:",
"internal_server_error": "Sunucu Hatası: Yönetici Anubisi yanlış yapılandırmış. Lütfen yöneticinizle iletişime geçin ve şu civardaki kayıtlara bakmasını isteyin:",
"invalid_redirect": "Geçersiz yönlendirme",
"redirect_not_parseable": "Yönlendirme URL'si çözümlenemiyor",
"redirect_not_parseable": "Yönlendirme URLsi çö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.",
"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!",
@@ -60,5 +60,6 @@
"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:"
}
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:",
"simplified_explanation": "Bu, botlara ve kötü niyetli isteklere karşı CAPTCHA'ya benzer bir önlemdir. Ancak, kendiniz çalışmak yerine, tarayıcınıza geçerli bir istemci olduğundan emin olmak için çözmesi gereken bir hesaplama görevi verilir. Bu kavrama <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">İş Kanıtı</a> denir. Görev birkaç saniye içinde hesaplanır ve web sitesine erişim hakkı kazanırsınız. Anlayışınız ve sabrınız için teşekkür ederiz."
}

View File

@@ -7,7 +7,7 @@
"mascot_design": "吉祥物由",
"ai_companies_explanation": "您会看到这个画面,是因为网站管理员启用了 Anubis 来保护服务器,避免 AI 公司大量爬取网站内容。这类行为会导致网站崩溃,让所有用户都无法正常访问资源。",
"anubis_compromise": "Anubis 是一种折中做法。它采用了类似 Hashcash 的工作量证明机制Proof-of-Work该机制最初是为了减少垃圾邮件而提出。其核心概念是对个别用户而言额外的计算负担可以忽略但对大规模爬虫来说累积起来的成本将大幅增加从而让爬取行为变得更困难。",
"hack_purpose": "本质上,这是一种权宜的解法,目的是提供一个“够用”的暂时性防护措施,好让开发者有更多时间针对无头浏览器进行指纹特征识别(例如:分析其字体渲染方式),以便未来不再需要对可能合法用户的访客展示工作量证明页面。",
"hack_purpose": "最终,这是一个占位符解决方案,以便将更多时间用于指纹识别和识别无头浏览器(例如:通过它们如何进行字体渲染),从而无需向更可能合法用户的用户呈现挑战工作量证明页面。",
"jshelter_note": "请注意Anubis 需要使用现代 JavaScript 功能,而像 JShelter 这类插件可能会阻挡这些功能。请为此域名停用 JShelter 或类似的插件。",
"version_info": "这个网站正在运行的 Anubis 版本为",
"try_again": "再试一次",
@@ -60,5 +60,6 @@
"js_iterations": "迭代",
"js_finished_reading": "我读完了,继续 →",
"js_calculation_error": "计算错误!",
"js_calculation_error_msg": "计算挑战失败:"
"js_calculation_error_msg": "计算挑战失败:",
"simplified_explanation": "这是一种类似于验证码的措施,用于防止机器人和恶意请求。但是,您无需自己动手,您的浏览器会收到一个计算任务,必须解决该任务以确保它是有效的客户端。这个概念称为<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">工作量证明</a>。该任务在几秒钟内计算完毕,您将被授予访问网站的权限。感谢您的理解和耐心。"
}

View File

@@ -7,7 +7,7 @@
"mascot_design": "吉祥物由",
"ai_companies_explanation": "您會看到這個畫面,是因為網站管理員啟用了 Anubis 來保護伺服器,避免 AI 公司大量爬取網站內容。這類行為會導致網站當機,讓所有使用者都無法正常存取資源。",
"anubis_compromise": "Anubis 是一種折衷做法。它採用了類似 Hashcash 的工作量證明機制Proof-of-Work該機制最初是為了減少垃圾郵件而提出。其核心概念是對個別使用者而言額外的運算負擔可以忽略但對大規模爬蟲來說累積起來的成本將大幅增加從而讓爬取行為變得更困難。",
"hack_purpose": "本質上,這是一種權宜的解法,目的是提供一個「夠用」的暫時性防護措施,好讓開發者有更多時間針對無頭瀏覽器進行指紋特徵辨識(例如:分析其字型渲染方式),以便未來不再需要對可能合法使用者的訪客展示工作量證明頁面。",
"hack_purpose": "最終,這是一個佔位符解決方案,以便將更多時間用於指紋識別和識別無頭瀏覽器(例如:透過它們如何進行字體渲染),從而無需向更可能合法用戶的用戶呈現挑戰工作量證明頁面。",
"jshelter_note": "請注意Anubis 需要使用現代 JavaScript 功能,而像 JShelter 這類外掛可能會阻擋這些功能。請為此網域停用 JShelter 或類似的插件。",
"version_info": "這個網站正在運行 Anubis 版本",
"try_again": "再試一次",
@@ -60,5 +60,6 @@
"js_iterations": "迭代",
"js_finished_reading": "我讀完了,繼續 →",
"js_calculation_error": "計算錯誤!",
"js_calculation_error_msg": "計算挑戰失敗:"
"js_calculation_error_msg": "計算挑戰失敗:",
"simplified_explanation": "這是一種類似於驗證碼的措施,用於防止機器人和惡意請求。但是,您無需自己動手,您的瀏覽器會收到一個計算任務,必須解決該任務以確保它是有效的客戶端。這個概念稱為<a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">工作量證明</a>。該任務在幾秒鐘內計算完畢,您將被授予訪問網站的權限。感謝您的理解和耐心。"
}

View File

@@ -22,12 +22,14 @@ func TestLocalizationService(t *testing.T) {
"ja": "ロード中...",
"is": "Hleður...",
"nb": "Laster inn...",
"nl": "Laden...",
"nn": "Lastar inn...",
"pt-BR": "Carregando...",
"tr": "Yükleniyor...",
"ru": "Загрузка...",
"zh-CN": "加载中...",
"zh-TW": "載入中...",
"sv" : "Laddar...",
}
var keys []string

View File

@@ -0,0 +1,53 @@
//go:build ignore
package config
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/TecharoHQ/anubis/lib/checker"
)
var (
ErrUnknownCheckType = errors.New("config.Bot.Check: unknown check type")
)
type AllChecks struct {
All []Check `json:"all"`
}
type AnyChecks struct {
All []Check `json:"any"`
}
type Check struct {
Type string `json:"type"`
Args json.RawMessage `json:"args"`
}
func (c *Check) Valid(ctx context.Context) error {
var errs []error
if len(c.Type) == 0 {
errs = append(errs, ErrNoStoreBackend)
}
fac, ok := checker.Get(c.Type)
switch ok {
case true:
if err := fac.Valid(ctx, c.Args); err != nil {
errs = append(errs, err)
}
case false:
errs = append(errs, fmt.Errorf("%w: %q", ErrUnknownCheckType, c.Type))
}
if len(errs) != 0 {
return errors.Join(errs...)
}
return nil
}

View File

@@ -194,7 +194,7 @@ type ChallengeRules struct {
}
var (
ErrChallengeDifficultyTooLow = errors.New("config.ChallengeRules: difficulty is too low (must be >= 1)")
ErrChallengeDifficultyTooLow = errors.New("config.ChallengeRules: difficulty is too low (must be >= 0)")
ErrChallengeDifficultyTooHigh = errors.New("config.ChallengeRules: difficulty is too high (must be <= 64)")
ErrChallengeMustHaveAlgorithm = errors.New("config.ChallengeRules: must have algorithm name set")
)
@@ -206,7 +206,7 @@ func (cr ChallengeRules) Valid() error {
errs = append(errs, ErrChallengeMustHaveAlgorithm)
}
if cr.Difficulty < 1 {
if cr.Difficulty < 0 {
errs = append(errs, fmt.Errorf("%w, got: %d", ErrChallengeDifficultyTooLow, cr.Difficulty))
}

View File

@@ -109,7 +109,7 @@ func TestBotValid(t *testing.T) {
Action: RuleChallenge,
PathRegex: p("Mozilla"),
Challenge: &ChallengeRules{
Difficulty: 0,
Difficulty: -1,
ReportAs: 4,
Algorithm: "fast",
},

View File

@@ -70,7 +70,7 @@ func TestThresholdValid(t *testing.T) {
name: "challenge invalid",
input: &Threshold{
Action: RuleChallenge,
Challenge: &ChallengeRules{Difficulty: 0, ReportAs: 0},
Challenge: &ChallengeRules{Difficulty: -1, ReportAs: -1},
},
err: ErrChallengeDifficultyTooLow,
},

View File

@@ -2,6 +2,7 @@ package expressions
import (
"math/rand/v2"
"strings"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
@@ -54,6 +55,28 @@ func BotEnvironment() (*cel.Env, error) {
}),
),
),
cel.Function("segments",
cel.Overload("segments_string_list_string",
[]*cel.Type{cel.StringType},
cel.ListType(cel.StringType),
cel.UnaryBinding(func(path ref.Val) ref.Val {
pathStrType, ok := path.(types.String)
if !ok {
return types.ValOrErr(path, "path is not a string, but is %T", path)
}
pathStr := string(pathStrType)
if !strings.HasPrefix(pathStr, "/") {
return types.ValOrErr(path, "path does not start with /")
}
pathList := strings.Split(string(pathStr), "/")[1:]
return types.NewStringList(types.DefaultTypeAdapter, pathList)
}),
),
),
)
}

View File

@@ -12,99 +12,228 @@ func TestBotEnvironment(t *testing.T) {
t.Fatalf("failed to create bot environment: %v", err)
}
tests := []struct {
name string
expression string
headers map[string]string
expected types.Bool
description string
}{
{
name: "missing-header",
expression: `missingHeader(headers, "Missing-Header")`,
headers: map[string]string{
"User-Agent": "test-agent",
"Content-Type": "application/json",
t.Run("missingHeader", func(t *testing.T) {
tests := []struct {
name string
expression string
headers map[string]string
expected types.Bool
description string
}{
{
name: "missing-header",
expression: `missingHeader(headers, "Missing-Header")`,
headers: map[string]string{
"User-Agent": "test-agent",
"Content-Type": "application/json",
},
expected: types.Bool(true),
description: "should return true when header is missing",
},
expected: types.Bool(true),
description: "should return true when header is missing",
},
{
name: "existing-header",
expression: `missingHeader(headers, "User-Agent")`,
headers: map[string]string{
"User-Agent": "test-agent",
"Content-Type": "application/json",
{
name: "existing-header",
expression: `missingHeader(headers, "User-Agent")`,
headers: map[string]string{
"User-Agent": "test-agent",
"Content-Type": "application/json",
},
expected: types.Bool(false),
description: "should return false when header exists",
},
expected: types.Bool(false),
description: "should return false when header exists",
},
{
name: "case-sensitive",
expression: `missingHeader(headers, "user-agent")`,
headers: map[string]string{
"User-Agent": "test-agent",
{
name: "case-sensitive",
expression: `missingHeader(headers, "user-agent")`,
headers: map[string]string{
"User-Agent": "test-agent",
},
expected: types.Bool(true),
description: "should be case-sensitive (user-agent != User-Agent)",
},
expected: types.Bool(true),
description: "should be case-sensitive (user-agent != User-Agent)",
},
{
name: "empty-headers",
expression: `missingHeader(headers, "Any-Header")`,
headers: map[string]string{},
expected: types.Bool(true),
description: "should return true for any header when map is empty",
},
{
name: "real-world-sec-ch-ua",
expression: `missingHeader(headers, "Sec-Ch-Ua")`,
headers: map[string]string{
"User-Agent": "curl/7.68.0",
"Accept": "*/*",
"Host": "example.com",
{
name: "empty-headers",
expression: `missingHeader(headers, "Any-Header")`,
headers: map[string]string{},
expected: types.Bool(true),
description: "should return true for any header when map is empty",
},
expected: types.Bool(true),
description: "should detect missing browser-specific headers from bots",
},
{
name: "browser-with-sec-ch-ua",
expression: `missingHeader(headers, "Sec-Ch-Ua")`,
headers: map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Sec-Ch-Ua": `"Chrome"; v="91", "Not A Brand"; v="99"`,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
{
name: "real-world-sec-ch-ua",
expression: `missingHeader(headers, "Sec-Ch-Ua")`,
headers: map[string]string{
"User-Agent": "curl/7.68.0",
"Accept": "*/*",
"Host": "example.com",
},
expected: types.Bool(true),
description: "should detect missing browser-specific headers from bots",
},
expected: types.Bool(false),
description: "should return false when browser sends Sec-Ch-Ua header",
},
}
{
name: "browser-with-sec-ch-ua",
expression: `missingHeader(headers, "Sec-Ch-Ua")`,
headers: map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Sec-Ch-Ua": `"Chrome"; v="91", "Not A Brand"; v="99"`,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
},
expected: types.Bool(false),
description: "should return false when browser sends Sec-Ch-Ua header",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prog, err := Compile(env, tt.expression)
if err != nil {
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prog, err := Compile(env, tt.expression)
if err != nil {
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
}
result, _, err := prog.Eval(map[string]interface{}{
"headers": tt.headers,
result, _, err := prog.Eval(map[string]interface{}{
"headers": tt.headers,
})
if err != nil {
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
}
if result != tt.expected {
t.Errorf("%s: expected %v, got %v", tt.description, tt.expected, result)
}
})
if err != nil {
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
}
}
if result != tt.expected {
t.Errorf("%s: expected %v, got %v", tt.description, tt.expected, result)
t.Run("function-compilation", func(t *testing.T) {
src := `missingHeader(headers, "Test-Header")`
_, err := Compile(env, src)
if err != nil {
t.Fatalf("failed to compile missingHeader expression: %v", err)
}
})
}
})
t.Run("function-compilation", func(t *testing.T) {
src := `missingHeader(headers, "Test-Header")`
_, err := Compile(env, src)
if err != nil {
t.Fatalf("failed to compile missingHeader expression: %v", err)
t.Run("segments", func(t *testing.T) {
for _, tt := range []struct {
name string
description string
expression string
path string
expected types.Bool
}{
{
name: "simple",
description: "/ should have one path segment",
expression: `size(segments(path)) == 1`,
path: "/",
expected: types.Bool(true),
},
{
name: "two segments without trailing slash",
description: "/user/foo should have two segments",
expression: `size(segments(path)) == 2`,
path: "/user/foo",
expected: types.Bool(true),
},
{
name: "at least two segments",
description: "/foo/bar/ should have at least two path segments",
expression: `size(segments(path)) >= 2`,
path: "/foo/bar/",
expected: types.Bool(true),
},
{
name: "at most two segments",
description: "/foo/bar/ does not have less than two path segments",
expression: `size(segments(path)) < 2`,
path: "/foo/bar/",
expected: types.Bool(false),
},
} {
t.Run(tt.name, func(t *testing.T) {
prog, err := Compile(env, tt.expression)
if err != nil {
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
}
result, _, err := prog.Eval(map[string]interface{}{
"path": tt.path,
})
if err != nil {
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
}
if result != tt.expected {
t.Errorf("%s: expected %v, got %v", tt.description, tt.expected, result)
}
})
}
t.Run("invalid", func(t *testing.T) {
for _, tt := range []struct {
name string
description string
expression string
env any
wantFailCompile bool
wantFailEval bool
}{
{
name: "segments of headers",
description: "headers are not a path list",
expression: `segments(headers)`,
env: map[string]any{
"headers": map[string]string{
"foo": "bar",
},
},
wantFailCompile: true,
},
{
name: "invalid path type",
description: "a path should be a sting",
expression: `size(segments(path)) != 0`,
env: map[string]any{
"path": 4,
},
wantFailEval: true,
},
{
name: "invalid path",
description: "a path should start with a leading slash",
expression: `size(segments(path)) != 0`,
env: map[string]any{
"path": "foo",
},
wantFailEval: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
prog, err := Compile(env, tt.expression)
if err != nil {
if !tt.wantFailCompile {
t.Log(tt.description)
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
} else {
return
}
}
_, _, err = prog.Eval(tt.env)
if err == nil {
t.Log(tt.description)
t.Fatal("wanted an error but got none")
}
t.Log(err)
})
}
})
t.Run("function-compilation", func(t *testing.T) {
src := `size(segments(path)) <= 2`
_, err := Compile(env, src)
if err != nil {
t.Fatalf("failed to compile missingHeader expression: %v", err)
}
})
})
}

View File

@@ -8,10 +8,10 @@ import (
"log/slog"
"sync/atomic"
"github.com/TecharoHQ/anubis/internal/thoth"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/store"
"github.com/TecharoHQ/anubis/lib/thoth"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
@@ -149,6 +149,10 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
if parsedBot.Challenge.Algorithm == "" {
parsedBot.Challenge.Algorithm = config.DefaultAlgorithm
}
if parsedBot.Challenge.Algorithm == "slow" {
slog.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", parsedBot.Name)
}
}
if b.Weight != nil {
@@ -163,6 +167,10 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
}
for _, t := range c.Thresholds {
if t.Challenge != nil && t.Challenge.Algorithm == "slow" {
slog.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", t.Name)
}
if t.Name == "legacy-anubis-behaviour" && t.Expression.String() == "true" {
if !warnedAboutThresholds.Load() {
slog.Warn("configuration file does not contain thresholds, see docs for details on how to upgrade", "fname", fname, "docs_url", "https://anubis.techaro.lol/docs/admin/configuration/thresholds/")

View File

@@ -7,19 +7,19 @@ import (
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/data"
"github.com/TecharoHQ/anubis/internal/thoth/thothmock"
"github.com/TecharoHQ/anubis/lib/thoth/thothmock"
)
func TestDefaultPolicyMustParse(t *testing.T) {
ctx := thothmock.WithMockThoth(t)
fin, err := data.BotPolicies.Open("botPolicies.json")
fin, err := data.BotPolicies.Open("botPolicies.yaml")
if err != nil {
t.Fatal(err)
}
defer fin.Close()
if _, err := ParseConfig(ctx, fin, "botPolicies.json", anubis.DefaultDifficulty); err != nil {
if _, err := ParseConfig(ctx, fin, "botPolicies.yaml", anubis.DefaultDifficulty); err != nil {
t.Fatalf("can't parse config: %v", err)
}
}

4
lib/testdata/permissive.yaml vendored Normal file
View File

@@ -0,0 +1,4 @@
bots:
- import: (data)/common/allow-private-addresses.yaml
dnsbl: false

View File

@@ -35,4 +35,11 @@ status_codes:
CHALLENGE: 200
DENY: 200
thresholds: []
thresholds:
- name: minimal-suspicion
expression: "true"
action: CHALLENGE
challenge:
algorithm: fast
difficulty: 1
report_as: 1

View File

@@ -0,0 +1,38 @@
bots:
- import: (data)/bots/_deny-pathological.yaml
- import: (data)/bots/aggressive-brazilian-scrapers.yaml
- import: (data)/meta/ai-block-aggressive.yaml
- import: (data)/crawlers/_allow-good.yaml
- import: (data)/clients/x-firefox-ai.yaml
- import: (data)/common/keep-internet-working.yaml
- name: countries-with-aggressive-scrapers
action: WEIGH
geoip:
countries:
- BR
- CN
weight:
adjust: 10
- name: aggressive-asns-without-functional-abuse-contact
action: WEIGH
asns:
match:
- 13335 # Cloudflare
- 136907 # Huawei Cloud
- 45102 # Alibaba Cloud
weight:
adjust: 10
- name: generic-browser
user_agent_regex: >-
Mozilla|Opera
action: WEIGH
weight:
adjust: 10
dnsbl: false
status_codes:
CHALLENGE: 200
DENY: 200
thresholds: []

45
lib/testdata/zero_difficulty.yaml vendored Normal file
View File

@@ -0,0 +1,45 @@
bots:
- import: (data)/bots/_deny-pathological.yaml
- import: (data)/bots/aggressive-brazilian-scrapers.yaml
- import: (data)/meta/ai-block-aggressive.yaml
- import: (data)/crawlers/_allow-good.yaml
- import: (data)/clients/x-firefox-ai.yaml
- import: (data)/common/keep-internet-working.yaml
- name: countries-with-aggressive-scrapers
action: WEIGH
geoip:
countries:
- BR
- CN
weight:
adjust: 10
- name: aggressive-asns-without-functional-abuse-contact
action: WEIGH
asns:
match:
- 13335 # Cloudflare
- 136907 # Huawei Cloud
- 45102 # Alibaba Cloud
weight:
adjust: 10
- name: generic-browser
user_agent_regex: >-
Mozilla|Opera
action: WEIGH
weight:
adjust: 10
dnsbl: false
status_codes:
CHALLENGE: 200
DENY: 200
thresholds:
- name: minimal-suspicion
expression: "true"
action: CHALLENGE
challenge:
algorithm: fast
difficulty: 0
report_as: 0

View File

@@ -5,8 +5,8 @@ import (
"net/http/httptest"
"testing"
"github.com/TecharoHQ/anubis/internal/thoth"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/thoth"
iptoasnv1 "github.com/TecharoHQ/thoth-proto/gen/techaro/thoth/iptoasn/v1"
)

View File

@@ -5,8 +5,8 @@ import (
"net/http/httptest"
"testing"
"github.com/TecharoHQ/anubis/internal/thoth"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/thoth"
)
var _ checker.Impl = &thoth.GeoIPChecker{}

View File

@@ -4,8 +4,8 @@ import (
"os"
"testing"
"github.com/TecharoHQ/anubis/internal/thoth"
"github.com/TecharoHQ/anubis/internal/thoth/thothmock"
"github.com/TecharoHQ/anubis/lib/thoth"
"github.com/TecharoHQ/anubis/lib/thoth/thothmock"
"github.com/joho/godotenv"
)

View File

@@ -4,7 +4,7 @@ import (
"context"
"testing"
"github.com/TecharoHQ/anubis/internal/thoth"
"github.com/TecharoHQ/anubis/lib/thoth"
)
func WithMockThoth(t *testing.T) context.Context {

97
package-lock.json generated
View File

@@ -8,6 +8,9 @@
"name": "@techaro/anubis",
"version": "1.21.3",
"license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0"
},
"devDependencies": {
"cssnano": "^7.1.0",
"cssnano-preset-advanced": "^7.0.8",
@@ -19,6 +22,44 @@
"postcss-url": "^10.1.3"
}
},
"node_modules/@aws-crypto/sha256-js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
"integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-crypto/util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
"integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.222.0",
"@smithy/util-utf8": "^2.0.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-sdk/types": {
"version": "3.840.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz",
"integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.3.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -461,6 +502,56 @@
"node": ">=18"
}
},
"node_modules/@smithy/is-array-buffer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/types": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz",
"integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/util-buffer-from": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/is-array-buffer": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-utf8": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/util-buffer-from": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2515,6 +2606,12 @@
"node": ">=8.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",

View File

@@ -26,5 +26,8 @@
"postcss-import": "^16.1.1",
"postcss-import-url": "^7.2.0",
"postcss-url": "^10.1.3"
},
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0"
}
}
}

View File

@@ -0,0 +1,33 @@
package internal
import (
"context"
"fmt"
"github.com/docker/docker/client"
)
// GetContainerIPAddress returns the first non-empty IP address of the container with the given name.
// It returns the IP address as a string or an error.
func GetContainerIPAddress(containerName string) (string, error) {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return "", err
}
// Get container details
containerJSON, err := cli.ContainerInspect(ctx, containerName)
if err != nil {
return "", err
}
// Loop through all networks and return the first IP address found
for _, net := range containerJSON.NetworkSettings.Networks {
if net.IPAddress != "" {
return net.IPAddress, nil
}
}
return "", fmt.Errorf("no IP address found for container %q", containerName)
}

View File

@@ -0,0 +1,50 @@
package internal
import (
"fmt"
"net"
)
// GetLANIP returns the first non-loopback IPv4 LAN IP address.
func GetLANIP() (net.IP, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
// Skip down or loopback interfaces
if iface.Flags&(net.FlagUp|net.FlagLoopback) != net.FlagUp {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue // skip interfaces we can't query
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an IPv4 address
}
return ip, nil
}
}
return nil, fmt.Errorf("no connected LAN IPv4 address found")
}

View File

@@ -0,0 +1,34 @@
package internal
import (
"context"
"log"
"os"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
)
// UnbreakDocker connects the container named after the current hostname
// to the specified Docker network.
func UnbreakDocker(networkName string) error {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return err
}
hostname, err := os.Hostname()
if err != nil {
return err
}
err = cli.NetworkConnect(ctx, networkName, hostname, &network.EndpointSettings{})
if err != nil {
return err
}
log.Printf("Connected container %q to network %q\n", hostname, networkName)
return nil
}

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