diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0602ff19..9d17aaa4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,9 +2,7 @@ // README at: https://github.com/devcontainers/templates/tree/main/src/debian { "name": "Dev", - "dockerComposeFile": [ - "./docker-compose.yaml" - ], + "dockerComposeFile": ["./docker-compose.yaml"], "service": "workspace", "workspaceFolder": "/workspace/anubis", "postStartCommand": "bash ./.devcontainer/poststart.sh", @@ -31,4 +29,4 @@ } } } -} \ No newline at end of file +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index f7f94316..4a275b0a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -58,4 +58,3 @@ body: attributes: label: Additional context description: Add any other context about the problem here. - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 64f710b9..fd5b6707 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,6 +1,6 @@ name: Feature request description: Suggest an idea for this project -title: '[Feature request] ' +title: "[Feature request] " body: - type: textarea diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1f61d82c..370a73ed 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,12 +1,12 @@ Checklist: - [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md -- [ ] Added test cases to [the relevant parts of the codebase](https://anubis.techaro.lol/docs/developer/code-quality) +- [ ] Added test cases to [the relevant parts of the codebase](https://github.com/TecharoHQ/anubis/blob/main/CONTRIBUTING.md) - [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL) - [ ] All of my commits have [verified signatures](https://anubis.techaro.lol/docs/developer/signed-commits) diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md index 1f699f3d..ef73ac26 100644 --- a/.github/actions/spelling/README.md +++ b/.github/actions/spelling/README.md @@ -1,17 +1,17 @@ # check-spelling/check-spelling configuration -File | Purpose | Format | Info --|-|-|- -[dictionary.txt](dictionary.txt) | Replacement dictionary (creating this file will override the default dictionary) | one word per line | [dictionary](https://github.com/check-spelling/check-spelling/wiki/Configuration#dictionary) -[allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow) -[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject) -[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes) -[only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only) -[patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) -[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns) -[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) -[expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect) -[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) +| File | Purpose | Format | Info | +| -------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| [dictionary.txt](dictionary.txt) | Replacement dictionary (creating this file will override the default dictionary) | one word per line | [dictionary](https://github.com/check-spelling/check-spelling/wiki/Configuration#dictionary) | +| [allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow) | +| [reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject) | +| [excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes) | +| [only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only) | +| [patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) | +| [candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns) | +| [line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) | +| [expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect) | +| [advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) | Note: you can replace any of these files with a directory by the same name (minus the suffix) and then include multiple files inside that directory (with that suffix) to merge multiple files together. diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md index a32d1090..17445e53 100644 --- a/.github/actions/spelling/advice.md +++ b/.github/actions/spelling/advice.md @@ -2,30 +2,27 @@
If the flagged items are :exploding_head: false positives If items relate to a ... -* binary file (or some other file you wouldn't want to check at all). + +- binary file (or some other file you wouldn't want to check at all). Please add a file path to the `excludes.txt` file matching the containing file. - File paths are Perl 5 Regular Expressions - you can [test]( -https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files. + File paths are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files. - `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md]( -../tree/HEAD/README.md) (on whichever branch you're using). + `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](../tree/HEAD/README.md) (on whichever branch you're using). -* well-formed pattern. +- well-formed pattern. - If you can write a [pattern]( -https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns -) that would match it, + If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it, try adding it to the `patterns.txt` file. - Patterns are Perl 5 Regular Expressions - you can [test]( -https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines. + Patterns are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines. Note that patterns can't match multiline strings.
+ :steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling, please merge to your PR's base branch to get the version configured for your repository. diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 6ec3321d..e24f79ce 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -24,3 +24,13 @@ iplist NArg blocklists rififi +prolocation +Prolocation +Necron +Stargate +FFXIV +uvensys +de +resourced +envoyproxy +unipromos diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 7f22bb41..32506348 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -2,10 +2,12 @@ acs Actorified actorifiedstore actorify +agentic Aibrew alibaba alrest amazonbot +anexia anthro anubis anubistest @@ -61,7 +63,9 @@ checkresult chibi cidranger ckie +CLAUDE cloudflare +cloudsolutions Codespaces confd containerbuild @@ -74,6 +78,7 @@ Cscript daemonizing databento dayjob +dco DDOS Debian debrpm @@ -114,6 +119,8 @@ FCr fcrdns fediverse ffprobe +FFXIV +fhdr financials finfos Firecrawl @@ -134,6 +141,7 @@ gipc gitea GLM godotenv +goimports goland gomod goodbot @@ -150,6 +158,7 @@ grw gzw Hashcash hashrate +hdr headermap healthcheck healthz @@ -159,6 +168,7 @@ Hetzner hmc homelab hostable +HSTS htmlc htmx httpdebug @@ -229,6 +239,7 @@ mymaster mypass myuser nbf +Necron nepeat netsurf nginx @@ -321,11 +332,14 @@ Spambot spammer sparkline spyderbot +srcip srv stackoverflow +Stargate startprecmd stoppostcmd storetest +strcmp subgrid subr subrequest @@ -350,12 +364,14 @@ Timpibot TLog traefik trunc +txn uberspace Unbreak unbreakdocker unifiedjs unmarshal unparseable +updown uvx UXP valkey @@ -368,6 +384,7 @@ VKE vnd VPS Vultr +WAIFU weblate webmaster webpage @@ -405,3 +422,4 @@ Zenos zizmor zombocom zos +zst diff --git a/.github/workflows/asset-verification.yml b/.github/workflows/asset-verification.yml index 86938f39..1ade7d19 100644 --- a/.github/workflows/asset-verification.yml +++ b/.github/workflows/asset-verification.yml @@ -13,7 +13,7 @@ jobs: asset_verification: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -22,12 +22,12 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: '24.11.0' - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + node-version: "24.11.0" + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "1.25.7" - name: install node deps run: | diff --git a/.github/workflows/dco-check.yaml b/.github/workflows/dco-check.yaml new file mode 100644 index 00000000..1b7561a6 --- /dev/null +++ b/.github/workflows/dco-check.yaml @@ -0,0 +1,9 @@ +name: DCO Check + +on: [pull_request] + +jobs: + dco_check: + runs-on: ubuntu-latest + steps: + - uses: tisonkun/actions-dco@f1024cd563550b5632e754df11b7d30b73be54a5 # v1.1 diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 0281ba07..823c460b 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-tags: true fetch-depth: 0 @@ -26,18 +26,18 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: '24.11.0' - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + node-version: "24.11.0" + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "stable" - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 - name: Docker meta id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ghcr.io/${{ github.repository }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 653afeb6..76c04dc2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-tags: true fetch-depth: 0 @@ -36,17 +36,17 @@ jobs: run: | echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: '24.11.0' - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + node-version: "24.11.0" + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "stable" - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 - name: Log into registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -54,7 +54,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ${{ env.IMAGE }} @@ -68,7 +68,7 @@ jobs: SLOG_LEVEL: debug - name: Generate artifact attestation - uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-name: ${{ env.IMAGE }} subject-digest: ${{ steps.build.outputs.digest }} diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 687e3240..8a587a0c 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -17,15 +17,15 @@ jobs: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Log into registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: techarohq @@ -33,7 +33,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ghcr.io/techarohq/anubis/docs tags: | @@ -42,7 +42,7 @@ jobs: - name: Build and push id: build - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./docs cache-to: type=gha @@ -53,14 +53,14 @@ jobs: push: true - name: Apply k8s manifests to limsa lominsa - uses: actions-hub/kubectl@f6d776bd78f4523e36d6c74d34f9941c242b2213 # v1.35.0 + uses: actions-hub/kubectl@5ada4e2c02eacc03978c2437e95c8b0f979a9619 # v1.35.2 env: KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }} with: args: apply -k docs/manifest - name: Apply k8s manifests to limsa lominsa - uses: actions-hub/kubectl@f6d776bd78f4523e36d6c74d34f9941c242b2213 # v1.35.0 + uses: actions-hub/kubectl@5ada4e2c02eacc03978c2437e95c8b0f979a9619 # v1.35.2 env: KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }} with: diff --git a/.github/workflows/docs-test.yml b/.github/workflows/docs-test.yml index 15795824..122f0ef9 100644 --- a/.github/workflows/docs-test.yml +++ b/.github/workflows/docs-test.yml @@ -13,16 +13,16 @@ jobs: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Docker meta id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ghcr.io/techarohq/anubis/docs tags: | @@ -31,7 +31,7 @@ jobs: - name: Build and push id: build - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: context: ./docs cache-to: type=gha diff --git a/.github/workflows/go-mod-tidy-check.yml b/.github/workflows/go-mod-tidy-check.yml index 0d8a96d0..81274a56 100644 --- a/.github/workflows/go-mod-tidy-check.yml +++ b/.github/workflows/go-mod-tidy-check.yml @@ -13,13 +13,13 @@ jobs: go_mod_tidy_check: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "stable" - name: Check go.mod and go.sum in main directory run: | diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c20ab9bf..2ef3c52f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -20,7 +20,7 @@ jobs: #runs-on: alrest-techarohq runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -29,7 +29,7 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "latest" @@ -38,7 +38,7 @@ jobs: go-version: ${{ matrix.go_version }} - name: Cache playwright binaries - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 id: playwright-cache with: path: | @@ -61,10 +61,10 @@ jobs: run: npm run test - name: Lint with staticcheck - uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0 + uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431 # v1.4.1 with: version: "latest" - name: Govulncheck run: | - go tool govulncheck ./... + go tool govulncheck ./... ||: diff --git a/.github/workflows/lint-pr-title.yaml b/.github/workflows/lint-pr-title.yaml new file mode 100644 index 00000000..eb1bba44 --- /dev/null +++ b/.github/workflows/lint-pr-title.yaml @@ -0,0 +1,19 @@ +name: "Lint PR" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + lint_pr_title: + name: Validate PR title + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/package-builds-stable.yml b/.github/workflows/package-builds-stable.yml index 0bbe3246..d1623a91 100644 --- a/.github/workflows/package-builds-stable.yml +++ b/.github/workflows/package-builds-stable.yml @@ -14,7 +14,7 @@ jobs: #runs-on: alrest-techarohq runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false fetch-tags: true @@ -25,12 +25,12 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: '24.11.0' - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + node-version: "24.11.0" + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "stable" - name: install node deps run: | diff --git a/.github/workflows/package-builds-unstable.yml b/.github/workflows/package-builds-unstable.yml index 4ddb9f3c..e244262c 100644 --- a/.github/workflows/package-builds-unstable.yml +++ b/.github/workflows/package-builds-unstable.yml @@ -15,7 +15,7 @@ jobs: #runs-on: alrest-techarohq runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false fetch-tags: true @@ -26,12 +26,12 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version: '24.11.0' - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + node-version: "24.11.0" + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "stable" - name: install node deps run: | @@ -41,7 +41,7 @@ jobs: run: | go tool yeet - - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: packages path: var/* diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index 4257bc23..2746e312 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -30,16 +30,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "24.11.0" - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: "1.25.4" + go-version: "stable" - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 @@ -57,7 +57,7 @@ jobs: run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV - name: Upload artifact - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f if: always() with: name: ${{ env.ARTIFACT_NAME }} diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 6ce8bd8c..599cc784 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -59,16 +59,16 @@ name: Check Spelling on: push: branches: - - '**' + - "**" tags-ignore: - - '**' + - "**" pull_request: branches: - - '**' + - "**" types: - - 'opened' - - 'reopened' - - 'synchronize' + - "opened" + - "reopened" + - "synchronize" jobs: spelling: diff --git a/.github/workflows/ssh-ci-runner-cron.yml b/.github/workflows/ssh-ci-runner-cron.yml index f12ea301..12a21133 100644 --- a/.github/workflows/ssh-ci-runner-cron.yml +++ b/.github/workflows/ssh-ci-runner-cron.yml @@ -18,19 +18,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-tags: true fetch-depth: 0 persist-credentials: false - name: Log into registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build and push run: | cd ./test/ssh-ci diff --git a/.github/workflows/ssh-ci.yml b/.github/workflows/ssh-ci.yml index 62b9a139..a89a992e 100644 --- a/.github/workflows/ssh-ci.yml +++ b/.github/workflows/ssh-ci.yml @@ -12,32 +12,33 @@ permissions: jobs: ssh: if: github.repository == 'TecharoHQ/anubis' - runs-on: alrest-techarohq + #runs-on: alrest-techarohq + runs-on: ubuntu-latest strategy: matrix: host: - riscv64 - ppc64le - - aarch64-4k - - aarch64-16k + #- aarch64-4k + #- aarch64-16k steps: - name: Checkout code - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-tags: true fetch-depth: 0 persist-credentials: false - name: Install CI target SSH key - uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0 + uses: shimataro/ssh-key-action@6b84f2e793b32fa0b03a379cadadec75cc539391 # v2.8.0 with: key: ${{ secrets.CI_SSH_KEY }} name: id_rsa known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }} - - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: '1.25.4' + go-version: "stable" - name: Run CI run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }} diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index a3f3750c..a382e618 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -1,12 +1,12 @@ name: zizmor on: - push: - paths: - - '.github/workflows/*.ya?ml' - pull_request: - paths: - - '.github/workflows/*.ya?ml' + push: + paths: + - ".github/workflows/*.ya?ml" + pull_request: + paths: + - ".github/workflows/*.ya?ml" jobs: zizmor: @@ -16,17 +16,17 @@ jobs: security-events: write steps: - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6 + uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0 - name: Run zizmor 🌈 - run: uvx zizmor --format sarif . > results.sarif + run: uvx zizmor --format sarif . > results.sarif env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 00000000..cbed8544 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,8 @@ +npx --no-install commitlint --edit "$1" + +# Check if commit message contains Signed-off-by line +if ! grep -q "^Signed-off-by:" "$1"; then + echo "Commit message must contain a 'Signed-off-by:' line." + echo "Please use 'git commit --signoff' or add a Signed-off-by line to your commit message." + exit 1 +fi diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..ab4c1191 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +npm run lint +npm run test diff --git a/.ko.yaml b/.ko.yaml index 35c1fa01..3be52db6 100644 --- a/.ko.yaml +++ b/.ko.yaml @@ -1,13 +1,13 @@ defaultBaseImage: cgr.dev/chainguard/static defaultPlatforms: -- linux/arm64 -- linux/amd64 -- linux/arm/v7 + - linux/arm64 + - linux/amd64 + - linux/arm/v7 builds: -- id: anubis - main: ./cmd/anubis - ldflags: - - -s -w - - -extldflags "-static" - - -X github.com/TecharoHQ/anubis.Version={{.Env.VERSION}} + - id: anubis + main: ./cmd/anubis + ldflags: + - -s -w + - -extldflags "-static" + - -X github.com/TecharoHQ/anubis.Version={{.Env.VERSION}} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..19ab088d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +lib/config/testdata/bad/* +*.inc +AGENTS.md +CLAUDE.md \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6e2ae177..5305d550 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,4 +8,4 @@ "redhat.vscode-yaml", "streetsidesoftware.code-spell-checker" ] -} \ No newline at end of file +} diff --git a/.vscode/launch.json b/.vscode/launch.json index f6db59f0..4e495937 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,4 +24,4 @@ "type": "node-terminal" } ] -} \ No newline at end of file +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..32082218 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,75 @@ +# Agent instructions + +Primary agent documentation is in `CONTRIBUTING.md`. You MUST read this file before proceeding. + +## Useful Commands + +```shell +npm ci # install node dependencies +npm run assets # build JS/CSS (required before any Go build/test) +npm run build # assets + go build -> ./var/anubis +npm run dev # assets + run locally with --use-remote-address +``` + +## Testing + +```shell +npm run test +``` + +## Linting + +```shell +go vet ./... +go tool staticcheck ./... +go tool govulncheck ./... +``` + +## Commit Messages + +Commit messages follow the [**Conventional Commits**](https://www.conventionalcommits.org/en/v1.0.0/) format: + +```text +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert` + +- Add `!` after type/scope for breaking changes or include `BREAKING CHANGE:` in the footer. +- Keep descriptions concise, imperative, lowercase, and without a trailing period. +- Reference issues/PRs in the footer when applicable. +- **ALL git commits MUST be made with `--signoff`.** This is mandatory. + +### Attribution Requirements + +AI agents must disclose what tool and model they are using in the "Assisted-by" commit footer: + +```text +Assisted-by: [Model Name] via [Tool Name] +``` + +Example: + +```text +Assisted-by: GLM 4.6 via Claude Code +``` + +## PR Checklist + +- Add description of changes to `[Unreleased]` in `docs/docs/CHANGELOG.md`. +- Add test cases for bug fixes and behavior changes. +- Run integration tests: `npm run test:integration`. +- All commits must have verified (signed) signatures. + +## Key Conventions + +- **Security-first**: This is security software. Code reviews are strict. Always add tests for bug fixes. Consider adversarial inputs. +- **Configuration**: YAML-based policy files. Config structs validate via `Valid() error` methods returning sentinel errors. +- **Store interface**: `lib/store.Interface` abstracts key-value storage. +- **Environment variables**: Parsed from flags via `flagenv`. Use `.env` files locally (loaded by `godotenv/autoload`). Never commit `.env` files. +- **Assets must be built first**: JS/CSS assets are embedded into the Go binary. Always run `npm run assets` before `go test` or `go build`. +- **CEL expressions**: Policy rules support CEL (Common Expression Language) expressions for advanced matching. See `lib/policy/expressions/`. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..af124701 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,2 @@ +@AGENTS.md +@CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..d7013fa7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,144 @@ +# Contributing to Anubis + +Anubis is a Web AI Firewall Utility (WAIFU) written in Go. It uses sha256 proof-of-work challenges to protect upstream HTTP resources from scraper bots. This is security software -- correctness matters. + +## Build & Run + +Prerequisites: Go 1.24+, Node.js (any supported version), esbuild, gzip, zstd, brotli. Install all with `brew bundle` if you are using Homebrew. + +```shell +npm ci # install node dependencies +npm run assets # build JS/CSS (required before any Go build/test) +npm run build # assets + go build -> ./var/anubis +npm run dev # assets + run locally with --use-remote-address +``` + +## Testing + +```shell +# Run all unit tests (assets must be built first) +npm run test # or: make test + +# Run a single test by name +go test -run TestClampIP ./internal/ + +# Run a single test file's package +go test ./lib/config/ + +# Run tests with verbose output +go test -v -run TestBotValid ./lib/config/ +``` + +### Smoke tests + +The `tests` folder contains "smoke tests" that are intended to set up Anubis in production-adjacent settings and testing it against real infrastructure tools. A smoke test is a folder with `test.sh` that sets up infrastructure, validates the behaviour, and then tears it down. Smoke tests are run in GitHub actions with `.github/workflows/smoke-tests.yaml`. + +## Linting + +```shell +go vet ./... +go tool staticcheck ./... +go tool govulncheck ./... +``` + +## Code Generation + +The project uses `go generate` for templ templates and stringer. Always run `npm run generate` (or `make assets`) before building or testing. Generated files include: + +- `web/*.templ` -> templ-generated Go code +- `web/static/` -> bundled/minified JS and CSS (with .gz, .zst, .br variants) + +## Project Layout + +Important folders: + +- `cmd/anubis`: Main entrypoint for the project. This is the program that runs on servers. +- `lib/*`: The core library for Anubis and all of its features. This is internal code that is made public for ease of downstream consumption. No API stability is guaranteed. Use at your own risk. +- `internal/*`: Actual internal code that is private to the implementation of Anubis. If you need to use a package in this, please copy it out and manually vendor it in your own project. +- `test/*` Smoke tests (see dedicated section for details). +- `web`: Frontend HTML templates. +- `xess`: Frontend CSS framework and build logic. + +## Code Style + +### Go + +This project follows the idioms of the Go standard library. Generally follow the patterns that upstream Go uses, including: + +- Prefer packages from the standard library unless there is no other option. +- Use package import aliases only when package names collide. +- Use `goimports` to format code. Run with `npm run format`. +- Use sentinel errors as package-level variables prefixed with `Err` (such as `ErrBotMustHaveName`). Wrap with `fmt.Errorf("package: small message giving context: %w", err)`. +- Use `log/slog` for structured logging. Pass loggers as arguments to functions. Use `lg.With` to preload with context. Prefer using `slog.Debug` unless you absolutely need to report messages to users, some users have magical thinking about log verbosity. +- Name PublicFunctionsAndTypes in PascalCase. Name privateFunctionsAndTypes in camelCase. +- Acronyms stay uppercase (`URL`, `HTTP`, `IP`, `DNS`, etc.) +- Enumerations should use strong types with validation logic for parsing remote input. +- Be conservative in what you send but liberal in what you accept. +- Anything reading configuration values should use both `json` and `yaml` struct tags. Use pointer values for optional configuration values. +- Use [table-driven tests](https://go.dev/wiki/TableDrivenTests) when writing test code. +- Use [`t.Helper()`](https://pkg.go.dev/testing#T.Helper) in helper code (setup/teardown scaffolding). +- Use [`t.Cleanup()`](https://pkg.go.dev/testing#T.Cleanup) to tear down per-test or per-suite scaffolding. +- Use [`errors.Is`](https://pkg.go.dev/errors#Is) for validating function results against sentinel errors. +- Prefer same-package tests over black-box tests (`_test` packages). + +### JavaScript / TypeScript + +- Source lives in `web/js/`. Built with esbuild, bundled and minified. +- Uses Preact (not React). +- No linter config. Keep functions small. Use `const` by default. + +### Templ Templates + +Anubis uses [Templ](https://templ.guide) for generating HTML on the server. + +- `.templ` files in `web/` generate Go code. Run `go generate ./...` (or `npm run assets`) after modifying them. +- Templates receive typed Go parameters. Keep logic in Go, not templates. + +## Commit Messages + +Commit messages follow the [**Conventional Commits**](https://www.conventionalcommits.org/en/v1.0.0/) format: + +```text +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert` + +- Add `!` after type/scope for breaking changes or include `BREAKING CHANGE:` in the footer. +- Keep descriptions concise, imperative, lowercase, and without a trailing period. +- Reference issues/PRs in the footer when applicable. +- **ALL git commits MUST be made with `--signoff`.** This is mandatory. + +### Attribution Requirements + +AI agents must disclose what tool and model they are using in the "Assisted-by" commit footer: + +```text +Assisted-by: [Model Name] via [Tool Name] +``` + +Example: + +```text +Assisted-by: GLM 4.6 via Claude Code +``` + +## PR Checklist + +- Add description of changes to `[Unreleased]` in `docs/docs/CHANGELOG.md`. +- Add test cases for bug fixes and behavior changes. +- Run integration tests: `npm run test:integration`. +- All commits must have verified (signed) signatures. + +## Key Conventions + +- **Security-first**: This is security software. Code reviews are strict. Always add tests for bug fixes. Consider adversarial inputs. +- **Configuration**: YAML-based policy files. Config structs validate via `Valid() error` methods returning sentinel errors. +- **Store interface**: `lib/store.Interface` abstracts key-value storage. +- **Environment variables**: Parsed from flags via `flagenv`. Use `.env` files locally (loaded by `godotenv/autoload`). Never commit `.env` files. +- **Assets must be built first**: JS/CSS assets are embedded into the Go binary. Always run `npm run assets` before `go test` or `go build`. +- **CEL expressions**: Policy rules support CEL (Common Expression Language) expressions for advanced matching. See `lib/policy/expressions/`. diff --git a/Makefile b/Makefile index 534ae5be..3213e780 100644 --- a/Makefile +++ b/Makefile @@ -24,8 +24,7 @@ build: assets lint: assets $(GO) vet ./... $(GO) tool staticcheck ./... - $(GO) tool govulncheck ./... - + prebaked-build: $(GO) build -o ./var/anubis -ldflags "-X 'github.com/TecharoHQ/anubis.Version=$(VERSION)'" ./cmd/anubis $(GO) build -o ./var/robots2policy -ldflags "-X 'github.com/TecharoHQ/anubis.Version=$(VERSION)'" ./cmd/robots2policy diff --git a/README.md b/README.md index 6de0183f..25ae0912 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,21 @@ Anubis is brought to you by sponsors and donors like: ### Gold Tier + + Unipromos + + + Uvensys + Distrust + + Gitea + + + Prolocation + Terminal Trove @@ -58,6 +70,9 @@ Anubis is brought to you by sponsors and donors like: height="64" /> + + ANEXIA Cloud Solutions + ## Overview diff --git a/VERSION b/VERSION index 53cc1a6f..ad219194 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.24.0 +1.25.0 diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index e21eeca1..a38cfdb3 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -418,8 +418,8 @@ func main() { var redirectDomainsList []string if *redirectDomains != "" { - domains := strings.Split(*redirectDomains, ",") - for _, domain := range domains { + domains := strings.SplitSeq(*redirectDomains, ",") + for domain := range domains { _, err = url.Parse(domain) if err != nil { log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error()) diff --git a/cmd/containerbuild/main.go b/cmd/containerbuild/main.go index a351f347..3eb52ffd 100644 --- a/cmd/containerbuild/main.go +++ b/cmd/containerbuild/main.go @@ -159,5 +159,8 @@ func run(command string) (string, error) { } func setOutput(key, val string) { - fmt.Printf("::set-output name=%s::%s\n", key, val) + github_output := os.Getenv("GITHUB_OUTPUT") + f, _ := os.OpenFile(github_output, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + fmt.Fprintf(f, "%s=%s\n", key, val) + f.Close() } diff --git a/cmd/robots2policy/main.go b/cmd/robots2policy/main.go index 69fb2f96..e79a6262 100644 --- a/cmd/robots2policy/main.go +++ b/cmd/robots2policy/main.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "regexp" + "slices" "strings" "github.com/TecharoHQ/anubis/lib/config" @@ -210,11 +211,8 @@ func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) { // Mark blacklisted user agents (those with "Disallow: /") for i := range rules { - for _, disallow := range rules[i].Disallows { - if disallow == "/" { - rules[i].IsBlacklist = true - break - } + if slices.Contains(rules[i].Disallows, "/") { + rules[i].IsBlacklist = true } } diff --git a/cmd/robots2policy/robots2policy_test.go b/cmd/robots2policy/robots2policy_test.go index 523d69a7..ef3008f4 100644 --- a/cmd/robots2policy/robots2policy_test.go +++ b/cmd/robots2policy/robots2policy_test.go @@ -158,8 +158,8 @@ func TestDataFileConversion(t *testing.T) { } if strings.ToLower(*outputFormat) == "yaml" { - var actualData []interface{} - var expectedData []interface{} + var actualData []any + var expectedData []any err = yaml.Unmarshal(actualOutput, &actualData) if err != nil { @@ -178,8 +178,8 @@ func TestDataFileConversion(t *testing.T) { t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr) } } else { - var actualData []interface{} - var expectedData []interface{} + var actualData []any + var expectedData []any err = json.Unmarshal(actualOutput, &actualData) if err != nil { @@ -419,6 +419,6 @@ Disallow: /` // compareData performs a deep comparison of two data structures, // ignoring differences that are semantically equivalent in YAML/JSON -func compareData(actual, expected interface{}) bool { +func compareData(actual, expected any) bool { return reflect.DeepEqual(actual, expected) } diff --git a/cmd/robots2policy/testdata/blacklist.yaml b/cmd/robots2policy/testdata/blacklist.yaml index a3096f51..1f8fbabd 100644 --- a/cmd/robots2policy/testdata/blacklist.yaml +++ b/cmd/robots2policy/testdata/blacklist.yaml @@ -25,6 +25,6 @@ - action: CHALLENGE expression: all: - - userAgent.contains("Googlebot") - - path.startsWith("/search") + - userAgent.contains("Googlebot") + - path.startsWith("/search") name: robots-txt-policy-disallow-7 diff --git a/cmd/robots2policy/testdata/complex.yaml b/cmd/robots2policy/testdata/complex.yaml index 6e677ad2..d6ddeb63 100644 --- a/cmd/robots2policy/testdata/complex.yaml +++ b/cmd/robots2policy/testdata/complex.yaml @@ -20,8 +20,8 @@ - action: CHALLENGE expression: all: - - userAgent.contains("Googlebot") - - path.startsWith("/search/") + - userAgent.contains("Googlebot") + - path.startsWith("/search/") name: robots-txt-policy-disallow-6 - action: WEIGH expression: userAgent.contains("Bingbot") @@ -31,14 +31,14 @@ - action: CHALLENGE expression: all: - - userAgent.contains("Bingbot") - - path.startsWith("/search/") + - userAgent.contains("Bingbot") + - path.startsWith("/search/") name: robots-txt-policy-disallow-8 - action: CHALLENGE expression: all: - - userAgent.contains("Bingbot") - - path.startsWith("/admin/") + - userAgent.contains("Bingbot") + - path.startsWith("/admin/") name: robots-txt-policy-disallow-9 - action: DENY expression: userAgent.contains("BadBot") @@ -54,18 +54,18 @@ - action: CHALLENGE expression: all: - - userAgent.contains("TestBot") - - path.matches("^/.*/admin") + - userAgent.contains("TestBot") + - path.matches("^/.*/admin") name: robots-txt-policy-disallow-13 - action: CHALLENGE expression: all: - - userAgent.contains("TestBot") - - path.matches("^/temp.*\\.html") + - userAgent.contains("TestBot") + - path.matches("^/temp.*\\.html") name: robots-txt-policy-disallow-14 - action: CHALLENGE expression: all: - - userAgent.contains("TestBot") - - path.matches("^/file.\\.log") + - userAgent.contains("TestBot") + - path.matches("^/file.\\.log") name: robots-txt-policy-disallow-15 diff --git a/cmd/robots2policy/testdata/consecutive.yaml b/cmd/robots2policy/testdata/consecutive.yaml index 144abda0..3ba3dde5 100644 --- a/cmd/robots2policy/testdata/consecutive.yaml +++ b/cmd/robots2policy/testdata/consecutive.yaml @@ -9,39 +9,39 @@ - action: DENY expression: any: - - userAgent.contains("BadBot") - - userAgent.contains("SpamBot") - - userAgent.contains("EvilBot") + - userAgent.contains("BadBot") + - userAgent.contains("SpamBot") + - userAgent.contains("EvilBot") name: robots-txt-policy-blacklist-3 - action: CHALLENGE expression: all: - - userAgent.contains("GoodBot") - - path.startsWith("/private") + - userAgent.contains("GoodBot") + - path.startsWith("/private") name: robots-txt-policy-disallow-4 - action: WEIGH expression: any: - - userAgent.contains("SlowBot1") - - userAgent.contains("SlowBot2") + - userAgent.contains("SlowBot1") + - userAgent.contains("SlowBot2") name: robots-txt-policy-crawl-delay-5 weight: adjust: 3 - action: CHALLENGE expression: all: - - userAgent.contains("SearchBot1") - - path.startsWith("/search") + - userAgent.contains("SearchBot1") + - path.startsWith("/search") name: robots-txt-policy-disallow-7 - action: CHALLENGE expression: all: - - userAgent.contains("SearchBot2") - - path.startsWith("/search") + - userAgent.contains("SearchBot2") + - path.startsWith("/search") name: robots-txt-policy-disallow-8 - action: CHALLENGE expression: all: - - userAgent.contains("SearchBot3") - - path.startsWith("/search") + - userAgent.contains("SearchBot3") + - path.startsWith("/search") name: robots-txt-policy-disallow-9 diff --git a/cmd/robots2policy/testdata/empty.yaml b/cmd/robots2policy/testdata/empty.yaml index 0637a088..fe51488c 100644 --- a/cmd/robots2policy/testdata/empty.yaml +++ b/cmd/robots2policy/testdata/empty.yaml @@ -1 +1 @@ -[] \ No newline at end of file +[] diff --git a/cmd/robots2policy/testdata/simple.json b/cmd/robots2policy/testdata/simple.json index c8e1de09..72207e9c 100644 --- a/cmd/robots2policy/testdata/simple.json +++ b/cmd/robots2policy/testdata/simple.json @@ -9,4 +9,4 @@ "name": "robots-txt-policy-disallow-2", "action": "CHALLENGE" } -] \ No newline at end of file +] diff --git a/data/apps/allow-api-routes.yaml b/data/apps/allow-api-routes.yaml index 0cc3e3bf..61d1541b 100644 --- a/data/apps/allow-api-routes.yaml +++ b/data/apps/allow-api-routes.yaml @@ -2,5 +2,5 @@ action: ALLOW expression: all: - - '!(method == "HEAD" || method == "GET")' - - path.startsWith("/api/") \ No newline at end of file + - '!(method == "HEAD" || method == "GET")' + - path.startsWith("/api/") diff --git a/data/apps/gitea-rss-feeds.yaml b/data/apps/gitea-rss-feeds.yaml index 7bd34ceb..89dec844 100644 --- a/data/apps/gitea-rss-feeds.yaml +++ b/data/apps/gitea-rss-feeds.yaml @@ -4,4 +4,4 @@ path_regex: ^/[.A-Za-z0-9_-]{1,256}?[./A-Za-z0-9_-]*\.atom$ - name: gitea-feed-rss action: ALLOW - path_regex: ^/[.A-Za-z0-9_-]{1,256}?[./A-Za-z0-9_-]*\.rss$ \ No newline at end of file + path_regex: ^/[.A-Za-z0-9_-]{1,256}?[./A-Za-z0-9_-]*\.rss$ diff --git a/data/apps/qualys-ssl-labs.yml b/data/apps/qualys-ssl-labs.yml index 3a9ed386..8dc6ab6e 100644 --- a/data/apps/qualys-ssl-labs.yml +++ b/data/apps/qualys-ssl-labs.yml @@ -3,6 +3,6 @@ - name: qualys-ssl-labs action: ALLOW remote_addresses: - - 69.67.183.0/24 - - 2600:C02:1020:4202::/64 - - 2602:fdaa:c6:2::/64 \ No newline at end of file + - 69.67.183.0/24 + - 2600:C02:1020:4202::/64 + - 2602:fdaa:c6:2::/64 diff --git a/data/apps/searx-checker.yml b/data/apps/searx-checker.yml index c6da25f8..a6063c91 100644 --- a/data/apps/searx-checker.yml +++ b/data/apps/searx-checker.yml @@ -5,5 +5,5 @@ - name: searx-checker action: ALLOW remote_addresses: - - 167.235.158.251/32 - - 2a01:4f8:1c1c:8fc2::1/128 \ No newline at end of file + - 167.235.158.251/32 + - 2a01:4f8:1c1c:8fc2::1/128 diff --git a/data/bots/headless-browsers.yaml b/data/bots/headless-browsers.yaml index 98052906..bac2d8ed 100644 --- a/data/bots/headless-browsers.yaml +++ b/data/bots/headless-browsers.yaml @@ -6,4 +6,4 @@ action: DENY - name: headless-chromium user_agent_regex: HeadlessChromium - action: DENY \ No newline at end of file + action: DENY diff --git a/data/bots/irc-bots/archlinux-phrik.yaml b/data/bots/irc-bots/archlinux-phrik.yaml index 6124e2e3..93e37915 100644 --- a/data/bots/irc-bots/archlinux-phrik.yaml +++ b/data/bots/irc-bots/archlinux-phrik.yaml @@ -3,7 +3,7 @@ action: ALLOW expression: all: - - remoteAddress == "159.69.213.214" || remoteAddress == "2a01:4f8:c2c:7bf4::1" - - userAgent == "Mozilla/5.0 (compatible; utils.web Limnoria module)" - - '"X-Http-Version" in headers' - - headers["X-Http-Version"] == "HTTP/1.1" \ No newline at end of file + - remoteAddress == "159.69.213.214" || remoteAddress == "2a01:4f8:c2c:7bf4::1" + - userAgent == "Mozilla/5.0 (compatible; utils.web Limnoria module)" + - '"X-Http-Version" in headers' + - headers["X-Http-Version"] == "HTTP/1.1" diff --git a/data/bots/irc-bots/gentoo-chat.yaml b/data/bots/irc-bots/gentoo-chat.yaml index 92f2a4f5..30db4a11 100644 --- a/data/bots/irc-bots/gentoo-chat.yaml +++ b/data/bots/irc-bots/gentoo-chat.yaml @@ -3,7 +3,7 @@ action: ALLOW expression: all: - - remoteAddress == "45.76.166.57" - - userAgent == "Mozilla/5.0 (Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0" - - '"X-Http-Version" in headers' - - headers["X-Http-Version"] == "HTTP/1.1" \ No newline at end of file + - remoteAddress == "45.76.166.57" + - userAgent == "Mozilla/5.0 (Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0" + - '"X-Http-Version" in headers' + - headers["X-Http-Version"] == "HTTP/1.1" diff --git a/data/bots/us-ai-scraper.yaml b/data/bots/us-ai-scraper.yaml index b68920f7..90b4994b 100644 --- a/data/bots/us-ai-scraper.yaml +++ b/data/bots/us-ai-scraper.yaml @@ -1,3 +1,3 @@ - name: us-artificial-intelligence-scraper user_agent_regex: \+https\://github\.com/US-Artificial-Intelligence/scraper - action: DENY \ No newline at end of file + action: DENY diff --git a/data/clients/go-get.yaml b/data/clients/go-get.yaml index 701bd5d8..d9e8aa52 100644 --- a/data/clients/go-get.yaml +++ b/data/clients/go-get.yaml @@ -2,6 +2,6 @@ action: ALLOW expression: all: - - userAgent.startsWith("Go-http-client/") - - '"go-get" in query' - - query["go-get"] == "1" \ No newline at end of file + - userAgent.startsWith("Go-http-client/") + - '"go-get" in query' + - query["go-get"] == "1" diff --git a/data/clients/mistral-mistralai-user.yaml b/data/clients/mistral-mistralai-user.yaml index 59b86fbd..cf2dfa05 100644 --- a/data/clients/mistral-mistralai-user.yaml +++ b/data/clients/mistral-mistralai-user.yaml @@ -4,7 +4,4 @@ user_agent_regex: MistralAI-User/.+; \+https\://docs\.mistral\.ai/robots action: ALLOW # https://mistral.ai/mistralai-user-ips.json - remote_addresses: [ - "20.240.160.161/32", - "20.240.160.1/32", - ] \ No newline at end of file + remote_addresses: ["20.240.160.161/32", "20.240.160.1/32"] diff --git a/data/clients/openai-chatgpt-user.yaml b/data/clients/openai-chatgpt-user.yaml index 063cf08d..9b3208bd 100644 --- a/data/clients/openai-chatgpt-user.yaml +++ b/data/clients/openai-chatgpt-user.yaml @@ -5,89 +5,90 @@ action: ALLOW # https://openai.com/chatgpt-user.json # curl 'https://openai.com/chatgpt-user.json' | jq '.prefixes.[].ipv4Prefix' | sed 's/$/,/' - remote_addresses: [ - "13.65.138.112/28", - "23.98.179.16/28", - "13.65.138.96/28", - "172.183.222.128/28", - "20.102.212.144/28", - "40.116.73.208/28", - "172.183.143.224/28", - "52.190.190.16/28", - "13.83.237.176/28", - "51.8.155.64/28", - "74.249.86.176/28", - "51.8.155.48/28", - "20.55.229.144/28", - "135.237.131.208/28", - "135.237.133.48/28", - "51.8.155.112/28", - "135.237.133.112/28", - "52.159.249.96/28", - "52.190.137.16/28", - "52.255.111.112/28", - "40.84.181.32/28", - "172.178.141.112/28", - "52.190.142.64/28", - "172.178.140.144/28", - "52.190.137.144/28", - "172.178.141.128/28", - "57.154.187.32/28", - "4.196.118.112/28", - "20.193.50.32/28", - "20.215.188.192/28", - "20.215.214.16/28", - "4.197.22.112/28", - "4.197.115.112/28", - "172.213.21.16/28", - "172.213.11.144/28", - "172.213.12.112/28", - "172.213.21.144/28", - "20.90.7.144/28", - "57.154.175.0/28", - "57.154.174.112/28", - "52.236.94.144/28", - "137.135.191.176/28", - "23.98.186.192/28", - "23.98.186.96/28", - "23.98.186.176/28", - "23.98.186.64/28", - "68.221.67.192/28", - "68.221.67.160/28", - "13.83.167.128/28", - "20.228.106.176/28", - "52.159.227.32/28", - "68.220.57.64/28", - "172.213.21.112/28", - "68.221.67.224/28", - "68.221.75.16/28", - "20.97.189.96/28", - "52.252.113.240/28", - "52.230.163.32/28", - "172.212.159.64/28", - "52.255.111.80/28", - "52.255.111.0/28", - "4.151.241.240/28", - "52.255.111.32/28", - "52.255.111.48/28", - "52.255.111.16/28", - "52.230.164.176/28", - "52.176.139.176/28", - "52.173.234.16/28", - "4.151.71.176/28", - "4.151.119.48/28", - "52.255.109.112/28", - "52.255.109.80/28", - "20.161.75.208/28", - "68.154.28.96/28", - "52.255.109.128/28", - "52.225.75.208/28", - "52.190.139.48/28", - "68.221.67.240/28", - "52.156.77.144/28", - "52.148.129.32/28", - "40.84.221.208/28", - "104.210.139.224/28", - "40.84.221.224/28", - "104.210.139.192/28", - ] \ No newline at end of file + remote_addresses: + [ + "13.65.138.112/28", + "23.98.179.16/28", + "13.65.138.96/28", + "172.183.222.128/28", + "20.102.212.144/28", + "40.116.73.208/28", + "172.183.143.224/28", + "52.190.190.16/28", + "13.83.237.176/28", + "51.8.155.64/28", + "74.249.86.176/28", + "51.8.155.48/28", + "20.55.229.144/28", + "135.237.131.208/28", + "135.237.133.48/28", + "51.8.155.112/28", + "135.237.133.112/28", + "52.159.249.96/28", + "52.190.137.16/28", + "52.255.111.112/28", + "40.84.181.32/28", + "172.178.141.112/28", + "52.190.142.64/28", + "172.178.140.144/28", + "52.190.137.144/28", + "172.178.141.128/28", + "57.154.187.32/28", + "4.196.118.112/28", + "20.193.50.32/28", + "20.215.188.192/28", + "20.215.214.16/28", + "4.197.22.112/28", + "4.197.115.112/28", + "172.213.21.16/28", + "172.213.11.144/28", + "172.213.12.112/28", + "172.213.21.144/28", + "20.90.7.144/28", + "57.154.175.0/28", + "57.154.174.112/28", + "52.236.94.144/28", + "137.135.191.176/28", + "23.98.186.192/28", + "23.98.186.96/28", + "23.98.186.176/28", + "23.98.186.64/28", + "68.221.67.192/28", + "68.221.67.160/28", + "13.83.167.128/28", + "20.228.106.176/28", + "52.159.227.32/28", + "68.220.57.64/28", + "172.213.21.112/28", + "68.221.67.224/28", + "68.221.75.16/28", + "20.97.189.96/28", + "52.252.113.240/28", + "52.230.163.32/28", + "172.212.159.64/28", + "52.255.111.80/28", + "52.255.111.0/28", + "4.151.241.240/28", + "52.255.111.32/28", + "52.255.111.48/28", + "52.255.111.16/28", + "52.230.164.176/28", + "52.176.139.176/28", + "52.173.234.16/28", + "4.151.71.176/28", + "4.151.119.48/28", + "52.255.109.112/28", + "52.255.109.80/28", + "20.161.75.208/28", + "68.154.28.96/28", + "52.255.109.128/28", + "52.225.75.208/28", + "52.190.139.48/28", + "68.221.67.240/28", + "52.156.77.144/28", + "52.148.129.32/28", + "40.84.221.208/28", + "104.210.139.224/28", + "40.84.221.224/28", + "104.210.139.192/28", + ] diff --git a/data/clients/perplexity-user.yaml b/data/clients/perplexity-user.yaml index 13f64fda..cb95b2fc 100644 --- a/data/clients/perplexity-user.yaml +++ b/data/clients/perplexity-user.yaml @@ -4,9 +4,5 @@ user_agent_regex: Perplexity-User/.+; \+https\://perplexity\.ai/perplexity-user action: ALLOW # https://www.perplexity.com/perplexity-user.json - remote_addresses: [ - "44.208.221.197/32", - "34.193.163.52/32", - "18.97.21.0/30", - "18.97.43.80/29", - ] + remote_addresses: + ["44.208.221.197/32", "34.193.163.52/32", "18.97.21.0/30", "18.97.43.80/29"] diff --git a/data/clients/telegram-preview.yaml b/data/clients/telegram-preview.yaml index 85076dca..f6be7a8a 100644 --- a/data/clients/telegram-preview.yaml +++ b/data/clients/telegram-preview.yaml @@ -1,6 +1,6 @@ -- name: telegrambot - action: ALLOW - expression: - all: - - userAgent.matches("TelegramBot") - - verifyFCrDNS(remoteAddress, "ptr\\.telegram\\.org$") +- name: telegrambot + action: ALLOW + expression: + all: + - userAgent.matches("TelegramBot") + - verifyFCrDNS(remoteAddress, "ptr\\.telegram\\.org$") diff --git a/data/clients/vk-preview.yaml b/data/clients/vk-preview.yaml index 3e23067a..580afd94 100644 --- a/data/clients/vk-preview.yaml +++ b/data/clients/vk-preview.yaml @@ -1,6 +1,6 @@ -- name: vkbot - action: ALLOW - expression: - all: - - userAgent.matches("vkShare[^+]+\\+http\\://vk\\.com/dev/Share") - - verifyFCrDNS(remoteAddress, "^snipster\\d+\\.go\\.mail\\.ru$") +- name: vkbot + action: ALLOW + expression: + all: + - userAgent.matches("vkShare[^+]+\\+http\\://vk\\.com/dev/Share") + - verifyFCrDNS(remoteAddress, "^snipster\\d+\\.go\\.mail\\.ru$") diff --git a/data/common/allow-api-like.yaml b/data/common/allow-api-like.yaml index 0cc3e3bf..61d1541b 100644 --- a/data/common/allow-api-like.yaml +++ b/data/common/allow-api-like.yaml @@ -2,5 +2,5 @@ action: ALLOW expression: all: - - '!(method == "HEAD" || method == "GET")' - - path.startsWith("/api/") \ No newline at end of file + - '!(method == "HEAD" || method == "GET")' + - path.startsWith("/api/") diff --git a/data/common/json-api.yaml b/data/common/json-api.yaml index b5b51d62..bfe44cf0 100644 --- a/data/common/json-api.yaml +++ b/data/common/json-api.yaml @@ -4,4 +4,4 @@ all: - '"Accept" in headers' - 'headers["Accept"] == "application/json"' - - 'path.startsWith("/api/")' \ No newline at end of file + - 'path.startsWith("/api/")' diff --git a/data/common/rfc-violations.yaml b/data/common/rfc-violations.yaml index 6f933608..eec89f25 100644 --- a/data/common/rfc-violations.yaml +++ b/data/common/rfc-violations.yaml @@ -1,3 +1,3 @@ - name: no-user-agent-string action: DENY - expression: userAgent == "" \ No newline at end of file + expression: userAgent == "" diff --git a/data/crawlers/_allow-good.yaml b/data/crawlers/_allow-good.yaml index 44a6fed6..98f6fdca 100644 --- a/data/crawlers/_allow-good.yaml +++ b/data/crawlers/_allow-good.yaml @@ -8,4 +8,5 @@ - import: (data)/crawlers/marginalia.yaml - import: (data)/crawlers/mojeekbot.yaml - import: (data)/crawlers/commoncrawl.yaml +- import: (data)/crawlers/wikimedia-citoid.yaml - import: (data)/crawlers/yandexbot.yaml diff --git a/data/crawlers/applebot.yaml b/data/crawlers/applebot.yaml index e75dfe14..9fae2485 100644 --- a/data/crawlers/applebot.yaml +++ b/data/crawlers/applebot.yaml @@ -4,17 +4,18 @@ user_agent_regex: Applebot action: ALLOW # https://search.developer.apple.com/applebot.json - remote_addresses: [ - "17.241.208.160/27", - "17.241.193.160/27", - "17.241.200.160/27", - "17.22.237.0/24", - "17.22.245.0/24", - "17.22.253.0/24", - "17.241.75.0/24", - "17.241.219.0/24", - "17.241.227.0/24", - "17.246.15.0/24", - "17.246.19.0/24", - "17.246.23.0/24", - ] + remote_addresses: + [ + "17.241.208.160/27", + "17.241.193.160/27", + "17.241.200.160/27", + "17.22.237.0/24", + "17.22.245.0/24", + "17.22.253.0/24", + "17.241.75.0/24", + "17.241.219.0/24", + "17.241.227.0/24", + "17.246.15.0/24", + "17.246.19.0/24", + "17.246.23.0/24", + ] diff --git a/data/crawlers/bingbot.yaml b/data/crawlers/bingbot.yaml index 2f7885dd..8d5b2e0f 100644 --- a/data/crawlers/bingbot.yaml +++ b/data/crawlers/bingbot.yaml @@ -2,33 +2,34 @@ user_agent_regex: \+http\://www\.bing\.com/bingbot\.htm action: ALLOW # https://www.bing.com/toolbox/bingbot.json - remote_addresses: [ - "157.55.39.0/24", - "207.46.13.0/24", - "40.77.167.0/24", - "13.66.139.0/24", - "13.66.144.0/24", - "52.167.144.0/24", - "13.67.10.16/28", - "13.69.66.240/28", - "13.71.172.224/28", - "139.217.52.0/28", - "191.233.204.224/28", - "20.36.108.32/28", - "20.43.120.16/28", - "40.79.131.208/28", - "40.79.186.176/28", - "52.231.148.0/28", - "20.79.107.240/28", - "51.105.67.0/28", - "20.125.163.80/28", - "40.77.188.0/22", - "65.55.210.0/24", - "199.30.24.0/23", - "40.77.202.0/24", - "40.77.139.0/25", - "20.74.197.0/28", - "20.15.133.160/27", - "40.77.177.0/24", - "40.77.178.0/23" - ] + remote_addresses: + [ + "157.55.39.0/24", + "207.46.13.0/24", + "40.77.167.0/24", + "13.66.139.0/24", + "13.66.144.0/24", + "52.167.144.0/24", + "13.67.10.16/28", + "13.69.66.240/28", + "13.71.172.224/28", + "139.217.52.0/28", + "191.233.204.224/28", + "20.36.108.32/28", + "20.43.120.16/28", + "40.79.131.208/28", + "40.79.186.176/28", + "52.231.148.0/28", + "20.79.107.240/28", + "51.105.67.0/28", + "20.125.163.80/28", + "40.77.188.0/22", + "65.55.210.0/24", + "199.30.24.0/23", + "40.77.202.0/24", + "40.77.139.0/25", + "20.74.197.0/28", + "20.15.133.160/27", + "40.77.177.0/24", + "40.77.178.0/23", + ] diff --git a/data/crawlers/duckduckbot.yaml b/data/crawlers/duckduckbot.yaml index 302a1e38..4b3ed161 100644 --- a/data/crawlers/duckduckbot.yaml +++ b/data/crawlers/duckduckbot.yaml @@ -2,274 +2,275 @@ user_agent_regex: DuckDuckBot/1\.1; \(\+http\://duckduckgo\.com/duckduckbot\.html\) action: ALLOW # https://duckduckgo.com/duckduckgo-help-pages/results/duckduckbot - remote_addresses: [ - "57.152.72.128/32", - "51.8.253.152/32", - "40.80.242.63/32", - "20.12.141.99/32", - "20.49.136.28/32", - "51.116.131.221/32", - "51.107.40.209/32", - "20.40.133.240/32", - "20.50.168.91/32", - "51.120.48.122/32", - "20.193.45.113/32", - "40.76.173.151/32", - "40.76.163.7/32", - "20.185.79.47/32", - "52.142.26.175/32", - "20.185.79.15/32", - "52.142.24.149/32", - "40.76.162.208/32", - "40.76.163.23/32", - "40.76.162.191/32", - "40.76.162.247/32", - "40.88.21.235/32", - "20.191.45.212/32", - "52.146.59.12/32", - "52.146.59.156/32", - "52.146.59.154/32", - "52.146.58.236/32", - "20.62.224.44/32", - "51.104.180.53/32", - "51.104.180.47/32", - "51.104.180.26/32", - "51.104.146.225/32", - "51.104.146.235/32", - "20.73.202.147/32", - "20.73.132.240/32", - "20.71.12.143/32", - "20.56.197.58/32", - "20.56.197.63/32", - "20.43.150.93/32", - "20.43.150.85/32", - "20.44.222.1/32", - "40.89.243.175/32", - "13.89.106.77/32", - "52.143.242.6/32", - "52.143.241.111/32", - "52.154.60.82/32", - "20.197.209.11/32", - "20.197.209.27/32", - "20.226.133.105/32", - "191.234.216.4/32", - "191.234.216.178/32", - "20.53.92.211/32", - "20.53.91.2/32", - "20.207.99.197/32", - "20.207.97.190/32", - "40.81.250.205/32", - "40.64.106.11/32", - "40.64.105.247/32", - "20.72.242.93/32", - "20.99.255.235/32", - "20.113.3.121/32", - "52.224.16.221/32", - "52.224.21.53/32", - "52.224.20.204/32", - "52.224.21.19/32", - "52.224.20.249/32", - "52.224.20.203/32", - "52.224.20.190/32", - "52.224.16.229/32", - "52.224.21.20/32", - "52.146.63.80/32", - "52.224.20.227/32", - "52.224.20.193/32", - "52.190.37.160/32", - "52.224.21.23/32", - "52.224.20.223/32", - "52.224.20.181/32", - "52.224.21.49/32", - "52.224.21.55/32", - "52.224.21.61/32", - "52.224.19.152/32", - "52.224.20.186/32", - "52.224.21.27/32", - "52.224.21.51/32", - "52.224.20.174/32", - "52.224.21.4/32", - "51.104.164.109/32", - "51.104.167.71/32", - "51.104.160.177/32", - "51.104.162.149/32", - "51.104.167.95/32", - "51.104.167.54/32", - "51.104.166.111/32", - "51.104.167.88/32", - "51.104.161.32/32", - "51.104.163.250/32", - "51.104.164.189/32", - "51.104.167.19/32", - "51.104.160.167/32", - "51.104.167.110/32", - "20.191.44.119/32", - "51.104.167.104/32", - "20.191.44.234/32", - "51.104.164.215/32", - "51.104.167.52/32", - "20.191.44.22/32", - "51.104.167.87/32", - "51.104.167.96/32", - "20.191.44.16/32", - "51.104.167.61/32", - "51.104.164.147/32", - "20.50.48.159/32", - "40.114.182.172/32", - "20.50.50.130/32", - "20.50.50.163/32", - "20.50.50.46/32", - "40.114.182.153/32", - "20.50.50.118/32", - "20.50.49.55/32", - "20.50.49.25/32", - "40.114.183.251/32", - "20.50.50.123/32", - "20.50.49.237/32", - "20.50.48.192/32", - "20.50.50.134/32", - "51.138.90.233/32", - "40.114.183.196/32", - "20.50.50.146/32", - "40.114.183.88/32", - "20.50.50.145/32", - "20.50.50.121/32", - "20.50.49.40/32", - "51.138.90.206/32", - "40.114.182.45/32", - "51.138.90.161/32", - "20.50.49.0/32", - "40.119.232.215/32", - "104.43.55.167/32", - "40.119.232.251/32", - "40.119.232.50/32", - "40.119.232.146/32", - "40.119.232.218/32", - "104.43.54.127/32", - "104.43.55.117/32", - "104.43.55.116/32", - "104.43.55.166/32", - "52.154.169.50/32", - "52.154.171.70/32", - "52.154.170.229/32", - "52.154.170.113/32", - "52.154.171.44/32", - "52.154.172.2/32", - "52.143.244.81/32", - "52.154.171.87/32", - "52.154.171.250/32", - "52.154.170.28/32", - "52.154.170.122/32", - "52.143.243.117/32", - "52.143.247.235/32", - "52.154.171.235/32", - "52.154.171.196/32", - "52.154.171.0/32", - "52.154.170.243/32", - "52.154.170.26/32", - "52.154.169.200/32", - "52.154.170.96/32", - "52.154.170.88/32", - "52.154.171.150/32", - "52.154.171.205/32", - "52.154.170.117/32", - "52.154.170.209/32", - "191.235.202.48/32", - "191.233.3.202/32", - "191.235.201.214/32", - "191.233.3.197/32", - "191.235.202.38/32", - "20.53.78.144/32", - "20.193.24.10/32", - "20.53.78.236/32", - "20.53.78.138/32", - "20.53.78.123/32", - "20.53.78.106/32", - "20.193.27.215/32", - "20.193.25.197/32", - "20.193.12.126/32", - "20.193.24.251/32", - "20.204.242.101/32", - "20.207.72.113/32", - "20.204.242.19/32", - "20.219.45.67/32", - "20.207.72.11/32", - "20.219.45.190/32", - "20.204.243.55/32", - "20.204.241.148/32", - "20.207.72.110/32", - "20.204.240.172/32", - "20.207.72.21/32", - "20.204.246.81/32", - "20.207.107.181/32", - "20.204.246.254/32", - "20.219.43.246/32", - "52.149.25.43/32", - "52.149.61.51/32", - "52.149.58.139/32", - "52.149.60.38/32", - "52.148.165.38/32", - "52.143.95.162/32", - "52.149.56.151/32", - "52.149.30.45/32", - "52.149.58.173/32", - "52.143.95.204/32", - "52.149.28.83/32", - "52.149.58.69/32", - "52.148.161.87/32", - "52.149.58.27/32", - "52.149.28.18/32", - "20.79.226.26/32", - "20.79.239.66/32", - "20.79.238.198/32", - "20.113.14.159/32", - "20.75.144.152/32", - "20.43.172.120/32", - "20.53.134.160/32", - "20.201.15.208/32", - "20.93.28.24/32", - "20.61.34.40/32", - "52.242.224.168/32", - "20.80.129.80/32", - "20.195.108.47/32", - "4.195.133.120/32", - "4.228.76.163/32", - "4.182.131.108/32", - "4.209.224.56/32", - "108.141.83.74/32", - "4.213.46.14/32", - "172.169.17.165/32", - "51.8.71.117/32", - "20.3.1.178/32", - "52.149.56.151/32", - "52.149.30.45/32", - "52.149.58.173/32", - "52.143.95.204/32", - "52.149.28.83/32", - "52.149.58.69/32", - "52.148.161.87/32", - "52.149.58.27/32", - "52.149.28.18/32", - "20.79.226.26/32", - "20.79.239.66/32", - "20.79.238.198/32", - "20.113.14.159/32", - "20.75.144.152/32", - "20.43.172.120/32", - "20.53.134.160/32", - "20.201.15.208/32", - "20.93.28.24/32", - "20.61.34.40/32", - "52.242.224.168/32", - "20.80.129.80/32", - "20.195.108.47/32", - "4.195.133.120/32", - "4.228.76.163/32", - "4.182.131.108/32", - "4.209.224.56/32", - "108.141.83.74/32", - "4.213.46.14/32", - "172.169.17.165/32", - "51.8.71.117/32", - "20.3.1.178/32" - ] + remote_addresses: + [ + "57.152.72.128/32", + "51.8.253.152/32", + "40.80.242.63/32", + "20.12.141.99/32", + "20.49.136.28/32", + "51.116.131.221/32", + "51.107.40.209/32", + "20.40.133.240/32", + "20.50.168.91/32", + "51.120.48.122/32", + "20.193.45.113/32", + "40.76.173.151/32", + "40.76.163.7/32", + "20.185.79.47/32", + "52.142.26.175/32", + "20.185.79.15/32", + "52.142.24.149/32", + "40.76.162.208/32", + "40.76.163.23/32", + "40.76.162.191/32", + "40.76.162.247/32", + "40.88.21.235/32", + "20.191.45.212/32", + "52.146.59.12/32", + "52.146.59.156/32", + "52.146.59.154/32", + "52.146.58.236/32", + "20.62.224.44/32", + "51.104.180.53/32", + "51.104.180.47/32", + "51.104.180.26/32", + "51.104.146.225/32", + "51.104.146.235/32", + "20.73.202.147/32", + "20.73.132.240/32", + "20.71.12.143/32", + "20.56.197.58/32", + "20.56.197.63/32", + "20.43.150.93/32", + "20.43.150.85/32", + "20.44.222.1/32", + "40.89.243.175/32", + "13.89.106.77/32", + "52.143.242.6/32", + "52.143.241.111/32", + "52.154.60.82/32", + "20.197.209.11/32", + "20.197.209.27/32", + "20.226.133.105/32", + "191.234.216.4/32", + "191.234.216.178/32", + "20.53.92.211/32", + "20.53.91.2/32", + "20.207.99.197/32", + "20.207.97.190/32", + "40.81.250.205/32", + "40.64.106.11/32", + "40.64.105.247/32", + "20.72.242.93/32", + "20.99.255.235/32", + "20.113.3.121/32", + "52.224.16.221/32", + "52.224.21.53/32", + "52.224.20.204/32", + "52.224.21.19/32", + "52.224.20.249/32", + "52.224.20.203/32", + "52.224.20.190/32", + "52.224.16.229/32", + "52.224.21.20/32", + "52.146.63.80/32", + "52.224.20.227/32", + "52.224.20.193/32", + "52.190.37.160/32", + "52.224.21.23/32", + "52.224.20.223/32", + "52.224.20.181/32", + "52.224.21.49/32", + "52.224.21.55/32", + "52.224.21.61/32", + "52.224.19.152/32", + "52.224.20.186/32", + "52.224.21.27/32", + "52.224.21.51/32", + "52.224.20.174/32", + "52.224.21.4/32", + "51.104.164.109/32", + "51.104.167.71/32", + "51.104.160.177/32", + "51.104.162.149/32", + "51.104.167.95/32", + "51.104.167.54/32", + "51.104.166.111/32", + "51.104.167.88/32", + "51.104.161.32/32", + "51.104.163.250/32", + "51.104.164.189/32", + "51.104.167.19/32", + "51.104.160.167/32", + "51.104.167.110/32", + "20.191.44.119/32", + "51.104.167.104/32", + "20.191.44.234/32", + "51.104.164.215/32", + "51.104.167.52/32", + "20.191.44.22/32", + "51.104.167.87/32", + "51.104.167.96/32", + "20.191.44.16/32", + "51.104.167.61/32", + "51.104.164.147/32", + "20.50.48.159/32", + "40.114.182.172/32", + "20.50.50.130/32", + "20.50.50.163/32", + "20.50.50.46/32", + "40.114.182.153/32", + "20.50.50.118/32", + "20.50.49.55/32", + "20.50.49.25/32", + "40.114.183.251/32", + "20.50.50.123/32", + "20.50.49.237/32", + "20.50.48.192/32", + "20.50.50.134/32", + "51.138.90.233/32", + "40.114.183.196/32", + "20.50.50.146/32", + "40.114.183.88/32", + "20.50.50.145/32", + "20.50.50.121/32", + "20.50.49.40/32", + "51.138.90.206/32", + "40.114.182.45/32", + "51.138.90.161/32", + "20.50.49.0/32", + "40.119.232.215/32", + "104.43.55.167/32", + "40.119.232.251/32", + "40.119.232.50/32", + "40.119.232.146/32", + "40.119.232.218/32", + "104.43.54.127/32", + "104.43.55.117/32", + "104.43.55.116/32", + "104.43.55.166/32", + "52.154.169.50/32", + "52.154.171.70/32", + "52.154.170.229/32", + "52.154.170.113/32", + "52.154.171.44/32", + "52.154.172.2/32", + "52.143.244.81/32", + "52.154.171.87/32", + "52.154.171.250/32", + "52.154.170.28/32", + "52.154.170.122/32", + "52.143.243.117/32", + "52.143.247.235/32", + "52.154.171.235/32", + "52.154.171.196/32", + "52.154.171.0/32", + "52.154.170.243/32", + "52.154.170.26/32", + "52.154.169.200/32", + "52.154.170.96/32", + "52.154.170.88/32", + "52.154.171.150/32", + "52.154.171.205/32", + "52.154.170.117/32", + "52.154.170.209/32", + "191.235.202.48/32", + "191.233.3.202/32", + "191.235.201.214/32", + "191.233.3.197/32", + "191.235.202.38/32", + "20.53.78.144/32", + "20.193.24.10/32", + "20.53.78.236/32", + "20.53.78.138/32", + "20.53.78.123/32", + "20.53.78.106/32", + "20.193.27.215/32", + "20.193.25.197/32", + "20.193.12.126/32", + "20.193.24.251/32", + "20.204.242.101/32", + "20.207.72.113/32", + "20.204.242.19/32", + "20.219.45.67/32", + "20.207.72.11/32", + "20.219.45.190/32", + "20.204.243.55/32", + "20.204.241.148/32", + "20.207.72.110/32", + "20.204.240.172/32", + "20.207.72.21/32", + "20.204.246.81/32", + "20.207.107.181/32", + "20.204.246.254/32", + "20.219.43.246/32", + "52.149.25.43/32", + "52.149.61.51/32", + "52.149.58.139/32", + "52.149.60.38/32", + "52.148.165.38/32", + "52.143.95.162/32", + "52.149.56.151/32", + "52.149.30.45/32", + "52.149.58.173/32", + "52.143.95.204/32", + "52.149.28.83/32", + "52.149.58.69/32", + "52.148.161.87/32", + "52.149.58.27/32", + "52.149.28.18/32", + "20.79.226.26/32", + "20.79.239.66/32", + "20.79.238.198/32", + "20.113.14.159/32", + "20.75.144.152/32", + "20.43.172.120/32", + "20.53.134.160/32", + "20.201.15.208/32", + "20.93.28.24/32", + "20.61.34.40/32", + "52.242.224.168/32", + "20.80.129.80/32", + "20.195.108.47/32", + "4.195.133.120/32", + "4.228.76.163/32", + "4.182.131.108/32", + "4.209.224.56/32", + "108.141.83.74/32", + "4.213.46.14/32", + "172.169.17.165/32", + "51.8.71.117/32", + "20.3.1.178/32", + "52.149.56.151/32", + "52.149.30.45/32", + "52.149.58.173/32", + "52.143.95.204/32", + "52.149.28.83/32", + "52.149.58.69/32", + "52.148.161.87/32", + "52.149.58.27/32", + "52.149.28.18/32", + "20.79.226.26/32", + "20.79.239.66/32", + "20.79.238.198/32", + "20.113.14.159/32", + "20.75.144.152/32", + "20.43.172.120/32", + "20.53.134.160/32", + "20.201.15.208/32", + "20.93.28.24/32", + "20.61.34.40/32", + "52.242.224.168/32", + "20.80.129.80/32", + "20.195.108.47/32", + "4.195.133.120/32", + "4.228.76.163/32", + "4.182.131.108/32", + "4.209.224.56/32", + "108.141.83.74/32", + "4.213.46.14/32", + "172.169.17.165/32", + "51.8.71.117/32", + "20.3.1.178/32", + ] diff --git a/data/crawlers/googlebot.yaml b/data/crawlers/googlebot.yaml index f1735126..e304cf9c 100644 --- a/data/crawlers/googlebot.yaml +++ b/data/crawlers/googlebot.yaml @@ -2,262 +2,263 @@ user_agent_regex: \+http\://www\.google\.com/bot\.html action: ALLOW # https://developers.google.com/static/search/apis/ipranges/googlebot.json - remote_addresses: [ - "2001:4860:4801:10::/64", - "2001:4860:4801:11::/64", - "2001:4860:4801:12::/64", - "2001:4860:4801:13::/64", - "2001:4860:4801:14::/64", - "2001:4860:4801:15::/64", - "2001:4860:4801:16::/64", - "2001:4860:4801:17::/64", - "2001:4860:4801:18::/64", - "2001:4860:4801:19::/64", - "2001:4860:4801:1a::/64", - "2001:4860:4801:1b::/64", - "2001:4860:4801:1c::/64", - "2001:4860:4801:1d::/64", - "2001:4860:4801:1e::/64", - "2001:4860:4801:1f::/64", - "2001:4860:4801:20::/64", - "2001:4860:4801:21::/64", - "2001:4860:4801:22::/64", - "2001:4860:4801:23::/64", - "2001:4860:4801:24::/64", - "2001:4860:4801:25::/64", - "2001:4860:4801:26::/64", - "2001:4860:4801:27::/64", - "2001:4860:4801:28::/64", - "2001:4860:4801:29::/64", - "2001:4860:4801:2::/64", - "2001:4860:4801:2a::/64", - "2001:4860:4801:2b::/64", - "2001:4860:4801:2c::/64", - "2001:4860:4801:2d::/64", - "2001:4860:4801:2e::/64", - "2001:4860:4801:2f::/64", - "2001:4860:4801:31::/64", - "2001:4860:4801:32::/64", - "2001:4860:4801:33::/64", - "2001:4860:4801:34::/64", - "2001:4860:4801:35::/64", - "2001:4860:4801:36::/64", - "2001:4860:4801:37::/64", - "2001:4860:4801:38::/64", - "2001:4860:4801:39::/64", - "2001:4860:4801:3a::/64", - "2001:4860:4801:3b::/64", - "2001:4860:4801:3c::/64", - "2001:4860:4801:3d::/64", - "2001:4860:4801:3e::/64", - "2001:4860:4801:40::/64", - "2001:4860:4801:41::/64", - "2001:4860:4801:42::/64", - "2001:4860:4801:43::/64", - "2001:4860:4801:44::/64", - "2001:4860:4801:45::/64", - "2001:4860:4801:46::/64", - "2001:4860:4801:47::/64", - "2001:4860:4801:48::/64", - "2001:4860:4801:49::/64", - "2001:4860:4801:4a::/64", - "2001:4860:4801:4b::/64", - "2001:4860:4801:4c::/64", - "2001:4860:4801:50::/64", - "2001:4860:4801:51::/64", - "2001:4860:4801:52::/64", - "2001:4860:4801:53::/64", - "2001:4860:4801:54::/64", - "2001:4860:4801:55::/64", - "2001:4860:4801:56::/64", - "2001:4860:4801:60::/64", - "2001:4860:4801:61::/64", - "2001:4860:4801:62::/64", - "2001:4860:4801:63::/64", - "2001:4860:4801:64::/64", - "2001:4860:4801:65::/64", - "2001:4860:4801:66::/64", - "2001:4860:4801:67::/64", - "2001:4860:4801:68::/64", - "2001:4860:4801:69::/64", - "2001:4860:4801:6a::/64", - "2001:4860:4801:6b::/64", - "2001:4860:4801:6c::/64", - "2001:4860:4801:6d::/64", - "2001:4860:4801:6e::/64", - "2001:4860:4801:6f::/64", - "2001:4860:4801:70::/64", - "2001:4860:4801:71::/64", - "2001:4860:4801:72::/64", - "2001:4860:4801:73::/64", - "2001:4860:4801:74::/64", - "2001:4860:4801:75::/64", - "2001:4860:4801:76::/64", - "2001:4860:4801:77::/64", - "2001:4860:4801:78::/64", - "2001:4860:4801:79::/64", - "2001:4860:4801:80::/64", - "2001:4860:4801:81::/64", - "2001:4860:4801:82::/64", - "2001:4860:4801:83::/64", - "2001:4860:4801:84::/64", - "2001:4860:4801:85::/64", - "2001:4860:4801:86::/64", - "2001:4860:4801:87::/64", - "2001:4860:4801:88::/64", - "2001:4860:4801:90::/64", - "2001:4860:4801:91::/64", - "2001:4860:4801:92::/64", - "2001:4860:4801:93::/64", - "2001:4860:4801:94::/64", - "2001:4860:4801:95::/64", - "2001:4860:4801:96::/64", - "2001:4860:4801:a0::/64", - "2001:4860:4801:a1::/64", - "2001:4860:4801:a2::/64", - "2001:4860:4801:a3::/64", - "2001:4860:4801:a4::/64", - "2001:4860:4801:a5::/64", - "2001:4860:4801:c::/64", - "2001:4860:4801:f::/64", - "192.178.5.0/27", - "192.178.6.0/27", - "192.178.6.128/27", - "192.178.6.160/27", - "192.178.6.192/27", - "192.178.6.32/27", - "192.178.6.64/27", - "192.178.6.96/27", - "34.100.182.96/28", - "34.101.50.144/28", - "34.118.254.0/28", - "34.118.66.0/28", - "34.126.178.96/28", - "34.146.150.144/28", - "34.147.110.144/28", - "34.151.74.144/28", - "34.152.50.64/28", - "34.154.114.144/28", - "34.155.98.32/28", - "34.165.18.176/28", - "34.175.160.64/28", - "34.176.130.16/28", - "34.22.85.0/27", - "34.64.82.64/28", - "34.65.242.112/28", - "34.80.50.80/28", - "34.88.194.0/28", - "34.89.10.80/28", - "34.89.198.80/28", - "34.96.162.48/28", - "35.247.243.240/28", - "66.249.64.0/27", - "66.249.64.128/27", - "66.249.64.160/27", - "66.249.64.224/27", - "66.249.64.32/27", - "66.249.64.64/27", - "66.249.64.96/27", - "66.249.65.0/27", - "66.249.65.128/27", - "66.249.65.160/27", - "66.249.65.192/27", - "66.249.65.224/27", - "66.249.65.32/27", - "66.249.65.64/27", - "66.249.65.96/27", - "66.249.66.0/27", - "66.249.66.128/27", - "66.249.66.160/27", - "66.249.66.192/27", - "66.249.66.224/27", - "66.249.66.32/27", - "66.249.66.64/27", - "66.249.66.96/27", - "66.249.68.0/27", - "66.249.68.128/27", - "66.249.68.32/27", - "66.249.68.64/27", - "66.249.68.96/27", - "66.249.69.0/27", - "66.249.69.128/27", - "66.249.69.160/27", - "66.249.69.192/27", - "66.249.69.224/27", - "66.249.69.32/27", - "66.249.69.64/27", - "66.249.69.96/27", - "66.249.70.0/27", - "66.249.70.128/27", - "66.249.70.160/27", - "66.249.70.192/27", - "66.249.70.224/27", - "66.249.70.32/27", - "66.249.70.64/27", - "66.249.70.96/27", - "66.249.71.0/27", - "66.249.71.128/27", - "66.249.71.160/27", - "66.249.71.192/27", - "66.249.71.224/27", - "66.249.71.32/27", - "66.249.71.64/27", - "66.249.71.96/27", - "66.249.72.0/27", - "66.249.72.128/27", - "66.249.72.160/27", - "66.249.72.192/27", - "66.249.72.224/27", - "66.249.72.32/27", - "66.249.72.64/27", - "66.249.72.96/27", - "66.249.73.0/27", - "66.249.73.128/27", - "66.249.73.160/27", - "66.249.73.192/27", - "66.249.73.224/27", - "66.249.73.32/27", - "66.249.73.64/27", - "66.249.73.96/27", - "66.249.74.0/27", - "66.249.74.128/27", - "66.249.74.160/27", - "66.249.74.192/27", - "66.249.74.32/27", - "66.249.74.64/27", - "66.249.74.96/27", - "66.249.75.0/27", - "66.249.75.128/27", - "66.249.75.160/27", - "66.249.75.192/27", - "66.249.75.224/27", - "66.249.75.32/27", - "66.249.75.64/27", - "66.249.75.96/27", - "66.249.76.0/27", - "66.249.76.128/27", - "66.249.76.160/27", - "66.249.76.192/27", - "66.249.76.224/27", - "66.249.76.32/27", - "66.249.76.64/27", - "66.249.76.96/27", - "66.249.77.0/27", - "66.249.77.128/27", - "66.249.77.160/27", - "66.249.77.192/27", - "66.249.77.224/27", - "66.249.77.32/27", - "66.249.77.64/27", - "66.249.77.96/27", - "66.249.78.0/27", - "66.249.78.32/27", - "66.249.79.0/27", - "66.249.79.128/27", - "66.249.79.160/27", - "66.249.79.192/27", - "66.249.79.224/27", - "66.249.79.32/27", - "66.249.79.64/27", - "66.249.79.96/27" - ] + remote_addresses: + [ + "2001:4860:4801:10::/64", + "2001:4860:4801:11::/64", + "2001:4860:4801:12::/64", + "2001:4860:4801:13::/64", + "2001:4860:4801:14::/64", + "2001:4860:4801:15::/64", + "2001:4860:4801:16::/64", + "2001:4860:4801:17::/64", + "2001:4860:4801:18::/64", + "2001:4860:4801:19::/64", + "2001:4860:4801:1a::/64", + "2001:4860:4801:1b::/64", + "2001:4860:4801:1c::/64", + "2001:4860:4801:1d::/64", + "2001:4860:4801:1e::/64", + "2001:4860:4801:1f::/64", + "2001:4860:4801:20::/64", + "2001:4860:4801:21::/64", + "2001:4860:4801:22::/64", + "2001:4860:4801:23::/64", + "2001:4860:4801:24::/64", + "2001:4860:4801:25::/64", + "2001:4860:4801:26::/64", + "2001:4860:4801:27::/64", + "2001:4860:4801:28::/64", + "2001:4860:4801:29::/64", + "2001:4860:4801:2::/64", + "2001:4860:4801:2a::/64", + "2001:4860:4801:2b::/64", + "2001:4860:4801:2c::/64", + "2001:4860:4801:2d::/64", + "2001:4860:4801:2e::/64", + "2001:4860:4801:2f::/64", + "2001:4860:4801:31::/64", + "2001:4860:4801:32::/64", + "2001:4860:4801:33::/64", + "2001:4860:4801:34::/64", + "2001:4860:4801:35::/64", + "2001:4860:4801:36::/64", + "2001:4860:4801:37::/64", + "2001:4860:4801:38::/64", + "2001:4860:4801:39::/64", + "2001:4860:4801:3a::/64", + "2001:4860:4801:3b::/64", + "2001:4860:4801:3c::/64", + "2001:4860:4801:3d::/64", + "2001:4860:4801:3e::/64", + "2001:4860:4801:40::/64", + "2001:4860:4801:41::/64", + "2001:4860:4801:42::/64", + "2001:4860:4801:43::/64", + "2001:4860:4801:44::/64", + "2001:4860:4801:45::/64", + "2001:4860:4801:46::/64", + "2001:4860:4801:47::/64", + "2001:4860:4801:48::/64", + "2001:4860:4801:49::/64", + "2001:4860:4801:4a::/64", + "2001:4860:4801:4b::/64", + "2001:4860:4801:4c::/64", + "2001:4860:4801:50::/64", + "2001:4860:4801:51::/64", + "2001:4860:4801:52::/64", + "2001:4860:4801:53::/64", + "2001:4860:4801:54::/64", + "2001:4860:4801:55::/64", + "2001:4860:4801:56::/64", + "2001:4860:4801:60::/64", + "2001:4860:4801:61::/64", + "2001:4860:4801:62::/64", + "2001:4860:4801:63::/64", + "2001:4860:4801:64::/64", + "2001:4860:4801:65::/64", + "2001:4860:4801:66::/64", + "2001:4860:4801:67::/64", + "2001:4860:4801:68::/64", + "2001:4860:4801:69::/64", + "2001:4860:4801:6a::/64", + "2001:4860:4801:6b::/64", + "2001:4860:4801:6c::/64", + "2001:4860:4801:6d::/64", + "2001:4860:4801:6e::/64", + "2001:4860:4801:6f::/64", + "2001:4860:4801:70::/64", + "2001:4860:4801:71::/64", + "2001:4860:4801:72::/64", + "2001:4860:4801:73::/64", + "2001:4860:4801:74::/64", + "2001:4860:4801:75::/64", + "2001:4860:4801:76::/64", + "2001:4860:4801:77::/64", + "2001:4860:4801:78::/64", + "2001:4860:4801:79::/64", + "2001:4860:4801:80::/64", + "2001:4860:4801:81::/64", + "2001:4860:4801:82::/64", + "2001:4860:4801:83::/64", + "2001:4860:4801:84::/64", + "2001:4860:4801:85::/64", + "2001:4860:4801:86::/64", + "2001:4860:4801:87::/64", + "2001:4860:4801:88::/64", + "2001:4860:4801:90::/64", + "2001:4860:4801:91::/64", + "2001:4860:4801:92::/64", + "2001:4860:4801:93::/64", + "2001:4860:4801:94::/64", + "2001:4860:4801:95::/64", + "2001:4860:4801:96::/64", + "2001:4860:4801:a0::/64", + "2001:4860:4801:a1::/64", + "2001:4860:4801:a2::/64", + "2001:4860:4801:a3::/64", + "2001:4860:4801:a4::/64", + "2001:4860:4801:a5::/64", + "2001:4860:4801:c::/64", + "2001:4860:4801:f::/64", + "192.178.5.0/27", + "192.178.6.0/27", + "192.178.6.128/27", + "192.178.6.160/27", + "192.178.6.192/27", + "192.178.6.32/27", + "192.178.6.64/27", + "192.178.6.96/27", + "34.100.182.96/28", + "34.101.50.144/28", + "34.118.254.0/28", + "34.118.66.0/28", + "34.126.178.96/28", + "34.146.150.144/28", + "34.147.110.144/28", + "34.151.74.144/28", + "34.152.50.64/28", + "34.154.114.144/28", + "34.155.98.32/28", + "34.165.18.176/28", + "34.175.160.64/28", + "34.176.130.16/28", + "34.22.85.0/27", + "34.64.82.64/28", + "34.65.242.112/28", + "34.80.50.80/28", + "34.88.194.0/28", + "34.89.10.80/28", + "34.89.198.80/28", + "34.96.162.48/28", + "35.247.243.240/28", + "66.249.64.0/27", + "66.249.64.128/27", + "66.249.64.160/27", + "66.249.64.224/27", + "66.249.64.32/27", + "66.249.64.64/27", + "66.249.64.96/27", + "66.249.65.0/27", + "66.249.65.128/27", + "66.249.65.160/27", + "66.249.65.192/27", + "66.249.65.224/27", + "66.249.65.32/27", + "66.249.65.64/27", + "66.249.65.96/27", + "66.249.66.0/27", + "66.249.66.128/27", + "66.249.66.160/27", + "66.249.66.192/27", + "66.249.66.224/27", + "66.249.66.32/27", + "66.249.66.64/27", + "66.249.66.96/27", + "66.249.68.0/27", + "66.249.68.128/27", + "66.249.68.32/27", + "66.249.68.64/27", + "66.249.68.96/27", + "66.249.69.0/27", + "66.249.69.128/27", + "66.249.69.160/27", + "66.249.69.192/27", + "66.249.69.224/27", + "66.249.69.32/27", + "66.249.69.64/27", + "66.249.69.96/27", + "66.249.70.0/27", + "66.249.70.128/27", + "66.249.70.160/27", + "66.249.70.192/27", + "66.249.70.224/27", + "66.249.70.32/27", + "66.249.70.64/27", + "66.249.70.96/27", + "66.249.71.0/27", + "66.249.71.128/27", + "66.249.71.160/27", + "66.249.71.192/27", + "66.249.71.224/27", + "66.249.71.32/27", + "66.249.71.64/27", + "66.249.71.96/27", + "66.249.72.0/27", + "66.249.72.128/27", + "66.249.72.160/27", + "66.249.72.192/27", + "66.249.72.224/27", + "66.249.72.32/27", + "66.249.72.64/27", + "66.249.72.96/27", + "66.249.73.0/27", + "66.249.73.128/27", + "66.249.73.160/27", + "66.249.73.192/27", + "66.249.73.224/27", + "66.249.73.32/27", + "66.249.73.64/27", + "66.249.73.96/27", + "66.249.74.0/27", + "66.249.74.128/27", + "66.249.74.160/27", + "66.249.74.192/27", + "66.249.74.32/27", + "66.249.74.64/27", + "66.249.74.96/27", + "66.249.75.0/27", + "66.249.75.128/27", + "66.249.75.160/27", + "66.249.75.192/27", + "66.249.75.224/27", + "66.249.75.32/27", + "66.249.75.64/27", + "66.249.75.96/27", + "66.249.76.0/27", + "66.249.76.128/27", + "66.249.76.160/27", + "66.249.76.192/27", + "66.249.76.224/27", + "66.249.76.32/27", + "66.249.76.64/27", + "66.249.76.96/27", + "66.249.77.0/27", + "66.249.77.128/27", + "66.249.77.160/27", + "66.249.77.192/27", + "66.249.77.224/27", + "66.249.77.32/27", + "66.249.77.64/27", + "66.249.77.96/27", + "66.249.78.0/27", + "66.249.78.32/27", + "66.249.79.0/27", + "66.249.79.128/27", + "66.249.79.160/27", + "66.249.79.192/27", + "66.249.79.224/27", + "66.249.79.32/27", + "66.249.79.64/27", + "66.249.79.96/27", + ] diff --git a/data/crawlers/internet-archive.yaml b/data/crawlers/internet-archive.yaml index 5e209e85..99548409 100644 --- a/data/crawlers/internet-archive.yaml +++ b/data/crawlers/internet-archive.yaml @@ -1,8 +1,4 @@ - name: internet-archive action: ALLOW # https://ipinfo.io/AS7941 - remote_addresses: [ - "207.241.224.0/20", - "208.70.24.0/21", - "2620:0:9c0::/48" - ] \ No newline at end of file + remote_addresses: ["207.241.224.0/20", "208.70.24.0/21", "2620:0:9c0::/48"] diff --git a/data/crawlers/kagibot.yaml b/data/crawlers/kagibot.yaml index db62b579..2bbd5fc3 100644 --- a/data/crawlers/kagibot.yaml +++ b/data/crawlers/kagibot.yaml @@ -2,9 +2,10 @@ user_agent_regex: \+https\://kagi\.com/bot action: ALLOW # https://kagi.com/bot - remote_addresses: [ - "216.18.205.234/32", - "35.212.27.76/32", - "104.254.65.50/32", - "209.151.156.194/32" - ] + remote_addresses: + [ + "216.18.205.234/32", + "35.212.27.76/32", + "104.254.65.50/32", + "209.151.156.194/32", + ] diff --git a/data/crawlers/marginalia.yaml b/data/crawlers/marginalia.yaml index e12ebc45..ab6724b1 100644 --- a/data/crawlers/marginalia.yaml +++ b/data/crawlers/marginalia.yaml @@ -2,10 +2,11 @@ user_agent_regex: search\.marginalia\.nu action: ALLOW # Received directly over email - remote_addresses: [ - "193.183.0.162/31", - "193.183.0.164/30", - "193.183.0.168/30", - "193.183.0.172/31", - "193.183.0.174/32" - ] \ No newline at end of file + remote_addresses: + [ + "193.183.0.162/31", + "193.183.0.164/30", + "193.183.0.168/30", + "193.183.0.172/31", + "193.183.0.174/32", + ] diff --git a/data/crawlers/mojeekbot.yaml b/data/crawlers/mojeekbot.yaml index 40661203..79fd17b0 100644 --- a/data/crawlers/mojeekbot.yaml +++ b/data/crawlers/mojeekbot.yaml @@ -2,4 +2,4 @@ user_agent_regex: \+https\://www\.mojeek\.com/bot\.html action: ALLOW # https://www.mojeek.com/bot.html - remote_addresses: [ "5.102.173.71/32" ] \ No newline at end of file + remote_addresses: ["5.102.173.71/32"] diff --git a/data/crawlers/openai-gptbot.yaml b/data/crawlers/openai-gptbot.yaml index 42658e77..c414d42b 100644 --- a/data/crawlers/openai-gptbot.yaml +++ b/data/crawlers/openai-gptbot.yaml @@ -4,13 +4,14 @@ user_agent_regex: GPTBot/1\.1; \+https\://openai\.com/gptbot action: ALLOW # https://openai.com/gptbot.json - remote_addresses: [ - "52.230.152.0/24", - "20.171.206.0/24", - "20.171.207.0/24", - "4.227.36.0/25", - "20.125.66.80/28", - "172.182.204.0/24", - "172.182.214.0/24", - "172.182.215.0/24", - ] \ No newline at end of file + remote_addresses: + [ + "52.230.152.0/24", + "20.171.206.0/24", + "20.171.207.0/24", + "4.227.36.0/25", + "20.125.66.80/28", + "172.182.204.0/24", + "172.182.214.0/24", + "172.182.215.0/24", + ] diff --git a/data/crawlers/openai-searchbot.yaml b/data/crawlers/openai-searchbot.yaml index 05796be9..2a7a306e 100644 --- a/data/crawlers/openai-searchbot.yaml +++ b/data/crawlers/openai-searchbot.yaml @@ -4,10 +4,11 @@ user_agent_regex: OAI-SearchBot/1\.0; \+https\://openai\.com/searchbot action: ALLOW # https://openai.com/searchbot.json - remote_addresses: [ - "20.42.10.176/28", - "172.203.190.128/28", - "104.210.140.128/28", - "51.8.102.0/24", - "135.234.64.0/24" - ] \ No newline at end of file + remote_addresses: + [ + "20.42.10.176/28", + "172.203.190.128/28", + "104.210.140.128/28", + "51.8.102.0/24", + "135.234.64.0/24", + ] diff --git a/data/crawlers/perplexitybot.yaml b/data/crawlers/perplexitybot.yaml index d217162a..3da45b1c 100644 --- a/data/crawlers/perplexitybot.yaml +++ b/data/crawlers/perplexitybot.yaml @@ -4,13 +4,14 @@ user_agent_regex: PerplexityBot/.+; \+https\://perplexity\.ai/perplexitybot action: ALLOW # https://www.perplexity.com/perplexitybot.json - remote_addresses: [ - "107.20.236.150/32", - "3.224.62.45/32", - "18.210.92.235/32", - "3.222.232.239/32", - "3.211.124.183/32", - "3.231.139.107/32", - "18.97.1.228/30", - "18.97.9.96/29", - ] + remote_addresses: + [ + "107.20.236.150/32", + "3.224.62.45/32", + "18.210.92.235/32", + "3.222.232.239/32", + "3.211.124.183/32", + "3.231.139.107/32", + "18.97.1.228/30", + "18.97.9.96/29", + ] diff --git a/data/crawlers/qwantbot.yaml b/data/crawlers/qwantbot.yaml index a4021549..d43d06b6 100644 --- a/data/crawlers/qwantbot.yaml +++ b/data/crawlers/qwantbot.yaml @@ -2,4 +2,4 @@ user_agent_regex: \+https\://help\.qwant\.com/bot/ action: ALLOW # https://help.qwant.com/wp-content/uploads/sites/2/2025/01/qwantbot.json - remote_addresses: [ "91.242.162.0/24" ] + remote_addresses: ["91.242.162.0/24"] diff --git a/data/crawlers/wikimedia-citoid.yaml b/data/crawlers/wikimedia-citoid.yaml new file mode 100644 index 00000000..e5d4ede7 --- /dev/null +++ b/data/crawlers/wikimedia-citoid.yaml @@ -0,0 +1,18 @@ +# Wikimedia Foundation citation services +# https://www.mediawiki.org/wiki/Citoid + +- name: wikimedia-citoid + user_agent_regex: "Citoid/WMF" + action: ALLOW + remote_addresses: [ + "208.80.152.0/22", + "2620:0:860::/46", + ] + +- name: wikimedia-zotero-translation-server + user_agent_regex: "ZoteroTranslationServer/WMF" + action: ALLOW + remote_addresses: [ + "208.80.152.0/22", + "2620:0:860::/46", + ] \ No newline at end of file diff --git a/data/crawlers/yandexbot.yaml b/data/crawlers/yandexbot.yaml index f7f84376..94f8ed8e 100644 --- a/data/crawlers/yandexbot.yaml +++ b/data/crawlers/yandexbot.yaml @@ -1,6 +1,6 @@ -- name: yandexbot - action: ALLOW - expression: - all: - - userAgent.matches("\\+http\\://yandex\\.com/bots") - - verifyFCrDNS(remoteAddress, "^.*\\.yandex\\.(ru|com|net)$") \ No newline at end of file +- name: yandexbot + action: ALLOW + expression: + all: + - userAgent.matches("\\+http\\://yandex\\.com/bots") + - verifyFCrDNS(remoteAddress, "^.*\\.yandex\\.(ru|com|net)$") diff --git a/data/meta/README.md b/data/meta/README.md index bf758c79..ad737ed3 100644 --- a/data/meta/README.md +++ b/data/meta/README.md @@ -2,4 +2,4 @@ Contains policies that exclusively reference policies in _multiple_ other data folders. -Akin to "stances" that the administrator can take, with reference to various topics, such as AI/LLM systems. \ No newline at end of file +Akin to "stances" that the administrator can take, with reference to various topics, such as AI/LLM systems. diff --git a/data/meta/ai-block-aggressive.yaml b/data/meta/ai-block-aggressive.yaml index f76e15ef..8c7db5e0 100644 --- a/data/meta/ai-block-aggressive.yaml +++ b/data/meta/ai-block-aggressive.yaml @@ -3,4 +3,4 @@ - import: (data)/bots/ai-catchall.yaml - import: (data)/clients/ai.yaml - import: (data)/crawlers/ai-search.yaml -- import: (data)/crawlers/ai-training.yaml \ No newline at end of file +- import: (data)/crawlers/ai-training.yaml diff --git a/data/meta/messengers-preview.yaml b/data/meta/messengers-preview.yaml index 08f3a3eb..3696832c 100644 --- a/data/meta/messengers-preview.yaml +++ b/data/meta/messengers-preview.yaml @@ -1,2 +1,2 @@ -- import: (data)/clients/telegram-preview.yaml -- import: (data)/clients/vk-preview.yaml \ No newline at end of file +- import: (data)/clients/telegram-preview.yaml +- import: (data)/clients/vk-preview.yaml diff --git a/data/services/updown.yaml b/data/services/updown.yaml new file mode 100644 index 00000000..e95089f5 --- /dev/null +++ b/data/services/updown.yaml @@ -0,0 +1,26 @@ +# https://updown.io/about +- name: updown + user_agent_regex: updown.io + action: ALLOW + remote_addresses: [ + "45.32.74.41/32", + "104.238.136.194/32", + "192.99.37.47/32", + "91.121.222.175/32", + "104.238.159.87/32", + "102.212.60.78/32", + "135.181.102.135/32", + "45.32.107.181/32", + "45.76.104.117/32", + "45.63.29.207/32", + "2001:19f0:6001:2c6::1/128", + "2001:19f0:9002:11a::1/128", + "2607:5300:60:4c2f::1/128", + "2001:41d0:2:85af::1/128", + "2001:19f0:6c01:145::1/128", + "2c0f:c40:4003:4::2/128", + "2a01:4f9:c010:d5f9::1/128", + "2001:19f0:4400:402e::1/128", + "2001:19f0:7001:45a::1/128", + "2001:19f0:5801:1d8::1/128" + ] \ No newline at end of file diff --git a/data/services/uptime-robot.yaml b/data/services/uptime-robot.yaml index b0d9e089..d4b8c6b1 100644 --- a/data/services/uptime-robot.yaml +++ b/data/services/uptime-robot.yaml @@ -2,222 +2,223 @@ user_agent_regex: UptimeRobot action: ALLOW # https://api.uptimerobot.com/meta/ips - remote_addresses: [ - "3.12.251.153/32", - "3.20.63.178/32", - "3.77.67.4/32", - "3.79.134.69/32", - "3.105.133.239/32", - "3.105.190.221/32", - "3.133.226.214/32", - "3.149.57.90/32", - "3.212.128.62/32", - "5.161.61.238/32", - "5.161.73.160/32", - "5.161.75.7/32", - "5.161.113.195/32", - "5.161.117.52/32", - "5.161.177.47/32", - "5.161.194.92/32", - "5.161.215.244/32", - "5.223.43.32/32", - "5.223.53.147/32", - "5.223.57.22/32", - "18.116.205.62/32", - "18.180.208.214/32", - "18.192.166.72/32", - "18.193.252.127/32", - "24.144.78.39/32", - "24.144.78.185/32", - "34.198.201.66/32", - "45.55.123.175/32", - "45.55.127.146/32", - "49.13.24.81/32", - "49.13.130.29/32", - "49.13.134.145/32", - "49.13.164.148/32", - "49.13.167.123/32", - "52.15.147.27/32", - "52.22.236.30/32", - "52.28.162.93/32", - "52.59.43.236/32", - "52.87.72.16/32", - "54.64.67.106/32", - "54.79.28.129/32", - "54.87.112.51/32", - "54.167.223.174/32", - "54.249.170.27/32", - "63.178.84.147/32", - "64.225.81.248/32", - "64.225.82.147/32", - "69.162.124.227/32", - "69.162.124.235/32", - "69.162.124.238/32", - "78.46.190.63/32", - "78.46.215.1/32", - "78.47.98.55/32", - "78.47.173.76/32", - "88.99.80.227/32", - "91.99.101.207/32", - "128.140.41.193/32", - "128.140.106.114/32", - "129.212.132.140/32", - "134.199.240.137/32", - "138.197.53.117/32", - "138.197.53.138/32", - "138.197.54.143/32", - "138.197.54.247/32", - "138.197.63.92/32", - "139.59.50.44/32", - "142.132.180.39/32", - "143.198.249.237/32", - "143.198.250.89/32", - "143.244.196.21/32", - "143.244.196.211/32", - "143.244.221.177/32", - "144.126.251.21/32", - "146.190.9.187/32", - "152.42.149.135/32", - "157.90.155.240/32", - "157.90.156.63/32", - "159.69.158.189/32", - "159.223.243.219/32", - "161.35.247.201/32", - "167.99.18.52/32", - "167.235.143.113/32", - "168.119.53.160/32", - "168.119.96.239/32", - "168.119.123.75/32", - "170.64.250.64/32", - "170.64.250.132/32", - "170.64.250.235/32", - "178.156.181.172/32", - "178.156.184.20/32", - "178.156.185.127/32", - "178.156.185.231/32", - "178.156.187.238/32", - "178.156.189.113/32", - "178.156.189.249/32", - "188.166.201.79/32", - "206.189.241.133/32", - "209.38.49.1/32", - "209.38.49.206/32", - "209.38.49.226/32", - "209.38.51.43/32", - "209.38.53.7/32", - "209.38.124.252/32", - "216.144.248.18/31", - "216.144.248.21/32", - "216.144.248.22/31", - "216.144.248.24/30", - "216.144.248.28/31", - "216.144.248.30/32", - "216.245.221.83/32", - "2400:6180:10:200::56a0:b000/128", - "2400:6180:10:200::56a0:c000/128", - "2400:6180:10:200::56a0:e000/128", - "2400:6180:100:d0::94b6:4001/128", - "2400:6180:100:d0::94b6:5001/128", - "2400:6180:100:d0::94b6:7001/128", - "2406:da14:94d:8601:9d0d:7754:bedf:e4f5/128", - "2406:da14:94d:8601:b325:ff58:2bba:7934/128", - "2406:da14:94d:8601:db4b:c5ac:2cbe:9a79/128", - "2406:da1c:9c8:dc02:7ae1:f2ea:ab91:2fde/128", - "2406:da1c:9c8:dc02:7db9:f38b:7b9f:402e/128", - "2406:da1c:9c8:dc02:82b2:f0fd:ee96:579/128", - "2600:1f16:775:3a00:ac3:c5eb:7081:942e/128", - "2600:1f16:775:3a00:37bf:6026:e54a:f03a/128", - "2600:1f16:775:3a00:3f24:5bb0:95d7:5a6b/128", - "2600:1f16:775:3a00:8c2c:2ba6:778f:5be5/128", - "2600:1f16:775:3a00:91ac:3120:ff38:92b5/128", - "2600:1f16:775:3a00:dbbe:36b0:3c45:da32/128", - "2600:1f18:179:f900:71:af9a:ade7:d772/128", - "2600:1f18:179:f900:2406:9399:4ae6:c5d3/128", - "2600:1f18:179:f900:4696:7729:7bb3:f52f/128", - "2600:1f18:179:f900:4b7d:d1cc:2d10:211/128", - "2600:1f18:179:f900:5c68:91b6:5d75:5d7/128", - "2600:1f18:179:f900:e8dd:eed1:a6c:183b/128", - "2604:a880:800:14:0:1:68ba:d000/128", - "2604:a880:800:14:0:1:68ba:e000/128", - "2604:a880:800:14:0:1:68bb:0/128", - "2604:a880:800:14:0:1:68bb:1000/128", - "2604:a880:800:14:0:1:68bb:3000/128", - "2604:a880:800:14:0:1:68bb:4000/128", - "2604:a880:800:14:0:1:68bb:5000/128", - "2604:a880:800:14:0:1:68bb:6000/128", - "2604:a880:800:14:0:1:68bb:7000/128", - "2604:a880:800:14:0:1:68bb:a000/128", - "2604:a880:800:14:0:1:68bb:b000/128", - "2604:a880:800:14:0:1:68bb:c000/128", - "2604:a880:800:14:0:1:68bb:d000/128", - "2604:a880:800:14:0:1:68bb:e000/128", - "2604:a880:800:14:0:1:68bb:f000/128", - "2607:ff68:107::4/128", - "2607:ff68:107::14/128", - "2607:ff68:107::33/128", - "2607:ff68:107::48/127", - "2607:ff68:107::50/125", - "2607:ff68:107::58/127", - "2607:ff68:107::60/128", - "2a01:4f8:c0c:83fa::1/128", - "2a01:4f8:c17:42e4::1/128", - "2a01:4f8:c2c:9fc6::1/128", - "2a01:4f8:c2c:beae::1/128", - "2a01:4f8:1c1a:3d53::1/128", - "2a01:4f8:1c1b:4ef4::1/128", - "2a01:4f8:1c1b:5b5a::1/128", - "2a01:4f8:1c1b:7ecc::1/128", - "2a01:4f8:1c1c:11aa::1/128", - "2a01:4f8:1c1c:5353::1/128", - "2a01:4f8:1c1c:7240::1/128", - "2a01:4f8:1c1c:a98a::1/128", - "2a01:4f8:c012:c60e::1/128", - "2a01:4f8:c013:c18::1/128", - "2a01:4f8:c013:34c0::1/128", - "2a01:4f8:c013:3b0f::1/128", - "2a01:4f8:c013:3c52::1/128", - "2a01:4f8:c013:3c53::1/128", - "2a01:4f8:c013:3c54::1/128", - "2a01:4f8:c013:3c55::1/128", - "2a01:4f8:c013:3c56::1/128", - "2a01:4ff:f0:bfd::1/128", - "2a01:4ff:f0:2219::1/128", - "2a01:4ff:f0:3e03::1/128", - "2a01:4ff:f0:5f80::1/128", - "2a01:4ff:f0:7fad::1/128", - "2a01:4ff:f0:9c5f::1/128", - "2a01:4ff:f0:b2f2::1/128", - "2a01:4ff:f0:b6f1::1/128", - "2a01:4ff:f0:d283::1/128", - "2a01:4ff:f0:d3cd::1/128", - "2a01:4ff:f0:e516::1/128", - "2a01:4ff:f0:e9cf::1/128", - "2a01:4ff:f0:eccb::1/128", - "2a01:4ff:f0:efd1::1/128", - "2a01:4ff:f0:fdc7::1/128", - "2a01:4ff:2f0:193c::1/128", - "2a01:4ff:2f0:27de::1/128", - "2a01:4ff:2f0:3b3a::1/128", - "2a03:b0c0:2:f0::bd91:f001/128", - "2a03:b0c0:2:f0::bd92:1/128", - "2a03:b0c0:2:f0::bd92:1001/128", - "2a03:b0c0:2:f0::bd92:2001/128", - "2a03:b0c0:2:f0::bd92:4001/128", - "2a03:b0c0:2:f0::bd92:5001/128", - "2a03:b0c0:2:f0::bd92:6001/128", - "2a03:b0c0:2:f0::bd92:7001/128", - "2a03:b0c0:2:f0::bd92:8001/128", - "2a03:b0c0:2:f0::bd92:9001/128", - "2a03:b0c0:2:f0::bd92:a001/128", - "2a03:b0c0:2:f0::bd92:b001/128", - "2a03:b0c0:2:f0::bd92:c001/128", - "2a03:b0c0:2:f0::bd92:e001/128", - "2a03:b0c0:2:f0::bd92:f001/128", - "2a05:d014:1815:3400:6d:9235:c1c0:96ad/128", - "2a05:d014:1815:3400:654f:bd37:724c:212b/128", - "2a05:d014:1815:3400:90b4:4ef9:5631:b170/128", - "2a05:d014:1815:3400:9779:d8e9:100a:9642/128", - "2a05:d014:1815:3400:af29:e95e:64ff:df81/128", - "2a05:d014:1815:3400:c7d6:f7f3:6cc1:30d1/128", - "2a05:d014:1815:3400:d784:e5dd:8e0:67cb/128", - ] + remote_addresses: + [ + "3.12.251.153/32", + "3.20.63.178/32", + "3.77.67.4/32", + "3.79.134.69/32", + "3.105.133.239/32", + "3.105.190.221/32", + "3.133.226.214/32", + "3.149.57.90/32", + "3.212.128.62/32", + "5.161.61.238/32", + "5.161.73.160/32", + "5.161.75.7/32", + "5.161.113.195/32", + "5.161.117.52/32", + "5.161.177.47/32", + "5.161.194.92/32", + "5.161.215.244/32", + "5.223.43.32/32", + "5.223.53.147/32", + "5.223.57.22/32", + "18.116.205.62/32", + "18.180.208.214/32", + "18.192.166.72/32", + "18.193.252.127/32", + "24.144.78.39/32", + "24.144.78.185/32", + "34.198.201.66/32", + "45.55.123.175/32", + "45.55.127.146/32", + "49.13.24.81/32", + "49.13.130.29/32", + "49.13.134.145/32", + "49.13.164.148/32", + "49.13.167.123/32", + "52.15.147.27/32", + "52.22.236.30/32", + "52.28.162.93/32", + "52.59.43.236/32", + "52.87.72.16/32", + "54.64.67.106/32", + "54.79.28.129/32", + "54.87.112.51/32", + "54.167.223.174/32", + "54.249.170.27/32", + "63.178.84.147/32", + "64.225.81.248/32", + "64.225.82.147/32", + "69.162.124.227/32", + "69.162.124.235/32", + "69.162.124.238/32", + "78.46.190.63/32", + "78.46.215.1/32", + "78.47.98.55/32", + "78.47.173.76/32", + "88.99.80.227/32", + "91.99.101.207/32", + "128.140.41.193/32", + "128.140.106.114/32", + "129.212.132.140/32", + "134.199.240.137/32", + "138.197.53.117/32", + "138.197.53.138/32", + "138.197.54.143/32", + "138.197.54.247/32", + "138.197.63.92/32", + "139.59.50.44/32", + "142.132.180.39/32", + "143.198.249.237/32", + "143.198.250.89/32", + "143.244.196.21/32", + "143.244.196.211/32", + "143.244.221.177/32", + "144.126.251.21/32", + "146.190.9.187/32", + "152.42.149.135/32", + "157.90.155.240/32", + "157.90.156.63/32", + "159.69.158.189/32", + "159.223.243.219/32", + "161.35.247.201/32", + "167.99.18.52/32", + "167.235.143.113/32", + "168.119.53.160/32", + "168.119.96.239/32", + "168.119.123.75/32", + "170.64.250.64/32", + "170.64.250.132/32", + "170.64.250.235/32", + "178.156.181.172/32", + "178.156.184.20/32", + "178.156.185.127/32", + "178.156.185.231/32", + "178.156.187.238/32", + "178.156.189.113/32", + "178.156.189.249/32", + "188.166.201.79/32", + "206.189.241.133/32", + "209.38.49.1/32", + "209.38.49.206/32", + "209.38.49.226/32", + "209.38.51.43/32", + "209.38.53.7/32", + "209.38.124.252/32", + "216.144.248.18/31", + "216.144.248.21/32", + "216.144.248.22/31", + "216.144.248.24/30", + "216.144.248.28/31", + "216.144.248.30/32", + "216.245.221.83/32", + "2400:6180:10:200::56a0:b000/128", + "2400:6180:10:200::56a0:c000/128", + "2400:6180:10:200::56a0:e000/128", + "2400:6180:100:d0::94b6:4001/128", + "2400:6180:100:d0::94b6:5001/128", + "2400:6180:100:d0::94b6:7001/128", + "2406:da14:94d:8601:9d0d:7754:bedf:e4f5/128", + "2406:da14:94d:8601:b325:ff58:2bba:7934/128", + "2406:da14:94d:8601:db4b:c5ac:2cbe:9a79/128", + "2406:da1c:9c8:dc02:7ae1:f2ea:ab91:2fde/128", + "2406:da1c:9c8:dc02:7db9:f38b:7b9f:402e/128", + "2406:da1c:9c8:dc02:82b2:f0fd:ee96:579/128", + "2600:1f16:775:3a00:ac3:c5eb:7081:942e/128", + "2600:1f16:775:3a00:37bf:6026:e54a:f03a/128", + "2600:1f16:775:3a00:3f24:5bb0:95d7:5a6b/128", + "2600:1f16:775:3a00:8c2c:2ba6:778f:5be5/128", + "2600:1f16:775:3a00:91ac:3120:ff38:92b5/128", + "2600:1f16:775:3a00:dbbe:36b0:3c45:da32/128", + "2600:1f18:179:f900:71:af9a:ade7:d772/128", + "2600:1f18:179:f900:2406:9399:4ae6:c5d3/128", + "2600:1f18:179:f900:4696:7729:7bb3:f52f/128", + "2600:1f18:179:f900:4b7d:d1cc:2d10:211/128", + "2600:1f18:179:f900:5c68:91b6:5d75:5d7/128", + "2600:1f18:179:f900:e8dd:eed1:a6c:183b/128", + "2604:a880:800:14:0:1:68ba:d000/128", + "2604:a880:800:14:0:1:68ba:e000/128", + "2604:a880:800:14:0:1:68bb:0/128", + "2604:a880:800:14:0:1:68bb:1000/128", + "2604:a880:800:14:0:1:68bb:3000/128", + "2604:a880:800:14:0:1:68bb:4000/128", + "2604:a880:800:14:0:1:68bb:5000/128", + "2604:a880:800:14:0:1:68bb:6000/128", + "2604:a880:800:14:0:1:68bb:7000/128", + "2604:a880:800:14:0:1:68bb:a000/128", + "2604:a880:800:14:0:1:68bb:b000/128", + "2604:a880:800:14:0:1:68bb:c000/128", + "2604:a880:800:14:0:1:68bb:d000/128", + "2604:a880:800:14:0:1:68bb:e000/128", + "2604:a880:800:14:0:1:68bb:f000/128", + "2607:ff68:107::4/128", + "2607:ff68:107::14/128", + "2607:ff68:107::33/128", + "2607:ff68:107::48/127", + "2607:ff68:107::50/125", + "2607:ff68:107::58/127", + "2607:ff68:107::60/128", + "2a01:4f8:c0c:83fa::1/128", + "2a01:4f8:c17:42e4::1/128", + "2a01:4f8:c2c:9fc6::1/128", + "2a01:4f8:c2c:beae::1/128", + "2a01:4f8:1c1a:3d53::1/128", + "2a01:4f8:1c1b:4ef4::1/128", + "2a01:4f8:1c1b:5b5a::1/128", + "2a01:4f8:1c1b:7ecc::1/128", + "2a01:4f8:1c1c:11aa::1/128", + "2a01:4f8:1c1c:5353::1/128", + "2a01:4f8:1c1c:7240::1/128", + "2a01:4f8:1c1c:a98a::1/128", + "2a01:4f8:c012:c60e::1/128", + "2a01:4f8:c013:c18::1/128", + "2a01:4f8:c013:34c0::1/128", + "2a01:4f8:c013:3b0f::1/128", + "2a01:4f8:c013:3c52::1/128", + "2a01:4f8:c013:3c53::1/128", + "2a01:4f8:c013:3c54::1/128", + "2a01:4f8:c013:3c55::1/128", + "2a01:4f8:c013:3c56::1/128", + "2a01:4ff:f0:bfd::1/128", + "2a01:4ff:f0:2219::1/128", + "2a01:4ff:f0:3e03::1/128", + "2a01:4ff:f0:5f80::1/128", + "2a01:4ff:f0:7fad::1/128", + "2a01:4ff:f0:9c5f::1/128", + "2a01:4ff:f0:b2f2::1/128", + "2a01:4ff:f0:b6f1::1/128", + "2a01:4ff:f0:d283::1/128", + "2a01:4ff:f0:d3cd::1/128", + "2a01:4ff:f0:e516::1/128", + "2a01:4ff:f0:e9cf::1/128", + "2a01:4ff:f0:eccb::1/128", + "2a01:4ff:f0:efd1::1/128", + "2a01:4ff:f0:fdc7::1/128", + "2a01:4ff:2f0:193c::1/128", + "2a01:4ff:2f0:27de::1/128", + "2a01:4ff:2f0:3b3a::1/128", + "2a03:b0c0:2:f0::bd91:f001/128", + "2a03:b0c0:2:f0::bd92:1/128", + "2a03:b0c0:2:f0::bd92:1001/128", + "2a03:b0c0:2:f0::bd92:2001/128", + "2a03:b0c0:2:f0::bd92:4001/128", + "2a03:b0c0:2:f0::bd92:5001/128", + "2a03:b0c0:2:f0::bd92:6001/128", + "2a03:b0c0:2:f0::bd92:7001/128", + "2a03:b0c0:2:f0::bd92:8001/128", + "2a03:b0c0:2:f0::bd92:9001/128", + "2a03:b0c0:2:f0::bd92:a001/128", + "2a03:b0c0:2:f0::bd92:b001/128", + "2a03:b0c0:2:f0::bd92:c001/128", + "2a03:b0c0:2:f0::bd92:e001/128", + "2a03:b0c0:2:f0::bd92:f001/128", + "2a05:d014:1815:3400:6d:9235:c1c0:96ad/128", + "2a05:d014:1815:3400:654f:bd37:724c:212b/128", + "2a05:d014:1815:3400:90b4:4ef9:5631:b170/128", + "2a05:d014:1815:3400:9779:d8e9:100a:9642/128", + "2a05:d014:1815:3400:af29:e95e:64ff:df81/128", + "2a05:d014:1815:3400:c7d6:f7f3:6cc1:30d1/128", + "2a05:d014:1815:3400:d784:e5dd:8e0:67cb/128", + ] diff --git a/decaymap/decaymap.go b/decaymap/decaymap.go index 37997e1a..5fe9572f 100644 --- a/decaymap/decaymap.go +++ b/decaymap/decaymap.go @@ -146,7 +146,7 @@ func (m *Impl[K, V]) Close() { func (m *Impl[K, V]) cleanupWorker() { defer m.wg.Done() batch := make([]deleteReq[K], 0, 64) - ticker := time.NewTicker(10 * time.Millisecond) + ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop() flush := func() { diff --git a/decaymap/decaymap_test.go b/decaymap/decaymap_test.go index e9bc824d..887ca000 100644 --- a/decaymap/decaymap_test.go +++ b/decaymap/decaymap_test.go @@ -32,7 +32,7 @@ func TestImpl(t *testing.T) { // Deletion of expired entries after Get is deferred to a background worker. // Assert it eventually disappears from the map. - deadline := time.Now().Add(200 * time.Millisecond) + deadline := time.Now().Add(700 * time.Millisecond) for time.Now().Before(deadline) { if dm.Len() == 0 { break diff --git a/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx b/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx index b4ac4776..020fff04 100644 --- a/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx +++ b/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx @@ -1,14 +1,16 @@ -import React, { useState, useEffect, useMemo } from 'react'; -import styles from './styles.module.css'; +import React, { useState, useEffect, useMemo } from "react"; +import styles from "./styles.module.css"; // A helper function to perform SHA-256 hashing. // It takes a string, encodes it, hashes it, and returns a hex string. async function sha256(message) { try { const msgBuffer = new TextEncoder().encode(message); - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); + const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + const hashHex = hashArray + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); return hashHex; } catch (error) { console.error("Hashing failed:", error); @@ -21,21 +23,42 @@ const generateRandomHex = (bytes = 16) => { const buffer = new Uint8Array(bytes); crypto.getRandomValues(buffer); return Array.from(buffer) - .map(byte => byte.toString(16).padStart(2, '0')) - .join(''); + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); }; - // Icon components for better visual feedback const CheckIcon = () => ( - - + + ); const XCircleIcon = () => ( - - + + ); @@ -46,7 +69,7 @@ export default function App() { // State for the nonce, which is the variable we can change const [nonce, setNonce] = useState(0); // State to store the resulting hash - const [hash, setHash] = useState(''); + const [hash, setHash] = useState(""); // A flag to indicate if the current hash is the "winning" one const [isMining, setIsMining] = useState(false); const [isFound, setIsFound] = useState(false); @@ -55,7 +78,10 @@ export default function App() { const difficulty = "00"; // Memoize the combined data to avoid recalculating on every render - const combinedData = useMemo(() => `${challenge}${nonce}`, [challenge, nonce]); + const combinedData = useMemo( + () => `${challenge}${nonce}`, + [challenge, nonce], + ); // This effect hook recalculates the hash whenever the combinedData changes. useEffect(() => { @@ -68,7 +94,9 @@ export default function App() { } }; calculateHash(); - return () => { isMounted = false; }; + return () => { + isMounted = false; + }; }, [combinedData, difficulty]); // This effect handles the automatic mining process @@ -93,7 +121,7 @@ export default function App() { // Update the UI periodically to avoid freezing the browser if (miningNonce % 100 === 0) { setNonce(miningNonce); - await new Promise(resolve => setTimeout(resolve, 0)); // Yield to the browser + await new Promise((resolve) => setTimeout(resolve, 0)); // Yield to the browser } } }; @@ -102,28 +130,27 @@ export default function App() { return () => { continueMining = false; - } + }; }, [isMining, challenge, nonce, difficulty]); - const handleMineClick = () => { setIsMining(true); - } + }; const handleStopClick = () => { setIsMining(false); - } + }; const handleResetClick = () => { setIsMining(false); setNonce(0); - } + }; const handleNewChallengeClick = () => { setIsMining(false); setChallenge(generateRandomHex(16)); setNonce(0); - } + }; // Helper to render the hash with colored leading characters const renderHash = () => { @@ -153,12 +180,46 @@ export default function App() {

2. Nonce

- {nonce} -
@@ -172,13 +233,26 @@ export default function App() { {/* Arrow pointing down */}
- - + +
{/* Hash Output Block */} -
+

4. Resulting Hash (SHA-256)

@@ -193,18 +267,30 @@ export default function App() { {/* Mining Controls */}
{!isMining ? ( - ) : ( - )} - -
diff --git a/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css b/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css index 419b4048..157f8402 100644 --- a/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css +++ b/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css @@ -48,7 +48,9 @@ background-color: rgb(31 41 55); padding: 1.5rem; border-radius: 0.5rem; - box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); height: 100%; display: flex; flex-direction: column; @@ -158,7 +160,9 @@ .hashContainer { padding: 1.5rem; border-radius: 0.5rem; - box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); transition: all 300ms; border: 2px solid; } diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 175e001b..a96b8e6f 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -11,11 +11,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463) +- Instruct reverse proxies to not cache error pages. +- Fixed mixed tab/space indentation in Caddy documentation code block + + +- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)). + +## v1.25.0: Necron + +Hey all, + +I'm sure you've all been aware that things have been slowing down a little with Anubis development, and I want to apologize for that. A lot has been going on in my life lately (my blog will have a post out on Friday with more information), and as a result I haven't really had the energy to work on Anubis in publicly visible ways. There are things going on behind the scenes, but nothing is really shippable yet, sorry! + +I've also been feeling some burnout in the wake of perennial waves of anger directed towards me. I'm handling it, I'll be fine, I've just had a lot going on in my life and it's been rough. + +I've been missing the sense of wanderlust and discovery that comes with the artistic way I playfully develop software. I suspect that some of the stresses I've been through (setting up a complicated surgery in a country whose language you aren't fluent in is kind of an experience) have been sapping my energy. I'd gonna try to mess with things on my break, but realistically I'm probably just gonna be either watching Stargate SG-1 or doing unreasonable amounts of ocean fishing in Final Fantasy 14. Normally I'd love to keep the details about my medical state fairly private, but I'm more of a public figure now than I was this time last year so I don't really get the invisibility I'm used to for this. + +I've also had a fair amount of negativity directed at me for simply being much more visible than the anonymous threat actors running the scrapers that are ruining everything, which though understandable has not helped. + +Anyways, it all worked out and I'm about to be in the hospital for a week, so if things go really badly with this release please downgrade to the last version and/or upgrade to the main branch when the fix PR is inevitably merged. I hoped to have time to tame GPG and set up full release automation in the Anubis repo, but that didn't work out this time and that's okay. + +If I can challenge you all to do something, go out there and try to actually create something new somehow. Combine ideas you've never mixed before. Be creative, be human, make something purely for yourself to scratch an itch that you've always had yet never gotten around to actually mending. + +At the very least, try to be an example of how you want other people to act, even when you're in a situation where software written by someone else is configured to require a user agent to execute javascript to access a webpage. + +Be well, + +Xe + +PS: if you're well-versed in FFXIV lore, the release title should give you an idea of the kind of stuff I've been going through mentally. + - Add iplist2rule tool that lets admins turn an IP address blocklist into an Anubis ruleset. - Add Polish locale ([#1292](https://github.com/TecharoHQ/anubis/pull/1309)) - Fix honeypot and imprint links missing `BASE_PREFIX` when deployed behind a path prefix ([#1402](https://github.com/TecharoHQ/anubis/issues/1402)) - - +- Add ANEXIA Sponsor logo to docs ([#1409](https://github.com/TecharoHQ/anubis/pull/1409)) +- Improve idle performance in memory storage +- Add HAProxy Configurations to Docs ([#1424](https://github.com/TecharoHQ/anubis/pull/1424)) ## v1.24.0: Y'shtola Rhul diff --git a/docs/docs/admin/_category_.json b/docs/docs/admin/_category_.json index 4b7ba50a..a5586551 100644 --- a/docs/docs/admin/_category_.json +++ b/docs/docs/admin/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Tradeoffs and considerations you may want to keep in mind when using Anubis." } -} \ No newline at end of file +} diff --git a/docs/docs/admin/configuration/_category_.json b/docs/docs/admin/configuration/_category_.json index e22c7a2f..6b797b2b 100644 --- a/docs/docs/admin/configuration/_category_.json +++ b/docs/docs/admin/configuration/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Detailed information about configuring parts of Anubis." } -} \ No newline at end of file +} diff --git a/docs/docs/admin/configuration/challenges/_category_.json b/docs/docs/admin/configuration/challenges/_category_.json index 96a70d2f..a01e84f1 100644 --- a/docs/docs/admin/configuration/challenges/_category_.json +++ b/docs/docs/admin/configuration/challenges/_category_.json @@ -2,4 +2,4 @@ "label": "Challenges", "position": 10, "link": null -} \ No newline at end of file +} diff --git a/docs/docs/admin/configuration/expressions.mdx b/docs/docs/admin/configuration/expressions.mdx index 762b848b..109b9c0a 100644 --- a/docs/docs/admin/configuration/expressions.mdx +++ b/docs/docs/admin/configuration/expressions.mdx @@ -243,16 +243,16 @@ function regexSafe(input: string): string; `regexSafe` takes a string and escapes it for safe use inside of a regular expression. This is useful when you are creating regular expressions from headers or variables such as `remoteAddress`. -| Input | Output | -| :------------------------ | :------------------------------ | -| `regexSafe("1.2.3.4")` | `1\\.2\\.3\\.4` | -| `regexSafe("techaro.lol")` | `techaro\\.lol` | -| `regexSafe("star*")` | `star\\*` | -| `regexSafe("plus+")` | `plus\\+` | -| `regexSafe("{braces}")` | `\\{braces\\}` | -| `regexSafe("start^")` | `start\\^` | -| `regexSafe("back\\slash")` | `back\\\\slash` | -| `regexSafe("dash-dash")` | `dash\\-dash` | +| Input | Output | +| :------------------------- | :-------------- | +| `regexSafe("1.2.3.4")` | `1\\.2\\.3\\.4` | +| `regexSafe("techaro.lol")` | `techaro\\.lol` | +| `regexSafe("star*")` | `star\\*` | +| `regexSafe("plus+")` | `plus\\+` | +| `regexSafe("{braces}")` | `\\{braces\\}` | +| `regexSafe("start^")` | `start\\^` | +| `regexSafe("back\\slash")` | `back\\\\slash` | +| `regexSafe("dash-dash")` | `dash\\-dash` | ### `segments` @@ -301,9 +301,9 @@ function arpaReverseIP(ip: string): string; `arpaReverseIP` takes an IP address and returns its value in [ARPA notation](https://www.ietf.org/rfc/rfc2317.html). This can be useful when matching PTR record patterns. -| Input | Output | -| :----------------------------- | :------------------------------------------------------------------- | -| `arpaReverseIP("1.2.3.4")` | `4.3.2.1` | +| Input | Output | +| :----------------------------- | :---------------------------------------------------------------- | +| `arpaReverseIP("1.2.3.4")` | `4.3.2.1` | | `arpaReverseIP("2001:db8::1")` | `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2` | #### `lookupHost` diff --git a/docs/docs/admin/default-allow-behavior.mdx b/docs/docs/admin/default-allow-behavior.mdx index 6249e735..1b87183e 100644 --- a/docs/docs/admin/default-allow-behavior.mdx +++ b/docs/docs/admin/default-allow-behavior.mdx @@ -89,4 +89,4 @@ If you want to deny all traffic except what you explicitly allow, add a catch-al - The implicit allow rule is always last and cannot be removed. - Use your logs to monitor what traffic is being allowed by default. -See [Policy Definitions](./policies) for more details on writing rules. \ No newline at end of file +See [Policy Definitions](./policies) for more details on writing rules. diff --git a/docs/docs/admin/environments/_category_.json b/docs/docs/admin/environments/_category_.json index 31522719..a9150a73 100644 --- a/docs/docs/admin/environments/_category_.json +++ b/docs/docs/admin/environments/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Detailed information about individual environments (such as HTTP servers, platforms, etc.) Anubis is known to work with." } -} \ No newline at end of file +} diff --git a/docs/docs/admin/environments/caddy.mdx b/docs/docs/admin/environments/caddy.mdx index 3e5343a1..30edec17 100644 --- a/docs/docs/admin/environments/caddy.mdx +++ b/docs/docs/admin/environments/caddy.mdx @@ -62,9 +62,9 @@ yourdomain.example.com { tls your@email.address reverse_proxy http://anubis:3000 { - header_up X-Real-Ip {remote_host} - header_up X-Http-Version {http.request.proto} - } + header_up X-Real-Ip {remote_host} + header_up X-Http-Version {http.request.proto} + } } ``` diff --git a/docs/docs/admin/environments/haproxy.mdx b/docs/docs/admin/environments/haproxy.mdx new file mode 100644 index 00000000..3acc5766 --- /dev/null +++ b/docs/docs/admin/environments/haproxy.mdx @@ -0,0 +1,101 @@ +# HAProxy + +import CodeBlock from "@theme/CodeBlock"; + +To use Anubis with HAProxy, you have two variants: + - simple - stick Anubis between HAProxy and your application backend (simple) + - perfect if you only have a single application in general + - advanced - force Anubis challenge by default and route to the application backend by HAProxy if the challenge is correct + - useful for complex setups + - routing can be done in HAProxy + - define ACLs in HAProxy for domains, paths etc which are required/excluded regarding Anubis + - HAProxy 3.0 recommended + +## Simple Variant + +```mermaid +--- +title: HAProxy with simple config +--- +flowchart LR + T(User Traffic) + HAProxy(HAProxy Port 80/443) + Anubis + Application + + T --> HAProxy + HAProxy --> Anubis + Anubis --> |Happy Traffic| Application +``` + +Your Anubis env file configuration may look like this: + +import simpleAnubis from "!!raw-loader!./haproxy/simple-config.env"; + +{simpleAnubis} + +The important part is that `TARGET` points to your actual application and if Anubis and HAProxy are on the same machine, a UNIX socket can be used. + +Your frontend and backend configuration of HAProxy may look like the following: + +import simpleHAProxy from "!!raw-loader!./haproxy/simple-haproxy.cfg"; + +{simpleHAProxy} + +This simply enables SSL offloading, sets some useful and required headers and routes to Anubis directly. + +## Advanced Variant + +Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves. + +Mind that rule logic to allow Git HTTP and other legit bot traffic to bypass is delegated from Anubis to HAProxy then. If required, you should implement any whitelisting in HAProxy using `acl_anubis_ignore` yourself. + +In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone. + +```mermaid +--- +title: HAProxy with advanced config +--- + +flowchart LR + T(User Traffic) + HAProxy(HAProxy Port 80/443) + B1(App1) + B2(App2) + B3(App3) + Anubis + + T --> HAProxy + HAProxy --> |Traffic for App1 and App2 without valid challenge| Anubis + HAProxy --> |app1.example.com | B1 + HAProxy --> |app2.example.com| B2 + HAProxy --> |app3.example.com| B3 +``` + +:::note + +For an improved JWT decoding performance, it's recommended to use HAProxy version 3.0 or above. + +::: + +Your Anubis env file configuration may look like this: + +import advancedAnubis from "!!raw-loader!./haproxy/advanced-config.env"; + +{advancedAnubis} + +It's important to use `HS512_SECRET` which HAProxy understands. Please replace `` with your own secret string (alphanumerical string with 128 characters recommended). + +You can set Anubis to force a challenge for every request using the following policy file: + +import advancedAnubisPolicy from "!!raw-loader!./haproxy/advanced-config-policy.yml"; + +{advancedAnubisPolicy} + +The HAProxy config file may look like this: + +import advancedHAProxy from "!!raw-loader!./haproxy/advanced-haproxy.cfg"; + +{advancedHAProxy} + +Please replace `` with the same secret from the Anubis config. diff --git a/docs/docs/admin/environments/haproxy/advanced-config-policy.yml b/docs/docs/admin/environments/haproxy/advanced-config-policy.yml new file mode 100644 index 00000000..93c06dc3 --- /dev/null +++ b/docs/docs/admin/environments/haproxy/advanced-config-policy.yml @@ -0,0 +1,15 @@ +# /etc/anubis/challenge-any.yml + +bots: + - name: any + action: CHALLENGE + user_agent_regex: .* + +status_codes: + CHALLENGE: 403 + DENY: 403 + +thresholds: [] + +dnsbl: false + diff --git a/docs/docs/admin/environments/haproxy/advanced-config.env b/docs/docs/admin/environments/haproxy/advanced-config.env new file mode 100644 index 00000000..712fdeeb --- /dev/null +++ b/docs/docs/admin/environments/haproxy/advanced-config.env @@ -0,0 +1,11 @@ +# /etc/anubis/default.env + +BIND=/run/anubis/default.sock +BIND_NETWORK=unix +DIFFICULTY=4 +METRICS_BIND=:9090 +# target is irrelevant here, backend routing happens in HAProxy +TARGET=http://0.0.0.0 +HS512_SECRET= +COOKIE_DYNAMIC_DOMAIN=True +POLICY_FNAME=/etc/anubis/challenge-any.yml diff --git a/docs/docs/admin/environments/haproxy/advanced-haproxy.cfg b/docs/docs/admin/environments/haproxy/advanced-haproxy.cfg new file mode 100644 index 00000000..1540067b --- /dev/null +++ b/docs/docs/admin/environments/haproxy/advanced-haproxy.cfg @@ -0,0 +1,59 @@ +# /etc/haproxy/haproxy.cfg + +frontend FE-multiple-applications + mode http + bind :80 + # ssl offloading on port 443 using a certificate from /etc/haproxy/ssl/ directory + bind :443 ssl crt /etc/haproxy/ssl/ alpn h2,http/1.1 ssl-min-ver TLSv1.2 no-tls-tickets + + # set X-Real-IP header required for Anubis + http-request set-header X-Real-IP "%[src]" + + # redirect HTTP to HTTPS + http-request redirect scheme https code 301 unless { ssl_fc } + # add HSTS header + http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + # only force Anubis challenge for app1 and app2 + acl acl_anubis_required hdr(host) -i "app1.example.com" + acl acl_anubis_required hdr(host) -i "app2.example.com" + + # exclude Anubis for a specific path + acl acl_anubis_ignore path /excluded/path + + # use Anubis if auth cookie not found + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ req.cook(techaro.lol-anubis-auth) -m found } + + # get payload of the JWT such as algorithm, expire time, restrictions + http-request set-var(txn.anubis_jwt_alg) req.cook(techaro.lol-anubis-auth),jwt_header_query('$.alg') if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.anubis_jwt_exp) cook(techaro.lol-anubis-auth),jwt_payload_query('$.exp','int') if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.anubis_jwt_res) cook(techaro.lol-anubis-auth),jwt_payload_query('$.restriction') if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.srcip) req.fhdr(X-Real-IP) if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.now) date() if acl_anubis_required !acl_anubis_ignore + + # use Anubis if JWT has wrong algorithm, is expired, restrictions don't match or isn't signed with the correct key + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ var(txn.anubis_jwt_alg) -m str HS512 } + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore { var(txn.anubis_jwt_exp),sub(txn.now) -m int lt 0 } + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ var(txn.srcip),digest(sha256),hex,lower,strcmp(txn.anubis_jwt_res) eq 0 } + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ cook(techaro.lol-anubis-auth),jwt_verify(txn.anubis_jwt_alg,"") -m int 1 } + + # custom routing in HAProxy + use_backend BE-app1 if { hdr(host) -i "app1.example.com" } + use_backend BE-app2 if { hdr(host) -i "app2.example.com" } + use_backend BE-app3 if { hdr(host) -i "app3.example.com" } + +backend BE-app1 + mode http + server app1-server 127.0.0.1:3000 + +backend BE-app2 + mode http + server app2-server 127.0.0.1:4000 + +backend BE-app3 + mode http + server app3-server 127.0.0.1:5000 + +BE-anubis + mode http + server anubis /run/anubis/default.sock diff --git a/docs/docs/admin/environments/haproxy/simple-config.env b/docs/docs/admin/environments/haproxy/simple-config.env new file mode 100644 index 00000000..02a3f667 --- /dev/null +++ b/docs/docs/admin/environments/haproxy/simple-config.env @@ -0,0 +1,10 @@ +# /etc/anubis/default.env + +BIND=/run/anubis/default.sock +BIND_NETWORK=unix +SOCKET_MODE=0666 +DIFFICULTY=4 +METRICS_BIND=:9090 +COOKIE_DYNAMIC_DOMAIN=true +# address and port of the actual application +TARGET=http://localhost:3000 diff --git a/docs/docs/admin/environments/haproxy/simple-haproxy.cfg b/docs/docs/admin/environments/haproxy/simple-haproxy.cfg new file mode 100644 index 00000000..445d0788 --- /dev/null +++ b/docs/docs/admin/environments/haproxy/simple-haproxy.cfg @@ -0,0 +1,22 @@ +# /etc/haproxy/haproxy.cfg + +frontend FE-application + mode http + bind :80 + # ssl offloading on port 443 using a certificate from /etc/haproxy/ssl/ directory + bind :443 ssl crt /etc/haproxy/ssl/ alpn h2,http/1.1 ssl-min-ver TLSv1.2 no-tls-tickets + + # set X-Real-IP header required for Anubis + http-request set-header X-Real-IP "%[src]" + + # redirect HTTP to HTTPS + http-request redirect scheme https code 301 unless { ssl_fc } + # add HSTS header + http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + # route to Anubis backend by default + default_backend BE-anubis-application + +BE-anubis-application + mode http + server anubis /run/anubis/default.sock diff --git a/docs/docs/admin/environments/kubernetes.mdx b/docs/docs/admin/environments/kubernetes.mdx index e10f18e2..6b81313e 100644 --- a/docs/docs/admin/environments/kubernetes.mdx +++ b/docs/docs/admin/environments/kubernetes.mdx @@ -94,10 +94,8 @@ containers: - ALL seccompProfile: type: RuntimeDefault - ``` - Then add a Service entry for Anubis: ```yaml @@ -132,3 +130,52 @@ Then point your Ingress to the Anubis port: # diff-add name: anubis ``` + +## Envoy Gateway + +If you are using envoy-gateway, the `X-Real-Ip` header is not set by default, but Anubis does require it. You can resolve this by adding the header, either on the specific `HTTPRoute` where Anubis is listening, or on the `ClientTrafficPolicy` to apply it to any number of Gateways: + +HTTPRoute: +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: app-route +spec: + hostnames: ["app.domain.tld"] + parentRefs: + - name: envoy-external + namespace: network + sectionName: https + rules: + - backendRefs: + - identifier: *app + port: anubis + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: X-Real-Ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" +``` + +Applying to any number of Gateways: +```yaml +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: envoy +spec: + headers: + earlyRequestHeaders: + set: + - name: X-Real-Ip + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + clientIPDetection: + xForwardedFor: + trustedCIDRs: + - 10.96.0.0/16 # Cluster pod CIDR + targetSelectors: # These will apply to all Gateways + - group: gateway.networking.k8s.io + kind: Gateway +``` diff --git a/docs/docs/admin/environments/nginx/conf-anubis.inc b/docs/docs/admin/environments/nginx/conf-anubis.inc index 6e5083ae..bc20e8f4 100644 --- a/docs/docs/admin/environments/nginx/conf-anubis.inc +++ b/docs/docs/admin/environments/nginx/conf-anubis.inc @@ -1,8 +1,2 @@ -# /etc/nginx/conf-anubis.inc - -# Forward to anubis -location / { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://anubis; -} \ No newline at end of file +# /etc/nginx/conf-anubis.inc # Forward to anubis location / { proxy_set_header +Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://anubis; } diff --git a/docs/docs/admin/environments/traefik.mdx b/docs/docs/admin/environments/traefik.mdx index 197ddc19..04df792d 100644 --- a/docs/docs/admin/environments/traefik.mdx +++ b/docs/docs/admin/environments/traefik.mdx @@ -75,7 +75,7 @@ services: # Telling Anubis, where to listen for Traefik - BIND=:8080 # Telling Anubis to do redirect — ensure there is a space after '=' - - 'TARGET= ' + - "TARGET= " # Specifies which domains Anubis is allowed to redirect to. - REDIRECT_DOMAINS=example.com # Should be the full external URL for Anubis (including scheme) diff --git a/docs/docs/admin/frameworks/_category_.json b/docs/docs/admin/frameworks/_category_.json index 28eefe88..253bf449 100644 --- a/docs/docs/admin/frameworks/_category_.json +++ b/docs/docs/admin/frameworks/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Information about getting specific frameworks or tools working with Anubis." } -} \ No newline at end of file +} diff --git a/docs/docs/admin/honeypot/_category_.json b/docs/docs/admin/honeypot/_category_.json index bc0581e9..90c63375 100644 --- a/docs/docs/admin/honeypot/_category_.json +++ b/docs/docs/admin/honeypot/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Honeypot features in Anubis, allowing Anubis to passively detect malicious crawlers." } -} \ No newline at end of file +} diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx index 7642526e..7000afef 100644 --- a/docs/docs/admin/installation.mdx +++ b/docs/docs/admin/installation.mdx @@ -67,7 +67,7 @@ Currently the following settings are configurable via the policy file: Anubis uses these environment variables for configuration: | Environment Variable | Default value | Explanation | -|:-------------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| :----------------------------- | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ASSET_LOOKUP_HEADER` | unset | If set, use the contents of this header in requests when looking up custom assets in `OVERLAY_FOLDER`. See [Header-based overlay dispatch](./botstopper.mdx#header-based-overlay-dispatch) for more details. | | `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. | | `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` | @@ -123,7 +123,7 @@ If you don't know or understand what these settings mean, ignore them. These are | `TARGET_DISABLE_KEEPALIVE` | `false` | If `true`, disables HTTP keep-alive for connections to the target backend. Useful for backends that don't handle keep-alive properly. | | `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. | | `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. | -| `TARGET_SNI` | unset | If set, TLS handshake hostname when forwarding requests to the `TARGET`. If set to auto, use Host header. | +| `TARGET_SNI` | unset | If set, TLS handshake hostname when forwarding requests to the `TARGET`. If set to auto, use Host header. | @@ -203,6 +203,7 @@ To get Anubis filtering your traffic, you need to make sure it's added to your H - [Kubernetes](./environments/kubernetes.mdx) - [Nginx](./environments/nginx.mdx) - [Traefik](./environments/traefik.mdx) +- [HAProxy](./environments/haproxy.mdx) :::note diff --git a/docs/docs/admin/native-install.mdx b/docs/docs/admin/native-install.mdx index bdda5954..9abafa91 100644 --- a/docs/docs/admin/native-install.mdx +++ b/docs/docs/admin/native-install.mdx @@ -143,3 +143,4 @@ For more details on particular reverse proxies, see here: - [Apache](./environments/apache.mdx) - [Nginx](./environments/nginx.mdx) +- [HAProxy](./environments/haproxy.mdx) diff --git a/docs/docs/admin/policies.mdx b/docs/docs/admin/policies.mdx index 159d1abc..24317a41 100644 --- a/docs/docs/admin/policies.mdx +++ b/docs/docs/admin/policies.mdx @@ -393,6 +393,32 @@ logging: When files are rotated out, the old files will be named after the rotation timestamp in [RFC 3339 format](https://www.rfc-editor.org/rfc/rfc3339). +:::note + +If you are running Anubis in systemd via a native package, the default systemd unit settings are very restrictive and will forbid writing to folders in `/var/log`. In order to fix this, please make a [drop-in unit](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/) like the following: + +```text +# /etc/systemd/anubis@instance-name.service.d/50-var-log-readwrite.conf +[Service] +ReadWritePaths=/run /var/log/anubis +``` + +Once you write this to the correct place, reload the systemd configuration: + +```text +sudo systemctl daemon-reload +``` + +And then restart Anubis: + +```text +sudo systemctl restart anubis@instance-name +``` + +You may be required to make drop-ins for each Anubis instance depending on the facts and circumstances of your deployment. + +::: + ### `stdio` sink By default, Anubis logs everything to the standard error stream of its process. This requires no configuration: diff --git a/docs/docs/admin/roles/_category_.json b/docs/docs/admin/roles/_category_.json index 2d5af0b3..d087404d 100644 --- a/docs/docs/admin/roles/_category_.json +++ b/docs/docs/admin/roles/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Various server roles you will need to keep in mind with Anubis." } -} \ No newline at end of file +} diff --git a/docs/docs/design/_category_.json b/docs/docs/design/_category_.json index 65764e85..87b1010e 100644 --- a/docs/docs/design/_category_.json +++ b/docs/docs/design/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "How Anubis is designed and the tradeoffs it makes." } -} \ No newline at end of file +} diff --git a/docs/docs/developer/_category_.json b/docs/docs/developer/_category_.json index cf3805e0..0b24e3b0 100644 --- a/docs/docs/developer/_category_.json +++ b/docs/docs/developer/_category_.json @@ -5,4 +5,4 @@ "type": "generated-index", "description": "Guides and suggestions to make Anubis development go smoothly for everyone." } -} \ No newline at end of file +} diff --git a/docs/docs/developer/ai-coding-policy.md b/docs/docs/developer/ai-coding-policy.md new file mode 100644 index 00000000..b53e5594 --- /dev/null +++ b/docs/docs/developer/ai-coding-policy.md @@ -0,0 +1,13 @@ +# AI Coding Policy + +At some level it would be nice to be able to have the following AI coding policy from an ideological standpoint: + +> Anubis does not accept code made primarily with the use of agentic AI tools such as Claude Code, Gemini CLI, GitHub Copilot, Zed, OpenCode, or any other similar tools. Please do not use them when contributing to this repo. + +However, I'd be in violation by doing this because I have knowingly committed minor bits of code to the Anubis repo that were generated by AI tools (mostly things for smoke tests). + +As such, Anubis is taking more of a centrist approach with regards to AI coding tools: regardless of what tool you use to make contributions to Anubis, when you sign off your code, you are taking responsibility for what you commit. You are also expected to understand what you are changing, what the implications are, and all other relevant factors. + +If you use AI coding tools for a majority of your committed work, you MUST disclose it with [the `Assisted-by` footer](https://xeiaso.net/notes/2025/assisted-by-footer/). The Anubis maintainers will be using tooling that looks for these footers and will prioritize scrutiny and level of attention appropriately. + +In order to ensure compliance with this policy, language has been placed in `AGENTS.md` and `CLAUDE.md` to entice AI coding tools to add these footers. diff --git a/docs/docs/developer/code-quality.md b/docs/docs/developer/code-quality.md deleted file mode 100644 index 6dcc44ab..00000000 --- a/docs/docs/developer/code-quality.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Code quality guidelines ---- - -When submitting code to Anubis, please take the time to consider the fact that this project is security software. If things go bad, bots can pummel sites into oblivion. This is not ideal for uptime. - -As such, code reviews will be a bit more strict than you have seen in other projects. This is not people trying to be mean, this is a side effect of taking the problem seriously. - -When making code changes, try to do the following: - -- If you're submitting a bugfix, add a test case for it -- If you're changing the JavaScript, make sure the integration tests pass (`npm run test:integration`) - -## Commit messages - -Anubis follows the Go project's conventions for commit messages. In general, an ideal commit message should read like this: - -```text -path/to/folder: brief description of the change - -If the change is subtle, has implementation consequences, or is otherwise -not entirely self-describing: take the time to spell out why. If things -are very subtle, please also amend the documentation accordingly -``` - -The subject of a commit message should be the second half of the sentence "This commit changes the Anubis project to:". Here's a few examples: - -- `disable DroneBL by default` -- `port the challenge to WebAssembly` - -The extended commit message is also your place to give rationale for a new feature. When maintainers are reviewing your code, they will use this to figure out if the burden from feature maintainership is worth the merge. diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx index 9898b85a..9db0a498 100644 --- a/docs/docs/index.mdx +++ b/docs/docs/index.mdx @@ -35,9 +35,21 @@ Anubis is brought to you by sponsors and donors like: ### Gold Tier + + Uvensys + + + Uvensys + Distrust + + Gitea + + + Prolocation + +-
+ Dolphin Emulator + - https://forums.dolphin-emu.org/ + - https://wiki.dolphin-emu.org/ +
-
Duke University - https://repository.duke.edu/ @@ -60,6 +63,11 @@ This page contains a non-exhaustive list with all websites using Anubis. - https://find.library.duke.edu/ - https://nicholas.duke.edu/
+-
+ FFmpeg + - https://git.ffmpeg.org/ + - https://trac.ffmpeg.org/ +
-
Forschungszentrum Jülich - https://juser.fz-juelich.de/ @@ -112,11 +120,8 @@ This page contains a non-exhaustive list with all websites using Anubis. - https://git.kernel.org/ - https://lore.kernel.org/
--
- The United Nations - - https://policytoolbox.iiep.unesco.org/ -
-
Valve Corporation - https://developer.valvesoftware.com/wiki/Main_Page + - https://wiki.teamfortress.com/wiki/Main_Page
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index df12939b..e686522f 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -1,62 +1,62 @@ -import { themes as prismThemes } from 'prism-react-renderer'; -import type { Config } from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; +import { themes as prismThemes } from "prism-react-renderer"; +import type { Config } from "@docusaurus/types"; +import type * as Preset from "@docusaurus/preset-classic"; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) const config: Config = { - title: 'Anubis', - tagline: 'Weigh the soul of incoming HTTP requests to protect your website!', - favicon: 'img/favicon.ico', + title: "Anubis", + tagline: "Weigh the soul of incoming HTTP requests to protect your website!", + favicon: "img/favicon.ico", // Set the production url of your site here - url: 'https://anubis.techaro.lol', + url: "https://anubis.techaro.lol", // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: '/', + baseUrl: "/", // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. - organizationName: 'TecharoHQ', // Usually your GitHub org/user name. - projectName: 'anubis', // Usually your repo name. + organizationName: "TecharoHQ", // Usually your GitHub org/user name. + projectName: "anubis", // Usually your repo name. - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', + onBrokenLinks: "throw", + onBrokenMarkdownLinks: "warn", // Even if you don't use internationalization, you can use this field to set // useful metadata like html lang. For example, if your site is Chinese, you // may want to replace "en" with "zh-Hans". i18n: { - defaultLocale: 'en', - locales: ['en'], + defaultLocale: "en", + locales: ["en"], }, markdown: { mermaid: true, }, - themes: ['@docusaurus/theme-mermaid'], + themes: ["@docusaurus/theme-mermaid"], presets: [ [ - 'classic', + "classic", { blog: { showReadingTime: true, feedOptions: { - type: ['rss', 'atom', "json"], + type: ["rss", "atom", "json"], xslt: true, }, - editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/', - onInlineTags: 'warn', - onInlineAuthors: 'warn', - onUntruncatedBlogPosts: 'throw', + editUrl: "https://github.com/TecharoHQ/anubis/tree/main/docs/", + onInlineTags: "warn", + onInlineAuthors: "warn", + onUntruncatedBlogPosts: "throw", }, docs: { - sidebarPath: './sidebars.ts', - editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/', + sidebarPath: "./sidebars.ts", + editUrl: "https://github.com/TecharoHQ/anubis/tree/main/docs/", }, theme: { - customCss: './src/css/custom.css', + customCss: "./src/css/custom.css", }, } satisfies Preset.Options, ], @@ -67,47 +67,47 @@ const config: Config = { respectPrefersColorScheme: true, }, // Replace with your project's social card - image: 'img/social-card.jpg', + image: "img/social-card.jpg", navbar: { - title: 'Anubis', + title: "Anubis", logo: { - alt: 'A happy jackal woman with brown hair and red eyes', - src: 'img/favicon.webp', + alt: "A happy jackal woman with brown hair and red eyes", + src: "img/favicon.webp", }, items: [ - { to: '/blog', label: 'Blog', position: 'left' }, + { to: "/blog", label: "Blog", position: "left" }, { - type: 'docSidebar', - sidebarId: 'tutorialSidebar', - position: 'left', - label: 'Docs', + type: "docSidebar", + sidebarId: "tutorialSidebar", + position: "left", + label: "Docs", }, { - to: '/docs/admin/botstopper', + to: "/docs/admin/botstopper", label: "Unbranded Version", - position: "left" + position: "left", }, { - href: 'https://github.com/TecharoHQ/anubis', - label: 'GitHub', - position: 'right', + href: "https://github.com/TecharoHQ/anubis", + label: "GitHub", + position: "right", }, { - href: 'https://github.com/sponsors/Xe', + href: "https://github.com/sponsors/Xe", label: "Sponsor the Project", - position: 'right' + position: "right", }, ], }, footer: { - style: 'dark', + style: "dark", links: [ { - title: 'Docs', + title: "Docs", items: [ { - label: 'Intro', - to: '/docs/', + label: "Intro", + to: "/docs/", }, { label: "Installation", @@ -116,32 +116,32 @@ const config: Config = { ], }, { - title: 'Community', + title: "Community", items: [ { - label: 'GitHub Discussions', - href: 'https://github.com/TecharoHQ/anubis/discussions', + label: "GitHub Discussions", + href: "https://github.com/TecharoHQ/anubis/discussions", }, { - label: 'Bluesky', - href: 'https://bsky.app/profile/techaro.lol', + label: "Bluesky", + href: "https://bsky.app/profile/techaro.lol", }, ], }, { - title: 'More', + title: "More", items: [ { - label: 'Blog', - to: '/blog', + label: "Blog", + to: "/blog", }, { - label: 'GitHub', - href: 'https://github.com/TecharoHQ/anubis', + label: "GitHub", + href: "https://github.com/TecharoHQ/anubis", }, { - label: 'Status', - href: 'https://techarohq.github.io/status/' + label: "Status", + href: "https://techarohq.github.io/status/", }, ], }, @@ -153,13 +153,13 @@ const config: Config = { darkTheme: prismThemes.dracula, magicComments: [ { - className: 'code-block-diff-add-line', - line: 'diff-add' + className: "code-block-diff-add-line", + line: "diff-add", }, { - className: 'code-block-diff-remove-line', - line: 'diff-remove' - } + className: "code-block-diff-remove-line", + line: "diff-remove", + }, ], }, } satisfies Preset.ThemeConfig, diff --git a/docs/manifest/ingress.yaml b/docs/manifest/ingress.yaml index 52478361..8ce3b9fc 100644 --- a/docs/manifest/ingress.yaml +++ b/docs/manifest/ingress.yaml @@ -21,4 +21,4 @@ spec: service: name: anubis-docs port: - name: anubis \ No newline at end of file + name: anubis diff --git a/docs/manifest/onionservice.yaml b/docs/manifest/onionservice.yaml index 81ab9729..e0584468 100644 --- a/docs/manifest/onionservice.yaml +++ b/docs/manifest/onionservice.yaml @@ -5,10 +5,10 @@ metadata: spec: version: 3 rules: - - port: - number: 80 - backend: - service: - name: anubis-docs - port: - number: 80 \ No newline at end of file + - port: + number: 80 + backend: + service: + name: anubis-docs + port: + number: 80 diff --git a/docs/manifest/service.yaml b/docs/manifest/service.yaml index dadac058..1acdc45d 100644 --- a/docs/manifest/service.yaml +++ b/docs/manifest/service.yaml @@ -6,9 +6,9 @@ spec: selector: app: anubis-docs ports: - - port: 80 - targetPort: 80 - name: http - - port: 8081 - targetPort: 8081 - name: anubis + - port: 80 + targetPort: 80 + name: http + - port: 8081 + targetPort: 8081 + name: anubis diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 28971397..574ed394 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -1,4 +1,4 @@ -import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) @@ -14,7 +14,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; */ const sidebars: SidebarsConfig = { // By default, Docusaurus generates a sidebar from the docs folder structure - tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], + tutorialSidebar: [{ type: "autogenerated", dirName: "." }], // But you can create a sidebar manually /* diff --git a/docs/src/components/EnterpriseOnly/index.jsx b/docs/src/components/EnterpriseOnly/index.jsx index bfd9486c..ac6bab1d 100644 --- a/docs/src/components/EnterpriseOnly/index.jsx +++ b/docs/src/components/EnterpriseOnly/index.jsx @@ -1,4 +1,4 @@ -import styles from './styles.module.css'; +import styles from "./styles.module.css"; export default function EnterpriseOnly({ link }) { return ( @@ -8,4 +8,4 @@ export default function EnterpriseOnly({ link }) {
); -} \ No newline at end of file +} diff --git a/docs/src/components/EnterpriseOnly/styles.module.css b/docs/src/components/EnterpriseOnly/styles.module.css index f3ac7a15..056cedc0 100644 --- a/docs/src/components/EnterpriseOnly/styles.module.css +++ b/docs/src/components/EnterpriseOnly/styles.module.css @@ -8,7 +8,9 @@ font-weight: 700; padding: 0.5rem 1rem; /* py-2 px-4 */ border-radius: 9999px; /* rounded-full */ - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg approximation */ + box-shadow: + 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg approximation */ display: inline-flex; /* flex */ align-items: center; /* items-center */ } diff --git a/docs/static/img/sponsors/anexia-cloudsolutions-logo.webp b/docs/static/img/sponsors/anexia-cloudsolutions-logo.webp new file mode 100644 index 00000000..3358e41b Binary files /dev/null and b/docs/static/img/sponsors/anexia-cloudsolutions-logo.webp differ diff --git a/docs/static/img/sponsors/gitea-logo.webp b/docs/static/img/sponsors/gitea-logo.webp new file mode 100644 index 00000000..97e0e8fe Binary files /dev/null and b/docs/static/img/sponsors/gitea-logo.webp differ diff --git a/docs/static/img/sponsors/prolocation-logo.svg b/docs/static/img/sponsors/prolocation-logo.svg new file mode 100644 index 00000000..43ffc0bf --- /dev/null +++ b/docs/static/img/sponsors/prolocation-logo.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/sponsors/unipromos.webp b/docs/static/img/sponsors/unipromos.webp new file mode 100644 index 00000000..c0a0213e Binary files /dev/null and b/docs/static/img/sponsors/unipromos.webp differ diff --git a/docs/static/img/sponsors/uvensys.webp b/docs/static/img/sponsors/uvensys.webp new file mode 100644 index 00000000..aaa641f9 Binary files /dev/null and b/docs/static/img/sponsors/uvensys.webp differ diff --git a/internal/glob/glob.go b/internal/glob/glob.go index 44c1a67a..e2e640de 100644 --- a/internal/glob/glob.go +++ b/internal/glob/glob.go @@ -36,7 +36,7 @@ func Glob(pattern, subj string) bool { end := len(parts) - 1 // Go over the leading parts and ensure they match. - for i := 0; i < end; i++ { + for i := range end { idx := strings.Index(subj, parts[i]) switch i { diff --git a/internal/hash_bench_test.go b/internal/hash_bench_test.go index 5384570a..88f87e7c 100644 --- a/internal/hash_bench_test.go +++ b/internal/hash_bench_test.go @@ -184,7 +184,7 @@ func TestHashCollisions(t *testing.T) { for _, prefix := range prefixes { for _, suffix := range suffixes { for _, variation := range variations { - for i := 0; i < 100; i++ { + for i := range 100 { input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i) hash := XXHash64sum(input) if existing, exists := xxhashHashes[hash]; exists { @@ -211,7 +211,7 @@ func TestHashCollisions(t *testing.T) { seqCount := 0 for _, pattern := range patterns { - for i := 0; i < 10000; i++ { + for i := range 10000 { input := fmt.Sprintf(pattern, i) hash := XXHash64sum(input) if existing, exists := xxhashHashes[hash]; exists { diff --git a/internal/honeypot/naive/naive.go b/internal/honeypot/naive/naive.go index f62f4ef8..95093bcf 100644 --- a/internal/honeypot/naive/naive.go +++ b/internal/honeypot/naive/naive.go @@ -120,7 +120,7 @@ func (i *Impl) makeAffirmations() []string { count := rand.IntN(5) + 1 var result []string - for j := 0; j < count; j++ { + for range count { result = append(result, i.affirmation.Spin()) } @@ -131,7 +131,7 @@ func (i *Impl) makeSpins() []string { count := rand.IntN(5) + 1 var result []string - for j := 0; j < count; j++ { + for range count { result = append(result, i.body.Spin()) } diff --git a/internal/listor.go b/internal/listor.go index b6ba57fe..73110b79 100644 --- a/internal/listor.go +++ b/internal/listor.go @@ -16,7 +16,7 @@ func (lo *ListOr[T]) UnmarshalJSON(data []byte) error { // Check if first non-whitespace character is '[' firstChar := data[0] - for i := 0; i < len(data); i++ { + for i := range data { if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { firstChar = data[i] break @@ -36,4 +36,4 @@ func (lo *ListOr[T]) UnmarshalJSON(data []byte) error { } return nil -} \ No newline at end of file +} diff --git a/internal/ogtags/mem_test.go b/internal/ogtags/mem_test.go index 3770a73f..aebb4f6f 100644 --- a/internal/ogtags/mem_test.go +++ b/internal/ogtags/mem_test.go @@ -95,7 +95,7 @@ func TestMemoryUsage(t *testing.T) { // Run getTarget many times u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux") - for i := 0; i < 10000; i++ { + for range 10000 { _ = cache.getTarget(u) } @@ -129,7 +129,7 @@ func TestMemoryUsage(t *testing.T) { runtime.GC() runtime.ReadMemStats(&m1) - for i := 0; i < 1000; i++ { + for range 1000 { _ = cache.extractOGTags(doc) } diff --git a/internal/ogtags/ogtags_fuzz_test.go b/internal/ogtags/ogtags_fuzz_test.go index 6355eebf..8b7abb91 100644 --- a/internal/ogtags/ogtags_fuzz_test.go +++ b/internal/ogtags/ogtags_fuzz_test.go @@ -3,6 +3,7 @@ package ogtags import ( "context" "net/url" + "slices" "strings" "testing" "unicode/utf8" @@ -78,7 +79,7 @@ func FuzzGetTarget(f *testing.F) { } // Ensure no memory corruption by calling multiple times - for i := 0; i < 3; i++ { + for range 3 { result2 := cache.getTarget(u) if result != result2 { t.Errorf("getTarget not deterministic: %q != %q", result, result2) @@ -148,11 +149,8 @@ func FuzzExtractOGTags(f *testing.F) { } } if !approved { - for _, tag := range cache.approvedTags { - if property == tag { - approved = true - break - } + if slices.Contains(cache.approvedTags, property) { + approved = true } } if !approved { @@ -260,11 +258,8 @@ func FuzzExtractMetaTagInfo(f *testing.F) { } } if !approved { - for _, tag := range cache.approvedTags { - if property == tag { - approved = true - break - } + if slices.Contains(cache.approvedTags, property) { + approved = true } } if !approved { diff --git a/internal/ogtags/parse.go b/internal/ogtags/parse.go index c21fd795..98176558 100644 --- a/internal/ogtags/parse.go +++ b/internal/ogtags/parse.go @@ -1,6 +1,7 @@ package ogtags import ( + "slices" "strings" "golang.org/x/net/html" @@ -65,10 +66,8 @@ func (c *OGTagCache) extractMetaTagInfo(n *html.Node) (property, content string) } // Check exact matches - for _, tag := range c.approvedTags { - if propertyKey == tag { - return propertyKey, content - } + if slices.Contains(c.approvedTags, propertyKey) { + return propertyKey, content } return "", content diff --git a/internal/test/playwright_test.go b/internal/test/playwright_test.go index b1cab340..4d6355ba 100644 --- a/internal/test/playwright_test.go +++ b/internal/test/playwright_test.go @@ -270,7 +270,7 @@ func TestPlaywrightBrowser(t *testing.T) { var performedAction action var err error - for i := 0; i < 5; i++ { + for i := range 5 { performedAction, err = executeTestCase(t, tc, typ, anubisURL) if performedAction == tc.action { break diff --git a/lib/anubis.go b/lib/anubis.go index feff53a3..2ff5ec92 100644 --- a/lib/anubis.go +++ b/lib/anubis.go @@ -81,11 +81,11 @@ type Server struct { func (s *Server) getTokenKeyfunc() jwt.Keyfunc { // return ED25519 key if HS512 is not set if len(s.hs512Secret) == 0 { - return func(token *jwt.Token) (interface{}, error) { + return func(token *jwt.Token) (any, error) { return s.ed25519Priv.Public().(ed25519.PublicKey), nil } } else { - return func(token *jwt.Token) (interface{}, error) { + return func(token *jwt.Token) (any, error) { return s.hs512Secret, nil } } @@ -106,6 +106,13 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L //return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule") } + if rule.Challenge == nil { + rule.Challenge = &config.ChallengeRules{ + Difficulty: s.policy.DefaultDifficulty, + Algorithm: config.DefaultAlgorithm, + } + } + id, err := uuid.NewV7() if err != nil { return nil, err @@ -491,7 +498,11 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) { 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), makeCode(err)) + algorithm := "unknown" + if rule.Challenge != nil { + algorithm = rule.Challenge.Algorithm + } + s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err)) return } @@ -638,8 +649,16 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p } if matches { + challRules := t.Challenge + if challRules == nil { + // Non-CHALLENGE thresholds (ALLOW/DENY) don't have challenge config. + // Use an empty struct so hydrateChallengeRule can fill from stored + // challenge data during validation, rather than baking in defaults + // that could mismatch the difficulty the client actually solved for. + challRules = &config.ChallengeRules{} + } return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{ - Challenge: t.Challenge, + Challenge: challRules, Rules: &checker.List{}, }, nil } diff --git a/lib/anubis_test.go b/lib/anubis_test.go index 07785d37..7c0f87d3 100644 --- a/lib/anubis_test.go +++ b/lib/anubis_test.go @@ -38,8 +38,8 @@ func NewTLogWriter(t *testing.T) io.Writer { // 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 { + lines := strings.SplitSeq(string(p), "\n") + for line := range lines { if line != "" { w.t.Log(line) } diff --git a/lib/challenge/challenge.go b/lib/challenge/challenge.go index d0cfbf77..d075930a 100644 --- a/lib/challenge/challenge.go +++ b/lib/challenge/challenge.go @@ -4,12 +4,12 @@ import "time" // Challenge is the metadata about a single challenge issuance. type Challenge struct { - IssuedAt time.Time `json:"issuedAt"` - Metadata map[string]string `json:"metadata"` - ID string `json:"id"` - Method string `json:"method"` - RandomData string `json:"randomData"` - PolicyRuleHash string `json:"policyRuleHash,omitempty"` - Difficulty int `json:"difficulty,omitempty"` - Spent bool `json:"spent"` + 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 + ID string `json:"id"` // UUID identifying the challenge + Method string `json:"method"` // Challenge method + RandomData string `json:"randomData"` // The random data the client processes + PolicyRuleHash string `json:"policyRuleHash,omitempty"` // Hash of the policy rule that issued this challenge + Difficulty int `json:"difficulty,omitempty"` // Difficulty that was in effect when issued + Spent bool `json:"spent"` // Has the challenge already been solved? } diff --git a/lib/challenge/error.go b/lib/challenge/error.go index 7a9d3094..8c28a1c6 100644 --- a/lib/challenge/error.go +++ b/lib/challenge/error.go @@ -10,6 +10,7 @@ var ( ErrFailed = errors.New("challenge: user failed challenge") ErrMissingField = errors.New("challenge: missing field") ErrInvalidFormat = errors.New("challenge: field has invalid format") + ErrInvalidInput = errors.New("challenge: input is nil or missing required fields") ) func NewError(verb, publicReason string, privateReason error) *Error { diff --git a/lib/challenge/interface.go b/lib/challenge/interface.go index 4bef2e7f..e6d64fd2 100644 --- a/lib/challenge/interface.go +++ b/lib/challenge/interface.go @@ -1,6 +1,7 @@ package challenge import ( + "fmt" "log/slog" "net/http" "sort" @@ -50,12 +51,44 @@ type IssueInput struct { Store store.Interface } +func (in *IssueInput) Valid() error { + if in == nil { + return fmt.Errorf("%w: IssueInput is nil", ErrInvalidInput) + } + if in.Rule == nil { + return fmt.Errorf("%w: Rule is nil", ErrInvalidInput) + } + if in.Rule.Challenge == nil { + return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput) + } + if in.Challenge == nil { + return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput) + } + return nil +} + type ValidateInput struct { Rule *policy.Bot Challenge *Challenge Store store.Interface } +func (in *ValidateInput) Valid() error { + if in == nil { + return fmt.Errorf("%w: ValidateInput is nil", ErrInvalidInput) + } + if in.Rule == nil { + return fmt.Errorf("%w: Rule is nil", ErrInvalidInput) + } + if in.Rule.Challenge == nil { + return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput) + } + if in.Challenge == nil { + return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput) + } + return nil +} + type Impl interface { // Setup registers any additional routes with the Impl for assets or API routes. Setup(mux *http.ServeMux) diff --git a/lib/challenge/metarefresh/metarefresh.go b/lib/challenge/metarefresh/metarefresh.go index cb5b023c..cb8dbf8c 100644 --- a/lib/challenge/metarefresh/metarefresh.go +++ b/lib/challenge/metarefresh/metarefresh.go @@ -24,6 +24,10 @@ type Impl struct{} func (i *Impl) Setup(mux *http.ServeMux) {} func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) { + if err := in.Valid(); err != nil { + return nil, err + } + u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge") if err != nil { return nil, fmt.Errorf("can't render page: %w", err) @@ -49,6 +53,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in } func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error { + if err := in.Valid(); err != nil { + return challenge.NewError("validate", "invalid input", err) + } + wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond) if time.Now().Before(wantTime) { diff --git a/lib/challenge/preact/js/xeact.js b/lib/challenge/preact/js/xeact.js index cf4a102e..1488f8b8 100644 --- a/lib/challenge/preact/js/xeact.js +++ b/lib/challenge/preact/js/xeact.js @@ -5,7 +5,9 @@ */ const h = (name, data = {}, children = []) => { const result = - typeof name == "function" ? name(data) : Object.assign(document.createElement(name), data); + typeof name == "function" + ? name(data) + : Object.assign(document.createElement(name), data); if (!Array.isArray(children)) { children = [children]; } @@ -118,10 +120,10 @@ const d = (ms) => { /** * Parse the contents of a given HTML page element as JSON and * return the results. - * + * * This is useful when using templ to pass complicated data from * the server to the client via HTML[1]. - * + * * [1]: https://templ.guide/syntax-and-usage/script-templates/#pass-server-side-data-to-the-client-in-a-html-attribute */ const j = (id) => JSON.parse(g(id).textContent); diff --git a/lib/challenge/preact/preact.go b/lib/challenge/preact/preact.go index 896642e2..e39cd4d8 100644 --- a/lib/challenge/preact/preact.go +++ b/lib/challenge/preact/preact.go @@ -39,6 +39,10 @@ type impl struct{} func (i *impl) Setup(mux *http.ServeMux) {} func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) { + if err := in.Valid(); err != nil { + return nil, err + } + u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge") if err != nil { return nil, fmt.Errorf("can't render page: %w", err) @@ -57,6 +61,10 @@ func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in } func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error { + if err := in.Valid(); err != nil { + return challenge.NewError("validate", "invalid input", err) + } + wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond) if time.Now().Before(wantTime) { diff --git a/lib/challenge/proofofwork/proofofwork.go b/lib/challenge/proofofwork/proofofwork.go index b9be014e..3e4bce3b 100644 --- a/lib/challenge/proofofwork/proofofwork.go +++ b/lib/challenge/proofofwork/proofofwork.go @@ -33,6 +33,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in } func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error { + if err := in.Valid(); err != nil { + return chall.NewError("validate", "invalid input", err) + } + rule := in.Rule challenge := in.Challenge.RandomData diff --git a/lib/challenge/proofofwork/proofofwork_test.go b/lib/challenge/proofofwork/proofofwork_test.go index 069636bd..f5d6dfb7 100644 --- a/lib/challenge/proofofwork/proofofwork_test.go +++ b/lib/challenge/proofofwork/proofofwork_test.go @@ -30,6 +30,62 @@ func mkRequest(t *testing.T, values map[string]string) *http.Request { return req } +// TestValidateNilRuleChallenge reproduces the panic from +// https://github.com/TecharoHQ/anubis/issues/1463 +// +// When a threshold rule matches during PassChallenge, check() can return +// a policy.Bot with Challenge == nil. After hydrateChallengeRule fails to +// run (or the error path hits before it), Validate dereferences +// rule.Challenge.Difficulty and panics. +func TestValidateNilRuleChallenge(t *testing.T) { + i := &Impl{Algorithm: "fast"} + lg := slog.With() + + // This is the exact response for SHA256("hunter" + "0") with 0 leading zeros required. + const challengeStr = "hunter" + const response = "2652bdba8fb4d2ab39ef28d8534d7694c557a4ae146c1e9237bd8d950280500e" + + req := mkRequest(t, map[string]string{ + "nonce": "0", + "elapsedTime": "69", + "response": response, + }) + + for _, tc := range []struct { + name string + input *challenge.ValidateInput + }{ + { + name: "nil-rule-challenge", + input: &challenge.ValidateInput{ + Rule: &policy.Bot{}, + Challenge: &challenge.Challenge{RandomData: challengeStr}, + }, + }, + { + name: "nil-rule", + input: &challenge.ValidateInput{ + Challenge: &challenge.Challenge{RandomData: challengeStr}, + }, + }, + { + name: "nil-challenge", + input: &challenge.ValidateInput{Rule: &policy.Bot{Challenge: &config.ChallengeRules{Algorithm: "fast"}}}, + }, + { + name: "nil-input", + input: nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + err := i.Validate(req, lg, tc.input) + if !errors.Is(err, challenge.ErrInvalidInput) { + t.Fatalf("expected ErrInvalidInput, got: %v", err) + } + }) + } +} + func TestBasic(t *testing.T) { i := &Impl{Algorithm: "fast"} bot := &policy.Bot{ diff --git a/lib/config/config.go b/lib/config/config.go index d50cdae3..01b46576 100644 --- a/lib/config/config.go +++ b/lib/config/config.go @@ -228,8 +228,8 @@ type ImportStatement struct { } func (is *ImportStatement) open() (fs.File, error) { - if strings.HasPrefix(is.Import, "(data)/") { - fname := strings.TrimPrefix(is.Import, "(data)/") + if after, ok := strings.CutPrefix(is.Import, "(data)/"); ok { + fname := after fin, err := data.BotPolicies.Open(fname) return fin, err } @@ -325,7 +325,7 @@ func (sc StatusCodes) Valid() error { } type fileConfig struct { - OpenGraph openGraphFileConfig `json:"openGraph,omitempty"` + OpenGraph openGraphFileConfig `json:"openGraph"` Impressum *Impressum `json:"impressum,omitempty"` Store *Store `json:"store"` Bots []BotOrImport `json:"bots"` diff --git a/lib/config/config_test.go b/lib/config/config_test.go index 1b933759..ce57feed 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -188,7 +188,6 @@ func TestBotValid(t *testing.T) { } for _, cs := range tests { - cs := cs t.Run(cs.name, func(t *testing.T) { err := cs.bot.Valid() if err == nil && cs.err == nil { @@ -216,7 +215,6 @@ func TestConfigValidKnownGood(t *testing.T) { } for _, st := range finfos { - st := st t.Run(st.Name(), func(t *testing.T) { fin, err := os.Open(filepath.Join("testdata", "good", st.Name())) if err != nil { @@ -303,7 +301,6 @@ func TestConfigValidBad(t *testing.T) { } for _, st := range finfos { - st := st t.Run(st.Name(), func(t *testing.T) { fin, err := os.Open(filepath.Join("testdata", "bad", st.Name())) if err != nil { diff --git a/lib/config/testdata/bad/badregexes.json b/lib/config/testdata/bad/badregexes.json index db371b0f..436f768d 100644 --- a/lib/config/testdata/bad/badregexes.json +++ b/lib/config/testdata/bad/badregexes.json @@ -18,4 +18,4 @@ "action": "DENY" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/bad/badregexes.yaml b/lib/config/testdata/bad/badregexes.yaml index 3880e407..c6caedbd 100644 --- a/lib/config/testdata/bad/badregexes.yaml +++ b/lib/config/testdata/bad/badregexes.yaml @@ -1,7 +1,7 @@ bots: -- name: path-bad - path_regex: "a(b" - action: DENY -- name: user-agent-bad - user_agent_regex: "a(b" - action: DENY \ No newline at end of file + - name: path-bad + path_regex: "a(b" + action: DENY + - name: user-agent-bad + user_agent_regex: "a(b" + action: DENY diff --git a/lib/config/testdata/bad/import_and_bot.json b/lib/config/testdata/bad/import_and_bot.json index 3d0519bc..ef9778cd 100644 --- a/lib/config/testdata/bad/import_and_bot.json +++ b/lib/config/testdata/bad/import_and_bot.json @@ -7,4 +7,4 @@ "action": "CHALLENGE" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/bad/import_and_bot.yaml b/lib/config/testdata/bad/import_and_bot.yaml index fdfaa43a..cc9142db 100644 --- a/lib/config/testdata/bad/import_and_bot.yaml +++ b/lib/config/testdata/bad/import_and_bot.yaml @@ -1,6 +1,6 @@ bots: -- import: (data)/bots/ai-catchall.yaml - name: generic-browser - user_agent_regex: > - Mozilla|Opera - action: CHALLENGE \ No newline at end of file + - import: (data)/bots/ai-catchall.yaml + name: generic-browser + user_agent_regex: > + Mozilla|Opera + action: CHALLENGE diff --git a/lib/config/testdata/bad/import_invalid_file.json b/lib/config/testdata/bad/import_invalid_file.json index c7546c09..26669635 100644 --- a/lib/config/testdata/bad/import_invalid_file.json +++ b/lib/config/testdata/bad/import_invalid_file.json @@ -4,4 +4,4 @@ "import": "(data)/does-not-exist-fake-file.yaml" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/bad/import_invalid_file.yaml b/lib/config/testdata/bad/import_invalid_file.yaml index df78c067..c82b4533 100644 --- a/lib/config/testdata/bad/import_invalid_file.yaml +++ b/lib/config/testdata/bad/import_invalid_file.yaml @@ -1,2 +1,2 @@ bots: -- import: (data)/does-not-exist-fake-file.yaml \ No newline at end of file + - import: (data)/does-not-exist-fake-file.yaml diff --git a/lib/config/testdata/bad/invalid.json b/lib/config/testdata/bad/invalid.json index c5d1ff6c..334e5db7 100644 --- a/lib/config/testdata/bad/invalid.json +++ b/lib/config/testdata/bad/invalid.json @@ -1,5 +1,3 @@ { - "bots": [ - {} - ] -} \ No newline at end of file + "bots": [{}] +} diff --git a/lib/config/testdata/bad/invalid.yaml b/lib/config/testdata/bad/invalid.yaml index 18625b61..b78eb920 100644 --- a/lib/config/testdata/bad/invalid.yaml +++ b/lib/config/testdata/bad/invalid.yaml @@ -1 +1 @@ -bots: [] \ No newline at end of file +bots: [] diff --git a/lib/config/testdata/bad/multiple_expression_types.json b/lib/config/testdata/bad/multiple_expression_types.json index 8b852768..0bdf5bfc 100644 --- a/lib/config/testdata/bad/multiple_expression_types.json +++ b/lib/config/testdata/bad/multiple_expression_types.json @@ -8,10 +8,8 @@ "userAgent.startsWith(\"git/\") || userAgent.contains(\"libgit\")", "\"Git-Protocol\" in headers && headers[\"Git-Protocol\"] == \"version=2\"\n" ], - "any": [ - "userAgent.startsWith(\"evilbot/\")" - ] + "any": ["userAgent.startsWith(\"evilbot/\")"] } } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/bad/multiple_expression_types.yaml b/lib/config/testdata/bad/multiple_expression_types.yaml index f7aa5463..da97d454 100644 --- a/lib/config/testdata/bad/multiple_expression_types.yaml +++ b/lib/config/testdata/bad/multiple_expression_types.yaml @@ -1,10 +1,10 @@ bots: -- name: multiple-expression-types - action: ALLOW - expression: - all: - - userAgent.startsWith("git/") || userAgent.contains("libgit") - - > - "Git-Protocol" in headers && headers["Git-Protocol"] == "version=2" - any: - - userAgent.startsWith("evilbot/") + - name: multiple-expression-types + action: ALLOW + expression: + all: + - userAgent.startsWith("git/") || userAgent.contains("libgit") + - > + "Git-Protocol" in headers && headers["Git-Protocol"] == "version=2" + any: + - userAgent.startsWith("evilbot/") diff --git a/lib/config/testdata/bad/nobots.json b/lib/config/testdata/bad/nobots.json index 9e26dfee..0967ef42 100644 --- a/lib/config/testdata/bad/nobots.json +++ b/lib/config/testdata/bad/nobots.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/lib/config/testdata/bad/nobots.yaml b/lib/config/testdata/bad/nobots.yaml index 9e26dfee..0967ef42 100644 --- a/lib/config/testdata/bad/nobots.yaml +++ b/lib/config/testdata/bad/nobots.yaml @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/lib/config/testdata/bad/regex_ends_newline.yaml b/lib/config/testdata/bad/regex_ends_newline.yaml index 1f0ae85b..7400016b 100644 --- a/lib/config/testdata/bad/regex_ends_newline.yaml +++ b/lib/config/testdata/bad/regex_ends_newline.yaml @@ -1,17 +1,17 @@ bots: -- name: user-agent-ends-newline - # Subtle bug: this ends with a newline - user_agent_regex: > - Mozilla - action: CHALLENGE -- name: path-ends-newline - # Subtle bug: this ends with a newline - path_regex: > - ^/evil/.*$ - action: CHALLENGE -- name: headers-ends-newline - # Subtle bug: this ends with a newline - headers_regex: - CF-Worker: > - .* - action: CHALLENGE \ No newline at end of file + - name: user-agent-ends-newline + # Subtle bug: this ends with a newline + user_agent_regex: > + Mozilla + action: CHALLENGE + - name: path-ends-newline + # Subtle bug: this ends with a newline + path_regex: > + ^/evil/.*$ + action: CHALLENGE + - name: headers-ends-newline + # Subtle bug: this ends with a newline + headers_regex: + CF-Worker: > + .* + action: CHALLENGE diff --git a/lib/config/testdata/bad/status-codes-0.yaml b/lib/config/testdata/bad/status-codes-0.yaml index 0d08322e..96df3d1d 100644 --- a/lib/config/testdata/bad/status-codes-0.yaml +++ b/lib/config/testdata/bad/status-codes-0.yaml @@ -1,8 +1,8 @@ bots: -- name: everything - user_agent_regex: .* - action: DENY + - name: everything + user_agent_regex: .* + action: DENY status_codes: CHALLENGE: 0 - DENY: 0 \ No newline at end of file + DENY: 0 diff --git a/lib/config/testdata/good/allow_everyone.json b/lib/config/testdata/good/allow_everyone.json index a7e1af76..de298c6b 100644 --- a/lib/config/testdata/good/allow_everyone.json +++ b/lib/config/testdata/good/allow_everyone.json @@ -2,11 +2,8 @@ "bots": [ { "name": "everyones-invited", - "remote_addresses": [ - "0.0.0.0/0", - "::/0" - ], + "remote_addresses": ["0.0.0.0/0", "::/0"], "action": "ALLOW" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/allow_everyone.yaml b/lib/config/testdata/good/allow_everyone.yaml index 5c495345..80dae8b2 100644 --- a/lib/config/testdata/good/allow_everyone.yaml +++ b/lib/config/testdata/good/allow_everyone.yaml @@ -1,6 +1,6 @@ bots: -- name: everyones-invited - remote_addresses: - - "0.0.0.0/0" - - "::/0" - action: ALLOW \ No newline at end of file + - name: everyones-invited + remote_addresses: + - "0.0.0.0/0" + - "::/0" + action: ALLOW diff --git a/lib/config/testdata/good/block_cf_workers.json b/lib/config/testdata/good/block_cf_workers.json index b84f1e0d..380fdf04 100644 --- a/lib/config/testdata/good/block_cf_workers.json +++ b/lib/config/testdata/good/block_cf_workers.json @@ -9,4 +9,4 @@ } ], "dnsbl": false -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/block_cf_workers.yaml b/lib/config/testdata/good/block_cf_workers.yaml index c66badef..353c921a 100644 --- a/lib/config/testdata/good/block_cf_workers.yaml +++ b/lib/config/testdata/good/block_cf_workers.yaml @@ -2,4 +2,4 @@ bots: - name: cloudflare-workers headers_regex: CF-Worker: .* - action: DENY \ No newline at end of file + action: DENY diff --git a/lib/config/testdata/good/challengemozilla.json b/lib/config/testdata/good/challengemozilla.json index e9d34eeb..eaee8490 100644 --- a/lib/config/testdata/good/challengemozilla.json +++ b/lib/config/testdata/good/challengemozilla.json @@ -6,4 +6,4 @@ "action": "CHALLENGE" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/challengemozilla.yaml b/lib/config/testdata/good/challengemozilla.yaml index 15922b0a..f6d8e9ac 100644 --- a/lib/config/testdata/good/challengemozilla.yaml +++ b/lib/config/testdata/good/challengemozilla.yaml @@ -1,4 +1,4 @@ bots: -- name: generic-browser - user_agent_regex: Mozilla - action: CHALLENGE \ No newline at end of file + - name: generic-browser + user_agent_regex: Mozilla + action: CHALLENGE diff --git a/lib/config/testdata/good/everything_blocked.json b/lib/config/testdata/good/everything_blocked.json index e1763e45..ab694f25 100644 --- a/lib/config/testdata/good/everything_blocked.json +++ b/lib/config/testdata/good/everything_blocked.json @@ -7,4 +7,4 @@ } ], "dnsbl": false -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/everything_blocked.yaml b/lib/config/testdata/good/everything_blocked.yaml index 323c5969..1c805581 100644 --- a/lib/config/testdata/good/everything_blocked.yaml +++ b/lib/config/testdata/good/everything_blocked.yaml @@ -1,4 +1,4 @@ bots: -- name: everything - user_agent_regex: .* - action: DENY + - name: everything + user_agent_regex: .* + action: DENY diff --git a/lib/config/testdata/good/git_client.json b/lib/config/testdata/good/git_client.json index 68a2b3e8..52c5e11f 100644 --- a/lib/config/testdata/good/git_client.json +++ b/lib/config/testdata/good/git_client.json @@ -11,4 +11,4 @@ } } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/git_client.yaml b/lib/config/testdata/good/git_client.yaml index 44aa2da3..1c496789 100644 --- a/lib/config/testdata/good/git_client.yaml +++ b/lib/config/testdata/good/git_client.yaml @@ -1,8 +1,8 @@ bots: -- name: allow-git-clients - action: ALLOW - expression: - all: - - userAgent.startsWith("git/") || userAgent.contains("libgit") - - > - "Git-Protocol" in headers && headers["Git-Protocol"] == "version=2" + - name: allow-git-clients + action: ALLOW + expression: + all: + - userAgent.startsWith("git/") || userAgent.contains("libgit") + - > + "Git-Protocol" in headers && headers["Git-Protocol"] == "version=2" diff --git a/lib/config/testdata/good/import_filesystem.json b/lib/config/testdata/good/import_filesystem.json index 23480c90..ba0228a7 100644 --- a/lib/config/testdata/good/import_filesystem.json +++ b/lib/config/testdata/good/import_filesystem.json @@ -4,4 +4,4 @@ "import": "./testdata/hack-test.json" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/import_filesystem.yaml b/lib/config/testdata/good/import_filesystem.yaml index 422ccc4e..2ea0d54e 100644 --- a/lib/config/testdata/good/import_filesystem.yaml +++ b/lib/config/testdata/good/import_filesystem.yaml @@ -1,2 +1,2 @@ bots: -- import: ./testdata/hack-test.yaml \ No newline at end of file + - import: ./testdata/hack-test.yaml diff --git a/lib/config/testdata/good/import_keep_internet_working.json b/lib/config/testdata/good/import_keep_internet_working.json index 68ff2dbc..06b08fe1 100644 --- a/lib/config/testdata/good/import_keep_internet_working.json +++ b/lib/config/testdata/good/import_keep_internet_working.json @@ -4,4 +4,4 @@ "import": "(data)/common/keep-internet-working.yaml" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/import_keep_internet_working.yaml b/lib/config/testdata/good/import_keep_internet_working.yaml index 923ffe32..19ff33eb 100644 --- a/lib/config/testdata/good/import_keep_internet_working.yaml +++ b/lib/config/testdata/good/import_keep_internet_working.yaml @@ -1,2 +1,2 @@ bots: -- import: (data)/common/keep-internet-working.yaml \ No newline at end of file + - import: (data)/common/keep-internet-working.yaml diff --git a/lib/config/testdata/good/old_xesite.json b/lib/config/testdata/good/old_xesite.json index 21816bdd..763d6785 100644 --- a/lib/config/testdata/good/old_xesite.json +++ b/lib/config/testdata/good/old_xesite.json @@ -76,4 +76,4 @@ "action": "CHALLENGE" } ] -} \ No newline at end of file +} diff --git a/lib/config/testdata/good/status-codes-paranoid.yaml b/lib/config/testdata/good/status-codes-paranoid.yaml index 89655a3d..0310388b 100644 --- a/lib/config/testdata/good/status-codes-paranoid.yaml +++ b/lib/config/testdata/good/status-codes-paranoid.yaml @@ -1,8 +1,8 @@ bots: -- name: everything - user_agent_regex: .* - action: DENY + - name: everything + user_agent_regex: .* + action: DENY status_codes: CHALLENGE: 200 - DENY: 200 \ No newline at end of file + DENY: 200 diff --git a/lib/config/testdata/good/status-codes-rfc.yaml b/lib/config/testdata/good/status-codes-rfc.yaml index 4e4e6d8d..c70ea126 100644 --- a/lib/config/testdata/good/status-codes-rfc.yaml +++ b/lib/config/testdata/good/status-codes-rfc.yaml @@ -1,8 +1,8 @@ bots: -- name: everything - user_agent_regex: .* - action: DENY + - name: everything + user_agent_regex: .* + action: DENY status_codes: CHALLENGE: 403 - DENY: 403 \ No newline at end of file + DENY: 403 diff --git a/lib/config/testdata/hack-test.json b/lib/config/testdata/hack-test.json index 652dcd87..a173bb18 100644 --- a/lib/config/testdata/hack-test.json +++ b/lib/config/testdata/hack-test.json @@ -2,8 +2,6 @@ { "name": "ipv6-ula", "action": "ALLOW", - "remote_addresses": [ - "fc00::/7" - ] + "remote_addresses": ["fc00::/7"] } -] \ No newline at end of file +] diff --git a/lib/config/testdata/hack-test.yaml b/lib/config/testdata/hack-test.yaml index cd4d7d05..29263e96 100644 --- a/lib/config/testdata/hack-test.yaml +++ b/lib/config/testdata/hack-test.yaml @@ -1,3 +1,3 @@ - name: well-known path_regex: ^/.well-known/.*$ - action: ALLOW \ No newline at end of file + action: ALLOW diff --git a/lib/config_test.go b/lib/config_test.go index 5d392dbb..99aab736 100644 --- a/lib/config_test.go +++ b/lib/config_test.go @@ -24,7 +24,6 @@ func TestBadConfigs(t *testing.T) { } for _, st := range finfos { - st := st t.Run(st.Name(), func(t *testing.T) { if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil { t.Fatal(err) @@ -42,7 +41,6 @@ func TestGoodConfigs(t *testing.T) { } for _, st := range finfos { - st := st t.Run(st.Name(), func(t *testing.T) { t.Run("with-thoth", func(t *testing.T) { ctx := thothmock.WithMockThoth(t) diff --git a/lib/http.go b/lib/http.go index 053470f0..a46cd83d 100644 --- a/lib/http.go +++ b/lib/http.go @@ -182,10 +182,7 @@ func makeCode(err error) string { enc := base64.StdEncoding.EncodeToString(buf.Bytes()) var builder strings.Builder for i := 0; i < len(enc); i += width { - end := i + width - if end > len(enc) { - end = len(enc) - } + end := min(i+width, len(enc)) builder.WriteString(enc[i:end]) builder.WriteByte('\n') } @@ -222,8 +219,12 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule) if err != nil { lg.Error("can't get challenge", "err", err) + algorithm := "unknown" + if rule.Challenge != nil { + algorithm = rule.Challenge.Algorithm + } s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host}) - s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err)) + s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err)) return } @@ -248,9 +249,13 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C impl, ok := challenge.Get(chall.Method) if !ok { - lg.Error("check failed", "err", "can't get algorithm", "algorithm", rule.Challenge.Algorithm) + algorithm := "unknown" + if rule.Challenge != nil { + algorithm = rule.Challenge.Algorithm + } + lg.Error("check failed", "err", "can't get algorithm", "algorithm", algorithm) s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host}) - s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err)) + s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err)) return } @@ -333,7 +338,14 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) { localizer := localization.GetLocalizer(r) - templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r) + component := web.Base( + localizer.T("oh_noes"), + web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer), + s.policy.Impressum, + localizer, + ) + handler := internal.NoStoreCache(templ.Handler(component, templ.WithStatus(status))) + handler.ServeHTTP(w, r) } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/lib/http_test.go b/lib/http_test.go index 255344f3..bacc8358 100644 --- a/lib/http_test.go +++ b/lib/http_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/TecharoHQ/anubis" + "github.com/TecharoHQ/anubis/internal" "github.com/TecharoHQ/anubis/lib/policy" ) @@ -191,3 +192,34 @@ func TestRenderIndexUnauthorized(t *testing.T) { t.Errorf("expected body %q, got %q", "Authorization required", body) } } + +func TestNoCacheOnError(t *testing.T) { + pol := loadPolicies(t, "testdata/useragent.yaml", 0) + srv := spawnAnubis(t, Options{Policy: pol}) + ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv)) + defer ts.Close() + + for userAgent, expectedCacheControl := range map[string]string{ + "DENY": "no-store", + "CHALLENGE": "no-store", + "ALLOW": "", + } { + t.Run(userAgent, func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, ts.URL, nil) + if err != nil { + t.Fatal(err) + } + + req.Header.Set("User-Agent", userAgent) + + resp, err := ts.Client().Do(req) + if err != nil { + t.Fatal(err) + } + + if resp.Header.Get("Cache-Control") != expectedCacheControl { + t.Errorf("wanted Cache-Control header %q, got %q", expectedCacheControl, resp.Header.Get("Cache-Control")) + } + }) + } +} diff --git a/lib/localization/locales/cs.json b/lib/localization/locales/cs.json index db4eded3..967da646 100644 --- a/lib/localization/locales/cs.json +++ b/lib/localization/locales/cs.json @@ -63,4 +63,4 @@ "js_calculation_error": "Chyba výpočtu!", "js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:", "missing_required_forwarded_headers": "Chybějící požadované hlavičky X-Forwarded-*" -} \ No newline at end of file +} diff --git a/lib/localization/locales/en.json b/lib/localization/locales/en.json index 0280b975..4dffb969 100644 --- a/lib/localization/locales/en.json +++ b/lib/localization/locales/en.json @@ -63,4 +63,4 @@ "js_finished_reading": "I've finished reading, continue →", "js_calculation_error": "Calculation error!", "js_calculation_error_msg": "Failed to calculate challenge:" -} \ No newline at end of file +} diff --git a/lib/localization/locales/es.json b/lib/localization/locales/es.json index 8beb3bf8..d4e91418 100644 --- a/lib/localization/locales/es.json +++ b/lib/localization/locales/es.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Falló al calcular el desafío:", "missing_required_forwarded_headers": "Faltan los encabezados X-Forwarded-* requeridos", "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 Prueba de trabajo. La tarea se calcula en unos segundos y se le concede acceso al sitio web. Gracias por su comprensión y paciencia." -} \ No newline at end of file +} diff --git a/lib/localization/locales/et.json b/lib/localization/locales/et.json index 09125b9c..6ecfd2fa 100644 --- a/lib/localization/locales/et.json +++ b/lib/localization/locales/et.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Ei suutnud kontrolli arvutada:", "missing_required_forwarded_headers": "Puuduvad nõutud X-Forwarded-* päised", "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 Töötõendiks. Ülesanne arvutatakse mõne sekundiga ja teile antakse juurdepääs veebisaidile. Täname teid mõistva suhtumise ja kannatlikkuse eest." -} \ No newline at end of file +} diff --git a/lib/localization/locales/fi.json b/lib/localization/locales/fi.json index bd06d3a7..fb313c25 100644 --- a/lib/localization/locales/fi.json +++ b/lib/localization/locales/fi.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Haasteen laskenta ei onnistunut:", "missing_required_forwarded_headers": "Puuttuvat vaaditut X-Forwarded-* otsikot", "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ä Työtodistus. Tehtävä lasketaan muutamassa sekunnissa ja saat pääsyn verkkosivustolle. Kiitos ymmärryksestäsi ja kärsivällisyydestäsi." -} \ No newline at end of file +} diff --git a/lib/localization/locales/fr.json b/lib/localization/locales/fr.json index b696e04c..13068b7c 100644 --- a/lib/localization/locales/fr.json +++ b/lib/localization/locales/fr.json @@ -1,66 +1,66 @@ { "loading": "Chargement...", - "why_am_i_seeing": "Pourquoi je vois ceci ?", + "why_am_i_seeing": "Comment suis-je arrivé·e ici ?", "protected_by": "Protégé par", - "protected_from": "From", + "protected_from": "de", "made_with": "Fait avec ❤️ au 🇨🇦", "mascot_design": "Design de la mascotte par", - "ai_companies_explanation": "Vous voyez ceci car l'administrateur de ce site web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui scrapent agressivement les sites web. Cela peut et cause des temps d'arrêt pour les sites web, ce qui rend leurs ressources inaccessibles pour tout le monde.", - "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, 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", + "ai_companies_explanation": "Vous voyez cette page car l'administrateur·rice de ce site Web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui récupèrent agressivement le contenu des sites Web. Cela perturbe leur fonctionnement et rend leurs ressources inaccessibles pour tout le monde.", + "anubis_compromise": "Anubis est un compromis. Anubis utilise un procédé de preuve de travail similaire à Hashcash, un procédé de preuve de travail proposé pour réduire le spam par e-mail. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, la charge s'accumule et le scraping devient beaucoup plus coûteux.", + "hack_purpose": "En fin de compte, il s'agit d'une solution de substitution permettant de consacrer plus de temps à l'identification et à la prise d'empreintes des navigateurs headless (par exemple, en reconnaissant leur rendu des polices), pour que, à terme, la page de défi utilisant la preuve de travail n'ait plus besoin d'être présentée aux utilisateur·rices qui sont beaucoup plus susceptibles d'être légitimes.", + "jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes qui peuvent être désactivées par des plugins comme JShelter. Veuillez désactiver JShelter ou tout autre plugin similaire pour ce domaine.", + "version_info": "Ce site Web utilise Anubis version", "try_again": "Réessayer", "go_home": "Accueil", - "contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à", + "contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à l'adresse", "connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.", - "javascript_required": "Malheureusement, vous devez activer JavaScript pour passer ce défi. Ceci est requis car les entreprises d'IA ont changé le contrat social autour du fonctionnement de l'hébergement de sites web. Une solution sans JS est en cours de développement.", + "javascript_required": "Malheureusement, vous devez activer JavaScript pour passer cette page de défi. Cette obligation est imposée par les entreprises d'IA, qui ont décidé de modifier unilatéralement les termes du contrat social régissant l'hébergement de sites Web. Une solution sans JavaScript est en cours de développement.", "benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.", - "difficulty": "Difficulté :", - "algorithm": "Algorithme :", - "compare": "Comparer :", + "difficulty": "Difficulté :", + "algorithm": "Algorithme :", + "compare": "Comparer :", "time": "Temps", "iters": "Itérations", "time_a": "Temps A", "iters_a": "Itér. A", "time_b": "Temps B", "iters_b": "Itér. B", - "static_check_endpoint": "Ceci est juste un point de terminaison de vérification pour votre proxy inverse à utiliser.", + "static_check_endpoint": "Ceci est juste un point de terminaison de vérification à utiliser par votre proxy inverse.", "authorization_required": "Autorisation requise", - "cookies_disabled": "Votre navigateur est configuré pour désactiver les cookies. Anubis nécessite des cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine", - "access_denied": "Accès refusé : code d'erreur", - "dronebl_entry": "DroneBL a signalé une entrée", + "cookies_disabled": "Les cookies sont désactivés dans votre navigateur. Anubis a recours aux cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine.", + "access_denied": "Accès refusé : code d'erreur", + "dronebl_entry": "DroneBL a rapporté une entrée", "see_dronebl_lookup": "voir", - "internal_server_error": "Erreur interne du serveur : l'administrateur a mal configuré Anubis. Veuillez contacter l'administrateur et lui demander de consulter les logs autour de", + "internal_server_error": "Erreur interne du serveur : l'administrateur·rice a mal configuré Anubis. Veuillez contacter l'administrateur·rice et lui demander de consulter les logs autour de", "invalid_redirect": "Redirection invalide", "redirect_not_parseable": "URL de redirection non analysable", "redirect_domain_not_allowed": "Domaine de redirection non autorisé", - "failed_to_sign_jwt": "échec de la signature JWT", + "failed_to_sign_jwt": "échec de la signature du JWT", "invalid_invocation": "Invocation invalide de MakeChallenge", - "client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.", - "oh_noes": "Oh non !", - "benchmarking_anubis": "Test de performance d'Anubis !", - "you_are_not_a_bot": "Vous n'êtes pas un robot !", - "making_sure_not_bot": "Vérification que vous n'êtes pas un robot !", - "celphase": "PHASE de CEL", - "js_web_crypto_error": "Votre navigateur n'a pas d'élément web.crypto fonctionnel. Consultez-vous cette page dans un contexte sécurisé ?", - "js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous un plugin comme JShelter installé ?", - "js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis utilise des cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Les noms des cookies qu'Anubis stocke peuvent varier sans préavis. Les noms et valeurs des cookies ne font pas partie de l'API publique.", - "js_context_not_secure": "Votre contexte n'est pas sécurisé !", - "js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou informez l'administrateur de configurer HTTPS. Pour plus d'informations, voir MDN.", + "client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.", + "oh_noes": "Oh non !", + "benchmarking_anubis": "Je vérifie les performances d'Anubis !", + "you_are_not_a_bot": "Vous n'êtes pas un robot !", + "making_sure_not_bot": "Je m'assure que vous n'êtes pas un robot !", + "celphase": "CELPHASE", + "js_web_crypto_error": "L'élément web.crypto de votre navigateur n'est pas fonctionnel. Consultez-vous bien cette page dans un contexte sécurisé ?", + "js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous installé un plugin comme JShelter ?", + "js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis a recours aux cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Le nom des cookies stockés par Anubis peut varier à tout moment. Le nom et la valeur des cookies ne font pas partie de l'API publique.", + "js_context_not_secure": "Votre contexte n'est pas sécurisé !", + "js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou demandez à l'administrateur·rice de configurer HTTPS. Pour plus d'informations, consultez MDN.", "js_calculating": "Calcul en cours...", "js_missing_feature": "Fonctionnalité manquante", - "js_challenge_error": "Erreur de défi !", + "js_challenge_error": "Erreur de défi !", "js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.", - "js_calculating_difficulty": "Calcul en cours...
Difficulté :", - "js_speed": "Vitesse :", + "js_calculating_difficulty": "Calcul en cours...
Difficulté :", + "js_speed": "Vitesse :", "js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.", - "js_success": "Succès !", - "js_done_took": "Terminé ! A pris", + "js_success": "Vérification réussie !", + "js_done_took": "Terminé ! Cela aura nécessité", "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 :", - "missing_required_forwarded_headers": "En-têtes X-Forwarded-* requis manquants", - "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 Preuve de travail. 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." -} \ No newline at end of file + "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 :", + "missing_required_forwarded_headers": "En-têtes X-Forwarded-* manquants", + "simplified_explanation": "Ceci est 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 confirmer qu'il est un client valide. Ce concept est nommé Preuve de travail. La tâche s'effectue en quelques secondes, puis vous avez accès au site Web. Merci pour votre compréhension et votre patience." +} diff --git a/lib/localization/locales/it.json b/lib/localization/locales/it.json index e2ecc11b..4b44c523 100644 --- a/lib/localization/locales/it.json +++ b/lib/localization/locales/it.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Impossibile superare il test:", "missing_required_forwarded_headers": "Mancano gli header X-Forwarded-* richiesti", "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 Proof of Work. Il compito viene calcolato in pochi secondi e ti viene concesso l'accesso al sito web. Grazie per la tua comprensione e pazienza." -} \ No newline at end of file +} diff --git a/lib/localization/locales/ja.json b/lib/localization/locales/ja.json index 3042173d..791c963f 100644 --- a/lib/localization/locales/ja.json +++ b/lib/localization/locales/ja.json @@ -9,7 +9,7 @@ "anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。", "hack_purpose": "最終的に、これはヘッドレスブラウザのフィンガープリントと識別に時間を費やすためのプレースホルダーソリューションです(例:フォントレンダリングの方法による)。これにより、正当なユーザーにはチャレンジのプルーフオブワークページを提示する必要がなくなります。", "jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。", - "version_info": "このウェブサイトはAnubisバージョンで動作しています", + "version_info": "このウェブサイトはAnubisで動作しています バージョン", "try_again": "再試行", "go_home": "ホームに戻る", "contact_webmaster": "もしブロックされるべきでないと思われる場合は、ウェブマスターにご連絡ください:", @@ -63,4 +63,4 @@ "js_calculation_error_msg": "チャレンジの計算に失敗しました:", "missing_required_forwarded_headers": "必要な X-Forwarded-* ヘッダーがありません", "simplified_explanation": "これは、CAPTCHAと同様の、ボットや悪意のあるリクエストに対する対策です。ただし、自分で作業する代わりに、ブラウザに計算タスクが与えられ、それを解決して有効なクライアントであることを確認する必要があります。この概念はProof of Workと呼ばれます。タスクは数秒で計算され、ウェブサイトへのアクセスが許可されます。ご理解とご協力をお願いいたします。" -} \ No newline at end of file +} diff --git a/lib/localization/locales/nb.json b/lib/localization/locales/nb.json index 0b8b6fcc..ada36f20 100644 --- a/lib/localization/locales/nb.json +++ b/lib/localization/locales/nb.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Mislyktes i å beregne utfordring:", "missing_required_forwarded_headers": "Mangler nødvendige X-Forwarded-* header", "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 Proof of Work. Oppgaven beregnes på noen få sekunder, og du får tilgang til nettstedet. Takk for din forståelse og tålmodighet." -} \ No newline at end of file +} diff --git a/lib/localization/locales/nl.json b/lib/localization/locales/nl.json index 20710847..bce5a2cf 100644 --- a/lib/localization/locales/nl.json +++ b/lib/localization/locales/nl.json @@ -4,18 +4,18 @@ "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.", + "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 de website voor iedereen ontoegankelijk wordt.", + "anubis_compromise": "Anubis is een compromis. Anubis gebruikt een proof-of-work-algoritme in de geest van Hashcash, een proof-of-work-algoritme voor het verminderen van e-mailspam. Het idee is dat voor individuen minimaal is, maar het voor scrapers veel duurder wordt.", "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.", + "jshelter_note": "Anubis vereist het gebruik van moderne JavaScript-functies die worden uitgeschakeld door plugins zoals JShelter. Schakel JShelter of soortgelijke 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.", + "go_home": "Naar de hoofdpagine", + "contact_webmaster": "of als je denkt dat je niet geblokkeerd had moeten worden, neem contact op met de webmaster op", + "connection_security": "Wacht even terwijl we de veiligheid van je 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.", + "benchmark_requires_js": "Voor het uitvoeren van de check moet JavaScript zijn ingeschakeld.", "difficulty": "Moeilijkheidsgraad:", "algorithm": "Algoritme:", "compare": "Vergelijken:", @@ -25,36 +25,36 @@ "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.", + "static_check_endpoint": "Dit is gewoon een controle-eindpunt voor je reverse proxy om te gebruiken.", "authorization_required": "Autorisatie vereist", - "cookies_disabled": "Uw browser is geconfigureerd om cookies uit te schakelen. Anubis heeft cookies nodig om er zeker van te zijn dat u een geldige klant bent. Schakel cookies in voor dit domein", + "cookies_disabled": "Cookies zijn uitgeschakeld in je browser. Anubis heeft cookies nodig om er zeker van te zijn dat je een echt persoon 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", + "internal_server_error": "Interne Serverfout: beheerder heeft Anubis verkeerd geconfigureerd. Vraag de beheerder om de logs te bekijken.", "invalid_redirect": "Ongeldige omleiding", - "redirect_not_parseable": "Redirect URL niet parseerbaar", + "redirect_not_parseable": "Redirect-URL kan niet verwerkt worden", "redirect_domain_not_allowed": "Redirect-domein niet toegestaan", - "failed_to_sign_jwt": "jWT niet ondertekend", + "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.", + "client_error_browser": "Fout bij client: Controleer of je 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!", + "making_sure_not_bot": "Even checken of je een 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_crypto_error": "Je 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 cookie op te slaan. Schakel het opslaan van cookies voor dit domein in. De namen van de cookies die Anubis opslaat, kunnen zonder voorafgaande kennisgeving variëren. cookiesnamen en -waarden maken geen deel uit van de openbare API.", + "js_cookies_error": "Je browser slaat geen cookies op. Anubis gebruikt cookies om te bepalen welke bezoekers echte personen zijn. Schakel het opslaan van cookies voor dit domein in. De namen van de cookies die Anubis opslaat, kunnen in de toekomst veranderen. De namen en waarden van cookies maken geen deel uit van de openbare API.", "js_context_not_secure": "Je context is niet veilig!", "js_context_not_secure_msg": "Probeer verbinding te maken via HTTPS of laat de beheerder weten dat HTTPS moet worden ingesteld. Zie MDN voor meer informatie.", "js_calculating": "Berekenen...", "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_challenge_error_msg": "De check is gefaald. Misschien wil je de pagina opnieuw laden.", "js_calculating_difficulty": "Rekenen...
Moeilijkheidsgraad:", "js_speed": "Snelheid:", - "js_verification_longer": "Verificatie duurt langer dan verwacht. Vernieuw de pagina niet a.u.b.", + "js_verification_longer": "Verificatie duurt langer dan verwacht. Ververs de pagina niet.", "js_success": "Gelukt!", "js_done_took": "Klaar! Nam", "js_iterations": "iteraties", @@ -62,5 +62,5 @@ "js_calculation_error": "Rekenfout!", "js_calculation_error_msg": "Uitdaging niet berekend:", "missing_required_forwarded_headers": "Ontbrekende vereiste X-Forwarded-* headers", - "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 Proof of Work genoemd. De taak wordt in een paar seconden berekend en u krijgt toegang tot de website. Bedankt voor uw begrip en geduld." + "simplified_explanation": "Dit is een maatregel tegen bots en kwaadwillende verzoeken, vergelijkbaar met een CAPTCHA. In plaats van dat je zelf werk moet verrichten, krijgt je browser een rekentaak die moet worden opgelost om ervoor te zorgen dat het een geldige client is. Dit concept wordt Proof of Work genoemd. De taak wordt in een paar seconden berekend en u krijgt toegang tot de website. Bedankt voor je begrip en geduld." } diff --git a/lib/localization/locales/pl.json b/lib/localization/locales/pl.json index 47e84c31..b7e7f6e8 100644 --- a/lib/localization/locales/pl.json +++ b/lib/localization/locales/pl.json @@ -1,66 +1,66 @@ { - "loading": "Ładowanie...", - "why_am_i_seeing": "Dlaczego to widzę?", - "protected_by": "Chronione przez", - "protected_from": "Przed", - "made_with": "Stworzone z ❤️ w 🇨🇦", - "mascot_design": "Projekt maskotki:", - "ai_companies_explanation": "Widzisz to, ponieważ administrator tej strony skonfigurował Anubisa, aby chronić serwer przed masowym skanowaniem treści przez firmy tworzące AI. Powoduje to obciążenie i przestoje, przez co zasoby strony stają się niedostępne dla wszystkich.", - "anubis_compromise": "Anubis jest kompromisem. Używa mechanizmu Proof-of-Work w stylu Hashcash — proponowanego systemu ograniczania spamu e-mail. Pomysł polega na tym, że dla indywidualnych użytkowników dodatkowe obciążenie jest niezauważalne, ale w skali masowego skanowania koszt szybko rośnie.", - "hack_purpose": "Docelowo jest to rozwiązanie tymczasowe, aby zyskać czas na ulepszenie metod identyfikacji przeglądarek bez interfejsu graficznego (np. poprzez analizę renderowania czcionek), by w przyszłości nie musieć wyświetlać strony z zadaniem Proof-of-Work użytkownikom, którzy najprawdopodobniej są prawidłowi.", - "simplified_explanation": "To zabezpieczenie przed botami i złośliwymi żądaniami, podobne do CAPTCHA. Jednak zamiast wykonywać zadanie samodzielnie, przeglądarka otrzymuje obliczenie do wykonania, aby potwierdzić, że jest prawidłowym klientem. Ten mechanizm to Proof of Work. Zadanie trwa kilka sekund i uzyskujesz dostęp do strony. Dziękujemy za cierpliwość.", - "jshelter_note": "Uwaga: Anubis wymaga nowoczesnych funkcji JavaScript, które wtyczki typu JShelter mogą blokować. Wyłącz JShelter lub podobne dodatki dla tej domeny.", - "version_info": "Ta strona działa na Anubis w wersji", - "try_again": "Spróbuj ponownie", - "go_home": "Wróć na stronę główną", - "contact_webmaster": "lub jeśli uważasz, że nie powinieneś być blokowany, skontaktuj się z administratorem pod adresem", - "connection_security": "Poczekaj chwilę, sprawdzamy bezpieczeństwo Twojego połączenia.", - "javascript_required": "Niestety, aby przejść tę próbę, musisz włączyć obsługę JavaScript. Jest to konieczne, ponieważ firmy zajmujące się sztuczną inteligencją zmieniły umowę społeczną dotyczącą funkcjonowania hostingu stron internetowych. Rozwiązanie bez obsługi JavaScript jest w trakcie opracowywania.", - "benchmark_requires_js": "Uruchomienie narzędzia testowego wymaga włączonego JavaScript.", - "difficulty": "Trudność:", - "algorithm": "Algorytm:", - "compare": "Porównaj:", - "time": "Czas", - "iters": "Iteracje", - "time_a": "Czas A", - "iters_a": "Iteracje A", - "time_b": "Czas B", - "iters_b": "Iteracje B", - "static_check_endpoint": "To jedynie punkt kontrolny do użytku przez Twój reverse proxy.", - "authorization_required": "Wymagane uwierzytelnienie", - "cookies_disabled": "Twoja przeglądarka blokuje ciasteczka. Anubis wymaga ich, aby potwierdzić, że jesteś prawidłowym klientem. Włącz ciasteczka dla tej domeny.", - "access_denied": "Brak dostępu: kod błędu", - "dronebl_entry": "DroneBL zgłosił wpis", - "see_dronebl_lookup": "zobacz", - "internal_server_error": "Błąd wewnętrzny serwera: administrator błędnie skonfigurował Anubis. Skontaktuj się z administratorem i poproś o sprawdzenie logów", - "invalid_redirect": "Nieprawidłowe przekierowanie", - "redirect_not_parseable": "Nie można odczytać adresu przekierowania", - "redirect_domain_not_allowed": "Domena przekierowania niedozwolona", - "missing_required_forwarded_headers": "Brak wymaganych nagłówków X-Forwarded-*", - "failed_to_sign_jwt": "Nie udało się podpisać JWT", - "invalid_invocation": "Nieprawidłowe wywołanie MakeChallenge", - "client_error_browser": "Błąd klienta: upewnij się, że Twoja przeglądarka jest aktualna i spróbuj ponownie później.", - "oh_noes": "O nie!", - "benchmarking_anubis": "Testowanie wydajności Anubis!", - "you_are_not_a_bot": "Nie jesteś botem!", - "making_sure_not_bot": "Sprawdzamy, czy nie jesteś botem!", - "celphase": "CELPHASE", - "js_web_crypto_error": "Twoja przeglądarka nie obsługuje web.crypto. Czy korzystasz z bezpiecznego połączenia?", - "js_web_workers_error": "Twoja przeglądarka nie obsługuje web workers (Anubis ich używa, by nie zawieszać przeglądarki). Czy masz zainstalowaną wtyczkę typu JShelter?", - "js_cookies_error": "Twoja przeglądarka nie zapisuje ciasteczek. Anubis używa ich do przechowywania podpisanego tokenu potwierdzającego przejście zabezpieczenia. Włącz zapis ciasteczek dla tej domeny. Nazwy ciasteczek mogą zmieniać się bez zapowiedzi. Nazwy oraz zawartość ciasteczek nie są cześcią publicznego API.", - "js_context_not_secure": "Kontekst nie jest bezpieczny!", - "js_context_not_secure_msg": "Spróbuj połączyć się przez HTTPS lub poinformuj administratora, by skonfigurował HTTPS. Więcej informacji na MDN.", - "js_calculating": "Obliczanie...", - "js_missing_feature": "Brakująca funkcja", - "js_challenge_error": "Błąd wyzwania!", - "js_challenge_error_msg": "Nie udało się ustalić algorytmu sprawdzającego. Możesz spróbować odświeżyć stronę.", - "js_calculating_difficulty": "Obliczanie...
Trudność:", - "js_speed": "Prędkość:", - "js_verification_longer": "Weryfikacja trwa dłużej niż zwykle. Proszę nie odświeżać strony.", - "js_success": "Sukces!", - "js_done_took": "Gotowe! Zajęło to", - "js_iterations": "iteracji", - "js_finished_reading": "Skończyłem czytać, kontynuuj →", - "js_calculation_error": "Błąd obliczeń!", - "js_calculation_error_msg": "Nie udało się obliczyć zadania:" -} \ No newline at end of file + "loading": "Ładowanie...", + "why_am_i_seeing": "Dlaczego to widzę?", + "protected_by": "Chronione przez", + "protected_from": "Przed", + "made_with": "Stworzone z ❤️ w 🇨🇦", + "mascot_design": "Projekt maskotki:", + "ai_companies_explanation": "Widzisz to, ponieważ administrator tej strony skonfigurował Anubisa, aby chronić serwer przed masowym skanowaniem treści przez firmy tworzące AI. Powoduje to obciążenie i przestoje, przez co zasoby strony stają się niedostępne dla wszystkich.", + "anubis_compromise": "Anubis jest kompromisem. Używa mechanizmu Proof-of-Work w stylu Hashcash — proponowanego systemu ograniczania spamu e-mail. Pomysł polega na tym, że dla indywidualnych użytkowników dodatkowe obciążenie jest niezauważalne, ale w skali masowego skanowania koszt szybko rośnie.", + "hack_purpose": "Docelowo jest to rozwiązanie tymczasowe, aby zyskać czas na ulepszenie metod identyfikacji przeglądarek bez interfejsu graficznego (np. poprzez analizę renderowania czcionek), by w przyszłości nie musieć wyświetlać strony z zadaniem Proof-of-Work użytkownikom, którzy najprawdopodobniej są prawidłowi.", + "simplified_explanation": "To zabezpieczenie przed botami i złośliwymi żądaniami, podobne do CAPTCHA. Jednak zamiast wykonywać zadanie samodzielnie, przeglądarka otrzymuje obliczenie do wykonania, aby potwierdzić, że jest prawidłowym klientem. Ten mechanizm to Proof of Work. Zadanie trwa kilka sekund i uzyskujesz dostęp do strony. Dziękujemy za cierpliwość.", + "jshelter_note": "Uwaga: Anubis wymaga nowoczesnych funkcji JavaScript, które wtyczki typu JShelter mogą blokować. Wyłącz JShelter lub podobne dodatki dla tej domeny.", + "version_info": "Ta strona działa na Anubis w wersji", + "try_again": "Spróbuj ponownie", + "go_home": "Wróć na stronę główną", + "contact_webmaster": "lub jeśli uważasz, że nie powinieneś być blokowany, skontaktuj się z administratorem pod adresem", + "connection_security": "Poczekaj chwilę, sprawdzamy bezpieczeństwo Twojego połączenia.", + "javascript_required": "Niestety, aby przejść tę próbę, musisz włączyć obsługę JavaScript. Jest to konieczne, ponieważ firmy zajmujące się sztuczną inteligencją zmieniły umowę społeczną dotyczącą funkcjonowania hostingu stron internetowych. Rozwiązanie bez obsługi JavaScript jest w trakcie opracowywania.", + "benchmark_requires_js": "Uruchomienie narzędzia testowego wymaga włączonego JavaScript.", + "difficulty": "Trudność:", + "algorithm": "Algorytm:", + "compare": "Porównaj:", + "time": "Czas", + "iters": "Iteracje", + "time_a": "Czas A", + "iters_a": "Iteracje A", + "time_b": "Czas B", + "iters_b": "Iteracje B", + "static_check_endpoint": "To jedynie punkt kontrolny do użytku przez Twój reverse proxy.", + "authorization_required": "Wymagane uwierzytelnienie", + "cookies_disabled": "Twoja przeglądarka blokuje ciasteczka. Anubis wymaga ich, aby potwierdzić, że jesteś prawidłowym klientem. Włącz ciasteczka dla tej domeny.", + "access_denied": "Brak dostępu: kod błędu", + "dronebl_entry": "DroneBL zgłosił wpis", + "see_dronebl_lookup": "zobacz", + "internal_server_error": "Błąd wewnętrzny serwera: administrator błędnie skonfigurował Anubis. Skontaktuj się z administratorem i poproś o sprawdzenie logów", + "invalid_redirect": "Nieprawidłowe przekierowanie", + "redirect_not_parseable": "Nie można odczytać adresu przekierowania", + "redirect_domain_not_allowed": "Domena przekierowania niedozwolona", + "missing_required_forwarded_headers": "Brak wymaganych nagłówków X-Forwarded-*", + "failed_to_sign_jwt": "Nie udało się podpisać JWT", + "invalid_invocation": "Nieprawidłowe wywołanie MakeChallenge", + "client_error_browser": "Błąd klienta: upewnij się, że Twoja przeglądarka jest aktualna i spróbuj ponownie później.", + "oh_noes": "O nie!", + "benchmarking_anubis": "Testowanie wydajności Anubis!", + "you_are_not_a_bot": "Nie jesteś botem!", + "making_sure_not_bot": "Sprawdzamy, czy nie jesteś botem!", + "celphase": "CELPHASE", + "js_web_crypto_error": "Twoja przeglądarka nie obsługuje web.crypto. Czy korzystasz z bezpiecznego połączenia?", + "js_web_workers_error": "Twoja przeglądarka nie obsługuje web workers (Anubis ich używa, by nie zawieszać przeglądarki). Czy masz zainstalowaną wtyczkę typu JShelter?", + "js_cookies_error": "Twoja przeglądarka nie zapisuje ciasteczek. Anubis używa ich do przechowywania podpisanego tokenu potwierdzającego przejście zabezpieczenia. Włącz zapis ciasteczek dla tej domeny. Nazwy ciasteczek mogą zmieniać się bez zapowiedzi. Nazwy oraz zawartość ciasteczek nie są cześcią publicznego API.", + "js_context_not_secure": "Kontekst nie jest bezpieczny!", + "js_context_not_secure_msg": "Spróbuj połączyć się przez HTTPS lub poinformuj administratora, by skonfigurował HTTPS. Więcej informacji na MDN.", + "js_calculating": "Obliczanie...", + "js_missing_feature": "Brakująca funkcja", + "js_challenge_error": "Błąd wyzwania!", + "js_challenge_error_msg": "Nie udało się ustalić algorytmu sprawdzającego. Możesz spróbować odświeżyć stronę.", + "js_calculating_difficulty": "Obliczanie...
Trudność:", + "js_speed": "Prędkość:", + "js_verification_longer": "Weryfikacja trwa dłużej niż zwykle. Proszę nie odświeżać strony.", + "js_success": "Sukces!", + "js_done_took": "Gotowe! Zajęło to", + "js_iterations": "iteracji", + "js_finished_reading": "Skończyłem czytać, kontynuuj →", + "js_calculation_error": "Błąd obliczeń!", + "js_calculation_error_msg": "Nie udało się obliczyć zadania:" +} diff --git a/lib/localization/locales/ru.json b/lib/localization/locales/ru.json index 632efd3d..f98e8e14 100644 --- a/lib/localization/locales/ru.json +++ b/lib/localization/locales/ru.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Не удалось рассчитать задачу:", "missing_required_forwarded_headers": "Отсутствуют требуемые заголовки X-Forwarded-*", "simplified_explanation": "Это мера против ботов и вредоносных запросов, аналогичная CAPTCHA. Однако вместо того, чтобы вам приходилось работать самостоятельно, вашему браузеру дается задача вычисления, которую он должен решить, чтобы убедиться, что он является действительным клиентом. Эта концепция называется Доказательство выполнения работы. Задача рассчитывается за несколько секунд, и вам предоставляется доступ к веб-сайту. Спасибо за понимание и терпение." -} \ No newline at end of file +} diff --git a/lib/localization/locales/sv.json b/lib/localization/locales/sv.json index dbb18cca..96bff72a 100644 --- a/lib/localization/locales/sv.json +++ b/lib/localization/locales/sv.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "Misslyckades att kalkylera utmaning:", "missing_required_forwarded_headers": "Saknar nödvändiga X-Forwarded-* headers", "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 Arbetsbevis. 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." -} \ No newline at end of file +} diff --git a/lib/localization/locales/vi.json b/lib/localization/locales/vi.json index edaf20fa..533212b2 100644 --- a/lib/localization/locales/vi.json +++ b/lib/localization/locales/vi.json @@ -63,4 +63,4 @@ "js_finished_reading": "Tôi đã đọc xong, tiếp tục →", "js_calculation_error": "Lỗi tính toán!", "js_calculation_error_msg": "Không thể tính toán thử thách:" -} \ No newline at end of file +} diff --git a/lib/localization/locales/zh-TW.json b/lib/localization/locales/zh-TW.json index 4e7da25c..9f6403b6 100644 --- a/lib/localization/locales/zh-TW.json +++ b/lib/localization/locales/zh-TW.json @@ -63,4 +63,4 @@ "js_calculation_error_msg": "計算挑戰失敗:", "missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 標頭", "simplified_explanation": "這是一種類似於驗證碼的措施,用於防止機器人和惡意請求。但是,您無需自己動手,您的瀏覽器會收到一個計算任務,必須解決該任務以確保它是有效的客戶端。這個概念稱為工作量證明。該任務在幾秒鐘內計算完畢,您將被授予訪問網站的權限。感謝您的理解和耐心。" -} \ No newline at end of file +} diff --git a/lib/policy/celchecker_test.go b/lib/policy/celchecker_test.go new file mode 100644 index 00000000..61c90819 --- /dev/null +++ b/lib/policy/celchecker_test.go @@ -0,0 +1,44 @@ +package policy + +import ( + "net/http" + "testing" + + "github.com/TecharoHQ/anubis/internal/dns" + "github.com/TecharoHQ/anubis/lib/config" + "github.com/TecharoHQ/anubis/lib/store/memory" +) + +func newTestDNS(t *testing.T) *dns.Dns { + t.Helper() + + ctx := t.Context() + memStore := memory.New(ctx) + cache := dns.NewDNSCache(300, 300, memStore) + return dns.New(ctx, cache) +} + +func TestCELChecker_MapIterationWrappers(t *testing.T) { + cfg := &config.ExpressionOrList{ + Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`, + } + + checker, err := NewCELChecker(cfg, newTestDNS(t)) + if err != nil { + t.Fatalf("creating CEL checker failed: %v", err) + } + + req, err := http.NewRequest(http.MethodGet, "https://example.com/?format=json", nil) + if err != nil { + t.Fatalf("making request failed: %v", err) + } + req.Header.Set("Accept", "application/json") + + got, err := checker.Check(req) + if err != nil { + t.Fatalf("checking expression failed: %v", err) + } + if !got { + t.Fatal("expected expression to evaluate true") + } +} diff --git a/lib/policy/expressions/environment_test.go b/lib/policy/expressions/environment_test.go index beb1c2c6..c33fbbcb 100644 --- a/lib/policy/expressions/environment_test.go +++ b/lib/policy/expressions/environment_test.go @@ -103,7 +103,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{ + result, _, err := prog.Eval(map[string]any{ "headers": tt.headers, }) if err != nil { @@ -168,7 +168,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{ + result, _, err := prog.Eval(map[string]any{ "path": tt.path, }) if err != nil { @@ -280,7 +280,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{}) + result, _, err := prog.Eval(map[string]any{}) if err != nil { t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err) } @@ -359,7 +359,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{}) + result, _, err := prog.Eval(map[string]any{}) if err != nil { t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err) } @@ -421,7 +421,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{}) + result, _, err := prog.Eval(map[string]any{}) if err != nil { t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err) } @@ -514,7 +514,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{}) + result, _, err := prog.Eval(map[string]any{}) if err != nil { t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err) } @@ -572,7 +572,7 @@ func TestBotEnvironment(t *testing.T) { t.Fatalf("failed to compile expression %q: %v", tt.expression, err) } - result, _, err := prog.Eval(map[string]interface{}{}) + result, _, err := prog.Eval(map[string]any{}) if tt.evalError { if err == nil { t.Errorf("%s: expected an evaluation error, but got none", tt.description) @@ -598,7 +598,7 @@ func TestThresholdEnvironment(t *testing.T) { } tests := []struct { - variables map[string]interface{} + variables map[string]any name string expression string description string @@ -608,7 +608,7 @@ func TestThresholdEnvironment(t *testing.T) { { name: "weight-variable-available", expression: `weight > 100`, - variables: map[string]interface{}{"weight": 150}, + variables: map[string]any{"weight": 150}, expected: types.Bool(true), description: "should support weight variable in expressions", shouldCompile: true, @@ -616,7 +616,7 @@ func TestThresholdEnvironment(t *testing.T) { { name: "weight-variable-false-case", expression: `weight > 100`, - variables: map[string]interface{}{"weight": 50}, + variables: map[string]any{"weight": 50}, expected: types.Bool(false), description: "should correctly evaluate weight comparisons", shouldCompile: true, @@ -624,7 +624,7 @@ func TestThresholdEnvironment(t *testing.T) { { name: "missingHeader-not-available", expression: `missingHeader(headers, "Test")`, - variables: map[string]interface{}{}, + variables: map[string]any{}, expected: types.Bool(false), // not used description: "should not have missingHeader function available", shouldCompile: false, @@ -667,7 +667,7 @@ func TestNewEnvironment(t *testing.T) { tests := []struct { name string expression string - variables map[string]interface{} + variables map[string]any expectBool *bool // nil if we just want to test compilation or non-bool result description string shouldCompile bool @@ -675,7 +675,7 @@ func TestNewEnvironment(t *testing.T) { { name: "randInt-function-compilation", expression: `randInt(10)`, - variables: map[string]interface{}{}, + variables: map[string]any{}, expectBool: nil, // Don't check result, just compilation description: "should compile randInt function", shouldCompile: true, @@ -683,7 +683,7 @@ func TestNewEnvironment(t *testing.T) { { name: "randInt-range-validation", expression: `randInt(10) >= 0 && randInt(10) < 10`, - variables: map[string]interface{}{}, + variables: map[string]any{}, expectBool: boolPtr(true), description: "should return values in correct range", shouldCompile: true, @@ -691,7 +691,7 @@ func TestNewEnvironment(t *testing.T) { { name: "strings-extension-size", expression: `"hello".size() == 5`, - variables: map[string]interface{}{}, + variables: map[string]any{}, expectBool: boolPtr(true), description: "should support string extension functions", shouldCompile: true, @@ -699,7 +699,7 @@ func TestNewEnvironment(t *testing.T) { { name: "strings-extension-contains", expression: `"hello world".contains("world")`, - variables: map[string]interface{}{}, + variables: map[string]any{}, expectBool: boolPtr(true), description: "should support string contains function", shouldCompile: true, @@ -707,7 +707,7 @@ func TestNewEnvironment(t *testing.T) { { name: "strings-extension-startsWith", expression: `"hello world".startsWith("hello")`, - variables: map[string]interface{}{}, + variables: map[string]any{}, expectBool: boolPtr(true), description: "should support string startsWith function", shouldCompile: true, diff --git a/lib/policy/expressions/http_headers.go b/lib/policy/expressions/http_headers.go index 57fcc841..c4342dc3 100644 --- a/lib/policy/expressions/http_headers.go +++ b/lib/policy/expressions/http_headers.go @@ -66,7 +66,9 @@ func (h HTTPHeaders) Get(key ref.Val) ref.Val { return result } -func (h HTTPHeaders) Iterator() traits.Iterator { panic("TODO(Xe): implement me") } +func (h HTTPHeaders) Iterator() traits.Iterator { + return newMapIterator(h.Header) +} func (h HTTPHeaders) IsZeroValue() bool { return len(h.Header) == 0 diff --git a/lib/policy/expressions/map_iterator.go b/lib/policy/expressions/map_iterator.go new file mode 100644 index 00000000..4a86269f --- /dev/null +++ b/lib/policy/expressions/map_iterator.go @@ -0,0 +1,60 @@ +package expressions + +import ( + "errors" + "maps" + "reflect" + "slices" + + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" +) + +var ErrNotImplemented = errors.New("expressions: not implemented") + +type stringSliceIterator struct { + keys []string + idx int +} + +func (s *stringSliceIterator) Value() any { + return s +} + +func (s *stringSliceIterator) ConvertToNative(typeDesc reflect.Type) (any, error) { + return nil, ErrNotImplemented +} + +func (s *stringSliceIterator) ConvertToType(typeValue ref.Type) ref.Val { + return types.NewErr("can't convert from %q to %q", types.IteratorType, typeValue) +} + +func (s *stringSliceIterator) Equal(other ref.Val) ref.Val { + return types.NewErr("can't compare %q to %q", types.IteratorType, other.Type()) +} + +func (s *stringSliceIterator) Type() ref.Type { + return types.IteratorType +} + +func (s *stringSliceIterator) HasNext() ref.Val { + return types.Bool(s.idx < len(s.keys)) +} + +func (s *stringSliceIterator) Next() ref.Val { + if s.HasNext() != types.True { + return nil + } + + val := s.keys[s.idx] + s.idx++ + return types.String(val) +} + +func newMapIterator(m map[string][]string) traits.Iterator { + return &stringSliceIterator{ + keys: slices.Collect(maps.Keys(m)), + idx: 0, + } +} diff --git a/lib/policy/expressions/url_values.go b/lib/policy/expressions/url_values.go index a4c63519..21d25d5b 100644 --- a/lib/policy/expressions/url_values.go +++ b/lib/policy/expressions/url_values.go @@ -1,7 +1,6 @@ package expressions import ( - "errors" "net/url" "reflect" "strings" @@ -11,8 +10,6 @@ import ( "github.com/google/cel-go/common/types/traits" ) -var ErrNotImplemented = errors.New("expressions: not implemented") - // URLValues is a type wrapper to expose url.Values into CEL programs. type URLValues struct { url.Values @@ -69,7 +66,9 @@ func (u URLValues) Get(key ref.Val) ref.Val { return result } -func (u URLValues) Iterator() traits.Iterator { panic("TODO(Xe): implement me") } +func (u URLValues) Iterator() traits.Iterator { + return newMapIterator(u.Values) +} func (u URLValues) IsZeroValue() bool { return len(u.Values) == 0 diff --git a/lib/policy/policy_test.go b/lib/policy/policy_test.go index 56ce3731..7f14d3f9 100644 --- a/lib/policy/policy_test.go +++ b/lib/policy/policy_test.go @@ -32,7 +32,6 @@ func TestGoodConfigs(t *testing.T) { } for _, st := range finfos { - st := st t.Run(st.Name(), func(t *testing.T) { t.Run("with-thoth", func(t *testing.T) { fin, err := os.Open(filepath.Join("..", "config", "testdata", "good", st.Name())) @@ -71,7 +70,6 @@ func TestBadConfigs(t *testing.T) { } for _, st := range finfos { - st := st t.Run(st.Name(), func(t *testing.T) { fin, err := os.Open(filepath.Join("..", "config", "testdata", "bad", st.Name())) if err != nil { diff --git a/lib/policy/testdata/hack-test.json b/lib/policy/testdata/hack-test.json index 652dcd87..a173bb18 100644 --- a/lib/policy/testdata/hack-test.json +++ b/lib/policy/testdata/hack-test.json @@ -2,8 +2,6 @@ { "name": "ipv6-ula", "action": "ALLOW", - "remote_addresses": [ - "fc00::/7" - ] + "remote_addresses": ["fc00::/7"] } -] \ No newline at end of file +] diff --git a/lib/policy/testdata/hack-test.yaml b/lib/policy/testdata/hack-test.yaml index cd4d7d05..29263e96 100644 --- a/lib/policy/testdata/hack-test.yaml +++ b/lib/policy/testdata/hack-test.yaml @@ -1,3 +1,3 @@ - name: well-known path_regex: ^/.well-known/.*$ - action: ALLOW \ No newline at end of file + action: ALLOW diff --git a/lib/store/s3api/s3api_test.go b/lib/store/s3api/s3api_test.go index 5b7a9c6b..37d58a76 100644 --- a/lib/store/s3api/s3api_test.go +++ b/lib/store/s3api/s3api_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "maps" "sync" "testing" "time" @@ -36,9 +37,7 @@ func (m *mockS3) PutObject(ctx context.Context, in *s3.PutObjectInput, _ ...func m.data[aws.ToString(in.Key)] = bytes.Clone(b) if in.Metadata != nil { m.meta[aws.ToString(in.Key)] = map[string]string{} - for k, v := range in.Metadata { - m.meta[aws.ToString(in.Key)][k] = v - } + maps.Copy(m.meta[aws.ToString(in.Key)], in.Metadata) } m.bucket = aws.ToString(in.Bucket) return &s3.PutObjectOutput{}, nil diff --git a/lib/store/valkey/factory.go b/lib/store/valkey/factory.go index 309bf656..49a63469 100644 --- a/lib/store/valkey/factory.go +++ b/lib/store/valkey/factory.go @@ -103,7 +103,7 @@ func (s Sentinel) Valid() error { // redisClient is satisfied by *valkey.Client and *valkey.ClusterClient. type redisClient interface { Get(ctx context.Context, key string) *valkey.StringCmd - Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *valkey.StatusCmd + Set(ctx context.Context, key string, value any, expiration time.Duration) *valkey.StatusCmd Del(ctx context.Context, keys ...string) *valkey.IntCmd Ping(ctx context.Context) *valkey.StatusCmd } diff --git a/lib/testdata/cloudflare-workers-cel.yaml b/lib/testdata/cloudflare-workers-cel.yaml index b2d6de5a..b165d503 100644 --- a/lib/testdata/cloudflare-workers-cel.yaml +++ b/lib/testdata/cloudflare-workers-cel.yaml @@ -1,8 +1,8 @@ bots: -- name: cloudflare-workers - expression: '"Cf-Worker" in headers' - action: DENY + - name: cloudflare-workers + expression: '"Cf-Worker" in headers' + action: DENY status_codes: CHALLENGE: 401 - DENY: 403 \ No newline at end of file + DENY: 403 diff --git a/lib/testdata/cloudflare-workers-header.yaml b/lib/testdata/cloudflare-workers-header.yaml index 4fa9671b..20306445 100644 --- a/lib/testdata/cloudflare-workers-header.yaml +++ b/lib/testdata/cloudflare-workers-header.yaml @@ -1,9 +1,9 @@ bots: -- name: cloudflare-workers - headers_regex: - CF-Worker: .* - action: DENY + - name: cloudflare-workers + headers_regex: + CF-Worker: .* + action: DENY status_codes: CHALLENGE: 401 - DENY: 403 \ No newline at end of file + DENY: 403 diff --git a/lib/testdata/hack-test.json b/lib/testdata/hack-test.json index 652dcd87..a173bb18 100644 --- a/lib/testdata/hack-test.json +++ b/lib/testdata/hack-test.json @@ -2,8 +2,6 @@ { "name": "ipv6-ula", "action": "ALLOW", - "remote_addresses": [ - "fc00::/7" - ] + "remote_addresses": ["fc00::/7"] } -] \ No newline at end of file +] diff --git a/lib/testdata/hack-test.yaml b/lib/testdata/hack-test.yaml index cd4d7d05..29263e96 100644 --- a/lib/testdata/hack-test.yaml +++ b/lib/testdata/hack-test.yaml @@ -1,3 +1,3 @@ - name: well-known path_regex: ^/.well-known/.*$ - action: ALLOW \ No newline at end of file + action: ALLOW diff --git a/lib/testdata/rule_change.yaml b/lib/testdata/rule_change.yaml index c4fe462d..737500e7 100644 --- a/lib/testdata/rule_change.yaml +++ b/lib/testdata/rule_change.yaml @@ -1,12 +1,12 @@ bots: -- name: old-rule - path_regex: ^/old$ - action: CHALLENGE + - name: old-rule + path_regex: ^/old$ + action: CHALLENGE -- name: new-rule - path_regex: ^/new$ - action: CHALLENGE + - name: new-rule + path_regex: ^/new$ + action: CHALLENGE status_codes: CHALLENGE: 401 - DENY: 403 \ No newline at end of file + DENY: 403 diff --git a/lib/testdata/useragent.yaml b/lib/testdata/useragent.yaml new file mode 100644 index 00000000..85cf73e2 --- /dev/null +++ b/lib/testdata/useragent.yaml @@ -0,0 +1,12 @@ +bots: + - name: deny + user_agent_regex: DENY + action: DENY + + - name: challenge + user_agent_regex: CHALLENGE + action: CHALLENGE + + - name: allow + user_agent_regex: ALLOW + action: ALLOW diff --git a/lib/thoth/auth.go b/lib/thoth/auth.go index f4d52c6d..5f7d0c91 100644 --- a/lib/thoth/auth.go +++ b/lib/thoth/auth.go @@ -11,8 +11,8 @@ func authUnaryClientInterceptor(token string) grpc.UnaryClientInterceptor { return func( ctx context.Context, method string, - req interface{}, - reply interface{}, + req any, + reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, diff --git a/package-lock.json b/package-lock.json index c688b3bb..c548a7d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,31 @@ { "name": "@techaro/anubis", - "version": "1.24.0", + "version": "1.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@techaro/anubis", - "version": "1.24.0", + "version": "1.25.0", "license": "ISC", "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", - "preact": "^10.28.1" + "preact": "^10.28.4" }, "devDependencies": { - "cssnano": "^7.1.2", - "cssnano-preset-advanced": "^7.0.10", - "esbuild": "^0.27.2", + "@commitlint/cli": "^20.4.3", + "@commitlint/config-conventional": "^20.4.3", + "baseline-browser-mapping": "^2.10.0", + "cssnano": "^7.1.3", + "cssnano-preset-advanced": "^7.0.11", + "esbuild": "^0.27.3", + "husky": "^9.1.7", "playwright": "^1.52.0", "postcss-cli": "^11.0.1", "postcss-import": "^16.1.1", "postcss-import-url": "^7.2.0", - "postcss-url": "^10.1.3" + "postcss-url": "^10.1.3", + "prettier": "^3.8.1" } }, "node_modules/@aws-crypto/sha256-js": { @@ -61,10 +66,344 @@ "node": ">=18.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@commitlint/cli": { + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.4.3.tgz", + "integrity": "sha512-Z37EMoDT7+Upg500vlr/vZrgRsb6Xc5JAA3Tv7BYbobnN/ZpqUeZnSLggBg2+1O+NptRDtyujr2DD1CPV2qwhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^20.4.3", + "@commitlint/lint": "^20.4.3", + "@commitlint/load": "^20.4.3", + "@commitlint/read": "^20.4.3", + "@commitlint/types": "^20.4.3", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.4.3.tgz", + "integrity": "sha512-9RtLySbYQAs8yEqWEqhSZo9nYhbm57jx7qHXtgRmv/nmeQIjjMcwf6Dl+y5UZcGWgWx435TAYBURONaJIuCjWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.4.3", + "conventional-changelog-conventionalcommits": "^9.2.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", + "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.0.tgz", + "integrity": "sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", + "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", + "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", + "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@commitlint/lint": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.0.tgz", + "integrity": "sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^20.5.0", + "@commitlint/parse": "^20.5.0", + "@commitlint/rules": "^20.5.0", + "@commitlint/types": "^20.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.0.tgz", + "integrity": "sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^20.5.0", + "@commitlint/execute-rule": "^20.0.0", + "@commitlint/resolve-extends": "^20.5.0", + "@commitlint/types": "^20.5.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "is-plain-obj": "^4.1.0", + "lodash.mergewith": "^4.6.2", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", + "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", + "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "conventional-changelog-angular": "^8.2.0", + "conventional-commits-parser": "^6.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", + "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^20.4.3", + "@commitlint/types": "^20.5.0", + "git-raw-commits": "^5.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.0.tgz", + "integrity": "sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^20.5.0", + "@commitlint/types": "^20.5.0", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.0.tgz", + "integrity": "sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^20.5.0", + "@commitlint/message": "^20.4.3", + "@commitlint/to-lines": "^20.0.0", + "@commitlint/types": "^20.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", + "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", + "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", + "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-parser": "^6.3.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@conventional-changelog/git-client": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.6.0.tgz", + "integrity": "sha512-T+uPDciKf0/ioNNDpMGc8FDsehJClZP0yR3Q5MN6wE/Y/1QZ7F+80OgznnTCOlMEG4AV0LvH2UJi3C/nBnaBUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^1.0.0", + "@simple-libs/stream-utils": "^1.2.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.3.0" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, + "node_modules/@conventional-changelog/git-client/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -79,9 +418,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -96,9 +435,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -113,9 +452,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -130,9 +469,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -147,9 +486,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -164,9 +503,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -181,9 +520,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -198,9 +537,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -215,9 +554,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -232,9 +571,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -249,9 +588,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -266,9 +605,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -283,9 +622,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -300,9 +639,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -317,9 +656,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -334,9 +673,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -351,9 +690,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", "cpu": [ "arm64" ], @@ -368,9 +707,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -385,9 +724,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", "cpu": [ "arm64" ], @@ -402,9 +741,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -419,9 +758,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", "cpu": [ "arm64" ], @@ -436,9 +775,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -453,9 +792,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -470,9 +809,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -487,9 +826,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -503,6 +842,35 @@ "node": ">=18" } }, + "node_modules/@simple-libs/child-process-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", + "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, "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", @@ -553,6 +921,34 @@ "node": ">=14.0.0" } }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -593,10 +989,24 @@ "node": ">= 8" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "dev": true, "funding": [ { @@ -614,10 +1024,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -639,13 +1048,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", - "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/binary-extensions": { @@ -693,9 +1105,9 @@ } }, "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -712,13 +1124,12 @@ } ], "license": "MIT", - "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -727,6 +1138,16 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -741,9 +1162,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", + "version": "1.0.30001779", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", + "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==", "dev": true, "funding": [ { @@ -838,6 +1259,17 @@ "node": ">=16" } }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -845,6 +1277,94 @@ "dev": true, "license": "MIT" }, + "node_modules/conventional-changelog-angular": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz", + "integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.0.tgz", + "integrity": "sha512-kYFx6gAyjSIMwNtASkI3ZE99U1fuVDJr0yTYgVy+I2QG46zNZfl2her+0+eoviG82c5WQvW1jMt1eOQTeJLodA==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-parser": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", + "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "^2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", @@ -876,14 +1396,14 @@ } }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -916,13 +1436,13 @@ } }, "node_modules/cssnano": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.2.tgz", - "integrity": "sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.3.tgz", + "integrity": "sha512-mLFHQAzyapMVFLiJIn7Ef4C2UCEvtlTlbyILR6B5ZsUAV3D/Pa761R5uC1YPhyBkRd3eqaDm2ncaNrD7R4mTRg==", "dev": true, "license": "MIT", "dependencies": { - "cssnano-preset-default": "^7.0.10", + "cssnano-preset-default": "^7.0.11", "lilconfig": "^3.1.3" }, "engines": { @@ -937,16 +1457,16 @@ } }, "node_modules/cssnano-preset-advanced": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-7.0.10.tgz", - "integrity": "sha512-lfsKxX4H6WS7BbNyDxkGOu2VgN4bbHQpY8llA3i3SJ9ozAPJ1MHq265Aw0aslM161qiS0zhCHaC6zRcEbNAgUA==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-7.0.11.tgz", + "integrity": "sha512-k9Dz/8TBOmLojoMZ+2xwrGjscU1XwYVDVB9T1AmvZcwiWz4ibWm8/y+/SN5jhYcpMPTeDe7FhI2uEYlNq1iDGg==", "dev": true, "license": "MIT", "dependencies": { - "autoprefixer": "^10.4.21", - "browserslist": "^4.27.0", - "cssnano-preset-default": "^7.0.10", - "postcss-discard-unused": "^7.0.4", + "autoprefixer": "^10.4.27", + "browserslist": "^4.28.1", + "cssnano-preset-default": "^7.0.11", + "postcss-discard-unused": "^7.0.5", "postcss-merge-idents": "^7.0.1", "postcss-reduce-idents": "^7.0.1", "postcss-zindex": "^7.0.1" @@ -959,42 +1479,42 @@ } }, "node_modules/cssnano-preset-default": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.10.tgz", - "integrity": "sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.11.tgz", + "integrity": "sha512-waWlAMuCakP7//UCY+JPrQS1z0OSLeOXk2sKWJximKWGupVxre50bzPlvpbUwZIDylhf/ptf0Pk+Yf7C+hoa3g==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.1", "postcss-calc": "^10.1.1", - "postcss-colormin": "^7.0.5", - "postcss-convert-values": "^7.0.8", - "postcss-discard-comments": "^7.0.5", + "postcss-colormin": "^7.0.6", + "postcss-convert-values": "^7.0.9", + "postcss-discard-comments": "^7.0.6", "postcss-discard-duplicates": "^7.0.2", "postcss-discard-empty": "^7.0.1", "postcss-discard-overridden": "^7.0.1", "postcss-merge-longhand": "^7.0.5", - "postcss-merge-rules": "^7.0.7", + "postcss-merge-rules": "^7.0.8", "postcss-minify-font-values": "^7.0.1", "postcss-minify-gradients": "^7.0.1", - "postcss-minify-params": "^7.0.5", - "postcss-minify-selectors": "^7.0.5", + "postcss-minify-params": "^7.0.6", + "postcss-minify-selectors": "^7.0.6", "postcss-normalize-charset": "^7.0.1", "postcss-normalize-display-values": "^7.0.1", "postcss-normalize-positions": "^7.0.1", "postcss-normalize-repeat-style": "^7.0.1", "postcss-normalize-string": "^7.0.1", "postcss-normalize-timing-functions": "^7.0.1", - "postcss-normalize-unicode": "^7.0.5", + "postcss-normalize-unicode": "^7.0.6", "postcss-normalize-url": "^7.0.1", "postcss-normalize-whitespace": "^7.0.1", "postcss-ordered-values": "^7.0.2", - "postcss-reduce-initial": "^7.0.5", + "postcss-reduce-initial": "^7.0.6", "postcss-reduce-transforms": "^7.0.1", - "postcss-svgo": "^7.1.0", - "postcss-unique-selectors": "^7.0.4" + "postcss-svgo": "^7.1.1", + "postcss-unique-selectors": "^7.0.5" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -1128,10 +1648,23 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.244", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", - "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", "dev": true, "license": "ISC" }, @@ -1155,10 +1688,30 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1169,32 +1722,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/escalade": { @@ -1207,6 +1760,30 @@ "node": ">=6" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1221,16 +1798,16 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -1284,6 +1861,23 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/git-raw-commits": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", + "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@conventional-changelog/git-client": "^2.6.0", + "meow": "^13.0.0" + }, + "bin": { + "git-raw-commits": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1297,6 +1891,22 @@ "node": ">= 6" } }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1324,6 +1934,77 @@ "dev": true, "license": "ISC" }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1396,6 +2077,29 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", @@ -1403,6 +2107,50 @@ "dev": true, "license": "MIT" }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", @@ -1429,6 +2177,13 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", @@ -1436,6 +2191,20 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -1443,6 +2212,27 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.trim": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.trim/-/lodash.trim-4.5.1.tgz", @@ -1457,6 +2247,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -1474,12 +2271,25 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", @@ -1506,6 +2316,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1518,6 +2338,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1542,16 +2363,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -1565,6 +2376,38 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -1711,13 +2554,13 @@ } }, "node_modules/postcss-colormin": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.5.tgz", - "integrity": "sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.6.tgz", + "integrity": "sha512-oXM2mdx6IBTRm39797QguYzVEWzbdlFiMNfq88fCCN1Wepw3CYmJ/1/Ifa/KjWo+j5ZURDl2NTldLJIw51IeNQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" @@ -1730,13 +2573,13 @@ } }, "node_modules/postcss-convert-values": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.8.tgz", - "integrity": "sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.9.tgz", + "integrity": "sha512-l6uATQATZaCa0bckHV+r6dLXfWtUBKXxO3jK+AtxxJJtgMPD+VhhPCCx51I4/5w8U5uHV67g3w7PXj+V3wlMlg==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -1747,13 +2590,13 @@ } }, "node_modules/postcss-discard-comments": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.5.tgz", - "integrity": "sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.6.tgz", + "integrity": "sha512-Sq+Fzj1Eg5/CPf1ERb0wS1Im5cvE2gDXCE+si4HCn1sf+jpQZxDI4DXEp8t77B/ImzDceWE2ebJQFXdqZ6GRJw==", "dev": true, "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.1.0" + "postcss-selector-parser": "^7.1.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -1802,13 +2645,13 @@ } }, "node_modules/postcss-discard-unused": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-7.0.4.tgz", - "integrity": "sha512-/d6sIm8SSJbDDzdHyt/BWZ5upC6Dtn6JIL0uQts+AuvA5ddVmkw/3H4NtDv7DybGzCA1o3Q9R6kt4qsnS2mCSQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-7.0.5.tgz", + "integrity": "sha512-CdTIk/qGQHCY/v+FCTTUHVEIfNQv3xSDF+TudDC4l7JS1XaKk0gv0GM6heLF5pbt57l8/YQ4GsFwl2hYz2L9gA==", "dev": true, "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.1.0" + "postcss-selector-parser": "^7.1.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -1930,16 +2773,16 @@ } }, "node_modules/postcss-merge-rules": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.7.tgz", - "integrity": "sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.8.tgz", + "integrity": "sha512-BOR1iAM8jnr7zoQSlpeBmCsWV5Uudi/+5j7k05D0O/WP3+OFMPD86c1j/20xiuRtyt45bhxw/7hnhZNhW2mNFA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.1", - "postcss-selector-parser": "^7.1.0" + "postcss-selector-parser": "^7.1.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -1983,13 +2826,13 @@ } }, "node_modules/postcss-minify-params": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.5.tgz", - "integrity": "sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.6.tgz", + "integrity": "sha512-YOn02gC68JijlaXVuKvFSCvQOhTpblkcfDre2hb/Aaa58r2BIaK4AtE/cyZf2wV7YKAG+UlP9DT+By0ry1E4VQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, @@ -2001,14 +2844,14 @@ } }, "node_modules/postcss-minify-selectors": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", - "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.6.tgz", + "integrity": "sha512-lIbC0jy3AAwDxEgciZlBullDiMBeBCT+fz5G8RcA9MWqh/hfUkpOI3vNDUNEZHgokaoiv0juB9Y8fGcON7rU/A==", "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", - "postcss-selector-parser": "^7.1.0" + "postcss-selector-parser": "^7.1.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -2111,13 +2954,13 @@ } }, "node_modules/postcss-normalize-unicode": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.5.tgz", - "integrity": "sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.6.tgz", + "integrity": "sha512-z6bwTV84YW6ZvvNoaNLuzRW4/uWxDKYI1iIDrzk6D2YTL7hICApy+Q1LP6vBEsljX8FM7YSuV9qI79XESd4ddQ==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2193,13 +3036,13 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.5.tgz", - "integrity": "sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.6.tgz", + "integrity": "sha512-G6ZyK68AmrPdMB6wyeA37ejnnRG2S8xinJrZJnOv+IaRKf6koPAVbQsiC7MfkmXaGmF1UO+QCijb27wfpxuRNg==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", + "browserslist": "^4.28.1", "caniuse-api": "^3.0.0" }, "engines": { @@ -2253,9 +3096,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", "dependencies": { @@ -2267,14 +3110,14 @@ } }, "node_modules/postcss-svgo": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", - "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.1.tgz", + "integrity": "sha512-zU9H9oEDrUFKa0JB7w+IYL7Qs9ey1mZyjhbf0KLxwJDdDRtoPvCmaEfknzqfHj44QS9VD6c5sJnBAVYTLRg/Sg==", "dev": true, "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^4.0.0" + "svgo": "^4.0.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >= 18" @@ -2284,13 +3127,13 @@ } }, "node_modules/postcss-unique-selectors": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", - "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.5.tgz", + "integrity": "sha512-3QoYmEt4qg/rUWDn6Tc8+ZVPmbp4G1hXDtCNWDx0st8SjtCbRcxRXDDM1QrEiXGG3A45zscSJFb4QH90LViyxg==", "dev": true, "license": "MIT", "dependencies": { - "postcss-selector-parser": "^7.1.0" + "postcss-selector-parser": "^7.1.1" }, "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -2339,15 +3182,31 @@ } }, "node_modules/preact": { - "version": "10.28.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.1.tgz", - "integrity": "sha512-u1/ixq/lVQI0CakKNvLDEcW5zfCjUQfZdK9qqWuIJtsezuyG6pk9TWj75GMuI/EzRSZB/VAE43sNWWZfiy8psw==", + "version": "10.28.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -2408,6 +3267,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -2429,6 +3298,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-relative-url": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-relative-url/-/resolve-relative-url-1.0.0.tgz", @@ -2440,11 +3319,14 @@ } }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/semver": { "version": "6.3.1", @@ -2538,9 +3420,9 @@ } }, "node_modules/svgo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", - "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, "license": "MIT", "dependencies": { @@ -2550,7 +3432,7 @@ "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", - "sax": "^1.4.1" + "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo.js" @@ -2570,6 +3452,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -2611,7 +3503,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2638,6 +3529,29 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -2649,9 +3563,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 89f86c6b..cdd08d27 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@techaro/anubis", - "version": "1.24.0", + "version": "1.25.0", "description": "", "main": "index.js", "scripts": { - "test": "npm run assets && go test ./...", + "test": "npm run assets && SKIP_INTEGRATION=1 go test ./...", "test:integration": "npm run assets && go test -v ./internal/test", "test:integration:podman": "npm run assets && go test -v ./internal/test --playwright-runner=podman", "test:integration:docker": "npm run assets && go test -v ./internal/test --playwright-runner=docker", @@ -12,23 +12,58 @@ "build": "npm run assets && go build -o ./var/anubis ./cmd/anubis", "dev": "npm run assets && go run ./cmd/anubis --use-remote-address --target http://localhost:3000", "container": "npm run assets && go run ./cmd/containerbuild", - "package": "yeet", - "lint": "make lint" + "package": "go tool yeet", + "lint": "make lint", + "prepare": "husky && go mod download", + "format": "prettier -w . 2>&1 >/dev/null && go run goimports -w ." }, "author": "", "license": "ISC", "devDependencies": { - "cssnano": "^7.1.2", - "cssnano-preset-advanced": "^7.0.10", - "esbuild": "^0.27.2", + "@commitlint/cli": "^20.4.3", + "@commitlint/config-conventional": "^20.4.3", + "baseline-browser-mapping": "^2.10.0", + "cssnano": "^7.1.3", + "cssnano-preset-advanced": "^7.0.11", + "esbuild": "^0.27.3", + "husky": "^9.1.7", "playwright": "^1.52.0", "postcss-cli": "^11.0.1", "postcss-import": "^16.1.1", "postcss-import-url": "^7.2.0", - "postcss-url": "^10.1.3" + "postcss-url": "^10.1.3", + "prettier": "^3.8.1" }, "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", - "preact": "^10.28.1" + "preact": "^10.28.4" + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ], + "rules": { + "body-max-line-length": [ + 2, + "always", + 99999 + ], + "footer-max-line-length": [ + 2, + "always", + 99999 + ], + "signed-off-by": [ + 2, + "always" + ] + } + }, + "prettier": { + "singleQuote": false, + "tabWidth": 2, + "semi": true, + "trailingComma": "all", + "printWidth": 80 } } \ No newline at end of file diff --git a/test/anubis_configs/aggressive_403.yaml b/test/anubis_configs/aggressive_403.yaml index facafd6f..ff966517 100644 --- a/test/anubis_configs/aggressive_403.yaml +++ b/test/anubis_configs/aggressive_403.yaml @@ -1,12 +1,12 @@ bots: -- name: deny - user_agent_regex: DENY - action: DENY + - name: deny + user_agent_regex: DENY + action: DENY -- name: challenge - user_agent_regex: CHALLENGE - action: CHALLENGE + - name: challenge + user_agent_regex: CHALLENGE + action: CHALLENGE status_codes: CHALLENGE: 401 - DENY: 403 \ No newline at end of file + DENY: 403 diff --git a/test/double_slash/test.mjs b/test/double_slash/test.mjs index 7ae9c5a8..15a54eb9 100644 --- a/test/double_slash/test.mjs +++ b/test/double_slash/test.mjs @@ -3,13 +3,13 @@ import { createInterface } from "readline"; async function getPage(path) { return fetch(`http://localhost:8923${path}`) - .then(resp => { + .then((resp) => { if (resp.status !== 200) { throw new Error(`wanted status 200, got status: ${resp.status}`); } return resp; }) - .then(resp => resp.text()); + .then((resp) => resp.text()); } (async () => { @@ -42,4 +42,4 @@ async function getPage(path) { } process.exit(failed ? 1 : 0); -})(); \ No newline at end of file +})(); diff --git a/test/forced-language/test.mjs b/test/forced-language/test.mjs index e64237ef..7fab657e 100644 --- a/test/forced-language/test.mjs +++ b/test/forced-language/test.mjs @@ -3,25 +3,25 @@ async function getChallengePage() { headers: { "Accept-Language": "en", "User-Agent": "CHALLENGE", - } + }, }) - .then(resp => { + .then((resp) => { if (resp.status !== 200) { throw new Error(`wanted status 200, got status: ${resp.status}`); } return resp; }) - .then(resp => resp.text()); + .then((resp) => resp.text()); } (async () => { const page = await getChallengePage(); if (!page.includes(``)) { - console.log(page) + console.log(page); throw new Error("force language smoke test failed"); } console.log("FORCED_LANGUAGE=de caused a page to be rendered in german"); process.exit(0); -})(); \ No newline at end of file +})(); diff --git a/test/i18n/test.mjs b/test/i18n/test.mjs index 64b32703..ba48ea28 100644 --- a/test/i18n/test.mjs +++ b/test/i18n/test.mjs @@ -1,12 +1,14 @@ async function fetchLanguages() { - return fetch("http://localhost:8923/.within.website/x/cmd/anubis/static/locales/manifest.json") - .then(resp => { + return fetch( + "http://localhost:8923/.within.website/x/cmd/anubis/static/locales/manifest.json", + ) + .then((resp) => { if (resp.status !== 200) { throw new Error(`wanted status 200, got status: ${resp.status}`); } return resp; }) - .then(resp => resp.json()); + .then((resp) => resp.json()); } async function getChallengePage(lang) { @@ -14,15 +16,15 @@ async function getChallengePage(lang) { headers: { "Accept-Language": lang, "User-Agent": "CHALLENGE", - } + }, }) - .then(resp => { + .then((resp) => { if (resp.status !== 200) { throw new Error(`wanted status 200, got status: ${resp.status}`); } return resp; }) - .then(resp => resp.text()); + .then((resp) => resp.text()); } (async () => { @@ -42,7 +44,7 @@ async function getChallengePage(lang) { console.log(`getting for ${lang}`); const page = await getChallengePage(lang); - resultSheet[lang] = page.includes(``) + resultSheet[lang] = page.includes(``); } for (const [lang, result] of Object.entries(resultSheet)) { @@ -59,4 +61,4 @@ async function getChallengePage(lang) { } process.exit(0); -})(); \ No newline at end of file +})(); diff --git a/test/k8s/cert-manager/selfsigned-issuer.yaml b/test/k8s/cert-manager/selfsigned-issuer.yaml index 07d2b7bc..8120e82a 100644 --- a/test/k8s/cert-manager/selfsigned-issuer.yaml +++ b/test/k8s/cert-manager/selfsigned-issuer.yaml @@ -3,4 +3,4 @@ kind: ClusterIssuer metadata: name: selfsigned spec: - selfSigned: {} \ No newline at end of file + selfSigned: {} diff --git a/test/k8s/deps/cert-manager.yaml b/test/k8s/deps/cert-manager.yaml index f3e17fae..1d3fda13 100644 --- a/test/k8s/deps/cert-manager.yaml +++ b/test/k8s/deps/cert-manager.yaml @@ -10,4 +10,4 @@ spec: createNamespace: true set: installCRDs: "true" - "prometheus.enabled": "false" \ No newline at end of file + "prometheus.enabled": "false" diff --git a/test/log-file/test.mjs b/test/log-file/test.mjs index 8b036cd3..f9db6454 100644 --- a/test/log-file/test.mjs +++ b/test/log-file/test.mjs @@ -3,16 +3,16 @@ import { statSync } from "fs"; async function getPage(path) { return fetch(`http://localhost:8923${path}`, { headers: { - 'User-Agent': 'CHALLENGE' - } + "User-Agent": "CHALLENGE", + }, }) - .then(resp => { + .then((resp) => { if (resp.status !== 200) { throw new Error(`wanted status 200, got status: ${resp.status}`); } return resp; }) - .then(resp => resp.text()); + .then((resp) => resp.text()); } async function getFileSize(filePath) { @@ -63,7 +63,9 @@ async function getFileSize(filePath) { // Verify that log file size increased if (finalSize <= initialSize) { - console.error("ERROR: Log file size did not increase after making requests!"); + console.error( + "ERROR: Log file size did not increase after making requests!", + ); failed = true; } @@ -79,10 +81,14 @@ async function getFileSize(filePath) { console.log(`Successful requests: ${successCount}/${requests.length}`); if (failed) { - console.error("Test failed: Some requests failed or log file size did not increase"); + console.error( + "Test failed: Some requests failed or log file size did not increase", + ); process.exit(1); } else { - console.log("Test passed: All requests succeeded and log file size increased"); + console.log( + "Test passed: All requests succeeded and log file size increased", + ); process.exit(0); } -})(); \ No newline at end of file +})(); diff --git a/test/nginx/conf/nginx/conf-anubis.inc b/test/nginx/conf/nginx/conf-anubis.inc index 6e5083ae..1535ec91 100644 --- a/test/nginx/conf/nginx/conf-anubis.inc +++ b/test/nginx/conf/nginx/conf-anubis.inc @@ -1,8 +1,7 @@ # /etc/nginx/conf-anubis.inc - # Forward to anubis location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://anubis; -} \ No newline at end of file +} diff --git a/test/robots_txt/test.mjs b/test/robots_txt/test.mjs index ce62b9f7..faa6d89d 100644 --- a/test/robots_txt/test.mjs +++ b/test/robots_txt/test.mjs @@ -3,25 +3,25 @@ async function getRobotsTxt() { headers: { "Accept-Language": "en", "User-Agent": "Mozilla/5.0", - } + }, }) - .then(resp => { + .then((resp) => { if (resp.status !== 200) { throw new Error(`wanted status 200, got status: ${resp.status}`); } return resp; }) - .then(resp => resp.text()); + .then((resp) => resp.text()); } (async () => { const page = await getRobotsTxt(); if (page.includes(``)) { - console.log(page) + console.log(page); throw new Error("serve robots.txt smoke test failed"); } console.log("serve-robots-txt serves robots.txt"); process.exit(0); -})(); \ No newline at end of file +})(); diff --git a/test/shared/www/index.html b/test/shared/www/index.html index 8c55c8cb..f259424f 100644 --- a/test/shared/www/index.html +++ b/test/shared/www/index.html @@ -1,9 +1,9 @@ - + Anubis works! - - + +
@@ -11,7 +11,10 @@

If you see this, everything has gone according to keikaku.

- +
- \ No newline at end of file + diff --git a/test/unix-socket-xff/test.mjs b/test/unix-socket-xff/test.mjs index 8b8479c0..5c85dba4 100644 --- a/test/unix-socket-xff/test.mjs +++ b/test/unix-socket-xff/test.mjs @@ -1,19 +1,20 @@ async function testWithUserAgent(userAgent) { - const statusCode = - await fetch("https://relayd.local.cetacean.club:3004/reqmeta", { + const statusCode = await fetch( + "https://relayd.local.cetacean.club:3004/reqmeta", + { headers: { "User-Agent": userAgent, - } - }) - .then(resp => resp.status); + }, + }, + ).then((resp) => resp.status); return statusCode; } const codes = { allow: await testWithUserAgent("ALLOW"), challenge: await testWithUserAgent("CHALLENGE"), - deny: await testWithUserAgent("DENY") -} + deny: await testWithUserAgent("DENY"), +}; const expected = { allow: 200, @@ -26,5 +27,7 @@ console.log("CHALLENGE:", codes.challenge); console.log("DENY: ", codes.deny); if (JSON.stringify(codes) !== JSON.stringify(expected)) { - throw new Error(`wanted ${JSON.stringify(expected)}, got: ${JSON.stringify(codes)}`); -} \ No newline at end of file + throw new Error( + `wanted ${JSON.stringify(expected)}, got: ${JSON.stringify(codes)}`, + ); +} diff --git a/web/index.templ b/web/index.templ index 9ad02b3f..330b6fa8 100644 --- a/web/index.templ +++ b/web/index.templ @@ -40,16 +40,16 @@ templ base(title string, body templ.Component, impressum *config.Impressum, chal } #progress { - display: none; - width: 90%; - width: min(20rem, 90%); - height: 2rem; - border-radius: 1rem; - overflow: hidden; - margin: 1rem 0 2rem; - outline-offset: 2px; - outline: #b16286 solid 4px; - } + display: none; + width: 90%; + width: min(20rem, 90%); + height: 2rem; + border-radius: 1rem; + overflow: hidden; + margin: 1rem 0 2rem; + outline-offset: 2px; + outline: #b16286 solid 4px; + } .bar-inner { background-color: #b16286; diff --git a/web/index_templ.go b/web/index_templ.go index de0af4fc..2099c7b0 100644 --- a/web/index_templ.go +++ b/web/index_templ.go @@ -113,7 +113,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/js/algorithms/fast.ts b/web/js/algorithms/fast.ts index 6330da56..82a9a778 100644 --- a/web/js/algorithms/fast.ts +++ b/web/js/algorithms/fast.ts @@ -6,7 +6,9 @@ interface ProcessOptions { } const getHardwareConcurrency = () => - navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1; + navigator.hardwareConcurrency !== undefined + ? navigator.hardwareConcurrency + : 1; export default function process( options: ProcessOptions, @@ -25,7 +27,10 @@ export default function process( workerMethod = "webcrypto"; } - if (navigator.userAgent.includes("Firefox") || navigator.userAgent.includes("Goanna")) { + if ( + navigator.userAgent.includes("Firefox") || + navigator.userAgent.includes("Goanna") + ) { console.log("Firefox detected, using pure-JS fallback"); workerMethod = "purejs"; } diff --git a/web/js/algorithms/index.ts b/web/js/algorithms/index.ts index 5b571837..2781aef0 100644 --- a/web/js/algorithms/index.ts +++ b/web/js/algorithms/index.ts @@ -3,4 +3,4 @@ import fast from "./fast"; export default { fast: fast, slow: fast, // XXX(Xe): slow is deprecated, but keep this around in case anything goes bad -} \ No newline at end of file +}; diff --git a/web/js/bench.ts b/web/js/bench.ts index 719b1675..1dd7805c 100644 --- a/web/js/bench.ts +++ b/web/js/bench.ts @@ -2,13 +2,27 @@ import algorithms from "./algorithms"; const defaultDifficulty = 4; -const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement; -const difficultyInput: HTMLInputElement = document.getElementById("difficulty-input") as HTMLInputElement; -const algorithmSelect: HTMLSelectElement = document.getElementById("algorithm-select") as HTMLSelectElement; -const compareSelect: HTMLSelectElement = document.getElementById("compare-select") as HTMLSelectElement; -const header: HTMLTableRowElement = document.getElementById("table-header") as HTMLTableRowElement; -const headerCompare: HTMLTableSectionElement = document.getElementById("table-header-compare") as HTMLTableSectionElement; -const results: HTMLTableRowElement = document.getElementById("results") as HTMLTableRowElement; +const status: HTMLParagraphElement = document.getElementById( + "status", +) as HTMLParagraphElement; +const difficultyInput: HTMLInputElement = document.getElementById( + "difficulty-input", +) as HTMLInputElement; +const algorithmSelect: HTMLSelectElement = document.getElementById( + "algorithm-select", +) as HTMLSelectElement; +const compareSelect: HTMLSelectElement = document.getElementById( + "compare-select", +) as HTMLSelectElement; +const header: HTMLTableRowElement = document.getElementById( + "table-header", +) as HTMLTableRowElement; +const headerCompare: HTMLTableSectionElement = document.getElementById( + "table-header-compare", +) as HTMLTableSectionElement; +const results: HTMLTableRowElement = document.getElementById( + "results", +) as HTMLTableRowElement; const setupControls = () => { if (defaultDifficulty == null) { @@ -41,7 +55,12 @@ const benchmarkTrial = async (stats, difficulty, algorithm, signal) => { .join(""); const t0 = performance.now(); - const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal); + const { hash, nonce } = await process( + { basePrefix: "/", version: "devel" }, + challenge, + Number(difficulty), + signal, + ); const t1 = performance.now(); console.log({ hash, nonce }); diff --git a/web/js/main.ts b/web/js/main.ts index fbedb3a6..b07d52d9 100644 --- a/web/js/main.ts +++ b/web/js/main.ts @@ -29,22 +29,25 @@ const getAvailableLanguages = async () => { } try { - const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`); + const response = await fetch( + `${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`, + ); if (response.ok) { const manifest = await response.json(); - return manifest.supportedLanguages || ['en']; + return manifest.supportedLanguages || ["en"]; } } catch (error) { - console.warn('Failed to load language manifest, falling back to default languages'); + console.warn( + "Failed to load language manifest, falling back to default languages", + ); } // Fallback to default languages if manifest loading fails - return ['en']; + return ["en"]; }; // Use the browser language from the HTML lang attribute which is set by the server settings or request headers -const getBrowserLanguage = async () => - document.documentElement.lang; +const getBrowserLanguage = async () => document.documentElement.lang; // Load translations from JSON files const loadTranslations = async (lang) => { @@ -54,12 +57,16 @@ const loadTranslations = async (lang) => { } try { - const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`); + const response = await fetch( + `${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`, + ); return await response.json(); } catch (error) { - console.warn(`Failed to load translations for ${lang}, falling back to English`); - if (lang !== 'en') { - return await loadTranslations('en'); + console.warn( + `Failed to load translations for ${lang}, falling back to English`, + ); + if (lang !== "en") { + return await loadTranslations("en"); } throw error; } @@ -72,10 +79,10 @@ const getRedirectUrl = () => { } if (publicUrl && window.location.href.startsWith(publicUrl)) { const urlParams = new URLSearchParams(window.location.search); - return urlParams.get('redir'); + return urlParams.get("redir"); } return window.location.href; -} +}; let translations = {}; let currentLang; @@ -95,20 +102,28 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; const dependencies = [ { name: "Web Workers", - msg: t('web_workers_error'), + msg: t("web_workers_error"), value: window.Worker, }, { name: "Cookies", - msg: t('cookies_error'), + msg: t("cookies_error"), value: navigator.cookieEnabled, }, ]; - const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement; - const image: HTMLImageElement = document.getElementById("image") as HTMLImageElement; - const title: HTMLHeadingElement = document.getElementById("title") as HTMLHeadingElement; - const progress: HTMLDivElement = document.getElementById("progress") as HTMLDivElement; + const status: HTMLParagraphElement = document.getElementById( + "status", + ) as HTMLParagraphElement; + const image: HTMLImageElement = document.getElementById( + "image", + ) as HTMLImageElement; + const title: HTMLHeadingElement = document.getElementById( + "title", + ) as HTMLHeadingElement; + const progress: HTMLDivElement = document.getElementById( + "progress", + ) as HTMLDivElement; const anubisVersion = j("anubis_version"); const basePrefix = j("anubis_base_prefix"); @@ -130,12 +145,12 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; progress.style.display = "none"; }; - status.innerHTML = t('calculating'); + status.innerHTML = t("calculating"); for (const { value, name, msg } of dependencies) { if (!value) { ohNoes({ - titleMsg: `${t('missing_feature')} ${name}`, + titleMsg: `${t("missing_feature")} ${name}`, statusMsg: msg, imageSrc: imageURL("reject", anubisVersion, basePrefix), }); @@ -148,20 +163,20 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; const process = algorithms[rules.algorithm]; if (!process) { ohNoes({ - titleMsg: t('challenge_error'), - statusMsg: t('challenge_error_msg'), + titleMsg: t("challenge_error"), + statusMsg: t("challenge_error_msg"), imageSrc: imageURL("reject", anubisVersion, basePrefix), }); return; } - status.innerHTML = `${t('calculating_difficulty')} ${rules.difficulty}, `; + status.innerHTML = `${t("calculating_difficulty")} ${rules.difficulty}, `; progress.style.display = "inline-block"; // the whole text, including "Speed:", as a single node, because some browsers // (Firefox mobile) present screen readers with each node as a separate piece // of text. - const rateText = document.createTextNode(`${t('speed')} 0kH/s`); + const rateText = document.createTextNode(`${t("speed")} 0kH/s`); status.appendChild(rateText); let lastSpeedUpdate = 0; @@ -180,7 +195,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; // only update the speed every second so it's less visually distracting if (delta - lastSpeedUpdate > 1000) { lastSpeedUpdate = delta; - rateText.data = `${t('speed')} ${(iters / delta).toFixed(3)}kH/s`; + rateText.data = `${t("speed")} ${(iters / delta).toFixed(3)}kH/s`; } // the probability of still being on the page is (1 - likelihood) ^ iters. // by definition, half of the time the progress bar only gets to half, so @@ -192,13 +207,14 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; const distance = (1 - Math.pow(probability, 2)) * 100; progress["aria-valuenow"] = distance; if (progress.firstElementChild !== null) { - (progress.firstElementChild as HTMLElement).style.width = `${distance}%`; + (progress.firstElementChild as HTMLElement).style.width = + `${distance}%`; } if (probability < 0.1 && !showingApology) { status.append( document.createElement("br"), - document.createTextNode(t('verification_longer')), + document.createTextNode(t("verification_longer")), ); showingApology = true; } @@ -208,7 +224,9 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; console.log({ hash, nonce }); if (userReadDetails) { - const container: HTMLDivElement = document.getElementById("progress") as HTMLDivElement; + const container: HTMLDivElement = document.getElementById( + "progress", + ) as HTMLDivElement; // Style progress bar as a continue button container.style.display = "flex"; @@ -224,7 +242,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; container.style.outlineOffset = "2px"; container.style.width = "min(20rem, 90%)"; container.style.margin = "1rem auto 2rem"; - container.innerHTML = t('finished_reading'); + container.innerHTML = t("finished_reading"); function onDetailsExpand() { const redir = getRedirectUrl(); @@ -255,8 +273,8 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key; } } catch (err) { ohNoes({ - titleMsg: t('calculation_error'), - statusMsg: `${t('calculation_error_msg')} ${err.message}`, + titleMsg: t("calculation_error"), + statusMsg: `${t("calculation_error_msg")} ${err.message}`, imageSrc: imageURL("reject", anubisVersion, basePrefix), }); } diff --git a/web/js/worker/sha256-purejs.ts b/web/js/worker/sha256-purejs.ts index 69060615..52ee1cda 100644 --- a/web/js/worker/sha256-purejs.ts +++ b/web/js/worker/sha256-purejs.ts @@ -1,4 +1,4 @@ -import { Sha256 } from '@aws-crypto/sha256-js'; +import { Sha256 } from "@aws-crypto/sha256-js"; const calculateSHA256 = (text) => { const hash = new Sha256(); @@ -12,7 +12,7 @@ function toHexString(arr: Uint8Array): string { .join(""); } -addEventListener('message', async ({ data: eventData }) => { +addEventListener("message", async ({ data: eventData }) => { const { data, difficulty, threads } = eventData; let nonce = eventData.nonce; const isMainThread = nonce === 0; @@ -21,7 +21,7 @@ addEventListener('message', async ({ data: eventData }) => { const requiredZeroBytes = Math.floor(difficulty / 2); const isDifficultyOdd = difficulty % 2 !== 0; - for (; ;) { + for (;;) { const hashBuffer = await calculateSHA256(data + nonce); const hashArray = new Uint8Array(hashBuffer); @@ -34,7 +34,7 @@ addEventListener('message', async ({ data: eventData }) => { } if (isValid && isDifficultyOdd) { - if ((hashArray[requiredZeroBytes] >> 4) !== 0) { + if (hashArray[requiredZeroBytes] >> 4 !== 0) { isValid = false; } } @@ -55,7 +55,7 @@ addEventListener('message', async ({ data: eventData }) => { /* Truncate the decimal portion of the nonce. This is a bit of an evil bit * hack, but it works reliably enough. The core of why this works is: - * + * * > 13.4 % 1 !== 0 * true * > 13 % 1 !== 0 @@ -70,4 +70,4 @@ addEventListener('message', async ({ data: eventData }) => { postMessage(nonce); } } -}); \ No newline at end of file +}); diff --git a/web/js/worker/sha256-webcrypto.ts b/web/js/worker/sha256-webcrypto.ts index c83f4665..d3cdeca2 100644 --- a/web/js/worker/sha256-webcrypto.ts +++ b/web/js/worker/sha256-webcrypto.ts @@ -6,7 +6,10 @@ const calculateSHA256 = async (input: string) => { }; const toHexString = (byteArray: Uint8Array) => { - return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); + return byteArray.reduce( + (str, byte) => str + byte.toString(16).padStart(2, "0"), + "", + ); }; addEventListener("message", async ({ data: eventData }) => { @@ -18,7 +21,7 @@ addEventListener("message", async ({ data: eventData }) => { const requiredZeroBytes = Math.floor(difficulty / 2); const isDifficultyOdd = difficulty % 2 !== 0; - for (; ;) { + for (;;) { const hashBuffer = await calculateSHA256(data + nonce); const hashArray = new Uint8Array(hashBuffer); @@ -31,7 +34,7 @@ addEventListener("message", async ({ data: eventData }) => { } if (isValid && isDifficultyOdd) { - if ((hashArray[requiredZeroBytes] >> 4) !== 0) { + if (hashArray[requiredZeroBytes] >> 4 !== 0) { isValid = false; } } @@ -52,7 +55,7 @@ addEventListener("message", async ({ data: eventData }) => { /* Truncate the decimal portion of the nonce. This is a bit of an evil bit * hack, but it works reliably enough. The core of why this works is: - * + * * > 13.4 % 1 !== 0 * true * > 13 % 1 !== 0 @@ -67,4 +70,4 @@ addEventListener("message", async ({ data: eventData }) => { postMessage(nonce); } } -}); \ No newline at end of file +}); diff --git a/xess/postcss.config.js b/xess/postcss.config.js index 7084661d..13c708c6 100644 --- a/xess/postcss.config.js +++ b/xess/postcss.config.js @@ -5,4 +5,4 @@ module.exports = { }), require("postcss-url")({ url: "inline" }), ], -}; \ No newline at end of file +}; diff --git a/yeetfile.js b/yeetfile.js index 47749aff..caab0a5e 100644 --- a/yeetfile.js +++ b/yeetfile.js @@ -1,43 +1,41 @@ $`npm run assets`; -[ - "amd64", - "arm64", - "ppc64le", - "riscv64", -].forEach(goarch => { - [deb, rpm, tarball].forEach(method => method.build({ - name: "anubis", - description: "Anubis weighs the souls of incoming HTTP requests and uses a sha256 proof-of-work challenge in order to protect upstream resources from scraper bots.", - homepage: "https://anubis.techaro.lol", - license: "MIT", - goarch, +["amd64", "arm64", "ppc64le", "riscv64"].forEach((goarch) => { + [deb, rpm, tarball].forEach((method) => + method.build({ + name: "anubis", + description: + "Anubis weighs the souls of incoming HTTP requests and uses a sha256 proof-of-work challenge in order to protect upstream resources from scraper bots.", + homepage: "https://anubis.techaro.lol", + license: "MIT", + goarch, - documentation: { - "./README.md": "README.md", - "./LICENSE": "LICENSE", - "./data/botPolicies.yaml": "botPolicies.yaml", - }, + documentation: { + "./README.md": "README.md", + "./LICENSE": "LICENSE", + "./data/botPolicies.yaml": "botPolicies.yaml", + }, - build: ({ bin, etc, systemd, doc }) => { - $`go build -o ${bin}/anubis -ldflags '-s -w -extldflags "-static" -X "github.com/TecharoHQ/anubis.Version=${git.tag()}"' ./cmd/anubis`; - $`go build -o ${bin}/anubis-robots2policy -ldflags '-s -w -extldflags "-static" -X "github.com/TecharoHQ/anubis.Version=${git.tag()}"' ./cmd/robots2policy`; + build: ({ bin, etc, systemd, doc }) => { + $`go build -o ${bin}/anubis -ldflags '-s -w -extldflags "-static" -X "github.com/TecharoHQ/anubis.Version=${git.tag()}"' ./cmd/anubis`; + $`go build -o ${bin}/anubis-robots2policy -ldflags '-s -w -extldflags "-static" -X "github.com/TecharoHQ/anubis.Version=${git.tag()}"' ./cmd/robots2policy`; - file.install("./run/anubis@.service", `${systemd}/anubis@.service`); - file.install("./run/default.env", `${etc}/default.env`); + file.install("./run/anubis@.service", `${systemd}/anubis@.service`); + file.install("./run/default.env", `${etc}/default.env`); - $`mkdir -p ${doc}/docs` - $`cp -a docs/docs ${doc}`; - $`find ${doc} -name _category_.json -delete`; - $`mkdir -p ${doc}/data`; - $`cp -a data/apps ${doc}/data/apps`; - $`cp -a data/bots ${doc}/data/bots`; - $`cp -a data/clients ${doc}/data/clients`; - $`cp -a data/common ${doc}/data/common`; - $`cp -a data/crawlers ${doc}/data/crawlers`; - $`cp -a data/meta ${doc}/data/meta`; - }, - })); + $`mkdir -p ${doc}/docs`; + $`cp -a docs/docs ${doc}`; + $`find ${doc} -name _category_.json -delete`; + $`mkdir -p ${doc}/data`; + $`cp -a data/apps ${doc}/data/apps`; + $`cp -a data/bots ${doc}/data/bots`; + $`cp -a data/clients ${doc}/data/clients`; + $`cp -a data/common ${doc}/data/common`; + $`cp -a data/crawlers ${doc}/data/crawlers`; + $`cp -a data/meta ${doc}/data/meta`; + }, + }), + ); }); // NOTE(Xe): Fixes #217. This is a "half baked" tarball that includes the harder @@ -46,41 +44,41 @@ $`npm run assets`; // model into the bazaar of round holes that various modern languages use. Needless // to say, this makes adoption easier. tarball.build({ - name: "anubis-src-vendor", - license: "MIT", - // XXX(Xe): This is needed otherwise go will be very sad. - platform: yeet.goos, - goarch: yeet.goarch, + name: "anubis-src-vendor", + license: "MIT", + // XXX(Xe): This is needed otherwise go will be very sad. + platform: yeet.goos, + goarch: yeet.goarch, - build: ({ out }) => { - // prepare clean checkout in $out - $`git archive --format=tar HEAD | tar xC ${out}`; - // vendor Go dependencies - $`cd ${out} && go mod vendor`; - // write VERSION file - $`echo ${git.tag()} > ${out}/VERSION`; - }, + build: ({ out }) => { + // prepare clean checkout in $out + $`git archive --format=tar HEAD | tar xC ${out}`; + // vendor Go dependencies + $`cd ${out} && go mod vendor`; + // write VERSION file + $`echo ${git.tag()} > ${out}/VERSION`; + }, - mkFilename: ({ name, version }) => `${name}-${version}`, + mkFilename: ({ name, version }) => `${name}-${version}`, }); tarball.build({ - name: "anubis-src-vendor-npm", - license: "MIT", - // XXX(Xe): This is needed otherwise go will be very sad. - platform: yeet.goos, - goarch: yeet.goarch, + name: "anubis-src-vendor-npm", + license: "MIT", + // XXX(Xe): This is needed otherwise go will be very sad. + platform: yeet.goos, + goarch: yeet.goarch, - build: ({ out }) => { - // prepare clean checkout in $out - $`git archive --format=tar HEAD | tar xC ${out}`; - // vendor Go dependencies - $`cd ${out} && go mod vendor`; - // build NPM-bound dependencies - $`cd ${out} && npm ci && npm run assets && rm -rf node_modules` - // write VERSION file - $`echo ${git.tag()} > ${out}/VERSION`; - }, + build: ({ out }) => { + // prepare clean checkout in $out + $`git archive --format=tar HEAD | tar xC ${out}`; + // vendor Go dependencies + $`cd ${out} && go mod vendor`; + // build NPM-bound dependencies + $`cd ${out} && npm ci && npm run assets && rm -rf node_modules`; + // write VERSION file + $`echo ${git.tag()} > ${out}/VERSION`; + }, - mkFilename: ({ name, version }) => `${name}-${version}`, -}); \ No newline at end of file + mkFilename: ({ name, version }) => `${name}-${version}`, +});