Full Code of TecharoHQ/anubis for AI

main 24857f430f52 cached
608 files
1.4 MB
483.1k tokens
735 symbols
1 requests
Download .txt
Showing preview only (1,613K chars total). Download the full file or copy to clipboard to get everything.
Repository: TecharoHQ/anubis
Branch: main
Commit: 24857f430f52
Files: 608
Total size: 1.4 MB

Directory structure:
gitextract_t1amwk0u/

├── .air.toml
├── .devcontainer/
│   ├── Dockerfile
│   ├── README.md
│   ├── devcontainer.json
│   ├── docker-compose.yaml
│   └── poststart.sh
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   ├── config.yml
│   │   └── feature_request.yaml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── actions/
│   │   └── spelling/
│   │       ├── README.md
│   │       ├── advice.md
│   │       ├── allow.txt
│   │       ├── candidate.patterns
│   │       ├── excludes.txt
│   │       ├── expect.txt
│   │       ├── line_forbidden.patterns
│   │       ├── patterns.txt
│   │       └── reject.txt
│   ├── dependabot.yml
│   ├── workflows/
│   │   ├── asset-verification.yml
│   │   ├── dco-check.yaml
│   │   ├── docker-pr.yml
│   │   ├── docker.yml
│   │   ├── docs-deploy.yml
│   │   ├── docs-test.yml
│   │   ├── go-mod-tidy-check.yml
│   │   ├── go.yml
│   │   ├── lint-pr-title.yaml
│   │   ├── package-builds-stable.yml
│   │   ├── package-builds-unstable.yml
│   │   ├── smoke-tests.yml
│   │   ├── spelling.yml
│   │   ├── ssh-ci-runner-cron.yml
│   │   ├── ssh-ci.yml
│   │   └── zizmor.yml
│   └── zizmor.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .ko.yaml
├── .prettierignore
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── AGENTS.md
├── Brewfile
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── VERSION
├── anubis.go
├── cmd/
│   ├── containerbuild/
│   │   ├── .gitignore
│   │   └── main.go
│   └── robots2policy/
│       ├── batch/
│       │   └── batch_process.go
│       ├── main.go
│       ├── robots2policy_test.go
│       └── testdata/
│           ├── blacklist.robots.txt
│           ├── blacklist.yaml
│           ├── complex.robots.txt
│           ├── complex.yaml
│           ├── consecutive.robots.txt
│           ├── consecutive.yaml
│           ├── custom-name.yaml
│           ├── deny-action.yaml
│           ├── empty.robots.txt
│           ├── empty.yaml
│           ├── simple.json
│           ├── simple.robots.txt
│           ├── simple.yaml
│           ├── wildcards.robots.txt
│           └── wildcards.yaml
├── data/
│   ├── apps/
│   │   ├── allow-api-routes.yaml
│   │   ├── bookstack-saml.yaml
│   │   ├── gitea-rss-feeds.yaml
│   │   ├── qualys-ssl-labs.yml
│   │   └── searx-checker.yml
│   ├── botPolicies.yaml
│   ├── bots/
│   │   ├── _deny-pathological.yaml
│   │   ├── aggressive-brazilian-scrapers.yaml
│   │   ├── ai-catchall.yaml
│   │   ├── ai-robots-txt.yaml
│   │   ├── cloudflare-workers.yaml
│   │   ├── custom-async-http-client.yaml
│   │   ├── headless-browsers.yaml
│   │   ├── irc-bots/
│   │   │   ├── archlinux-phrik.yaml
│   │   │   └── gentoo-chat.yaml
│   │   └── us-ai-scraper.yaml
│   ├── clients/
│   │   ├── ai.yaml
│   │   ├── docker-client.yaml
│   │   ├── git.yaml
│   │   ├── go-get.yaml
│   │   ├── mistral-mistralai-user.yaml
│   │   ├── openai-chatgpt-user.yaml
│   │   ├── perplexity-user.yaml
│   │   ├── small-internet-browsers/
│   │   │   ├── _permissive.yaml
│   │   │   ├── netsurf.yaml
│   │   │   └── palemoon.yaml
│   │   ├── telegram-preview.yaml
│   │   ├── vk-preview.yaml
│   │   └── x-firefox-ai.yaml
│   ├── common/
│   │   ├── acts-like-browser.yaml
│   │   ├── allow-api-like.yaml
│   │   ├── allow-private-addresses.yaml
│   │   ├── json-api.yaml
│   │   ├── keep-internet-working.yaml
│   │   └── rfc-violations.yaml
│   ├── crawlers/
│   │   ├── _allow-good.yaml
│   │   ├── ai-search.yaml
│   │   ├── ai-training.yaml
│   │   ├── alibaba-cloud.yaml
│   │   ├── applebot.yaml
│   │   ├── bingbot.yaml
│   │   ├── commoncrawl.yaml
│   │   ├── duckduckbot.yaml
│   │   ├── googlebot.yaml
│   │   ├── huawei-cloud.yaml
│   │   ├── internet-archive.yaml
│   │   ├── kagibot.yaml
│   │   ├── marginalia.yaml
│   │   ├── mojeekbot.yaml
│   │   ├── openai-gptbot.yaml
│   │   ├── openai-searchbot.yaml
│   │   ├── perplexitybot.yaml
│   │   ├── qwantbot.yaml
│   │   ├── tencent-cloud.yaml
│   │   ├── wikimedia-citoid.yaml
│   │   └── yandexbot.yaml
│   ├── embed.go
│   ├── embed_test.go
│   ├── meta/
│   │   ├── README.md
│   │   ├── ai-block-aggressive.yaml
│   │   ├── ai-block-moderate.yaml
│   │   ├── ai-block-permissive.yaml
│   │   ├── default-config.yaml
│   │   └── messengers-preview.yaml
│   └── services/
│       ├── updown.yaml
│       └── uptime-robot.yaml
├── decaymap/
│   ├── decaymap.go
│   └── decaymap_test.go
├── docs/
│   ├── .dockerignore
│   ├── .gitignore
│   ├── Dockerfile
│   ├── README.md
│   ├── blog/
│   │   ├── 2025-06-16-welcome/
│   │   │   └── index.mdx
│   │   ├── 2025-06-27-release-1.20.0/
│   │   │   └── index.mdx
│   │   ├── 2025-07-09-incident-report/
│   │   │   └── index.mdx
│   │   ├── 2025-07-22-release-1.21.1/
│   │   │   └── index.mdx
│   │   ├── 2025-08-18-funding-update/
│   │   │   └── index.mdx
│   │   ├── 2025-08-28-cpu-core-odd/
│   │   │   ├── ProofOfWorkDiagram/
│   │   │   │   ├── index.jsx
│   │   │   │   └── styles.module.css
│   │   │   └── index.mdx
│   │   ├── 2025-10-31-file-abuse-reports/
│   │   │   └── index.mdx
│   │   └── authors.yml
│   ├── docs/
│   │   ├── CHANGELOG.md
│   │   ├── admin/
│   │   │   ├── _category_.json
│   │   │   ├── botstopper.mdx
│   │   │   ├── caveats-gitea-forgejo.mdx
│   │   │   ├── caveats-xff.mdx
│   │   │   ├── configuration/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── challenges/
│   │   │   │   │   ├── _category_.json
│   │   │   │   │   ├── index.mdx
│   │   │   │   │   ├── metarefresh.mdx
│   │   │   │   │   ├── preact.mdx
│   │   │   │   │   └── proof-of-work.mdx
│   │   │   │   ├── custom-status-codes.mdx
│   │   │   │   ├── expressions.mdx
│   │   │   │   ├── import.mdx
│   │   │   │   ├── impressum.mdx
│   │   │   │   ├── open-graph.mdx
│   │   │   │   ├── redirect-domains.mdx
│   │   │   │   ├── subrequest-auth.mdx
│   │   │   │   └── thresholds.mdx
│   │   │   ├── default-allow-behavior.mdx
│   │   │   ├── environments/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── apache.mdx
│   │   │   │   ├── caddy.mdx
│   │   │   │   ├── cloudflare.mdx
│   │   │   │   ├── docker-compose.mdx
│   │   │   │   ├── haproxy/
│   │   │   │   │   ├── advanced-config-policy.yml
│   │   │   │   │   ├── advanced-config.env
│   │   │   │   │   ├── advanced-haproxy.cfg
│   │   │   │   │   ├── simple-config.env
│   │   │   │   │   └── simple-haproxy.cfg
│   │   │   │   ├── haproxy.mdx
│   │   │   │   ├── kubernetes.mdx
│   │   │   │   ├── nginx/
│   │   │   │   │   ├── conf-anubis.inc
│   │   │   │   │   ├── server-anubistest-techaro-lol.conf
│   │   │   │   │   ├── server-mimi-techaro-lol.conf
│   │   │   │   │   └── upstream-anubis.conf
│   │   │   │   ├── nginx.mdx
│   │   │   │   └── traefik.mdx
│   │   │   ├── frameworks/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── htmx.mdx
│   │   │   │   └── wordpress.mdx
│   │   │   ├── honeypot/
│   │   │   │   ├── _category_.json
│   │   │   │   └── overview.mdx
│   │   │   ├── installation.mdx
│   │   │   ├── iplist2rule.mdx
│   │   │   ├── native-install.mdx
│   │   │   ├── policies.mdx
│   │   │   ├── robots2policy.mdx
│   │   │   ├── roles/
│   │   │   │   ├── _category_.json
│   │   │   │   └── oci-registry.mdx
│   │   │   └── thoth.mdx
│   │   ├── design/
│   │   │   ├── _category_.json
│   │   │   ├── how-anubis-works.mdx
│   │   │   └── why-proof-of-work.mdx
│   │   ├── developer/
│   │   │   ├── _category_.json
│   │   │   ├── ai-coding-policy.md
│   │   │   ├── building-anubis.md
│   │   │   ├── local-dev.md
│   │   │   └── signed-commits.md
│   │   ├── funding.md
│   │   ├── index.mdx
│   │   └── user/
│   │       ├── _category_.json
│   │       ├── frequently-asked-questions.mdx
│   │       ├── known-broken-extensions.md
│   │       ├── known-instances.md
│   │       └── why-see-challenge.md
│   ├── docusaurus.config.ts
│   ├── fly.toml
│   ├── manifest/
│   │   ├── 1password.yaml
│   │   ├── cfg/
│   │   │   ├── anubis/
│   │   │   │   └── botPolicies.yaml
│   │   │   └── nginx/
│   │   │       ├── mime.types
│   │   │       └── nginx.conf
│   │   ├── deployment.yaml
│   │   ├── ingress.yaml
│   │   ├── kustomization.yaml
│   │   ├── onionservice.yaml
│   │   ├── poddisruptionbudget.yaml
│   │   └── service.yaml
│   ├── package.json
│   ├── sidebars.ts
│   ├── src/
│   │   ├── components/
│   │   │   ├── EnterpriseOnly/
│   │   │   │   ├── index.jsx
│   │   │   │   └── styles.module.css
│   │   │   ├── HomepageFeatures/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.module.css
│   │   │   └── RandomKey/
│   │   │       └── index.tsx
│   │   ├── css/
│   │   │   └── custom.css
│   │   └── pages/
│   │       ├── index.module.css
│   │       └── index.tsx
│   ├── static/
│   │   └── .nojekyll
│   └── tsconfig.json
├── go.mod
├── go.sum
├── internal/
│   ├── actorify/
│   │   └── actorify.go
│   ├── clampip.go
│   ├── clampip_test.go
│   ├── dns/
│   │   ├── cache.go
│   │   ├── dns.go
│   │   └── dns_test.go
│   ├── dnsbl/
│   │   ├── dnsbl.go
│   │   ├── dnsbl_test.go
│   │   └── droneblresponse_string.go
│   ├── glob/
│   │   ├── glob.go
│   │   └── glob_test.go
│   ├── gzip.go
│   ├── hash.go
│   ├── hash_bench_test.go
│   ├── headers.go
│   ├── health.go
│   ├── honeypot/
│   │   ├── honeypot.go
│   │   └── naive/
│   │       ├── 100bytes.css
│   │       ├── affirmations.txt
│   │       ├── naive.go
│   │       ├── page.templ
│   │       ├── page_templ.go
│   │       ├── spintext.txt
│   │       └── titles.txt
│   ├── ja4h.go
│   ├── listor.go
│   ├── listor_test.go
│   ├── log.go
│   ├── log_test.go
│   ├── mimetype.go
│   ├── ogtags/
│   │   ├── cache.go
│   │   ├── cache_test.go
│   │   ├── fetch.go
│   │   ├── fetch_test.go
│   │   ├── integration_test.go
│   │   ├── mem_test.go
│   │   ├── ogtags.go
│   │   ├── ogtags_fuzz_test.go
│   │   ├── ogtags_test.go
│   │   ├── parse.go
│   │   ├── parse_test.go
│   │   └── sni.go
│   ├── test/
│   │   ├── playwright_test.go
│   │   └── var/
│   │       └── .gitignore
│   ├── unbreakdocker.go
│   └── xff_test.go
├── lib/
│   ├── anubis.go
│   ├── anubis_test.go
│   ├── challenge/
│   │   ├── challenge.go
│   │   ├── challengetest/
│   │   │   ├── challengetest.go
│   │   │   └── challengetest_test.go
│   │   ├── error.go
│   │   ├── interface.go
│   │   ├── metarefresh/
│   │   │   ├── metarefresh.go
│   │   │   ├── metarefresh.templ
│   │   │   └── metarefresh_templ.go
│   │   ├── metrics.go
│   │   ├── preact/
│   │   │   ├── build.sh
│   │   │   ├── js/
│   │   │   │   ├── app.tsx
│   │   │   │   └── xeact.js
│   │   │   ├── preact.go
│   │   │   ├── preact.templ
│   │   │   ├── preact_templ.go
│   │   │   └── static/
│   │   │       └── .gitignore
│   │   └── proofofwork/
│   │       ├── proofofwork.go
│   │       ├── proofofwork.templ
│   │       ├── proofofwork_templ.go
│   │       └── proofofwork_test.go
│   ├── config/
│   │   ├── asn.go
│   │   ├── asn_test.go
│   │   ├── check.go
│   │   ├── config.go
│   │   ├── config_test.go
│   │   ├── expressionorlist.go
│   │   ├── expressionorlist_test.go
│   │   ├── geoip.go
│   │   ├── geoip_test.go
│   │   ├── impressum.go
│   │   ├── impressum_test.go
│   │   ├── logging.go
│   │   ├── logging_test.go
│   │   ├── opengraph.go
│   │   ├── opengraph_test.go
│   │   ├── store.go
│   │   ├── store_test.go
│   │   ├── testdata/
│   │   │   ├── bad/
│   │   │   │   ├── badregexes.json
│   │   │   │   ├── badregexes.yaml
│   │   │   │   ├── dns-ttl-custom.yaml
│   │   │   │   ├── import_and_bot.json
│   │   │   │   ├── import_and_bot.yaml
│   │   │   │   ├── import_invalid_file.json
│   │   │   │   ├── import_invalid_file.yaml
│   │   │   │   ├── impressum-no-footer.yaml
│   │   │   │   ├── impressum-no-page-contents.yaml
│   │   │   │   ├── invalid.json
│   │   │   │   ├── invalid.yaml
│   │   │   │   ├── logging-invalid-sink.yaml
│   │   │   │   ├── logging-no-parameters.yaml
│   │   │   │   ├── multiple_expression_types.json
│   │   │   │   ├── multiple_expression_types.yaml
│   │   │   │   ├── nobots.json
│   │   │   │   ├── nobots.yaml
│   │   │   │   ├── opengraph_bad_ttl.yaml
│   │   │   │   ├── regex_ends_newline.json
│   │   │   │   ├── regex_ends_newline.yaml
│   │   │   │   ├── status-codes-0.json
│   │   │   │   ├── status-codes-0.yaml
│   │   │   │   ├── threshold-challenge-without-challenge.yaml
│   │   │   │   ├── thresholds.yaml
│   │   │   │   ├── unparseable.json
│   │   │   │   └── unparseable.yaml
│   │   │   ├── good/
│   │   │   │   ├── allow_everyone.json
│   │   │   │   ├── allow_everyone.yaml
│   │   │   │   ├── block_cf_workers.json
│   │   │   │   ├── block_cf_workers.yaml
│   │   │   │   ├── challenge_cloudflare.yaml
│   │   │   │   ├── challengemozilla.json
│   │   │   │   ├── challengemozilla.yaml
│   │   │   │   ├── dns-ttl-custom.yaml
│   │   │   │   ├── entropy.yaml
│   │   │   │   ├── everything_blocked.json
│   │   │   │   ├── everything_blocked.yaml
│   │   │   │   ├── geoip_us.yaml
│   │   │   │   ├── git_client.json
│   │   │   │   ├── git_client.yaml
│   │   │   │   ├── import_filesystem.json
│   │   │   │   ├── import_filesystem.yaml
│   │   │   │   ├── import_keep_internet_working.json
│   │   │   │   ├── import_keep_internet_working.yaml
│   │   │   │   ├── impressum.yaml
│   │   │   │   ├── logging-file.yaml
│   │   │   │   ├── logging-stdio.yaml
│   │   │   │   ├── no-thresholds.yaml
│   │   │   │   ├── old_xesite.json
│   │   │   │   ├── opengraph_all_good.yaml
│   │   │   │   ├── simple-weight.yaml
│   │   │   │   ├── status-codes-paranoid.json
│   │   │   │   ├── status-codes-paranoid.yaml
│   │   │   │   ├── status-codes-rfc.json
│   │   │   │   ├── status-codes-rfc.yaml
│   │   │   │   ├── thresholds.yaml
│   │   │   │   └── weight-no-weight.yaml
│   │   │   ├── hack-test.json
│   │   │   └── hack-test.yaml
│   │   ├── threshold.go
│   │   ├── threshold_test.go
│   │   └── weight.go
│   ├── config.go
│   ├── config_test.go
│   ├── http.go
│   ├── http_test.go
│   ├── localization/
│   │   ├── locales/
│   │   │   ├── cs.json
│   │   │   ├── de.json
│   │   │   ├── en.json
│   │   │   ├── es.json
│   │   │   ├── et.json
│   │   │   ├── fi.json
│   │   │   ├── fil.json
│   │   │   ├── fr.json
│   │   │   ├── is.json
│   │   │   ├── it.json
│   │   │   ├── ja.json
│   │   │   ├── lt.json
│   │   │   ├── manifest.json
│   │   │   ├── nb.json
│   │   │   ├── nl.json
│   │   │   ├── nn.json
│   │   │   ├── pl.json
│   │   │   ├── pt-BR.json
│   │   │   ├── ru.json
│   │   │   ├── sv.json
│   │   │   ├── th.json
│   │   │   ├── tr.json
│   │   │   ├── uk.json
│   │   │   ├── vi.json
│   │   │   ├── zh-CN.json
│   │   │   └── zh-TW.json
│   │   ├── localization.go
│   │   └── localization_test.go
│   ├── policy/
│   │   ├── bot.go
│   │   ├── celchecker.go
│   │   ├── checker/
│   │   │   ├── checker.go
│   │   │   └── checker_test.go
│   │   ├── checker.go
│   │   ├── checker_test.go
│   │   ├── checkresult.go
│   │   ├── expressions/
│   │   │   ├── README.md
│   │   │   ├── environment.go
│   │   │   ├── environment_test.go
│   │   │   ├── http_headers.go
│   │   │   ├── http_headers_test.go
│   │   │   ├── loadavg.go
│   │   │   ├── url_values.go
│   │   │   └── url_values_test.go
│   │   ├── policy.go
│   │   ├── policy_test.go
│   │   ├── testdata/
│   │   │   ├── hack-test.json
│   │   │   └── hack-test.yaml
│   │   └── thresholds.go
│   ├── redirect_security_test.go
│   ├── store/
│   │   ├── actorifiedstore.go
│   │   ├── all/
│   │   │   └── all.go
│   │   ├── bbolt/
│   │   │   ├── bbolt.go
│   │   │   ├── bbolt_test.go
│   │   │   ├── factory.go
│   │   │   └── factory_test.go
│   │   ├── interface.go
│   │   ├── json_test.go
│   │   ├── memory/
│   │   │   ├── memory.go
│   │   │   └── memory_test.go
│   │   ├── registry.go
│   │   ├── s3api/
│   │   │   ├── factory.go
│   │   │   ├── s3api.go
│   │   │   └── s3api_test.go
│   │   ├── storetest/
│   │   │   └── storetest.go
│   │   └── valkey/
│   │       ├── factory.go
│   │       ├── valkey.go
│   │       └── valkey_test.go
│   ├── testdata/
│   │   ├── aggressive_403.yaml
│   │   ├── cloudflare-workers-cel.yaml
│   │   ├── cloudflare-workers-header.yaml
│   │   ├── hack-test.json
│   │   ├── hack-test.yaml
│   │   ├── invalid-challenge-method.yaml
│   │   ├── permissive.yaml
│   │   ├── rule_change.yaml
│   │   ├── test_config.yaml
│   │   ├── test_config_no_thresholds.yaml
│   │   ├── useragent.yaml
│   │   └── zero_difficulty.yaml
│   └── thoth/
│       ├── asnchecker.go
│       ├── asnchecker_test.go
│       ├── auth.go
│       ├── cachediptoasn.go
│       ├── context.go
│       ├── geoipchecker.go
│       ├── geoipchecker_test.go
│       ├── thoth.go
│       ├── thoth_test.go
│       └── thothmock/
│           ├── iptoasn.go
│           └── withthothmock.go
├── package.json
├── run/
│   ├── anubis.freebsd
│   ├── anubis@.service
│   ├── default.env
│   └── openrc/
│       ├── anubis.confd
│       └── anubis.initd
├── test/
│   ├── .gitignore
│   ├── anubis_configs/
│   │   └── aggressive_403.yaml
│   ├── caddy/
│   │   ├── Caddyfile
│   │   ├── Dockerfile
│   │   ├── docker-compose.yaml
│   │   └── start.sh
│   ├── cmd/
│   │   ├── cipra/
│   │   │   ├── internal/
│   │   │   │   ├── containerip.go
│   │   │   │   ├── getlanip.go
│   │   │   │   └── unbreakdocker.go
│   │   │   └── main.go
│   │   ├── httpdebug/
│   │   │   └── main.go
│   │   ├── relayd/
│   │   │   └── main.go
│   │   └── unixhttpd/
│   │       └── main.go
│   ├── default-config-macro/
│   │   ├── compare_bots.py
│   │   └── test.sh
│   ├── docker-registry/
│   │   ├── anubis.yaml
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── double_slash/
│   │   ├── anubis.yaml
│   │   ├── input.txt
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── forced-language/
│   │   ├── anubis.yaml
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── git-clone/
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── git-push/
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── go.mod
│   ├── go.sum
│   ├── healthcheck/
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── i18n/
│   │   ├── anubis.yaml
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── k8s/
│   │   ├── cert-manager/
│   │   │   └── selfsigned-issuer.yaml
│   │   └── deps/
│   │       └── cert-manager.yaml
│   ├── lib/
│   │   └── lib.sh
│   ├── log-file/
│   │   ├── anubis.yaml
│   │   ├── input.txt
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── nginx/
│   │   ├── conf/
│   │   │   └── nginx/
│   │   │       ├── conf-anubis.inc
│   │   │       ├── conf.d/
│   │   │       │   ├── server-mimi-techaro-lol.conf
│   │   │       │   └── upstream-anubis.conf
│   │   │       ├── mime.types
│   │   │       └── nginx.conf
│   │   └── test.sh
│   ├── nginx-external-auth/
│   │   ├── conf.d/
│   │   │   └── default.conf
│   │   ├── deployment.yaml
│   │   ├── ingress.yaml
│   │   ├── kustomization.yaml
│   │   ├── service.yaml
│   │   └── start.sh
│   ├── palemoon/
│   │   ├── README.md
│   │   ├── amd64/
│   │   │   ├── docker-compose.yml
│   │   │   ├── test.sh
│   │   │   └── var/
│   │   │       └── .gitignore
│   │   ├── anubis/
│   │   │   └── anubis.yaml
│   │   ├── i386/
│   │   │   ├── docker-compose.yml
│   │   │   ├── test.sh
│   │   │   └── var/
│   │   │       └── .gitignore
│   │   └── scripts/
│   │       └── install-cert.sh
│   ├── pki/
│   │   └── .gitignore
│   ├── robots_txt/
│   │   ├── anubis.yaml
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── shared/
│   │   └── www/
│   │       └── index.html
│   ├── ssh-ci/
│   │   ├── Dockerfile
│   │   ├── docker-bake.hcl
│   │   ├── in-container.sh
│   │   └── rigging.sh
│   └── unix-socket-xff/
│       ├── start.sh
│       └── test.mjs
├── utils/
│   └── cmd/
│       ├── backoff-retry/
│       │   └── main.go
│       └── iplist2rule/
│           ├── blocklist.go
│           └── main.go
├── var/
│   └── .gitignore
├── web/
│   ├── build.sh
│   ├── embed.go
│   ├── index.go
│   ├── index.templ
│   ├── index_templ.go
│   ├── index_test.go
│   ├── js/
│   │   ├── algorithms/
│   │   │   ├── fast.ts
│   │   │   └── index.ts
│   │   ├── bench.ts
│   │   ├── main.ts
│   │   └── worker/
│   │       ├── sha256-purejs.ts
│   │       └── sha256-webcrypto.ts
│   └── static/
│       ├── img/
│       │   └── ATTRIBUTIONS.txt
│       ├── js/
│       │   └── .gitignore
│       └── robots.txt
├── xess/
│   ├── .gitignore
│   ├── build.sh
│   ├── postcss.config.js
│   ├── static/
│   │   └── podkova.css
│   ├── xess.css
│   └── xess.go
└── yeetfile.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .air.toml
================================================
root = "."
tmp_dir = "var"

[build]
cmd = "go build -o ./var/main ./cmd/anubis"
bin = "./var/main"
args = ["--use-remote-address"]
exclude_dir = ["var", "vendor", "docs", "node_modules"]

[logger]
time = true
# to change flags at runtime, prepend with -- e.g. $ air -- --target http://localhost:3000 --difficulty 20 --use-remote-address


================================================
FILE: .devcontainer/Dockerfile
================================================
FROM ghcr.io/xe/devcontainer-base/pre/go

WORKDIR /app

COPY go.mod go.sum package.json package-lock.json ./
RUN apt-get update \
  && apt-get -y install zstd brotli redis \
  && mkdir -p /home/vscode/.local/share/fish \
  && chown -R vscode:vscode /home/vscode/.local/share/fish \
  && chown -R vscode:vscode /go

CMD ["/usr/bin/sleep", "infinity"]

================================================
FILE: .devcontainer/README.md
================================================
# Anubis Dev Container

Anubis offers a [development container](https://containers.dev/) image in order to make it easier to contribute to the project. This image is based on [Xe/devcontainer-base/go](https://github.com/Xe/devcontainer-base/tree/main/src/go), which is based on Debian Bookworm with the following customizations:

- [Fish](https://fishshell.com/) as the shell complete with a custom theme
- [Go](https://go.dev) at the most recent stable version
- [Node.js](https://nodejs.org/en) at the most recent stable version
- [Atuin](https://atuin.sh/) to sync shell history between your host OS and the development container
- [Docker](https://docker.com) to manage and build Anubis container images from inside the development container
- [Ko](https://ko.build/) to build production-ready Anubis container images
- [Neovim](https://neovim.io/) for use with Git

This development container is tested and known to work with [Visual Studio Code](https://code.visualstudio.com/). If you run into problems with it outside of VS Code, please file an issue and let us know what editor you are using.


================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
  "name": "Dev",
  "dockerComposeFile": ["./docker-compose.yaml"],
  "service": "workspace",
  "workspaceFolder": "/workspace/anubis",
  "postStartCommand": "bash ./.devcontainer/poststart.sh",
  "features": {
    "ghcr.io/xe/devcontainer-features/ko:1.1.0": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  },
  "initializeCommand": "mkdir -p ${localEnv:HOME}${localEnv:USERPROFILE}/.local/share/atuin",
  "customizations": {
    "vscode": {
      "extensions": [
        "esbenp.prettier-vscode",
        "ms-azuretools.vscode-containers",
        "golang.go",
        "unifiedjs.vscode-mdx",
        "a-h.templ",
        "redhat.vscode-yaml",
        "streetsidesoftware.code-spell-checker"
      ],
      "settings": {
        "chat.instructionsFilesLocations": {
          ".github/copilot-instructions.md": true
        }
      }
    }
  }
}


================================================
FILE: .devcontainer/docker-compose.yaml
================================================
services:
  playwright:
    image: mcr.microsoft.com/playwright:v1.52.0-noble
    init: true
    network_mode: service:workspace
    command:
      - /bin/sh
      - -c
      - npx -y playwright@1.52.0 run-server --port 9001 --host 0.0.0.0

  valkey:
    image: valkey/valkey:8
    pull_policy: always

  # VS Code workspace service
  workspace:
    image: ghcr.io/techarohq/anubis/devcontainer
    build:
      context: ..
      dockerfile: .devcontainer/Dockerfile
    volumes:
      - ../:/workspace/anubis:cached
    environment:
      VALKEY_URL: redis://valkey:6379/0
    #entrypoint: ["/usr/bin/sleep", "infinity"]
    user: vscode


================================================
FILE: .devcontainer/poststart.sh
================================================
#!/usr/bin/env bash

pwd

npm ci &
go mod download &
go install ./utils/cmd/... &

wait


================================================
FILE: .gitattributes
================================================
**/*_templ.go linguist-generated=true


================================================
FILE: .github/FUNDING.yml
================================================
patreon: cadey
github: xe
liberapay: Xe


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: Bug report
description: Create a report to help us improve

body:
  - type: textarea
    id: description-of-bug
    attributes:
      label: Describe the bug
      description: A clear and concise description of what the bug is.
      placeholder: I can reliably get an error when...
    validations:
      required: true

  - type: textarea
    id: steps-to-reproduce
    attributes:
      label: Steps to reproduce
      description: |
        Steps to reproduce the behavior.
      placeholder: |
        1. Go to the following url...
        2. Click on...
        3. You get the following error: ...
    validations:
      required: true

  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected behavior
      description: |
        A clear and concise description of what you expected to happen.
        Ideally also describe *why* you expect it to happen.
      placeholder: Instead of displaying an error, it would...
    validations:
      required: true

  - type: input
    id: version-os
    attributes:
      label: Your operating system and its version.
      description: Unsure? Visit https://whatsmyos.com/
      placeholder: Android 13
    validations:
      required: true

  - type: input
    id: version-browser
    attributes:
      label: Your browser and its version.
      description: Unsure? Visit https://www.whatsmybrowser.org/
      placeholder: Firefox 142
    validations:
      required: true

  - type: textarea
    id: additional-context
    attributes:
      label: Additional context
      description: Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Security
    url: https://techaro.lol/contact
    about: Do not file security reports here. Email security@techaro.lol.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yaml
================================================
name: Feature request
description: Suggest an idea for this project
title: "[Feature request] "

body:
  - type: textarea
    id: description-of-bug
    attributes:
      label: Is your feature request related to a problem? Please describe.
      description: A clear and concise description of what the problem is that made you submit this report.
      placeholder: I am always frustrated, when...
    validations:
      required: true

  - type: textarea
    id: description-of-solution
    attributes:
      label: Solution you would like.
      description: A clear and concise description of what you want to happen.
      placeholder: Instead of behaving like this, there should be...
    validations:
      required: true

  - type: textarea
    id: alternatives
    attributes:
      label: Describe alternatives you have considered.
      description: A clear and concise description of any alternative solutions or features you have considered.
      placeholder: Another workaround that would work, is...
    validations:
      required: false

  - type: textarea
    id: additional-context
    attributes:
      label: Additional context
      description: Add any other context (such as mock-ups, proof of concepts or screenshots) about the feature request here.
    validations:
      required: false


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
delete me and describe your change here, give enough context for a maintainer to understand what and why

See https://github.com/TecharoHQ/anubis/blob/main/CONTRIBUTING.md for more information
-->

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://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)


================================================
FILE: .github/actions/spelling/README.md
================================================
# 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)     |

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.


================================================
FILE: .github/actions/spelling/advice.md
================================================
<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-disable MD033 MD041 -->
<details><summary>If the flagged items are :exploding_head: false positives</summary>

If items relate to a ...

- 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.

  `^` 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.

  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.

  Note that patterns can't match multiline strings.

</details>

<!-- adoption information-->

: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.


================================================
FILE: .github/actions/spelling/allow.txt
================================================
github
https
ssh
ubuntu
workarounds
rjack
msgbox
xeact
ABee
tencent
maintnotifications
azurediamond
cooldown
verifyfcrdns
Spintax
spintax
clampip
pseudoprofound
reimagining
iocaine
admins
fout
iplist
NArg
blocklists
rififi
prolocation
Prolocation
Necron
Stargate
FFXIV
uvensys
de
envoyproxy
unipromos


================================================
FILE: .github/actions/spelling/candidate.patterns
================================================
# Repeated letters
#\b([a-z])\g{-1}{2,}\b

# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker to ignore all code on line
^.*\bno-spell-check(?:-line|)(?:\s.*|)$

# https://cspell.org/configuration/document-settings/
# cspell inline
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b

# copyright
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+

# patch hunk comments
^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
# git index header
index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}

# file permissions
['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]

# css fonts
\bfont(?:-family|):[^;}]+

# css url wrappings
\burl\([^)]+\)

# cid urls
(['"])cid:.*?\g{-1}

# data url in parens
\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\)
# data url in quotes
([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
# data url
\bdata:[-a-zA-Z=;:/0-9+]*,\S*

# https/http/file urls
(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|]

# mailto urls
mailto:[-a-zA-Z=;:/?%&0-9+@._]{3,}

# magnet urls
magnet:[?=:\w]+

# magnet urls
"magnet:[^"]+"

# obs:
"obs:[^"]*"

# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read
# In this examples content, I'm using a number of different ways to match things to show various approaches
# asciinema
\basciinema\.org/a/[0-9a-zA-Z]+

# asciinema v2
^\[\d+\.\d+, "[io]", ".*"\]$

# apple
\bdeveloper\.apple\.com/[-\w?=/]+
# Apple music
\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+

# appveyor api
\bci\.appveyor\.com/api/projects/status/[0-9a-z]+
# appveyor project
\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+

# Amazon

# Amazon
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
# AWS ARN
arn:aws:[-/:\w]+
# AWS S3
\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]*
# AWS execute-api
\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b
# AWS ELB
\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b
# AWS SNS
\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]*
# AWS VPC
vpc-\w+

# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
# YouTube music
\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*)
# YouTube tag
<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"]
# YouTube image
\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]*
# Google Accounts
\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]*
# Google Analytics
\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
# Google APIs
\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
# Google Artifact Registry
\.pkg\.dev(?:/[-\w]+)+(?::[-\w]+|)
# Google Storage
\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
# Google Calendar
\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+
\w+\@group\.calendar\.google\.com\b
# Google DataStudio
\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|)
# The leading `/` here is as opposed to the `\b` above
# ... a short way to match `https://` or `http://` since most urls have one of those prefixes
# Google Docs
/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\w=&]*|))
# Google Drive
\bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]*
# Google Groups
\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)*
# Google Maps
\bmaps\.google\.com/maps\?[\w&;=]*
# Google themes
themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
# Google CDN
\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]*
# Goo.gl
/goo\.gl/[a-zA-Z0-9]+
# Google Chrome Store
\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|)
# Google Books
\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]*
# Google Fonts
\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]*
# Google Forms
\bforms\.gle/\w+
# Google Scholar
\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
# Google Colab Research Drive
\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
# Google Cloud regions
(?:us|(?:north|south)america|europe|asia|australia|me|africa)-(?:north|south|east|west|central){1,2}\d+

# GitHub SHAs (api)
\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
# GitHub SHAs (markdown)
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
# GitHub SHAs
\bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b
# GitHub SHA refs
\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]*
# GitHub wiki
\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b
# githubusercontent
/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
# githubassets
\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+)
# gist github
\bgist\.github\.com/[^/\s"]+/[0-9a-f]+
# git.io
\bgit\.io/[0-9a-zA-Z]+
# GitHub JSON
"node_id": "[-a-zA-Z=;:/0-9+_]*"
# Contributor
\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
# GHSA
GHSA(?:-[0-9a-z]{4}){3}

# GitHub actions
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+

# GitLab commit
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
# GitLab merge requests
\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b
# GitLab uploads
\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]*
# GitLab commits
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b

# #includes
^\s*#include\s*(?:<.*?>|".*?")

# #pragma lib
^\s*#pragma comment\(lib, ".*?"\)

# binance
accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*

# bitbucket diff
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+
# bitbucket repositories commits
\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+
# bitbucket commits
\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+

# bit.ly
\bbit\.ly/\w+

# bitrise
\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]*

# bootstrapcdn.com
\bbootstrapcdn\.com/[-./\w]+

# cdn.cloudflare.com
\bcdnjs\.cloudflare\.com/[./\w]+

# circleci
\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+

# gitter
\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+

# gravatar
\bgravatar\.com/avatar/[0-9a-f]+

# ibm
[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]*

# imgur
\bimgur\.com/[^.]+

# Internet Archive
\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*)

# discord
/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,}

# Disqus
\bdisqus\.com/[-\w/%.()!?&=_]*

# medium link
\blink\.medium\.com/[a-zA-Z0-9]+
# medium
\bmedium\.com/@?[^/\s"]+/[-\w]+

# microsoft
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
# powerbi
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
# vs devops
\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
# microsoft store
\bmicrosoft\.com/store/apps/\w+

# mvnrepository.com
\bmvnrepository\.com/[-0-9a-z./]+

# now.sh
/[0-9a-z-.]+\.now\.sh\b

# oracle
\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]*

# chromatic.com
/\S+.chromatic.com\S*[")]

# codacy
\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+

# compai
\bcompai\.pub/v1/png/[0-9a-f]+

# mailgun api
\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]*
# mailgun
\b[0-9a-z]+.mailgun.org

# /message-id/
/message-id/[-\w@./%]+

# Reddit
\breddit\.com/r/[/\w_]*

# requestb.in
\brequestb\.in/[0-9a-z]+

# sched
\b[a-z0-9]+\.sched\.com\b

# Slack url
slack://[a-zA-Z0-9?&=]+
# Slack
\bslack\.com/[-0-9a-zA-Z/_~?&=.]*
# Slack edge
\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+
# Slack images
\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+

# shields.io
\bshields\.io/[-\w/%?=&.:+;,]*

# stackexchange -- https://stackexchange.com/feeds/sites
\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)

# Sentry
[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b

# Twitter markdown
\[@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
# Twitter hashtag
\btwitter\.com/hashtag/[\w?_=&]*
# Twitter status
\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)
# Twitter profile images
\btwimg\.com/profile_images/[_\w./]*
# Twitter media
\btwimg\.com/media/[-_\w./?=]*
# Twitter link shortened
\bt\.co/\w+

# facebook
\bfburl\.com/[0-9a-z_]+
# facebook CDN
\bfbcdn\.net/[\w/.,]*
# facebook watch
\bfb\.watch/[0-9A-Za-z]+

# dropbox
\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+

# ipfs protocol
ipfs://[0-9a-zA-Z]{3,}
# ipfs url
/ipfs/[0-9a-zA-Z]{3,}

# w3
\bw3\.org/[-0-9a-zA-Z/#.]+

# loom
\bloom\.com/embed/[0-9a-f]+

# regex101
\bregex101\.com/r/[^/\s"]+/\d+

# figma
\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+

# freecodecamp.org
\bfreecodecamp\.org/[-\w/.]+

# image.tmdb.org
\bimage\.tmdb\.org/[/\w.]+

# mermaid
\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+

# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+

# gitweb
[^"\s]+/gitweb/\S+;h=[0-9a-f]+

# HyperKitty lists
/archives/list/[^@/]+@[^/\s"]*/message/[^/\s"]*/

# lists
/thread\.html/[^"\s]+

# list-management
\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+

# kubectl.kubernetes.io/last-applied-configuration
"kubectl.kubernetes.io/last-applied-configuration": ".*"

# pgp
\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]*

# Spotify
\bopen\.spotify\.com/embed/playlist/\w+

# Mastodon
\bmastodon\.[-a-z.]*/(?:media/|@)[?&=0-9a-zA-Z_]*

# scastie
\bscastie\.scala-lang\.org/[^/]+/\w+

# images.unsplash.com
\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+

# pastebin
\bpastebin\.com/[\w/]+

# heroku
\b\w+\.heroku\.com/source/archive/\w+

# quip
\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)?

# badgen.net
\bbadgen\.net/badge/[^")\]'\s]+

# statuspage.io
\w+\.statuspage\.io\b

# media.giphy.com
\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+

# tinyurl
\btinyurl\.com/\w+

# codepen
\bcodepen\.io/[\w/]+

# registry.npmjs.org
\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+

# getopts
\bgetopts\s+(?:"[^"]+"|'[^']+')

# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m

# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
# lower URL escaped characters
%[0-9a-f][a-f](?=[a-z]{2,})
# IPv6
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
# c99 hex digits (not the full format, just one I've seen)
0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP]
# Punycode
\bxn--[-0-9a-z]+
# sha
sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
# hex runs
\b[0-9a-fA-F]{16,}\b
# hex in url queries
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
# ssh
(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,}

# PGP
\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b
# GPG keys
\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b
# Well known gpg keys
.well-known/openpgpkey/[\w./]+

# pki
-----BEGIN.*-----END

# pki (base64)
LS0tLS1CRUdJT.*

# C# includes
^\s*using [^;]+;

# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b

# integrity
integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}

# https://www.gnu.org/software/groff/manual/groff.html
# man troff content
\\f[BCIPR]
# '/"
\\\([ad]q

# .desktop mime types
^MimeTypes?=.*$
# .desktop localized entries
^[A-Z][a-z]+\[[a-z]+\]=.*$
# Localized .desktop content
Name\[[^\]]+\]=.*

# IServiceProvider / isAThing
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))

# crypt
(['"])\$2[ayb]\$.{56}\g{-1}

# apache/old crypt
(['"]|)\$+(?:apr|)1\$+.{8}\$+.{22}\g{-1}

# sha1 hash
\{SHA\}[-a-zA-Z=;:/0-9+]{3,}

# machine learning (?)
\b(?i)ml(?=[a-z]{2,})

# python
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})

# scrypt / argon
\$(?:scrypt|argon\d+[di]*)\$\S+

# go.sum
\bh1:\S+

# imports
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+

# scala modules
("[^"]+"\s*%%?\s*){2,3}"[^"]+"

# container images
image: [-\w./:@]+

# Docker images
^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|)

# `docker images` REPOSITORY TAG IMAGE ID CREATED SIZE
\s*\S+/\S+\s+\S+\s+[0-9a-f]{8,}\s+\d+\s+(?:hour|day|week)s ago\s+[\d.]+[KMGT]B

# Intel intrinsics
_mm_(?!dd)\w+

# Input to GitHub JSON
content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}

# This does not cover multiline strings, if your repository has them,
# you'll want to remove the `(?=.*?")` suffix.
# The `(?=.*?")` suffix should limit the false positives rate
# printf
%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA])(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"])

# Alternative printf
# %s
%(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%[^s])\b)(?=.*?['"])

# Python string prefix / binary prefix
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
(?<!['"])\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)['"](?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})

# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+

# JavaScript regular expressions
# javascript test regex
/.{3,}/[gim]*\.test\(
# javascript match regex
\.match\(/[^/\s"]{3,}/[gim]*\s*
# javascript match regex
\.match\(/\\[b].{3,}?/[gim]*\s*\)(?:;|$)
# javascript regex
^\s*/\\[b].{3,}?/[gim]*\s*(?:\)(?:;|$)|,$)
# javascript replace regex
\.replace\(/[^/\s"]{3,}/[gim]*\s*,
# assign regex
= /[^*].*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/[gim]*(?=\W|$)
# perl regex test
[!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1})

# perl qr regex
(?<!\$)\bqr(?:\{.*?\}|<.*?>|\(.*?\)|([|!/@#,;']).*?\g{-1})

# perl run
perl(?:\s+-[a-zA-Z]\w*)+

# C network byte conversions
(?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\()

# Go regular expressions
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)

# regex choice
\(\?:[^)]+\|[^)]+\)

# proto
^\s*(\w+)\s\g{-1} =

# sed regular expressions
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}

# node packages
(["'])@[^/'" ]+/[^/'" ]+\g{-1}

# go install
go install(?:\s+[a-z]+\.[-@\w/.]+)+

# pom.xml
<(?:group|artifact)Id>.*?<

# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571
urn:shemas-jetbrains-com

# Debian changelog severity
[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b

# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+

# kubectl - pods in CrashLoopBackOff
\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+

# kubernetes applications
\.apps/[-\w]+

# kubernetes object suffix
-[0-9a-f]{10}-\w{5}\s

# kubernetes crd patterns
^\s*pattern: .*$

# posthog secrets
([`'"])phc_[^"',]+\g{-1}

# xcode

# xcodeproject scenes
(?:Controller|destination|(?:first|second)Item|ID|id)="\w{3}-\w{2}-\w{3}"

# xcode api botches
customObjectInstantitationMethod

# msvc api botches
PrependWithABINamepsace

# configure flags
.* \| --\w{2,}.*?(?=\w+\s\w+)

# font awesome classes
\.fa-[-a-z0-9]+

# bearer auth
(['"])[Bb]ear[e][r] .{3,}?\g{-1}

# bearer auth
\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]{3,}

# basic auth
(['"])[Bb]asic [-a-zA-Z=;:/0-9+]{3,}\g{-1}

# basic auth
: [Bb]asic [-a-zA-Z=;:/0-9+.]{3,}

# base64 encoded content
([`'"])[-a-zA-Z=;:/0-9+]{3,}=\g{-1}
# base64 encoded content in xml/sgml
>[-a-zA-Z=;:/0-9+]{3,}=</
# base64 encoded content, possibly wrapped in mime
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# base64 encoded json
\beyJ[-a-zA-Z=;:/0-9+]+
# base64 encoded pkcs
\bMII[-a-zA-Z=;:/0-9+]+

# uuencoded
#[!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_]{40,}

# DNS rr data
(?:\d+\s+){3}(?:[-+/=.\w]{2,}\s*){1,2}

# encoded-word
=\?[-a-zA-Z0-9"*%]+\?[BQ]\?[^?]{0,75}\?=

# numerator
\bnumer\b(?=.*denom)

# Time Zones
\b(?:Africa|Atlantic|America|Antarctica|Arctic|Asia|Australia|Europe|Indian|Pacific)(?:/[-\w]+)+

# linux kernel info
^(?:bugs|flags|Features)\s+:.*

# systemd mode
systemd.*?running in system mode \([-+].*\)$

# Lorem
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
# ... Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
# ... You could manually change `(?i)X...` to use `[Xx]...`
# ... or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
(?:(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*)

# Non-English
# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
#
# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
#
# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}

# highlighted letters
\[[A-Z]\][a-z]+

# French
# This corpus only had capital letters, but you probably want lowercase ones as well.
\b[LN]'+[a-z]{2,}\b

# latex (check-spelling >= 0.0.22)
\\\w{2,}\{

# American Mathematical Society (AMS) / Doxygen
TeX/AMS

# File extensions
\*\.[+\w]+,

# eslint
"varsIgnorePattern": ".+"

# nolint
nolint:\s*[\w,]+

# Windows short paths
[/\\][^/\\]{5,6}~\d{1,2}(?=[/\\])

# Windows Resources with accelerators
\b[A-Z]&[a-z]+\b(?!;)

# signed off by
(?i)Signed-off-by: .*

# cygwin paths
/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+

# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
# printf markers
(?<!\\)\\[nrt](?=[a-z]{2,})
# alternate printf markers if you run into latex and friends
(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*['"`])

# Markdown anchor links
\(#\S*?[a-zA-Z]\S*?\)

# apache
a2(?:en|dis)

# weak e-tag
W/"[^"]+"

# authors/credits
^\*(?: [A-Z](?:\w+|\.)){2,} (?=\[|$)

# the negative lookahead here is to allow catching 'templatesz' as a misspelling
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
\\(?:necessary|r(?:elease|eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b

# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))

# Compiler flags (Unix, Java/Scala)
# Use if you have things like `-Pdocker` and want to treat them as `docker`
#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})

# Compiler flags (Windows / PowerShell)
# This is a subset of the more general compiler flags pattern.
# It avoids matching `-Path` to prevent it from being treated as `ath`
#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))

# Compiler flags (linker)
,-B

# libraries
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])

# WWNN/WWPN (NAA identifiers)
\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b

# iSCSI iqn (approximate regex)
\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b

# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
# macOS temp folders
/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
# github runner temp folders
/home/runner/work/_temp/[-_/a-z0-9]+


================================================
FILE: .github/actions/spelling/excludes.txt
================================================
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
(?:^|/)(?i)COPYRIGHT
(?:^|/)(?i)LICEN[CS]E
(?:^|/)(?i)third[-_]?party/
(?:^|/)3rdparty/
(?:^|/)generated/
(?:^|/)go\.sum$
(?:^|/)package(?:-lock|)\.json$
(?:^|/)Pipfile$
(?:^|/)pyproject.toml
(?:^|/)vendor/
(?:^|/|\b)requirements(?:-dev|-doc|-test|)\.txt$
\.a$
\.ai$
\.all-contributorsrc$
\.avi$
\.bmp$
\.bz2$
\.cert?$|\.crt$
\.class$
\.coveragerc$
\.crl$
\.csr$
\.dll$
\.docx?$
\.drawio$
\.DS_Store$
\.eot$
\.eps$
\.exe$
\.gif$
\.git-blame-ignore-revs$
\.gitattributes$
\.gitkeep$
\.graffle$
\.gz$
\.icns$
\.ico$
\.ipynb$
\.jar$
\.jks$
\.jpe?g$
\.key$
\.lib$
\.lock$
\.map$
\.min\..
\.mo$
\.mod$
\.mp[34]$
\.o$
\.ocf$
\.otf$
\.p12$
\.parquet$
\.pdf$
\.pem$
\.pfx$
\.png$
\.psd$
\.pyc$
\.pylintrc$
\.qm$
\.s$
\.sig$
\.so$
\.svgz?$
\.sys$
\.tar$
\.tgz$
\.tiff?$
\.ttf$
\.wav$
\.webm$
\.webp$
\.woff2?$
\.xcf$
\.xlsx?$
\.xpm$
\.xz$
\.zip$
^\.github/actions/spelling/
^\Q.github/FUNDING.yml\E$
^\Q.github/workflows/spelling.yml\E$
^data/crawlers/
^docs/blog/tags\.yml$
^docs/docs/user/known-instances.md$
^docs/manifest/.*$
^docs/static/\.nojekyll$
^internal/glob/glob_test.go$
^internal/honeypot/naive/affirmations\.txt$
^internal/honeypot/naive/spintext\.txt$
^internal/honeypot/naive/titles\.txt$
^lib/config/testdata/bad/unparseable\.json$
^lib/localization/.*_test.go$
^lib/localization/locales/.*\.json$
^lib/policy/config/testdata/bad/unparseable\.json$
^test/.*$
ignore$
robots.txt


================================================
FILE: .github/actions/spelling/expect.txt
================================================
acs
Actorified
actorifiedstore
actorify
agentic
Aibrew
alibaba
alrest
amazonbot
anexia
anthro
anubis
anubistest
apnic
APNICRANDNETAU
Applebot
archlinux
arpa
asnc
asnchecker
asns
aspirational
atuin
azuretools
badregexes
bbolt
bdba
berr
bezier
bingbot
Bitcoin
bitrate
Bluesky
blueskybot
boi
Bokm
botnet
botstopper
BPort
Brightbot
broked
buildah
byteslice
Bytespider
cachebuster
cachediptoasn
Caddyfile
caninetools
Cardyb
celchecker
celphase
cerr
certresolver
cespare
CGNAT
cgr
chainguard
chall
challengemozilla
challengetest
checkpath
checkresult
chibi
cidranger
ckie
CLAUDE
cloudflare
cloudsolutions
Codespaces
confd
containerbuild
containerregistry
coreutils
Cotoyogi
Cromite
crt
Cscript
daemonizing
databento
dayjob
dco
DDOS
Debian
debrpm
decaymap
devcontainers
Diffbot
discordapp
discordbot
distros
dnf
dnsbl
dnserr
DNSTTL
domainhere
dracula
dronebl
droneblresponse
dropin
dsilence
duckduckbot
eerror
ellenjoe
emacs
enbyware
etld
everyones
evilbot
evilsite
expressionorlist
externalagent
externalfetcher
extldflags
facebookgo
Factset
fahedouch
fastcgi
FCr
fcrdns
fediverse
ffprobe
FFXIV
fhdr
financials
finfos
Firecrawl
flagenv
Fordola
forgejo
forwardauth
fsys
fullchain
gaissmai
Galvus
geoip
geoipchecker
gha
GHSA
Ghz
gipc
gitea
GLM
godotenv
goimports
goland
gomod
goodbot
googlebot
gopsutil
govulncheck
goyaml
GPG
GPT
gptbot
Graphene
grpcprom
grw
gzw
Hashcash
hashrate
hdr
headermap
healthcheck
healthz
hec
helpdesk
Hetzner
hmc
homelab
hostable
HSTS
htmlc
htmx
httpdebug
huawei
hypertext
iaskspider
iaso
iat
ifm
Imagesift
imgproxy
impressum
inbox
ingressed
inp
internets
IPTo
iptoasn
isp
iss
isset
ivh
Jenomis
JGit
jhjj
joho
journalctl
jshelter
JWTs
kagi
kagibot
Keyfunc
keypair
KHTML
kinda
KUBECONFIG
lcj
ldflags
letsencrypt
Lexentale
lfc
lgbt
licend
licstart
lightpanda
limsa
Linting
listor
LLU
loadbalancer
lol
lominsa
maintainership
malware
mcr
memes
metarefresh
metrix
mimi
Minfilia
mistralai
mnt
Mojeek
mojeekbot
mozilla
myclient
mymaster
mypass
myuser
nbf
Necron
nepeat
netsurf
nginx
nicksnyder
nikandfor
nobots
NONINFRINGEMENT
nosleep
nullglob
oci
OCOB
ogtag
oklch
omgili
omgilibot
openai
opendns
opengraph
openrc
oswald
pag
pagegen
palemoon
Pangu
parseable
passthrough
Patreon
perplexitybot
pgrep
phrik
pidfile
pids
pipefail
pki
podkova
podman
Postgre
poststart
prebaked
privkey
promauto
promhttp
proofofwork
publicsuffix
purejs
pwcmd
pwuser
qualys
qwant
qwantbot
rac
rawler
rcvar
redhat
redir
redirectscheme
refactors
remoteip
reputational
Rhul
risc
ruleset
runlevels
RUnlock
runtimedir
runtimedirectory
Ryzen
sas
sasl
screenshots
searchbot
searx
sebest
secretplans
Semrush
Seo
setsebool
shellcheck
shirou
shoneypot
shopt
Sidetrade
simprint
sitemap
sls
sni
snipster
Spambot
spammer
sparkline
spyderbot
srcip
srv
stackoverflow
Stargate
startprecmd
stoppostcmd
storetest
strcmp
subgrid
subr
subrequest
SVCNAME
tagline
tarballs
tarrif
taviso
tbn
tbr
techaro
techarohq
telegrambot
templ
templruntime
testarea
Thancred
thoth
thothmock
Tik
Timpibot
TLog
traefik
trunc
txn
uberspace
Unbreak
unbreakdocker
unifiedjs
unmarshal
unparseable
updown
uvx
UXP
valkey
Varis
Velen
vendored
vhosts
vkbot
VKE
vnd
VPS
Vultr
WAIFU
weblate
webmaster
webpage
websecure
websites
Webzio
whois
wildbase
withthothmock
wolfbeast
wordpress
workaround
workdir
wpbot
XCircle
xeiaso
xeserv
xesite
xess
xff
XForwarded
XNG
XOB
XOriginal
XReal
Y'shtola
yae
YAMLTo
Yda
yeet
yeetfile
yourdomain
yyz
Zenos
zizmor
zombocom
zos
zst


================================================
FILE: .github/actions/spelling/line_forbidden.patterns
================================================
# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere
# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529)
# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46)
#\bm_data\b

# Were you debugging using a framework with `fit()`?
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
# you might not want to check in code where you skip all the other tests.
#\bfit\(

# English does not use a hyphen between adverbs and nouns
# https://twitter.com/nyttypos/status/1894815686192685239
(?:^|\s)[A-Z]?[a-z]+ly-(?=[a-z]{3,})(?:[.,?!]?\s|$)

# Don't use `requires that` + `to be`
# https://twitter.com/nyttypos/status/1894816551435641027
\brequires that \w+\b[^.]+to be\b

# A fully parenthetical sentence’s period goes inside the parentheses, not outside.
# https://twitter.com/nyttypos/status/1898844061873639490
#\([A-Z][a-z]{2,}(?: [a-z]+){3,}\)\.\s

# Complete sentences in parentheticals should not have a space before the period.
\s\.\)(?!.*\}\})

# Should be `HH:MM:SS`
\bHH:SS:MM\b

# Should be `86400` (seconds in a standard day)
\b84600\b(?:.*\bday\b)

# Should probably be `2006-01-02` (yyyy-mm-dd)
# Assuming that the time is being passed to https://go.dev/src/time/format.go
\b2006-02-01\b

# Should probably be `YYYYMMDD`
\b[Yy]{4}[Dd]{2}[Mm]{2}(?!.*[Yy]{4}[Dd]{2}[Mm]{2}).*$

# Should be `a priori` or `and prior`
(?i)(?<!posteriori)\sand priori\s

# Should be `a`
\san (?=(?:[b-df-gj-np-rtv-xz]|h(?!our|tml|ttp)|s(?!sh|vg))[a-z])

# Should only be one of `a`, `an`, or `the`
\b(?:(?:an?|the)\s+){2,}\b

# Should only be `are` or `can`, not both
\b(?:(?:are|can)\s+){2,}\b

# Should probably be `ABCDEFGHIJKLMNOPQRSTUVWXYZ`
(?i)(?!ABCDEFGHIJKLMNOPQRSTUVWXYZ)ABC[A-Z]{21}YZ

# Should be `anymore`
\bany more[,.]

# Should be `Ask`
(?:^|[.?]\s+)As\s+[A-Z][a-z]{2,}\s[^.?]*?(?:how|if|wh\w+)\b

# Should be `at one fell swoop`
# and only when talking about killing, not some other completion
# Act 4 Scene 3, Macbeth
# https://www.opensourceshakespeare.org/views/plays/play_view.php?WorkID=macbeth&Act=4&Scene=3&Scope=scene
\bin one fell s[lw]?oop\b

# Should be `'`
(?i)\b(?:(?:i|s?he|they|what|who|you)[`"]ll|(?:are|ca|did|do|does|ha[ds]|have|is|should|were|wo|would)n[`"]t|(?:s?he|let|that|there|what|where|who)[`"]s|(?:i|they|we|what|who|you)[`"]ve)\b

# Should be `background` / `intro text` / `introduction` / `prologue` unless it's a brand or relates to _subterfuge_
(?i)\bpretext\b

# Should be `branches`
# ... unless it's really about the meal that replaces breakfast and lunch.
\b[Bb]runches\b

# Should be `briefcase`
\bbrief-case\b

# Should be `by far` or `far and away`
\bby far and away\b

# Should be `can, not only ..., ... also...`
\bcan not only.*can also\b

# Should be `cannot` (or `can't`)
# See https://www.grammarly.com/blog/cannot-or-can-not/
# > Don't use `can not` when you mean `cannot`. The only time you're likely to see `can not` written as separate words is when the word `can` happens to precede some other phrase that happens to start with `not`.
# > `Can't` is a contraction of `cannot`, and it's best suited for informal writing.
# > In formal writing and where contractions are frowned upon, use `cannot`.
# > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.`
# - if you encounter such a case, add a pattern for that case to patterns.txt.
\b[Cc]an not\b(?! only\b)

# Should be `chart`
(?i)\bhelm\b.*\bchard\b

# Do not use `(click) here` links
# For more information, see:
# * https://www.w3.org/QA/Tips/noClickHere
# * https://webaim.org/techniques/hypertext/link_text
# * https://granicus.com/blog/why-click-here-links-are-bad/
# * https://heyoka.medium.com/dont-use-click-here-f32f445d1021
(?i)(?:>|\[)(?:(?:click |)here|link|(?:read |)more)(?:</|\]\()

# Including "image of" or "picture of" in alt text is unnecessary.
\balt=['"](?:an? |)(?:image|picture) of

# Alt text should be short
\balt=(?:'[^']{126,}'|"[^"]{126,}")

# Should be `equals` to `is equal to`
\bequals to\b

# Should be `ECMA` 262 (JavaScript)
(?i)\bTS\/EMCA\b|\bEMCA(?: \d|\s*Script)|\bEMCA\b(?=.*\bTS\b)

# Should be `ECMA` 340 (Near Field Communications)
(?i)EMCA[- ]340

# Should be `fall back`
\bfallback(?= to)\b

# Should be `GitHub`
(?<![&*.]|// |\b(?:from|import|type) )\bGithub\b(?![{()])

# Should be `GitLab`
(?<![&*.]|// |\b(?:from|import|type) )\bGitlab\b(?![{()])

# Should probably be `https://`...
# Markdown generally doesn't assume that links are to urls
\]\(www\.\w

# Should be `JavaScript`
\bJavascript\b

# Should be `macOS` or `Mac OS X` or ...
\bMacOS\b

# Should be `Microsoft`
\bMicroSoft\b

# Should be `OAuth`
(?:^|[^-/*$])[ '"]oAuth(?: [a-z]|\d+ |[^ a-zA-Z0-9:;_.()])

# Should be `RabbitMQ`
\bRabbitmq\b

# Should be `TensorFlow`
\bTensorflow\b

# Should be `TypeScript`
\bTypescript\b

# Should be `another`
\ban[- ]other(?!-)\b

# Should be `case-(in)sensitive`
\bcase (?:in|)sensitive\b

# Should be `coinciding`
\bco-inciding\b

# Should be `deprecation warning(s)`
\b[Dd]epreciation [Ww]arnings?\b

# Should be `greater than`
\bgreater then\b

# Should be `has`
\b[Ii]t only have\b

# Should be `here-in`, `the`, `them`, `this`, `these` or reworded in some other way
\bthe here(?:\.|,| (?!and|defined))

# Should be `greater than`
\bhigher than\b

# Should be `ID` (unless it's a flag/property)
(?<![-\.])\bId\b(?![(])

# Should be `in front of`
\bin from of\b

# Should be `into`
# when not phrasal and when `in order to` would be wrong:
# https://thewritepractice.com/into-vs-in-to/
\sin to\s(?!if\b)

# Should be `use`
\sin used by\b

# Should be `in-depth` if used as an adjective (but `in depth` when used as an adverb)
\bin depth\s(?!rather\b)\w{6,}

# Should be `in-flight` or `on the fly` (unless actually talking about airline flights)
\bon[- ]flight\b(?!=\s+(?:(?:\w{2}|)\d+|availability|booking|computer|data|delay|departure|management|performance|radar|reservation|scheduling|software|status|ticket|time|type|.*(?:hotel|taxi)))

# Should be `is obsolete`
\bis obsolescent\b

# Should be `it's` or `its`
\bits['’]

# Should be `its`
\bit's(?= own\b)

# Should be `its`
\bit's(?= only purpose\b)

# Should be `for its` (possessive) or `because it is`
\bfor it(?:'s| is)\b

# Should be `log in`
\blogin to the

# Should be `long-standing`
\blong standing\b

# `apt-key` is deprecated
# ... instead you should be writing a pair of files:
# ... * the gpg key added to a distinct key ring file based on your project/distro/key...
# ... * the sources.list in a district file -- not simply appended to `/etc/apt/sources.list` -- (there is a newer format [DEB822](https://manpages.debian.org/bookworm/dpkg-dev/deb822.5.en.html)) that references the gpg key.
# Consider:
# ````sh
# curl http://download.something.example.com/$DISTRO/Release.key | \
#     gpg --dearmor --yes --output /usr/share/keyrings/something-distro.gpg
# echo "deb [signed-by=/usr/share/keyrings/something-distro.gpg] http://download.something.example.com/repositories/home:/$DISTRO ./" \
#     >> /etc/apt/sources.list.d/something-distro.list
# ````
\bapt-key add\b

# Should be `nearby`
\bnear by\b

# Should probably be a person named `Nick` or the abbreviation `NIC`
\bNic\b

# Should be `not supposed`
\bsupposed not\b

# Should probably be `much more`
\bmore much\b

# Should be `perform its`
\bperform it's\b

# Should be `opt-in`
(?<!\scan|for)(?<!\smust)(?<!\sif)\sopt in\s

# Should be `less than`
\bless then\b

# Should be `load balancer`
\b[Ll]oud balancer

# Should be `moot`
\bmute point\b

# Should be `one of`
(?<!-)\bon of\b

# Should be `on the other hand`
\b(?i)on another hand\b

# Reword to `on at runtime` or `enabled at launch`
# The former if you mean it can be changed dynamically.
# The latter if you mean that it can be changed without recompiling but not after the program starts.
\bswitched on runtime\b

# Should be `Of course,`
[?.!]\s+Of course\s(?=[-\w\s]+[.?;!,])

# Most people only have two hands. Reword.
\b(?i)on the third hand\b

# Should be `OpenShift`
\bOpenshift\b

# Should be `otherwise`
\bother[- ]wise\b

# Should be `; otherwise` or `. Otherwise`
# https://study.com/learn/lesson/otherwise-in-a-sentence.html
, [Oo]therwise\b

# Should probably be `Otherwise,`
(?<=\. )Otherwise\s

# Should be `or (more|less)`
\bore (?:more|less)\b

# Should be `rather than`
\brather then\b

# Should be `Red Hat`
\bRed[Hh]at\b

# Should be `regardless, ...` or `regardless of (whether)`
\b[Rr]egardless if you\b

# Should be `self-signed`
\bself signed\b

# Should be `SendGrid`
\bSendgrid\b

# Should be `set up` (`setup` is a noun / `set up` is a verb)
\b[Ss]etup(?= (?:an?|the)\b)

# Should be `state`
\bsate(?=\b|[A-Z])|(?<=[a-z])Sate(?=\b|[A-Z])|(?<=[A-Z]{2})Sate(?=\b|[A-Z])

# Should be `no longer needed`
\bno more needed\b(?! than\b)

# Should be `<see|look> below for the`
(?i)\bfind below the\b

# Should be `then any` unless there's a comparison before the `,`
, than any\b

# Should be `did not exist`
\bwere not existent\b

# Should be `nonexistent`
\bnon existing\b

# Should be `nonexistent`
\b[Nn]o[nt][- ]existent\b

# Should be `our`
\bspending out time\b

# Should be `@brief` / `@details` / `@param` / `@return` / `@retval`
(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b

# Should be `more than` or `more, then`
\bmore then\b

# Should be `Pipeline`/`pipeline`
(?:(?<=\b|[A-Z])p|P)ipeLine(?:\b|(?=[A-Z]))

# Should be `preexisting`
[Pp]re[- ]existing

# Should be `preempt`
[Pp]re[- ]empt\b

# Should be `preemptively`
[Pp]re[- ]emptively

# Should be `prepopulate`
[Pp]re[- ]populate

# Should be `prerequisite`
[Pp]re[- ]requisite

# Should be `recently changed` or `recent changes`
[Rr]ecent changed

# Should be `reentrancy`
[Rr]e[- ]entrancy

# Should be `reentrant`
[Rr]e[- ]entrant

# Should be `room for`
\brooms for (?!lease|rent|sale)

# Should be `socioeconomic`
# https://dictionary.cambridge.org/us/dictionary/english/socioeconomic
socio-economic

# Should be `strong suit`
\b(?:my|his|her|their) strong suite\b

# Should probably be `temperatures` unless actually talking about thermal drafts (things birds may fly on)
\bthermals\b

# Should be `there are` or `they are` (or `they're`)
(?i)\btheir are\b

# Should be `understand`
\bunder stand\b

# Should be `URI` or `uri` unless it refers to a person named `Uri` (or a flag)
(?<![-\.])\bUri\b(?![(])

# Should be `it uses is`
/\bis uses is\b/

# Should be `uses it as`
(?:^|\. |and )uses is as (?!an?\b|follows|livestock|[^.]+\s+as\b)

# Should be `was`
\bhas been(?= removed in v?\d)

# Should be `where`
\bwere they are\b

# Should be `why`
, way(?= is [^.]*\?)

# should be `vCenter`
\bV[Cc]enter\b

# Should be `VM`
\bVm\b

# Should be `walkthrough(s)`
\bwalk-throughs?\b

# Should be `we'll`
\bwe 'll\b

# Should be `whereas`
\bwhere as\b

# Should be `WinGet`
\bWinget\b

# Should be `without` (unless `out` is a modifier of the next word)
\bwith out\b(?!-)

# Should be `work around`
\b[Ww]orkaround(?= an?\b)

# Should be `workarounds`
\bwork[- ]arounds\b

# Should be `workaround`
(?:(?:[Aa]|[Tt]he|ugly)\swork[- ]around\b|\swork[- ]around\s+for)

# Should be `worst`
(?i)worse-case

# Should be `you are not` or reworded
\byour not\b

# Should be `(coarse|fine)-grained`
\b(?:coarse|fine) grained\b

# Homoglyph (Cyrillic) should be `A`/`B`/`C`/`E`/`H`/`I`/`I`/`J`/`K`/`M`/`O`/`P`/`S`/`T`/`Y`
# It's possible that your content is intentionally mixing Cyrillic and Latin scripts, but if it isn't, you definitely want to correct this.
(?<=[A-Z]{2})[АВСЕНІӀЈКМОРЅТУ]|[АВСЕНІӀЈКМОРЅТУ](?=[A-Z]+(?:\b|[a-z]+)|[a-z]+(?:[^a-z]|$))

# Homoglyph (Cyrillic) should be `a`/`b`/`c`/`e`/`o`/`p`/`x`/`y`
# It's possible that your content is intentionally mixing Cyrillic and Latin scripts, but if it isn't, you definitely want to correct this.
[авсеорху](?=[A-Za-z]{2,})|(?<=[A-Za-z]{2})[авсеорху]|(?<=[A-Za-z])[авсеорху](?=[A-Za-z])

# Should be `neither/nor` -- or reword
(?!<do )\bnot\b([^.?!"/(](?!neither|,.*?,))+\bnor\b

# Should be `neither/nor` (plus rewording the beginning)
# This is probably a double negative...
\bnot\b[^.?!"/(]*\bneither\b[^.?!"/(]*\bnor\b

# In English, duplicated words are generally mistakes
# There are a few exceptions (e.g. "that that").
# If the highlighted doubled word pair is in:
# * code, write a pattern to mask it.
# * prose, have someone read the English before you dismiss this error.
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s


================================================
FILE: .github/actions/spelling/patterns.txt
================================================
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns

# Automatically suggested patterns

# hit-count: 198 file-count: 52
# https/http/file urls
(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|]

# hit-count: 22 file-count: 8
# GitHub actions
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+

# hit-count: 19 file-count: 5
# libraries
(?:\b|_)[Ll]ib(?:re(?=office)|era(?![lt])|)(?!ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])

# hit-count: 17 file-count: 8
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))

# hit-count: 15 file-count: 7
# container images
image: [-\w./:@]+

# hit-count: 14 file-count: 9
# imports
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+

# hit-count: 11 file-count: 2
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b

# hit-count: 8 file-count: 5
# node packages
(["'])@[^/'" ]+/[^/'" ]+\g{-1}

# hit-count: 5 file-count: 2
# css fonts
\bfont(?:-family|):[^;}]+

# hit-count: 4 file-count: 4
# set arguments
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*

# hit-count: 4 file-count: 2
# css url wrappings
\burl\([^)]+\)

# hit-count: 2 file-count: 2
# C network byte conversions
(?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\()

# hit-count: 2 file-count: 1
# GitHub SHA refs
\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]*

# hit-count: 1 file-count: 1
# copyright
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+

# hit-count: 1 file-count: 1
# IPv6
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b

# hit-count: 1 file-count: 1
# Docker images
^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|)

# hit-count: 1 file-count: 1
# perl run
perl(?:\s+-[a-zA-Z]\w*)+

# hit-count: 1 file-count: 1
# go install
go install(?:\s+[a-z]+\.[-@\w/.]+)+

# hit-count: 1 file-count: 1
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
# printf markers
(?<!\\)\\[nrt](?=[a-z]{2,})

# hit-count: 1 file-count: 1
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+

# Questionably acceptable forms of `in to`
# Personally, I prefer `log into`, but people object
# https://www.tprteaching.com/log-into-log-in-to-login/
\b(?:(?:[Ll]og(?:g(?=[a-z])|)|[Ss]ign)(?:ed|ing)?) in to\b

# to opt in
\bto opt in\b

# pass(ed|ing) in
\bpass(?:ed|ing) in\b

# acceptable duplicates
# ls directory listings
[-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+[.\d]+(?:[KMGT]|)\s+
# mount
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
# C types and repeated CSS values
\s(auto|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
# C enum and struct
\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
# go templates
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
# doxygen / javadoc / .net
(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s

# macOS file path
(?:Contents\W+|(?!iOS)/)MacOS\b

# Python package registry has incorrect spelling for macOS / Mac OS X
"Operating System :: MacOS :: MacOS X"

# "company" in Germany
\bGmbH\b

# IntelliJ
\bIntelliJ\b

# Commit message -- Signed-off-by and friends
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$

# Autogenerated revert commit message
^This reverts commit [0-9a-f]{40}\.$

# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b

# hit-count: 1 file-count: 1
# microsoft
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*

# hit-count: 1 file-count: 1
# data url
\bdata:[-a-zA-Z=;:/0-9+]*,\S*

================================================
FILE: .github/actions/spelling/reject.txt
================================================
^attache$
^bellows?$
benefitting
occurences?
^dependan.*
^develope$
^developement$
^developpe
^Devers?$
^devex
^devide
^Devinn?[ae]
^devisal
^devisor
^diables?$
^oer$
Sorce
^[Ss]pae.*
^Teh$
^untill$
^untilling$
^venders?$
^wether.*


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
    groups:
      github-actions:
        patterns:
          - "*"
    cooldown:
      default-days: 7

  - package-ecosystem: gomod
    directory: /
    schedule:
      interval: weekly
    groups:
      gomod:
        patterns:
          - "*"
    cooldown:
      default-days: 7

  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    groups:
      npm:
        patterns:
          - "*"
    cooldown:
      default-days: 7


================================================
FILE: .github/workflows/asset-verification.yml
================================================
name: Asset Build Verification

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  asset_verification:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: build essential
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "1.25.7"

      - name: install node deps
        run: |
          npm ci

      - name: Check for uncommitted changes before asset build
        id: check-changes-before
        run: |
          if [[ -n $(git status --porcelain) ]]; then
            echo "has_changes=true" >> $GITHUB_OUTPUT
          else
            echo "has_changes=false" >> $GITHUB_OUTPUT
          fi

      - name: Fail if there are uncommitted changes before build
        if: steps.check-changes-before.outputs.has_changes == 'true'
        run: |
          echo "There are uncommitted changes before running npm run assets"
          git status
          exit 1

      - name: Run asset build
        run: |
          npm run assets

      - name: Check for uncommitted changes after asset build
        id: check-changes-after
        run: |
          if [[ -n $(git status --porcelain) ]]; then
            echo "has_changes=true" >> $GITHUB_OUTPUT
          else
            echo "has_changes=false" >> $GITHUB_OUTPUT
          fi

      - name: Fail if assets generated changes
        if: steps.check-changes-after.outputs.has_changes == 'true'
        run: |
          echo "npm run assets generated uncommitted changes. This indicates the repository has outdated generated files."
          echo "Please run 'npm run assets' locally and commit the changes."
          git status
          git diff
          exit 1


================================================
FILE: .github/workflows/dco-check.yaml
================================================
name: DCO Check

on: [pull_request]

jobs:
  dco_check:
    runs-on: ubuntu-latest
    steps:
      - uses: tisonkun/actions-dco@f1024cd563550b5632e754df11b7d30b73be54a5 # v1.1


================================================
FILE: .github/workflows/docker-pr.yml
================================================
name: Docker image builds (pull requests)

on:
  pull_request:
    branches: ["main"]

env:
  DOCKER_METADATA_SET_OUTPUT_ENV: "true"

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-tags: true
          fetch-depth: 0
          persist-credentials: false

      - name: build essential
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: ghcr.io/${{ github.repository }}

      - name: Build and push
        id: build
        run: |
          npm ci
          npm run container
        env:
          PULL_REQUEST_ID: ${{ github.event.number }}
          DOCKER_REPO: ghcr.io/${{ github.repository }}
          SLOG_LEVEL: debug

      - run: |
          echo "Test this with:"
          echo "docker pull ${DOCKER_IMAGE}"
        env:
          DOCKER_IMAGE: ${{ steps.build.outputs.docker_image }}


================================================
FILE: .github/workflows/docker.yml
================================================
name: Docker image builds

on:
  workflow_dispatch:
  push:
    branches: ["main"]
    tags: ["v*"]

env:
  DOCKER_METADATA_SET_OUTPUT_ENV: "true"

permissions:
  contents: read
  packages: write
  attestations: write
  id-token: write
  pull-requests: write

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-tags: true
          fetch-depth: 0
          persist-credentials: false

      - name: build essential
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential

      - name: Set lowercase image name
        run: |
          echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9

      - name: Log into registry
        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: ${{ env.IMAGE }}

      - name: Build and push
        id: build
        run: |
          npm ci
          npm run container
        env:
          DOCKER_REPO: ${{ env.IMAGE }}
          SLOG_LEVEL: debug

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
        with:
          subject-name: ${{ env.IMAGE }}
          subject-digest: ${{ steps.build.outputs.digest }}
          push-to-registry: true


================================================
FILE: .github/workflows/docs-deploy.yml
================================================
name: Docs deploy

on:
  workflow_dispatch:
  push:
    branches: ["main"]

permissions:
  contents: read
  packages: write
  attestations: write
  id-token: write

jobs:
  build:
    if: github.repository == 'TecharoHQ/anubis'
    runs-on: ubuntu-24.04

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

      - name: Log into registry
        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
        with:
          registry: ghcr.io
          username: techarohq
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: ghcr.io/techarohq/anubis/docs
          tags: |
            type=sha,enable=true,priority=100,prefix=,suffix=,format=long
            main

      - name: Build and push
        id: build
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
        with:
          context: ./docs
          cache-to: type=gha
          cache-from: type=gha
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          platforms: linux/amd64
          push: true

      - name: Apply k8s manifests to limsa lominsa
        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@5ada4e2c02eacc03978c2437e95c8b0f979a9619 # v1.35.2
        env:
          KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
        with:
          args: rollout restart -n default deploy/anubis-docs


================================================
FILE: .github/workflows/docs-test.yml
================================================
name: Docs test build

on:
  pull_request:
    branches: ["main"]

permissions:
  contents: read
  actions: write

jobs:
  build:
    runs-on: ubuntu-24.04

    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
        with:
          images: ghcr.io/techarohq/anubis/docs
          tags: |
            type=sha,enable=true,priority=100,prefix=,suffix=,format=long
            main

      - name: Build and push
        id: build
        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
        with:
          context: ./docs
          cache-to: type=gha
          cache-from: type=gha
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          platforms: linux/amd64
          push: false


================================================
FILE: .github/workflows/go-mod-tidy-check.yml
================================================
name: Go Mod Tidy Check

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  go_mod_tidy_check:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - name: Check go.mod and go.sum in main directory
        run: |
          # Store original file state
          cp go.mod go.mod.orig
          cp go.sum go.sum.orig

          # Run go mod tidy
          go mod tidy

          # Check if files changed
          if ! diff -q go.mod.orig go.mod > /dev/null 2>&1; then
            echo "ERROR: go.mod in main directory has changed after running 'go mod tidy'"
            echo "Please run 'go mod tidy' locally and commit the changes"
            diff go.mod.orig go.mod
            exit 1
          fi

          if ! diff -q go.sum.orig go.sum > /dev/null 2>&1; then
            echo "ERROR: go.sum in main directory has changed after running 'go mod tidy'"
            echo "Please run 'go mod tidy' locally and commit the changes"
            diff go.sum.orig go.sum
            exit 1
          fi

          echo "SUCCESS: go.mod and go.sum in main directory are tidy"

      - name: Check go.mod and go.sum in test directory
        run: |
          cd test

          # Store original file state
          cp go.mod go.mod.orig
          cp go.sum go.sum.orig

          # Run go mod tidy
          go mod tidy

          # Check if files changed
          if ! diff -q go.mod.orig go.mod > /dev/null 2>&1; then
            echo "ERROR: go.mod in test directory has changed after running 'go mod tidy'"
            echo "Please run 'go mod tidy' locally and commit the changes"
            diff go.mod.orig go.mod
            exit 1
          fi

          if ! diff -q go.sum.orig go.sum > /dev/null 2>&1; then
            echo "ERROR: go.sum in test directory has changed after running 'go mod tidy'"
            echo "Please run 'go mod tidy' locally and commit the changes"
            diff go.sum.orig go.sum
            exit 1
          fi

          echo "SUCCESS: go.mod and go.sum in test directory are tidy"


================================================
FILE: .github/workflows/go.yml
================================================
name: Go

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read
  actions: write

jobs:
  go_tests:
    #runs-on: alrest-techarohq
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: build essential
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - name: Cache playwright binaries
        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
        id: playwright-cache
        with:
          path: |
            ~/.cache/ms-playwright
          key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}

      - name: install node deps
        run: |
          npm ci

      - name: install playwright browsers
        run: |
          npx --no-install playwright@1.52.0 install --with-deps
          npx --no-install playwright@1.52.0 run-server --port 9001 &

      - name: Build
        run: npm run build

      - name: Test
        run: npm run test

      - name: Lint with staticcheck
        uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431 # v1.4.1
        with:
          version: "latest"

      - name: Govulncheck
        run: |
          go tool govulncheck ./... ||:


================================================
FILE: .github/workflows/lint-pr-title.yaml
================================================
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 }}


================================================
FILE: .github/workflows/package-builds-stable.yml
================================================
name: Package builds (stable)

on:
  workflow_dispatch:
  # release:
  #   types: [published]

permissions:
  contents: write
  actions: write

jobs:
  package_builds:
    #runs-on: alrest-techarohq
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
          fetch-tags: true
          fetch-depth: 0

      - name: build essential
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - name: install node deps
        run: |
          npm ci

      - name: Build Packages
        run: |
          go tool yeet

      - name: Upload released artifacts
        env:
          GITHUB_TOKEN: ${{ github.TOKEN }}
          RELEASE_VERSION: ${{github.event.release.tag_name}}
        shell: bash
        run: |
          RELEASE="${RELEASE_VERSION}"
          cd var
          for file in *; do
            gh release upload $RELEASE $file
          done


================================================
FILE: .github/workflows/package-builds-unstable.yml
================================================
name: Package builds (unstable)

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read
  actions: write

jobs:
  package_builds:
    #runs-on: alrest-techarohq
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
          fetch-tags: true
          fetch-depth: 0

      - name: build essential
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - name: install node deps
        run: |
          npm ci

      - name: Build Packages
        run: |
          go tool yeet

      - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: packages
          path: var/*


================================================
FILE: .github/workflows/smoke-tests.yml
================================================
name: Smoke tests

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  smoke-test:
    strategy:
      matrix:
        test:
          - default-config-macro
          - docker-registry
          - double_slash
          - forced-language
          - git-clone
          - git-push
          - healthcheck
          - i18n
          - log-file
          - nginx
          - palemoon/amd64
          #- palemoon/i386
          - robots_txt
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: "24.11.0"
      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9

      - name: Install utils
        run: |
          go install ./utils/cmd/...

      - name: Run test
        run: |
          cd test/${{ matrix.test }}
          backoff-retry --try-count 10 ./test.sh

      - name: Sanitize artifact name
        if: always()
        run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV

      - name: Upload artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
        if: always()
        with:
          name: ${{ env.ARTIFACT_NAME }}
          path: test/${{ matrix.test }}/var


================================================
FILE: .github/workflows/spelling.yml
================================================
name: Check Spelling

# Comment management is handled through a secondary job, for details see:
# https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions
#
# `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment
#   (in odd cases, it might actually run just to collapse a comment, but that's fairly rare)
#   it needs `contents: write` in order to add a comment.
#
# `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment
#   or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment)
#   it needs `pull-requests: write` in order to manipulate those comments.

# Updating pull request branches is managed via comment handling.
# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list
#
# These elements work together to make it happen:
#
# `on.issue_comment`
#   This event listens to comments by users asking to update the metadata.
#
# `jobs.update`
#   This job runs in response to an issue_comment and will push a new commit
#   to update the spelling metadata.
#
# `with.experimental_apply_changes_via_bot`
#   Tells the action to support and generate messages that enable it
#   to make a commit to update the spelling metadata.
#
# `with.ssh_key`
#   In order to trigger workflows when the commit is made, you can provide a
#   secret (typically, a write-enabled github deploy key).
#
#   For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key

# SARIF reporting
#
# Access to SARIF reports is generally restricted (by GitHub) to members of the repository.
#
# Requires enabling `security-events: write`
# and configuring the action with `use_sarif: 1`
#
#   For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-SARIF-output

# Minimal workflow structure:
#
# on:
#   push:
#     ...
#   pull_request_target:
#     ...
# jobs:
#   # you only want the spelling job, all others should be omitted
#   spelling:
#     # remove `security-events: write` and `use_sarif: 1`
#     # remove `experimental_apply_changes_via_bot: 1`
#     ... otherwise adjust the `with:` as you wish

on:
  push:
    branches:
      - "**"
    tags-ignore:
      - "**"
  pull_request:
    branches:
      - "**"
    types:
      - "opened"
      - "reopened"
      - "synchronize"

jobs:
  spelling:
    name: Check Spelling
    permissions:
      contents: read
      pull-requests: read
      actions: read
      security-events: write
    outputs:
      followup: ${{ steps.spelling.outputs.followup }}
    runs-on: ubuntu-latest
    if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
    concurrency:
      group: spelling-${{ github.event.pull_request.number || github.ref }}
      # note: If you use only_check_changed_files, you do not want cancel-in-progress
      cancel-in-progress: true
    steps:
      - name: check-spelling
        id: spelling
        uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25
        with:
          suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
          checkout: true
          check_file_names: 1
          post_comment: 0
          use_magic_file: 1
          warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check,unclosed-block-ignore-begin,unclosed-block-ignore-end
          use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }}
          check_extra_dictionaries: ""
          dictionary_source_prefixes: >
            {
            "cspell": "https://raw.githubusercontent.com/check-spelling/cspell-dicts/v20241114/dictionaries/"
            }
          extra_dictionaries: |
            cspell:software-terms/softwareTerms.txt
            cspell:golang/go.txt
            cspell:npm/npm.txt
            cspell:k8s/k8s.txt
            cspell:python/python/python-lib.txt
            cspell:aws/aws.txt
            cspell:node/node.txt
            cspell:html/html.txt
            cspell:filetypes/filetypes.txt
            cspell:python/common/extra.txt
            cspell:docker/docker-words.txt
            cspell:fullstack/fullstack.txt


================================================
FILE: .github/workflows/ssh-ci-runner-cron.yml
================================================
name: Regenerate ssh ci runner image

on:
  # pull_request:
  #   branches: ["main"]
  schedule:
    - cron: "0 0 1,8,15,22 * *"
  workflow_dispatch:

permissions:
  pull-requests: write
  contents: write
  packages: write

jobs:
  ssh-ci-rebuild:
    if: github.repository == 'TecharoHQ/anubis'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        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@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@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
      - name: Build and push
        run: |
          cd ./test/ssh-ci
          docker buildx bake --push


================================================
FILE: .github/workflows/ssh-ci.yml
================================================
name: SSH CI

on:
  push:
    branches: ["main"]
  # pull_request:
  #   branches: ["main"]

permissions:
  contents: read

jobs:
  ssh:
    if: github.repository == 'TecharoHQ/anubis'
    #runs-on: alrest-techarohq
    runs-on: ubuntu-latest
    strategy:
      matrix:
        host:
          - riscv64
          - ppc64le
          #- aarch64-4k
          #- aarch64-16k
    steps:
      - name: Checkout code
        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@6b84f2e793b32fa0b03a379cadadec75cc539391 # v2.8.0
        with:
          key: ${{ secrets.CI_SSH_KEY }}
          name: id_rsa
          known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}

      - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
        with:
          go-version: "stable"

      - name: Run CI
        run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
        env:
          GITHUB_RUN_ID: ${{ github.run_id }}


================================================
FILE: .github/workflows/zizmor.yml
================================================
name: zizmor

on:
  push:
    paths:
      - ".github/workflows/*.ya?ml"
  pull_request:
    paths:
      - ".github/workflows/*.ya?ml"

jobs:
  zizmor:
    name: zizmor latest via PyPI
    runs-on: ubuntu-24.04
    permissions:
      security-events: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Install the latest version of uv
        uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0

      - name: Run zizmor 🌈
        run: uvx zizmor --format sarif . > results.sarif
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Upload SARIF file
        uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
        with:
          sarif_file: results.sarif
          category: zizmor


================================================
FILE: .github/zizmor.yml
================================================
rules:
  unpinned-uses:
    config:
      policies:
        Homebrew/actions/*: any


================================================
FILE: .gitignore
================================================
.env
*.deb
*.rpm

# Additional package locks
pnpm-lock.yaml
yarn.lock

# Go binaries and test artifacts
main
*.test

node_modules

# MacOS
.DS_store

# Intellij
.idea

# how does this get here
doc/VERSION

web/static/locales/*.json

================================================
FILE: .husky/commit-msg
================================================
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


================================================
FILE: .husky/pre-commit
================================================
npm run lint
npm run test


================================================
FILE: .ko.yaml
================================================
defaultBaseImage: cgr.dev/chainguard/static
defaultPlatforms:
  - 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}}


================================================
FILE: .prettierignore
================================================
lib/config/testdata/bad/*
*.inc
AGENTS.md
CLAUDE.md

================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "esbenp.prettier-vscode",
    "ms-azuretools.vscode-containers",
    "golang.go",
    "unifiedjs.vscode-mdx",
    "a-h.templ",
    "redhat.vscode-yaml",
    "streetsidesoftware.code-spell-checker"
  ]
}


================================================
FILE: .vscode/launch.json
================================================
{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch Package",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "program": "${fileDirname}"
    },
    {
      "name": "Anubis [dev]",
      "command": "npm run dev",
      "request": "launch",
      "type": "node-terminal"
    },
    {
      "name": "Start Docs",
      "command": "cd docs && npm ci && npm run start",
      "request": "launch",
      "type": "node-terminal"
    }
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "github.copilot.enable": {
    "*": false,
    "plaintext": false,
    "markdown": false,
    "mdx": false,
    "json": false,
    "scminput": false,
    "yaml": false,
    "go": false,
    "zig": false,
    "javascript": false,
    "properties": false
  },
  "[markdown]": {
    "editor.wordWrap": "wordWrapColumn",
    "editor.wordWrapColumn": 80,
    "editor.wordBasedSuggestions": "off"
  },
  "[mdx]": {
    "editor.wordWrap": "wordWrapColumn",
    "editor.wordWrapColumn": 80,
    "editor.wordBasedSuggestions": "off"
  },
  "[nunjucks]": {
    "editor.wordWrap": "wordWrapColumn",
    "editor.wordWrapColumn": 80,
    "editor.wordBasedSuggestions": "off"
  },
  "cSpell.enabledFileTypes": {
    "mdx": true,
    "md": true
  }
}


================================================
FILE: AGENTS.md
================================================
# 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
<type>[optional scope]: <description>

[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/`.

================================================
FILE: Brewfile
================================================
# programming languages
brew "go@1.24"
brew "node"
brew "ko"
brew "esbuild"
brew "zstd"
brew "brotli"

================================================
FILE: CLAUDE.md
================================================
@AGENTS.md
@CONTRIBUTING.md


================================================
FILE: CONTRIBUTING.md
================================================
# 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
<type>[optional scope]: <description>

[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/`.


================================================
FILE: LICENSE
================================================
Copyright (c) 2025 Xe Iaso <me@xeiaso.net>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Makefile
================================================
VERSION= $(shell cat ./VERSION)
GO?= go
NPM?= npm

.PHONY: build assets deps lint prebaked-build test

all: build

deps:
	$(NPM) ci
	$(GO) mod download

assets: PATH:=$(PWD)/node_modules/.bin:$(PATH)
assets: deps
	$(GO) generate ./...
	./web/build.sh
	./xess/build.sh

build: assets
	$(GO) build -o ./var/anubis ./cmd/anubis
	$(GO) build -o ./var/robots2policy ./cmd/robots2policy
	@echo "Anubis is now built to ./var/anubis"

lint: assets
	$(GO) vet ./...
	$(GO) tool staticcheck ./...
	
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

test: assets
	$(GO) test ./...


================================================
FILE: README.md
================================================
# Anubis

<center>
<img width=256 src="./web/static/img/happy.webp" alt="A smiling chibi dark-skinned anthro jackal with brown hair and tall ears looking victorious with a thumbs-up" />
</center>

![enbyware](https://pride-badges.pony.workers.dev/static/v1?label=enbyware&labelColor=%23555&stripeWidth=8&stripeColors=FCF434%2CFFFFFF%2C9C59D1%2C2C2C2C)
![GitHub Issues or Pull Requests by label](https://img.shields.io/github/issues/TecharoHQ/anubis)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/TecharoHQ/anubis)
![language count](https://img.shields.io/github/languages/count/TecharoHQ/anubis)
![repo size](https://img.shields.io/github/repo-size/TecharoHQ/anubis)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/Xe)](https://github.com/sponsors/Xe)

## Sponsors

Anubis is brought to you by sponsors and donors like:

### Diamond Tier

<a href="https://www.raptorcs.com/content/base/products.html">
  <img src="./docs/static/img/sponsors/raptor-computing-logo.webp" alt="Raptor Computing Systems" height=64 />
</a>
<a href="https://databento.com/?utm_source=anubis&utm_medium=sponsor&utm_campaign=anubis">
  <img src="./docs/static/img/sponsors/databento-logo.webp" alt="Databento" height="64" />
</a>

### Gold Tier

<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
  <img src="./docs/static/img/sponsors/unipromos.webp" alt="Unipromos" height="64" />
</a>
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
  <img src="./docs/static/img/sponsors/uvensys.webp" alt="Uvensys" height="64">
</a>
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
  <img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
</a>
<a href="https://about.gitea.com?utm_campaign=github&utm_medium=referral&utm_content=anubis">
  <img src="./docs/static/img/sponsors/gitea-logo.webp" alt="Gitea" height="64">
</a>
<a href="https://prolocation.net?utm_campaign=github&utm_medium=referral&utm_content=anubis">
  <img src="./docs/static/img/sponsors/prolocation-logo.svg" alt="Prolocation" height="64">
</a>
<a href="https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis&utm_source=abgh">
  <img src="./docs/static/img/sponsors/terminal-trove.webp" alt="Terminal Trove" height="64">
</a>
<a href="https://canine.tools?utm_campaign=github&utm_medium=referral&utm_content=anubis">
  <img src="./docs/static/img/sponsors/caninetools-logo.webp" alt="canine.tools" height="64">
</a>
<a href="https://weblate.org/">
  <img src="./docs/static/img/sponsors/weblate-logo.webp" alt="Weblate" height="64">
</a>
<a href="https://uberspace.de/">
  <img src="./docs/static/img/sponsors/uberspace-logo.webp" alt="Uberspace" height="64">
</a>
<a href="https://wildbase.xyz/">
  <img src="./docs/static/img/sponsors/wildbase-logo.webp" alt="Wildbase" height="64">
</a>
<a href="https://emma.pet">
  <img
    src="./docs/static/img/sponsors/nepeat-logo.webp"
    alt="Cat eyes over the word Emma in a serif font"
    height="64"
  />
</a>
<a href="https://fabulous.systems/">
  <img
    src="./docs/static/img/sponsors/fabulous-systems.webp"
    alt="Cat eyes over the word Emma in a serif font"
    height="64"
  />
</a>
<a href="https://www.anexia.com/">
  <img src="./docs/static/img/sponsors/anexia-cloudsolutions-logo.webp" alt="ANEXIA Cloud Solutions" height="64">
</a>

## Overview

Anubis is a Web AI Firewall Utility that [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using one or more challenges in order to protect upstream resources from scraper bots.

This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them.

Anubis is a bit of a nuclear response. This will result in your website being blocked from smaller scrapers and may inhibit "good bots" like the Internet Archive. You can configure [bot policy definitions](./docs/docs/admin/policies.mdx) to explicitly allowlist them and we are working on a curated set of "known good" bots to allow for a compromise between discoverability and uptime.

In most cases, you should not need this and can probably get by using Cloudflare to protect a given origin. However, for circumstances where you can't or won't use Cloudflare, Anubis is there for you.

If you want to try this out, visit the Anubis documentation site at [anubis.techaro.lol](https://anubis.techaro.lol).

## Support

If you run into any issues running Anubis, please [open an issue](https://github.com/TecharoHQ/anubis/issues/new?template=Blank+issue). Please include all the information I would need to diagnose your issue.

For live chat, please join the [Patreon](https://patreon.com/cadey) and ask in the Patron discord in the channel `#anubis`.

## Star History

<a href="https://www.star-history.com/#TecharoHQ/anubis&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=TecharoHQ/anubis&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=TecharoHQ/anubis&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=TecharoHQ/anubis&type=Date" />
 </picture>
</a>

## Packaging Status

[![Packaging status](https://repology.org/badge/vertical-allrepos/anubis-anti-crawler.svg?columns=3)](https://repology.org/project/anubis-anti-crawler/versions)

## Contributors

<a href="https://github.com/TecharoHQ/anubis/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=TecharoHQ/anubis" />
</a>

Made with [contrib.rocks](https://contrib.rocks).


================================================
FILE: SECURITY.md
================================================
# Security Policy

Techaro follows the [Semver 2.0 scheme](https://semver.org/).

## Supported Versions

Techaro strives to support the two most recent minor versions of Anubis. Patches to those versions will be published as patch releases.

## Reporting a Vulnerability

Email security@techaro.lol with details on the vulnerability and reproduction steps. You will get a response as soon as possible.

Please take care to send your email as a mixed plaintext and HTML message. Messages with GPG signatures or that are plaintext only may be blocked by the spam filter.


================================================
FILE: VERSION
================================================
1.25.0


================================================
FILE: anubis.go
================================================
// Package anubis contains the version number of Anubis.
package anubis

import "time"

// Version is the current version of Anubis.
//
// This variable is set at build time using the -X linker flag. If not set,
// it defaults to "devel".
var Version = "devel"

// CookieName is the name of the cookie that Anubis uses in order to validate
// access.
var CookieName = "techaro.lol-anubis"

// TestCookieName is the name of the cookie that Anubis uses in order to check
// if cookies are enabled on the client's browser.
var TestCookieName = "techaro.lol-anubis-cookie-verification"

// CookieDefaultExpirationTime is the amount of time before the cookie/JWT expires.
const CookieDefaultExpirationTime = 7 * 24 * time.Hour

// BasePrefix is a global prefix for all Anubis endpoints. Can be emptied to remove the prefix entirely.
var BasePrefix = ""

// PublicUrl is the externally accessible URL for this Anubis instance.
var PublicUrl = ""

// StaticPath is the location where all static Anubis assets are located.
const StaticPath = "/.within.website/x/cmd/anubis/"

// APIPrefix is the location where all Anubis API endpoints are located.
const APIPrefix = "/.within.website/x/cmd/anubis/api/"

// DefaultDifficulty is the default "difficulty" (number of leading zeroes)
// that must be met by the client in order to pass the challenge.
const DefaultDifficulty = 4

// ForcedLanguage is the language being used instead of the one of the request's Accept-Language header
// if being set.
var ForcedLanguage = ""

// UseSimplifiedExplanation can be set to true for using the simplified explanation
var UseSimplifiedExplanation = false


================================================
FILE: cmd/containerbuild/.gitignore
================================================
images

================================================
FILE: cmd/containerbuild/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"log/slog"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/TecharoHQ/anubis/internal"
	"github.com/facebookgo/flagenv"
)

var (
	dockerAnnotations = flag.String("docker-annotations", os.Getenv("DOCKER_METADATA_OUTPUT_ANNOTATIONS"), "Docker image annotations")
	dockerLabels      = flag.String("docker-labels", os.Getenv("DOCKER_METADATA_OUTPUT_LABELS"), "Docker image labels")
	dockerRepo        = flag.String("docker-repo", "registry.int.xeserv.us/techaro/anubis", "Docker image repository for Anubis")
	dockerTags        = flag.String("docker-tags", os.Getenv("DOCKER_METADATA_OUTPUT_TAGS"), "newline separated docker tags including the registry name")
	githubEventName   = flag.String("github-event-name", "", "GitHub event name")
	pullRequestID     = flag.Int("pull-request-id", -1, "GitHub pull request ID")
	slogLevel         = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
)

func main() {
	flagenv.Parse()
	flag.Parse()

	slog.SetDefault(internal.InitSlog(*slogLevel, os.Stderr))

	koDockerRepo := strings.TrimSuffix(*dockerRepo, "/"+filepath.Base(*dockerRepo))

	if *githubEventName == "pull_request" && *pullRequestID != -1 {
		*dockerRepo = fmt.Sprintf("ttl.sh/techaro/pr-%d/anubis", *pullRequestID)
		*dockerTags = fmt.Sprintf("ttl.sh/techaro/pr-%d/anubis:24h", *pullRequestID)
		koDockerRepo = fmt.Sprintf("ttl.sh/techaro/pr-%d", *pullRequestID)

		slog.Info(
			"Building image for pull request",
			"docker-repo", *dockerRepo,
			"docker-tags", *dockerTags,
			"github-event-name", *githubEventName,
			"pull-request-id", *pullRequestID,
		)
	}

	if strings.Contains(*dockerTags, ",") {
		newTags := strings.Join(strings.Split(*dockerTags, ","), "\n")
		dockerTags = &newTags
	}

	setOutput("docker_image", strings.SplitN(*dockerTags, "\n", 2)[0])

	version, err := run("git describe --tags --always --dirty")
	if err != nil {
		log.Fatal(err)
	}

	commitTimestamp, err := run("git log -1 --format='%ct'")
	if err != nil {
		log.Fatal(err)
	}

	slog.Debug(
		"ko env",
		"KO_DOCKER_REPO", koDockerRepo,
		"SOURCE_DATE_EPOCH", commitTimestamp,
		"VERSION", version,
	)

	os.Setenv("KO_DOCKER_REPO", koDockerRepo)
	os.Setenv("SOURCE_DATE_EPOCH", commitTimestamp)
	os.Setenv("VERSION", version)

	setOutput("version", version)

	if *dockerTags == "" {
		log.Fatal("Must set --docker-tags or DOCKER_METADATA_OUTPUT_TAGS")
	}

	images, err := parseImageList(*dockerTags)
	if err != nil {
		log.Fatalf("can't parse images: %v", err)
	}

	for _, img := range images {
		if img.repository != *dockerRepo {
			slog.Error(
				"Something weird is going on. Wanted docker repo differs from contents of --docker-tags. Did a flag get set incorrectly?",
				"wanted", *dockerRepo,
				"got", img.repository,
				"docker-tags", *dockerTags,
			)
			os.Exit(2)
		}
	}

	var tags []string
	for _, img := range images {
		tags = append(tags, img.tag)
	}

	output, err := run(fmt.Sprintf("ko build --platform=all --base-import-paths --tags=%q --image-user=1000 --image-annotation=%q --image-label=%q ./cmd/anubis | tail -n1", strings.Join(tags, ","), *dockerAnnotations, *dockerLabels))
	if err != nil {
		log.Fatalf("can't run ko build, check stderr: %v", err)
	}

	sp := strings.SplitN(output, "@", 2)

	setOutput("digest", sp[1])
}

type image struct {
	repository string
	tag        string
}

func parseImageList(imageList string) ([]image, error) {
	images := strings.Split(imageList, "\n")
	var result []image
	for _, img := range images {
		if img == "" {
			continue
		}

		// reg.xeiaso.net/techaro/anubis:latest
		// repository: reg.xeiaso.net/techaro/anubis
		// tag:        latest
		index := strings.LastIndex(img, ":")
		result = append(result, image{
			repository: img[:index],
			tag:        img[index+1:],
		})
	}

	if len(result) == 0 {
		return nil, fmt.Errorf("no images provided, bad flags")
	}

	return result, nil
}

// run executes a command and returns the trimmed output.
func run(command string) (string, error) {
	bin, err := exec.LookPath("sh")
	if err != nil {
		return "", err
	}
	slog.Debug("running command", "command", command)
	cmd := exec.Command(bin, "-c", command)
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(out)), nil
}

func setOutput(key, val string) {
    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()
}


================================================
FILE: cmd/robots2policy/batch/batch_process.go
================================================
/*
Batch process robots.txt files from archives like https://github.com/nrjones8/robots-dot-txt-archive-bot/tree/master/data/cleaned
into Anubis CEL policies. Usage: go run batch_process.go <directory with robots.txt files>
*/
package main

import (
	"fmt"
	"io/fs"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage: go run batch_process.go <cleaned_directory>")
		fmt.Println("Example: go run batch_process.go ./cleaned")
		os.Exit(1)
	}

	cleanedDir := os.Args[1]
	outputDir := "generated_policies"

	// Create output directory
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		log.Fatalf("Failed to create output directory: %v", err)
	}

	count := 0
	err := filepath.WalkDir(cleanedDir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		// Skip directories
		if d.IsDir() {
			return nil
		}

		// Generate policy name from file path
		relPath, _ := filepath.Rel(cleanedDir, path)
		policyName := strings.ReplaceAll(relPath, "/", "-")
		policyName = strings.TrimSuffix(policyName, "-robots.txt")
		policyName = strings.ReplaceAll(policyName, ".", "-")

		outputFile := filepath.Join(outputDir, policyName+".yaml")

		cmd := exec.Command("go", "run", "main.go",
			"-input", path,
			"-output", outputFile,
			"-name", policyName,
			"-format", "yaml")

		if err := cmd.Run(); err != nil {
			fmt.Printf("Warning: Failed to process %s: %v\n", path, err)
			return nil // Continue processing other files
		}

		count++
		if count%100 == 0 {
			fmt.Printf("Processed %d files...\n", count)
		} else if count%10 == 0 {
			fmt.Print(".")
		}

		return nil
	})

	if err != nil {
		log.Fatalf("Error walking directory: %v", err)
	}

	fmt.Printf("Successfully processed %d robots.txt files\n", count)
	fmt.Printf("Generated policies saved to: %s/\n", outputDir)
}


================================================
FILE: cmd/robots2policy/main.go
================================================
package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"slices"
	"strings"

	"github.com/TecharoHQ/anubis/lib/config"

	"sigs.k8s.io/yaml"
)

var (
	inputFile     = flag.String("input", "", "path to robots.txt file (use - for stdin)")
	outputFile    = flag.String("output", "", "output file path (use - for stdout, defaults to stdout)")
	outputFormat  = flag.String("format", "yaml", "output format: yaml or json")
	baseAction    = flag.String("action", "CHALLENGE", "default action for disallowed paths: ALLOW, DENY, CHALLENGE, WEIGH")
	crawlDelay    = flag.Int("crawl-delay-weight", 0, "if > 0, add weight adjustment for crawl-delay (difficulty adjustment)")
	policyName    = flag.String("name", "robots-txt-policy", "name for the generated policy")
	userAgentDeny = flag.String("deny-user-agents", "DENY", "action for specifically blocked user agents: DENY, CHALLENGE")
	helpFlag      = flag.Bool("help", false, "show help")
)

type RobotsRule struct {
	UserAgents  []string
	Disallows   []string
	Allows      []string
	CrawlDelay  int
	IsBlacklist bool // true if this is a specifically denied user agent
}

type AnubisRule struct {
	Expression *config.ExpressionOrList `yaml:"expression,omitempty" json:"expression,omitempty"`
	Challenge  *config.ChallengeRules   `yaml:"challenge,omitempty" json:"challenge,omitempty"`
	Weight     *config.Weight           `yaml:"weight,omitempty" json:"weight,omitempty"`
	Name       string                   `yaml:"name" json:"name"`
	Action     string                   `yaml:"action" json:"action"`
}

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "%s [options] -input <robots.txt>\n\n", os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintln(os.Stderr, "\nExamples:")
		fmt.Fprintln(os.Stderr, "  # Convert local robots.txt file")
		fmt.Fprintln(os.Stderr, "  robots2policy -input robots.txt -output policy.yaml")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "  # Convert from URL")
		fmt.Fprintln(os.Stderr, "  robots2policy -input https://example.com/robots.txt -format json")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "  # Read from stdin, write to stdout")
		fmt.Fprintln(os.Stderr, "  curl https://example.com/robots.txt | robots2policy -input -")
		os.Exit(2)
	}
}

func main() {
	flag.Parse()

	if len(flag.Args()) > 0 || *helpFlag || *inputFile == "" {
		flag.Usage()
	}

	// Read robots.txt
	var input io.Reader
	if *inputFile == "-" {
		input = os.Stdin
	} else if strings.HasPrefix(*inputFile, "http://") || strings.HasPrefix(*inputFile, "https://") {
		resp, err := http.Get(*inputFile)
		if err != nil {
			log.Fatalf("failed to fetch robots.txt from URL: %v", err)
		}
		defer resp.Body.Close()
		input = resp.Body
	} else {
		file, err := os.Open(*inputFile)
		if err != nil {
			log.Fatalf("failed to open input file: %v", err)
		}
		defer file.Close()
		input = file
	}

	// Parse robots.txt
	rules, err := parseRobotsTxt(input)
	if err != nil {
		log.Fatalf("failed to parse robots.txt: %v", err)
	}

	// Convert to Anubis rules
	anubisRules := convertToAnubisRules(rules)

	// Check if any rules were generated
	if len(anubisRules) == 0 {
		log.Fatal("no valid rules generated from robots.txt - file may be empty or contain no disallow directives")
	}

	// Generate output
	var output []byte
	switch strings.ToLower(*outputFormat) {
	case "yaml":
		output, err = yaml.Marshal(anubisRules)
	case "json":
		output, err = json.MarshalIndent(anubisRules, "", "  ")
	default:
		log.Fatalf("unsupported output format: %s (use yaml or json)", *outputFormat)
	}

	if err != nil {
		log.Fatalf("failed to marshal output: %v", err)
	}

	// Write output
	if *outputFile == "" || *outputFile == "-" {
		fmt.Print(string(output))
	} else {
		err = os.WriteFile(*outputFile, output, 0644)
		if err != nil {
			log.Fatalf("failed to write output file: %v", err)
		}
		fmt.Printf("Generated Anubis policy written to %s\n", *outputFile)
	}
}

func createRuleFromAccumulated(userAgents, disallows, allows []string, crawlDelay int) RobotsRule {
	rule := RobotsRule{
		UserAgents: make([]string, len(userAgents)),
		Disallows:  make([]string, len(disallows)),
		Allows:     make([]string, len(allows)),
		CrawlDelay: crawlDelay,
	}
	copy(rule.UserAgents, userAgents)
	copy(rule.Disallows, disallows)
	copy(rule.Allows, allows)
	return rule
}

func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
	scanner := bufio.NewScanner(input)
	var rules []RobotsRule
	var currentUserAgents []string
	var currentDisallows []string
	var currentAllows []string
	var currentCrawlDelay int

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		// Skip empty lines and comments
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		// Split on first colon
		parts := strings.SplitN(line, ":", 2)
		if len(parts) != 2 {
			continue
		}

		directive := strings.TrimSpace(strings.ToLower(parts[0]))
		value := strings.TrimSpace(parts[1])

		switch directive {
		case "user-agent":
			// If we have accumulated rules with directives and encounter a new user-agent,
			// flush the current rules
			if len(currentUserAgents) > 0 && (len(currentDisallows) > 0 || len(currentAllows) > 0 || currentCrawlDelay > 0) {
				rule := createRuleFromAccumulated(currentUserAgents, currentDisallows, currentAllows, currentCrawlDelay)
				rules = append(rules, rule)
				// Reset for next group
				currentUserAgents = nil
				currentDisallows = nil
				currentAllows = nil
				currentCrawlDelay = 0
			}
			currentUserAgents = append(currentUserAgents, value)

		case "disallow":
			if len(currentUserAgents) > 0 && value != "" {
				currentDisallows = append(currentDisallows, value)
			}

		case "allow":
			if len(currentUserAgents) > 0 && value != "" {
				currentAllows = append(currentAllows, value)
			}

		case "crawl-delay":
			if len(currentUserAgents) > 0 {
				if delay, err := parseIntSafe(value); err == nil {
					currentCrawlDelay = delay
				}
			}
		}
	}

	// Don't forget the last group of rules
	if len(currentUserAgents) > 0 {
		rule := createRuleFromAccumulated(currentUserAgents, currentDisallows, currentAllows, currentCrawlDelay)
		rules = append(rules, rule)
	}

	// Mark blacklisted user agents (those with "Disallow: /")
	for i := range rules {
		if slices.Contains(rules[i].Disallows, "/") {
			rules[i].IsBlacklist = true
		}
	}

	return rules, scanner.Err()
}

func parseIntSafe(s string) (int, error) {
	var result int
	_, err := fmt.Sscanf(s, "%d", &result)
	return result, err
}

func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
	var anubisRules []AnubisRule
	ruleCounter := 0

	// Process each robots rule individually
	for _, robotsRule := range robotsRules {
		userAgents := robotsRule.UserAgents

		// Handle crawl delay
		if robotsRule.CrawlDelay > 0 && *crawlDelay > 0 {
			ruleCounter++
			rule := AnubisRule{
				Name:   fmt.Sprintf("%s-crawl-delay-%d", *policyName, ruleCounter),
				Action: "WEIGH",
				Weight: &config.Weight{Adjust: *crawlDelay},
			}

			if len(userAgents) == 1 && userAgents[0] == "*" {
				rule.Expression = &config.ExpressionOrList{
					All: []string{"true"}, // Always applies
				}
			} else if len(userAgents) == 1 {
				rule.Expression = &config.ExpressionOrList{
					All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgents[0])},
				}
			} else {
				// Multiple user agents - use any block
				var expressions []string
				for _, ua := range userAgents {
					if ua == "*" {
						expressions = append(expressions, "true")
					} else {
						expressions = append(expressions, fmt.Sprintf("userAgent.contains(%q)", ua))
					}
				}
				rule.Expression = &config.ExpressionOrList{
					Any: expressions,
				}
			}
			anubisRules = append(anubisRules, rule)
		}

		// Handle blacklisted user agents
		if robotsRule.IsBlacklist {
			ruleCounter++
			rule := AnubisRule{
				Name:   fmt.Sprintf("%s-blacklist-%d", *policyName, ruleCounter),
				Action: *userAgentDeny,
			}

			if len(userAgents) == 1 {
				userAgent := userAgents[0]
				if userAgent == "*" {
					// This would block everything - convert to a weight adjustment instead
					rule.Name = fmt.Sprintf("%s-global-restriction-%d", *policyName, ruleCounter)
					rule.Action = "WEIGH"
					rule.Weight = &config.Weight{Adjust: 20} // Increase difficulty significantly
					rule.Expression = &config.ExpressionOrList{
						All: []string{"true"}, // Always applies
					}
				} else {
					rule.Expression = &config.ExpressionOrList{
						All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
					}
				}
			} else {
				// Multiple user agents - use any block
				var expressions []string
				for _, ua := range userAgents {
					if ua == "*" {
						expressions = append(expressions, "true")
					} else {
						expressions = append(expressions, fmt.Sprintf("userAgent.contains(%q)", ua))
					}
				}
				rule.Expression = &config.ExpressionOrList{
					Any: expressions,
				}
			}
			anubisRules = append(anubisRules, rule)
		}

		// Handle specific disallow rules
		for _, disallow := range robotsRule.Disallows {
			if disallow == "/" {
				continue // Already handled as blacklist above
			}

			ruleCounter++
			rule := AnubisRule{
				Name:   fmt.Sprintf("%s-disallow-%d", *policyName, ruleCounter),
				Action: *baseAction,
			}

			// Build CEL expression
			var conditions []string

			// Add user agent conditions
			if len(userAgents) == 1 && userAgents[0] == "*" {
				// Wildcard user agent - no user agent condition needed
			} else if len(userAgents) == 1 {
				conditions = append(conditions, fmt.Sprintf("userAgent.contains(%q)", userAgents[0]))
			} else {
				// For multiple user agents, we need to use a more complex expression
				// This is a limitation - we can't easily combine any for user agents with all for path
				// So we'll create separate rules for each user agent
				for _, ua := range userAgents {
					if ua == "*" {
						continue // Skip wildcard as it's handled separately
					}
					ruleCounter++
					subRule := AnubisRule{
						Name:   fmt.Sprintf("%s-disallow-%d", *policyName, ruleCounter),
						Action: *baseAction,
						Expression: &config.ExpressionOrList{
							All: []string{
								fmt.Sprintf("userAgent.contains(%q)", ua),
								buildPathCondition(disallow),
							},
						},
					}
					anubisRules = append(anubisRules, subRule)
				}
				continue
			}

			// Add path condition
			pathCondition := buildPathCondition(disallow)
			conditions = append(conditions, pathCondition)

			rule.Expression = &config.ExpressionOrList{
				All: conditions,
			}

			anubisRules = append(anubisRules, rule)
		}
	}

	return anubisRules
}

func buildPathCondition(robotsPath string) string {
	// Handle wildcards in robots.txt paths
	if strings.Contains(robotsPath, "*") || strings.Contains(robotsPath, "?") {
		// Convert robots.txt wildcards to regex
		regex := regexp.QuoteMeta(robotsPath)
		regex = strings.ReplaceAll(regex, `\*`, `.*`) // * becomes .*
		regex = strings.ReplaceAll(regex, `\?`, `.`)  // ? becomes .
		regex = "^" + regex
		return fmt.Sprintf("path.matches(%q)", regex)
	}

	// Simple prefix match for most cases
	return fmt.Sprintf("path.startsWith(%q)", robotsPath)
}


================================================
FILE: cmd/robots2policy/robots2policy_test.go
================================================
package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"

	"gopkg.in/yaml.v3"
)

type TestCase struct {
	name         string
	robotsFile   string
	expectedFile string
	options      TestOptions
}

type TestOptions struct {
	format           string
	action           string
	policyName       string
	deniedAction     string
	crawlDelayWeight int
}

func TestDataFileConversion(t *testing.T) {

	testCases := []TestCase{
		{
			name:         "simple_default",
			robotsFile:   "simple.robots.txt",
			expectedFile: "simple.yaml",
			options:      TestOptions{format: "yaml"},
		},
		{
			name:         "simple_json",
			robotsFile:   "simple.robots.txt",
			expectedFile: "simple.json",
			options:      TestOptions{format: "json"},
		},
		{
			name:         "simple_deny_action",
			robotsFile:   "simple.robots.txt",
			expectedFile: "deny-action.yaml",
			options:      TestOptions{format: "yaml", action: "DENY"},
		},
		{
			name:         "simple_custom_name",
			robotsFile:   "simple.robots.txt",
			expectedFile: "custom-name.yaml",
			options:      TestOptions{format: "yaml", policyName: "my-custom-policy"},
		},
		{
			name:         "blacklist_with_crawl_delay",
			robotsFile:   "blacklist.robots.txt",
			expectedFile: "blacklist.yaml",
			options:      TestOptions{format: "yaml", crawlDelayWeight: 3},
		},
		{
			name:         "wildcards",
			robotsFile:   "wildcards.robots.txt",
			expectedFile: "wildcards.yaml",
			options:      TestOptions{format: "yaml"},
		},
		{
			name:         "empty_file",
			robotsFile:   "empty.robots.txt",
			expectedFile: "empty.yaml",
			options:      TestOptions{format: "yaml"},
		},
		{
			name:         "complex_scenario",
			robotsFile:   "complex.robots.txt",
			expectedFile: "complex.yaml",
			options:      TestOptions{format: "yaml", crawlDelayWeight: 5},
		},
		{
			name:         "consecutive_user_agents",
			robotsFile:   "consecutive.robots.txt",
			expectedFile: "consecutive.yaml",
			options:      TestOptions{format: "yaml", crawlDelayWeight: 3},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			robotsPath := filepath.Join("testdata", tc.robotsFile)
			expectedPath := filepath.Join("testdata", tc.expectedFile)

			// Read robots.txt input
			robotsFile, err := os.Open(robotsPath)
			if err != nil {
				t.Fatalf("Failed to open robots file %s: %v", robotsPath, err)
			}
			defer robotsFile.Close()

			// Parse robots.txt
			rules, err := parseRobotsTxt(robotsFile)
			if err != nil {
				t.Fatalf("Failed to parse robots.txt: %v", err)
			}

			// Set test options
			oldFormat := *outputFormat
			oldAction := *baseAction
			oldCrawlDelay := *crawlDelay
			oldPolicyName := *policyName
			oldDeniedAction := *userAgentDeny

			if tc.options.format != "" {
				*outputFormat = tc.options.format
			}
			if tc.options.action != "" {
				*baseAction = tc.options.action
			}
			if tc.options.crawlDelayWeight > 0 {
				*crawlDelay = tc.options.crawlDelayWeight
			}
			if tc.options.policyName != "" {
				*policyName = tc.options.policyName
			}
			if tc.options.deniedAction != "" {
				*userAgentDeny = tc.options.deniedAction
			}

			// Restore options after test
			defer func() {
				*outputFormat = oldFormat
				*baseAction = oldAction
				*crawlDelay = oldCrawlDelay
				*policyName = oldPolicyName
				*userAgentDeny = oldDeniedAction
			}()

			// Convert to Anubis rules
			anubisRules := convertToAnubisRules(rules)

			// Generate output
			var actualOutput []byte
			switch strings.ToLower(*outputFormat) {
			case "yaml":
				actualOutput, err = yaml.Marshal(anubisRules)
			case "json":
				actualOutput, err = json.MarshalIndent(anubisRules, "", "  ")
			}
			if err != nil {
				t.Fatalf("Failed to marshal output: %v", err)
			}

			// Read expected output
			expectedOutput, err := os.ReadFile(expectedPath)
			if err != nil {
				t.Fatalf("Failed to read expected file %s: %v", expectedPath, err)
			}

			if strings.ToLower(*outputFormat) == "yaml" {
				var actualData []any
				var expectedData []any

				err = yaml.Unmarshal(actualOutput, &actualData)
				if err != nil {
					t.Fatalf("Failed to unmarshal actual output: %v", err)
				}

				err = yaml.Unmarshal(expectedOutput, &expectedData)
				if err != nil {
					t.Fatalf("Failed to unmarshal expected output: %v", err)
				}

				// Compare data structures
				if !compareData(actualData, expectedData) {
					actualStr := strings.TrimSpace(string(actualOutput))
					expectedStr := strings.TrimSpace(string(expectedOutput))
					t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
				}
			} else {
				var actualData []any
				var expectedData []any

				err = json.Unmarshal(actualOutput, &actualData)
				if err != nil {
					t.Fatalf("Failed to unmarshal actual JSON output: %v", err)
				}

				err = json.Unmarshal(expectedOutput, &expectedData)
				if err != nil {
					t.Fatalf("Failed to unmarshal expected JSON output: %v", err)
				}

				// Compare data structures
				if !compareData(actualData, expectedData) {
					actualStr := strings.TrimSpace(string(actualOutput))
					expectedStr := strings.TrimSpace(string(expectedOutput))
					t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
				}
			}
		})
	}
}

func TestCaseInsensitiveParsing(t *testing.T) {
	robotsTxt := `User-Agent: *
Disallow: /admin
Crawl-Delay: 10

User-agent: TestBot
disallow: /test
crawl-delay: 5

USER-AGENT: UpperBot
DISALLOW: /upper
CRAWL-DELAY: 20`

	reader := strings.NewReader(robotsTxt)
	rules, err := parseRobotsTxt(reader)
	if err != nil {
		t.Fatalf("Failed to parse case-insensitive robots.txt: %v", err)
	}

	expectedRules := 3
	if len(rules) != expectedRules {
		t.Errorf("Expected %d rules, got %d", expectedRules, len(rules))
	}

	// Check that all crawl delays were parsed
	for i, rule := range rules {
		expectedDelays := []int{10, 5, 20}
		if rule.CrawlDelay != expectedDelays[i] {
			t.Errorf("Rule %d: expected crawl delay %d, got %d", i, expectedDelays[i], rule.CrawlDelay)
		}
	}
}

func TestVariousOutputFormats(t *testing.T) {
	robotsTxt := `User-agent: *
Disallow: /admin`

	reader := strings.NewReader(robotsTxt)
	rules, err := parseRobotsTxt(reader)
	if err != nil {
		t.Fatalf("Failed to parse robots.txt: %v", err)
	}

	oldPolicyName := *policyName
	*policyName = "test-policy"
	defer func() { *policyName = oldPolicyName }()

	anubisRules := convertToAnubisRules(rules)

	// Test YAML output
	yamlOutput, err := yaml.Marshal(anubisRules)
	if err != nil {
		t.Fatalf("Failed to marshal YAML: %v", err)
	}

	if !strings.Contains(string(yamlOutput), "name: test-policy-disallow-1") {
		t.Errorf("YAML output doesn't contain expected rule name")
	}

	// Test JSON output
	jsonOutput, err := json.MarshalIndent(anubisRules, "", "  ")
	if err != nil {
		t.Fatalf("Failed to marshal JSON: %v", err)
	}

	if !strings.Contains(string(jsonOutput), `"name": "test-policy-disallow-1"`) {
		t.Errorf("JSON output doesn't contain expected rule name")
	}
}

func TestDifferentActions(t *testing.T) {
	robotsTxt := `User-agent: *
Disallow: /admin`

	testActions := []string{"ALLOW", "DENY", "CHALLENGE", "WEIGH"}

	for _, action := range testActions {
		t.Run("action_"+action, func(t *testing.T) {
			reader := strings.NewReader(robotsTxt)
			rules, err := parseRobotsTxt(reader)
			if err != nil {
				t.Fatalf("Failed to parse robots.txt: %v", err)
			}

			oldAction := *baseAction
			*baseAction = action
			defer func() { *baseAction = oldAction }()

			anubisRules := convertToAnubisRules(rules)

			if len(anubisRules) != 1 {
				t.Fatalf("Expected 1 rule, got %d", len(anubisRules))
			}

			if anubisRules[0].Action != action {
				t.Errorf("Expected action %s, got %s", action, anubisRules[0].Action)
			}
		})
	}
}

func TestPolicyNaming(t *testing.T) {
	robotsTxt := `User-agent: *
Disallow: /admin
Disallow: /private

User-agent: BadBot
Disallow: /`

	testNames := []string{"custom-policy", "my-rules", "site-protection"}

	for _, name := range testNames {
		t.Run("name_"+name, func(t *testing.T) {
			reader := strings.NewReader(robotsTxt)
			rules, err := parseRobotsTxt(reader)
			if err != nil {
				t.Fatalf("Failed to parse robots.txt: %v", err)
			}

			oldName := *policyName
			*policyName = name
			defer func() { *policyName = oldName }()

			anubisRules := convertToAnubisRules(rules)

			// Check that all rule names use the custom prefix
			for _, rule := range anubisRules {
				if !strings.HasPrefix(rule.Name, name+"-") {
					t.Errorf("Rule name %s doesn't start with expected prefix %s-", rule.Name, name)
				}
			}
		})
	}
}

func TestCrawlDelayWeights(t *testing.T) {
	robotsTxt := `User-agent: *
Disallow: /admin
Crawl-delay: 10

User-agent: SlowBot
Disallow: /slow
Crawl-delay: 60`

	testWeights := []int{1, 5, 10, 25}

	for _, weight := range testWeights {
		t.Run(fmt.Sprintf("weight_%d", weight), func(t *testing.T) {
			reader := strings.NewReader(robotsTxt)
			rules, err := parseRobotsTxt(reader)
			if err != nil {
				t.Fatalf("Failed to parse robots.txt: %v", err)
			}

			oldWeight := *crawlDelay
			*crawlDelay = weight
			defer func() { *crawlDelay = oldWeight }()

			anubisRules := convertToAnubisRules(rules)

			// Count weight rules and verify they have correct weight
			weightRules := 0
			for _, rule := range anubisRules {
				if rule.Action == "WEIGH" && rule.Weight != nil {
					weightRules++
					if rule.Weight.Adjust != weight {
						t.Errorf("Expected weight %d, got %d", weight, rule.Weight.Adjust)
					}
				}
			}

			expectedWeightRules := 2 // One for *, one for SlowBot
			if weightRules != expectedWeightRules {
				t.Errorf("Expected %d weight rules, got %d", expectedWeightRules, weightRules)
			}
		})
	}
}

func TestBlacklistActions(t *testing.T) {
	robotsTxt := `User-agent: BadBot
Disallow: /

User-agent: SpamBot
Disallow: /`

	testActions := []string{"DENY", "CHALLENGE"}

	for _, action := range testActions {
		t.Run("blacklist_"+action, func(t *testing.T) {
			reader := strings.NewReader(robotsTxt)
			rules, err := parseRobotsTxt(reader)
			if err != nil {
				t.Fatalf("Failed to parse robots.txt: %v", err)
			}

			oldAction := *userAgentDeny
			*userAgentDeny = action
			defer func() { *userAgentDeny = oldAction }()

			anubisRules := convertToAnubisRules(rules)

			// All rules should be blacklist rules with the specified action
			for _, rule := range anubisRules {
				if !strings.Contains(rule.Name, "blacklist") {
					t.Errorf("Expected blacklist rule, got %s", rule.Name)
				}
				if rule.Action != action {
					t.Errorf("Expected action %s, got %s", action, rule.Action)
				}
			}
		})
	}
}

// compareData performs a deep comparison of two data structures,
// ignoring differences that are semantically equivalent in YAML/JSON
func compareData(actual, expected any) bool {
	return reflect.DeepEqual(actual, expected)
}


================================================
FILE: cmd/robots2policy/testdata/blacklist.robots.txt
================================================
# Test with blacklisted user agents
User-agent: *
Disallow: /admin
Crawl-delay: 10

User-agent: BadBot
Disallow: /

User-agent: SpamBot
Disallow: /
Crawl-delay: 60

User-agent: Googlebot
Disallow: /search
Crawl-delay: 5

================================================
FILE: cmd/robots2policy/testdata/blacklist.yaml
================================================
- action: WEIGH
  expression: "true"
  name: robots-txt-policy-crawl-delay-1
  weight:
    adjust: 3
- action: CHALLENGE
  expression: path.startsWith("/admin")
  name: robots-txt-policy-disallow-2
- action: DENY
  expression: userAgent.contains("BadBot")
  name: robots-txt-policy-blacklist-3
- action: WEIGH
  expression: userAgent.contains("SpamBot")
  name: robots-txt-policy-crawl-delay-4
  weight:
    adjust: 3
- action: DENY
  expression: userAgent.contains("SpamBot")
  name: robots-txt-policy-blacklist-5
- action: WEIGH
  expression: userAgent.contains("Googlebot")
  name: robots-txt-policy-crawl-delay-6
  weight:
    adjust: 3
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("Googlebot")
      - path.startsWith("/search")
  name: robots-txt-policy-disallow-7


================================================
FILE: cmd/robots2policy/testdata/complex.robots.txt
================================================
# Complex real-world example
User-agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /api/internal/
Allow: /api/public/
Crawl-delay: 5

User-agent: Googlebot
Disallow: /search/
Allow: /api/
Crawl-delay: 2

User-agent: Bingbot
Disallow: /search/
Disallow: /admin/
Crawl-delay: 10

User-agent: BadBot
Disallow: /

User-agent: SeoBot
Disallow: /
Crawl-delay: 300

# Test with various patterns
User-agent: TestBot
Disallow: /*/admin
Disallow: /temp*.html
Disallow: /file?.log

================================================
FILE: cmd/robots2policy/testdata/complex.yaml
================================================
- action: WEIGH
  expression: "true"
  name: robots-txt-policy-crawl-delay-1
  weight:
    adjust: 5
- action: CHALLENGE
  expression: path.startsWith("/admin/")
  name: robots-txt-policy-disallow-2
- action: CHALLENGE
  expression: path.startsWith("/private/")
  name: robots-txt-policy-disallow-3
- action: CHALLENGE
  expression: path.startsWith("/api/internal/")
  name: robots-txt-policy-disallow-4
- action: WEIGH
  expression: userAgent.contains("Googlebot")
  name: robots-txt-policy-crawl-delay-5
  weight:
    adjust: 5
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("Googlebot")
      - path.startsWith("/search/")
  name: robots-txt-policy-disallow-6
- action: WEIGH
  expression: userAgent.contains("Bingbot")
  name: robots-txt-policy-crawl-delay-7
  weight:
    adjust: 5
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("Bingbot")
      - path.startsWith("/search/")
  name: robots-txt-policy-disallow-8
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("Bingbot")
      - path.startsWith("/admin/")
  name: robots-txt-policy-disallow-9
- action: DENY
  expression: userAgent.contains("BadBot")
  name: robots-txt-policy-blacklist-10
- action: WEIGH
  expression: userAgent.contains("SeoBot")
  name: robots-txt-policy-crawl-delay-11
  weight:
    adjust: 5
- action: DENY
  expression: userAgent.contains("SeoBot")
  name: robots-txt-policy-blacklist-12
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("TestBot")
      - path.matches("^/.*/admin")
  name: robots-txt-policy-disallow-13
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("TestBot")
      - path.matches("^/temp.*\\.html")
  name: robots-txt-policy-disallow-14
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("TestBot")
      - path.matches("^/file.\\.log")
  name: robots-txt-policy-disallow-15


================================================
FILE: cmd/robots2policy/testdata/consecutive.robots.txt
================================================
# Test consecutive user agents that should be grouped into any: blocks
User-agent: *
Disallow: /admin
Crawl-delay: 10

# Multiple consecutive user agents - should be grouped
User-agent: BadBot
User-agent: SpamBot
User-agent: EvilBot
Disallow: /

# Single user agent - should be separate
User-agent: GoodBot
Disallow: /private

# Multiple consecutive user agents with crawl delay
User-agent: SlowBot1
User-agent: SlowBot2
Crawl-delay: 5

# Multiple consecutive user agents with specific path
User-agent: SearchBot1
User-agent: SearchBot2
User-agent: SearchBot3
Disallow: /search 

================================================
FILE: cmd/robots2policy/testdata/consecutive.yaml
================================================
- action: WEIGH
  expression: "true"
  name: robots-txt-policy-crawl-delay-1
  weight:
    adjust: 3
- action: CHALLENGE
  expression: path.startsWith("/admin")
  name: robots-txt-policy-disallow-2
- action: DENY
  expression:
    any:
      - 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")
  name: robots-txt-policy-disallow-4
- action: WEIGH
  expression:
    any:
      - 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")
  name: robots-txt-policy-disallow-7
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("SearchBot2")
      - path.startsWith("/search")
  name: robots-txt-policy-disallow-8
- action: CHALLENGE
  expression:
    all:
      - userAgent.contains("SearchBot3")
      - path.startsWith("/search")
  name: robots-txt-policy-disallow-9


================================================
FILE: cmd/robots2policy/testdata/custom-name.yaml
================================================
- action: CHALLENGE
  expression: path.startsWith("/admin/")
  name: my-custom-policy-disallow-1
- action: CHALLENGE
  expression: path.startsWith("/private")
  name: my-custom-policy-disallow-2


================================================
FILE: cmd/robots2policy/testdata/deny-action.yaml
================================================
- action: DENY
  expression: path.startsWith("/admin/")
  name: robots-txt-policy-disallow-1
- action: DENY
  expression: path.startsWith("/private")
  name: robots-txt-policy-disallow-2


================================================
FILE: cmd/robots2policy/testdata/empty.robots.txt
================================================
# Empty robots.txt (comments only)
# No actual rules

================================================
FILE: cmd/robots2policy/testdata/empty.yaml
================================================
[]


================================================
FILE: cmd/robots2policy/testdata/simple.json
================================================
[
  {
    "expression": "path.startsWith(\"/admin/\")",
    "name": "robots-txt-policy-disallow-1",
    "action": "CHALLENGE"
  },
  {
    "expression": "path.startsWith(\"/private\")",
    "name": "robots-txt-policy-disallow-2",
    "action": "CHALLENGE"
  }
]


================================================
FILE: cmd/robots2policy/testdata/simple.robots.txt
================================================
# Simple robots.txt test
User-agent: *
Disallow: /admin/
Disallow: /private
Allow: /public

================================================
FILE: cmd/robots2policy/testdata/simple.yaml
================================================
- action: CHALLENGE
  expression: path.startsWith("/admin/")
  name: robots-txt-policy-disallow-1
- action: CHALLENGE
  expression: path.startsWith("/private")
  name: robots-txt-policy-disallow-2


================================================
FILE: cmd/robots2policy/testdata/wildcards.robots.txt
================================================
# Test wildcard patterns
User-agent: *
Disallow: /search*
Disallow: /*/private
Disallow: /file?.txt
Disallow: /admin/*?action=delete

================================================
FILE: cmd/robots2policy/testdata/wildcards.yaml
================================================
- action: CHALLENGE
  expression: path.matches("^/search.*")
  name: robots-txt-policy-disallow-1
- action: CHALLENGE
  expression: path.matches("^/.*/private")
  name: robots-txt-policy-disallow-2
- action: CHALLENGE
  expression: path.matches("^/file.\\.txt")
  name: robots-txt-policy-disallow-3
- action: CHALLENGE
  expression: path.matches("^/admin/.*.action=delete")
  name: robots-txt-policy-disallow-4


================================================
FILE: data/apps/allow-api-routes.yaml
================================================
- name: allow-api-routes
  action: ALLOW
  expression:
    all:
      - '!(method == "HEAD" || method == "GET")'
      - path.startsWith("/api/")


================================================
FILE: data/apps/bookstack-saml.yaml
================================================
# Make SASL login work on bookstack with Anubis
# https://www.bookstackapp.com/docs/admin/saml2-auth/
- name: allow-bookstack-sasl-login-routes
  action: ALLOW
  expression:
    all:
      - 'method == "POST"'
      - path.startsWith("/saml2/acs")
- name: allow-bookstack-sasl-metadata-routes
  action: ALLOW
  expression:
    all:
      - 'method == "GET"'
      - path.startsWith("/saml2/metadata")
- name: allow-bookstack-sasl-logout-routes
  action: ALLOW
  expression:
    all:
      - 'method == "GET"'
      - path.startsWith("/saml2/sls")


================================================
FILE: data/apps/gitea-rss-feeds.yaml
================================================
# By Aibrew: https://github.com/TecharoHQ/anubis/discussions/261#discussioncomment-12821065
- name: gitea-feed-atom
  action: ALLOW
  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$


================================================
FILE: data/apps/qualys-ssl-labs.yml
================================================
# This policy allows Qualys SSL Labs to fully work. (https://www.ssllabs.com/ssltest)
# IP ranges are taken from: https://qualys.my.site.com/discussions/s/article/000005823
- name: qualys-ssl-labs
  action: ALLOW
  remote_addresses:
    - 69.67.183.0/24
    - 2600:C02:1020:4202::/64
    - 2602:fdaa:c6:2::/64


================================================
FILE: data/apps/searx-checker.yml
================================================
# This policy allows SearXNG's instance tracker to work. (https://searx.space)
# IPs are taken from `check.searx.space` DNS records.
# https://toolbox.googleapps.com/apps/dig/#A/check.searx.space
# https://toolbox.googleapps.com/apps/dig/#AAAA/check.searx.space
- name: searx-checker
  action: ALLOW
  remote_addresses:
    - 167.235.158.251/32
    - 2a01:4f8:1c1c:8fc2::1/128


================================================
FILE: data/botPolicies.yaml
================================================
## Anubis has the ability to let you import snippets of configuration into the main
## configuration file. This allows you to break up your config into smaller parts
## that get logically assembled into one big file.
##
## Of note, a bot rule can either have inline bot configuration or import a
## bot config snippet. You cannot do both in a single bot rule.
##
## Import paths can either be prefixed with (data) to import from the common/shared
## rules in the data folder in the Anubis source tree or will point to absolute/relative
## paths in your filesystem. If you don't have access to the Anubis source tree, check
## /usr/share/docs/anubis/data or in the tarball you extracted Anubis from.

bots:
  # You can import the entire default config with this macro:
  # - import: (data)/meta/default-config.yaml

  # Pathological bots to deny
  - # This correlates to data/bots/_deny-pathological.yaml in the source tree
    # https://github.com/TecharoHQ/anubis/blob/main/data/bots/_deny-pathological.yaml
    import: (data)/bots/_deny-pathological.yaml
  - import: (data)/bots/aggressive-brazilian-scrapers.yaml

  # Aggressively block AI/LLM related bots/agents by default
  - import: (data)/meta/ai-block-aggressive.yaml

  # Consider replacing the aggressive AI policy with more selective policies:
  # - import: (data)/meta/ai-block-moderate.yaml
  # - import: (data)/meta/ai-block-permissive.yaml

  # Search engine crawlers to allow, defaults to:
  #   - Google (so they don't try to bypass Anubis)
  #   - Apple
  #   - Bing
  #   - DuckDuckGo
  #   - Qwant
  #   - The Internet Archive
  #   - Kagi
  #   - Marginalia
  #   - Mojeek
  - import: (data)/crawlers/_allow-good.yaml
  # Challenge Firefox AI previews
  - import: (data)/clients/x-firefox-ai.yaml

  # Allow common "keeping the internet working" routes (well-known, favicon, robots.txt)
  - import: (data)/common/keep-internet-working.yaml

  # # Punish any bot with "bot" in the user-agent string
  # # This is known to have a high false-positive rate, use at your own risk
  # - name: generic-bot-catchall
  #   user_agent_regex: (?i:bot|crawler)
  #   action: CHALLENGE
  #   challenge:
  #     difficulty: 16 # impossible
  #     algorithm: slow # intentionally waste CPU cycles and time

  # Requires a subscription to Thoth to use, see
  # https://anubis.techaro.lol/docs/admin/thoth#geoip-based-filtering
  - name: countries-with-aggressive-scrapers
    action: WEIGH
    geoip:
      countries:
        - BR
        - CN
    weight:
      adjust: 10

  # Requires a subscription to Thoth to use, see
  # https://anubis.techaro.lol/docs/admin/thoth#asn-based-filtering
  - name: aggressive-asns-without-functional-abuse-contact
    action: WEIGH
    asns:
      match:
        - 13335 # Cloudflare
        - 136907 # Huawei Cloud
        - 45102 # Alibaba Cloud
    weight:
      adjust: 10

  # ## System load based checks.
  # # If the system is under high load, add weight.
  # - name: high-load-average
  #   action: WEIGH
  #   expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
  #   weight:
  #     adjust: 20

  ## If your backend service is running on the same operating system as Anubis,
  ## you can uncomment this rule to make the challenge easier when the system is
  ## under low load.
  ##
  ## If it is not, remove weight.
  # - name: low-load-average
  #   action: WEIGH
  #   expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
  #   weight:
  #     adjust: -10

  # Generic catchall rule
  - name: generic-browser
    user_agent_regex: >-
      Mozilla|Opera
    action: WEIGH
    weight:
      adjust: 10

dnsbl: false

# #
# impressum:
#   # Displayed at the bottom of every page rendered by Anubis.
#   footer: >-
#     This website is hosted by Zombocom. If you have any complaints or notes
#     about the service, please contact
#     <a href="mailto:contact@domainhere.example">contact@domainhere.example</a>
#     and we will assist you as soon as possible.

#   # The imprint page that will be linked to at the footer of every Anubis page.
#   page:
#     # The HTML <title> of the page
#     title: Imprint and Privacy Policy
#     # The HTML contents of the page. The exact contents of this page can
#     # and will vary by locale. Please consult with a lawyer if you are not
#     # sure what to put here
#     body: >-
#       <p>Last updated: June 2025</p>

#       <h2>Information that is gathered from visitors</h2>

#       <p>In common with other websites, log files are stored on the web server saving details such as the visitor's IP address, browser type, referring page and time of visit.</p>

#       <p>Cookies may be used to remember visitor preferences when interacting with the website.</p>

#       <p>Where registration is required, the visitor's email and a username will be stored on the server.</p>

#       <!-- ... -->

# Open Graph passthrough configuration, see here for more information:
# https://anubis.techaro.lol/docs/admin/configuration/open-graph/
openGraph:
  # Enables Open Graph passthrough
  enabled: false
  # Enables the use of the HTTP host in the cache key, this enables
  # caching metadata for multiple http hosts at once.
  considerHost: false
  # How long cached OpenGraph metadata should last in memory
  ttl: 24h
  # # If set, return these opengraph values instead of looking them up with
  # # the target service.
  # #
  # # Correlates to properties in https://ogp.me/
  # override:
  #   # og:title is required, it is the title of the website
  #   "og:title": "Techaro Anubis"
  #   "og:description": >-
  #     Anubis is a Web AI Firewall Utility that helps you fight the bots
  #     away so that you can maintain uptime at work!
  #   "description": >-
  #     Anubis is a Web AI Firewall Utility that helps you fight the bots
  #     away so that you can maintain uptime at work!

# By default, send HTTP 200 back to clients that either get issued a challenge
# or a denial. This seems weird, but this is load-bearing due to the fact that
# the most aggressive scraper bots seem to really, really, want an HTTP 200 and
# will stop sending requests once they get it.
status_codes:
  CHALLENGE: 200
  DENY: 200

# Anubis can store temporary data in one of a few backends. See the storage
# backends section of the docs for more information:
#
# https://anubis.techaro.lol/docs/admin/policies#storage-backends
store:
  backend: memory
  parameters: {}

# The weight thresholds for when to trigger individual challenges. Any
# CHALLENGE will take precedence over this.
#
# A threshold has four configuration options:
#
#   - name: the name that is reported down the stack and used for metrics
#   - expression: A CEL expression with the request weight in the variable
#     weight
#   - action: the Anubis action to apply, similar to in a bot policy
#   - challenge: which challenge to send to the user, similar to in a bot policy
#
# See https://anubis.techaro.lol/docs/admin/configuration/thresholds for more
# information.
thresholds:
  # By default Anubis ships with the following thresholds:
  - name: minimal-suspicion # This client is likely fine, its soul is lighter than a feather
    expression: weight <= 0 # a feather weighs zero units
    action: ALLOW # Allow the traffic through
  # For clients that had some weight reduced through custom rules, give them a
  # lightweight challenge.
  - name: mild-suspicion
    expression:
      all:
        - weight > 0
        - weight < 10
    action: CHALLENGE
    challenge:
      # https://anubis.techaro.lol/docs/admin/configuration/challenges/metarefresh
      algorithm: metarefresh
      difficulty: 1
  # For clients that are browser-like but have either gained points from custom rules or
  # report as a standard browser.
  - name: moderate-suspicion
    expression:
      all:
        - weight >= 10
        - weight < 20
    action: CHALLENGE
    challenge:
      # https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
      algorithm: fast
      difficulty: 2 # two leading zeros, very fast for most clients
  - name: mild-proof-of-work
    expression:
      all:
        - weight >= 20
        - weight < 30
    action: CHALLENGE
    challenge:
      # https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
      algorithm: fast
      difficulty: 4
  # For clients that are browser like and have gained many points from custom rules
  - name: extreme-suspicion
    expression: weight >= 30
    action: CHALLENGE
    challenge:
      # https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
      algorithm: fast
      difficulty: 6


================================================
FILE: data/bots/_deny-pathological.yaml
================================================
- import: (data)/bots/cloudflare-workers.yaml
- import: (data)/bots/headless-browsers.yaml
- import: (data)/bots/us-ai-scraper.yaml
- import: (data)/bots/custom-async-http-client.yaml
- import: (data)/crawlers/alibaba-cloud.yaml
- import: (data)/crawlers/huawei-cloud.yaml


================================================
FILE: data/bots/aggressive-brazilian-scrapers.yaml
================================================
- name: deny-aggressive-brazilian-scrapers
  action: WEIGH
  weight:
    adjust: 20
  expression:
    any:
      # Internet Explorer should be out of support
      - userAgent.contains("MSIE")
      # Trident is the Internet Explorer browser engine
      - userAgent.contains("Trident")
      # Opera is a fork of chrome now
      - userAgent.contains("Presto")
      # Windows CE is discontinued
      - userAgent.contains("Windows CE")
      # Windows 95 is discontinued
      - userAgent.contains("Windows 95")
      # Windows 98 is discontinued
      - userAgent.contains("Windows 98")
      # Windows 9.x is discontinued
      - userAgent.contains("Win 9x")
      # Amazon does not have an Alexa Toolbar.
      - userAgent.contains("Alexa Toolbar")
      # This is not released, even Windows 11 calls itself Windows 10
      - userAgent.contains("Windows NT 11.0")
      # iPods are not in common use
      - userAgent.contains("iPod")


================================================
FILE: data/bots/ai-catchall.yaml
================================================
# Extensive list of AI-affiliated agents based on https://github.com/ai-robots-txt/ai.robots.txt
# Add new/undocumented agents here. Where documentation exists, consider moving to dedicated policy files.
# Notes on various agents:
#  - Amazonbot: Well documented, but they refuse to state which agent collects training data.
#  - anthropic-ai/Claude-Web: Undocumented by Anthropic. Possibly deprecated or hallucinations?
#  - Perplexity*: Well documented, but they refuse to state which agent collects training data.
# Warning: May contain user agents that _must_ be blocked in robots.txt, or the opt-out will have no effect.
- name: "ai-catchall"
  user_agent_regex: >-
    AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|anthropic-ai|Brightbot 1.0|Bytespider|Claude-Web|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|FacebookBot|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|GoogleOther|GoogleOther-Image|GoogleOther-Video|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|imgproxy|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|NovaAct|omgili|omgilibot|Operator|PanguBot|Perplexity-User|PerplexityBot|PetalBot|QualifiedBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YouBot
  action: DENY


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


================================================
FILE: data/bots/cloudflare-workers.yaml
================================================
- name: cloudflare-workers
  headers_regex:
    CF-Worker: .*
  action: WEIGH
  weight:
    adjust: 15


================================================
FILE: data/bots/custom-async-http-client.yaml
================================================
- name: "custom-async-http-client"
  user_agent_regex: "Custom-AsyncHttpClient"
  action: WEIGH
  weight:
    adjust: 10


================================================
FILE: data/bots/headless-browsers.yaml
================================================
- name: lightpanda
  user_agent_regex: ^LightPanda/.*$
  action: DENY
- name: headless-chrome
  user_agent_regex: HeadlessChrome
  action: DENY
- name: headless-chromium
  user_agent_regex: HeadlessChromium
  action: DENY


================================================
FILE: data/bots/irc-bots/archlinux-phrik.yaml
================================================
# phrik in the Arch Linux IRC channels
- name: archlinux-phrik
  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"


================================================
FILE: data/bots/irc-bots/gentoo-chat.yaml
================================================
# chat in the gentoo IRC channels
- name: gentoo-chat
  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"


================================================
FILE: data/bots/us-ai-scraper.yaml
================================================
- name: us-artificial-intelligence-scraper
  user_agent_regex: \+https\://github\.com/US-Artificial-Intelligence/scraper
  action: DENY


================================================
FILE: data/clients/ai.yaml
================================================
# User agents that act on behalf of humans in AI tools, e.g. searching the web.
# Each entry should have a positive/ALLOW entry created as well, with further documentation.
# Exceptions:
#  - Claude-User: No published IP allowlist
- name: "ai-clients"
  user_agent_regex: >-
    ChatGPT-User|Claude-User|MistralAI-User|Perplexity-User
  action: DENY


================================================
FILE: data/clients/docker-client.yaml
================================================
- name: allow-docker-client
  action: ALLOW
  expression:
    all:
      - path.startsWith("/v2/")
      - userAgent.contains("docker/")
      - userAgent.contains("git-commit/")
      - '"Accept" in headers'
      - headers["Accept"].contains("vnd.docker.distribution")
      - '"Baggage" in headers'
      - headers["Baggage"].contains("trigger")

- name: allow-crane-client
  action: ALLOW
  expression:
    all:
      - userAgent.contains("crane/")
      - userAgent.contains("go-containerregistry/")

- name: allow-docker-distribution-api-client
  action: ALLOW
  expression:
    all:
      - '"Docker-Distribution-Api-Version" in headers'
      - '!(userAgent.contains("Mozilla"))'

- name: allow-go-containerregistry-client
  action: ALLOW
  expression:
    all:
      - path.startsWith("/v2/")
      - userAgent.contains("go-containerregistry/")

- name: allow-buildah
  action: ALLOW
  expression:
    all:
      - path.startsWith("/v2/")
      - userAgent.contains("Buildah/")

- name: allow-podman
  action: ALLOW
  expression:
    all:
      - path.startsWith("/v2/")
      - userAgent.contains("containers/")

- name: allow-containerd
  action: ALLOW
  expression:
    all:
      - path.startsWith("/v2/")
      - userAgent.contains("containerd/")

- name: allow-renovate
  action: ALLOW
  expression:
    all:
      - path.startsWith("/v2/")
      - userAgent.contains("Renovate/")


================================================
FILE: data/clients/git.yaml
================================================
- name: allow-git-clients
  action: ALLOW
  expression:
    all:
      - >
        (  
          userAgent.startsWith("git/") ||
          userAgent.contains("libgit") ||
          userAgent.startsWith("go-git") ||
          userAgent.startsWith("JGit/") ||
          userAgent.startsWith("JGit-")
        )
      - '"Accept" in headers'
      - headers["Accept"] == "*/*"
      - '"Cache-Control" in headers'
      - headers["Cache-Control"] == "no-cache"
      - '"Pragma" in headers'
      - headers["Pragma"] == "no-cache"
      - '"Accept-Encoding" in headers'
      - headers["Accept-Encoding"].contains("gzip")


================================================
FILE: data/clients/go-get.yaml
================================================
- name: go-get
  action: ALLOW
  expression:
    all:
      - userAgent.startsWith("Go-http-client/")
      - '"go-get" in query'
      - query["go-get"] == "1"


================================================
FILE: data/clients/mistral-mistralai-user.yaml
================================================
# Acts on behalf of user requests
# https://docs.mistral.ai/robots/
- name: mistral-mistralai-user
  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"]


================================================
FILE: data/clients/openai-chatgpt-user.yaml
================================================
# Acts on behalf of user requests
# https://platform.openai.com/docs/bots/overview-of-openai-crawlers
- name: openai-chatgpt-user
  user_agent_regex: ChatGPT-User/.+; \+https\://openai\.com/bot
  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",
    ]


================================================
FILE: data/clients/perplexity-user.yaml
================================================
# Acts on behalf of user requests
# https://docs.perplexity.ai/guides/bots
- name: perplexity-user
  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"]


================================================
FILE: data/clients/small-internet-browsers/_permissive.yaml
================================================
- import: (data)/clients/small-internet-browsers/netsurf.yaml
- import: (data)/clients/small-internet-browsers/palemoon.yaml


================================================
FILE: data/clients/small-internet-browsers/netsurf.yaml
================================================
- name: "reduce-weight-netsurf"
  user_agent_regex: "NetSurf"
  action: WEIGH
  weight:
    adjust: -5


================================================
FILE: data/clients/small-internet-browsers/palemoon.yaml
================================================
- name: "reduce-weight-palemoon"
  user_agent_regex: "PaleMoon"
  action: WEIGH
  weight:
    adjust: -5


================================================
FILE: data/clients/telegram-preview.yaml
================================================
- name: telegrambot
  action: ALLOW
  expression:
    all:
      - userAgent.matches("TelegramBot")
      - verifyFCrDNS(remoteAddress, "ptr\\.telegram\\.org$")


================================================
FILE: data/clients/vk-preview.yaml
================================================
- name: vkbot
  action: ALLOW
  expression:
    all:
      - userAgent.matches("vkShare[^+]+\\+http\\://vk\\.com/dev/Share")
      - verifyFCrDNS(remoteAddress, "^snipster\\d+\\.go\\.mail\\.ru$")


================================================
FILE: data/clients/x-firefox-ai.yaml
================================================
# https://connect.mozilla.org/t5/firefox-labs/try-out-link-previews-in-firefox-labs-138-and-share-your/td-p/92012
- name: x-firefox-ai
  action: WEIGH
  expression: '"X-Firefox-Ai" in headers'
  weight:
    adjust: 5


================================================
FILE: data/common/acts-like-browser.yaml
================================================
# Assert behaviour that only genuine browsers display. This ensures that modern Chrome
# or Firefox versions will get through without a challenge.
#
# These rules have been known to be bypassed by some of the worst automated scrapers.
# Use at your own risk.

- name: realistic-browser-catchall
  expression:
    all:
      - '"User-Agent" in headers'
      - '( userAgent.contains("Firefox") ) || ( userAgent.contains("Chrome") ) || ( userAgent.contains("Safari") )'
      - '"Accept" in headers'
      - '"Sec-Fetch-Dest" in headers'
      - '"Sec-Fetch-Mode" in headers'
      - '"Sec-Fetch-Site" in headers'
      - '"Accept-Encoding" in headers'
      - '( headers["Accept-Encoding"].contains("zstd") || headers["Accept-Encoding"].contains("br") )'
      - '"Accept-Language" in headers'
  action: WEIGH
  weight:
    adjust: -10

# The Upgrade-Insecure-Requests header is typically sent by browsers, but not always
- name: upgrade-insecure-requests
  expression: '"Upgrade-Insecure-Requests" in headers'
  action: WEIGH
  weight:
    adjust: -2

# Chrome should behave like Chrome
- name: chrome-is-proper
  expression:
    all:
      - userAgent.contains("Chrome")
      - '"Sec-Ch-Ua" in headers'
      - 'headers["Sec-Ch-Ua"].contains("Chromium")'
      - '"Sec-Ch-Ua-Mobile" in headers'
      - '"Sec-Ch-Ua-Platform" in headers'
  action: WEIGH
  weight:
    adjust: -5

- name: should-have-accept
  expression: '!("Accept" in headers)'
  action: WEIGH
  weight:
    adjust: 5

# Generic catchall rule
- name: generic-browser
  user_agent_regex: >-
    Mozilla|Opera
  action: WEIGH
  weight:
    adjust: 10


================================================
FILE: data/common/allow-api-like.yaml
================================================
- name: allow-api-routes
  action: ALLOW
  expression:
    all:
      - '!(method == "HEAD" || method == "GET")'
      - path.startsWith("/api/")


================================================
FILE: data/common/allow-private-addresses.yaml
================================================
- name: ipv4-rfc-1918
  action: ALLOW
  remote_addresses:
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    - 100.64.0.0/10
- name: ipv6-ula
  action: ALLOW
  remote_addresses:
    - fc00::/7
- name: ipv6-link-local
  action: ALLOW
  remote_addresses:
    - fe80::/10


================================================
FILE: data/common/json-api.yaml
================================================
- name: allow-api-requests
  action: ALLOW
  expression:
    all:
      - '"Accept" in headers'
      - 'headers["Accept"] == "application/json"'
      - 'path.startsWith("/api/")'


================================================
FILE: data/common/keep-internet-working.yaml
================================================
# Common "keeping the internet working" routes
- name: well-known
  path_regex: ^/\.well-known/.*$
  action: ALLOW
- name: favicon
  path_regex: ^/favicon\.(?:ico|png|gif|jpg|jpeg|svg)$
  action: ALLOW
- name: robots-txt
  path_regex: ^/robots\.txt$
  action: ALLOW
- name: sitemap
  path_regex: ^/sitemap\.xml$
  action: ALLOW


================================================
FILE: data/common/rfc-violations.yaml
================================================
- name: no-user-agent-string
  action: DENY
  expression: userAgent == ""


================================================
FILE: data/crawlers/_allow-good.yaml
================================================
- import: (data)/crawlers/googlebot.yaml
- import: (data)/crawlers/applebot.yaml
- import: (data)/crawlers/bingbot.yaml
- import: (data)/crawlers/duckduckbot.yaml
- import: (data)/crawlers/qwantbot.yaml
- import: (data)/crawlers/internet-archive.yaml
- import: (data)/crawlers/kagibot.yaml
- 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


================================================
FILE: data/crawlers/ai-search.yaml
================================================
# User agents that index exclusively for search in for AI systems.
# Each entry should have a positive/ALLOW entry created as well, with further documentation.
# Exceptions:
#  - Claude-SearchBot: No published IP allowlist
- name: "ai-crawlers-search"
  user_agent_regex: >-
    OAI-SearchBot|Claude-SearchBot|PerplexityBot
  action: DENY


================================================
FILE: data/crawlers/ai-training.yaml
================================================
# User agents that crawl for training AI/LLM systems
# Each entry should have a positive/ALLOW entry created as well, with further documentation.
# Exceptions:
#  - ClaudeBot: No published IP allowlist
- name: "ai-crawlers-training"
  user_agent_regex: >-
    GPTBot|ClaudeBot
  action: DENY


================================================
FILE: data/crawlers/alibaba-cloud.yaml
================================================
- name: alibaba-cloud
  action: DENY
  # Updated 2025-08-20 from IP addresses for AS45102
  remote_addresses:
    - 103.81.186.0/23
    - 110.76.21.0/24
    - 110.76.23.0/24
    - 116.251.64.0/18
    - 139.95.0.0/23
    - 139.95.10.0/23
    - 139.95.12.0/23
    - 139.95.14.0/23
    - 139.95.16.0/23
    - 139.95.18.0/23
    - 139.95.2.0/23
    - 139.95.4.0/23
    - 139.95.6.0/23
    - 139.95.64.0/24
    - 139.95.8.0/23
    - 14.1.112.0/22
    - 14.1.115.0/24
    - 140.205.1.0/24
    - 140.205.122.0/24
    - 147.139.0.0/17
    - 147.139.0.0/18
    - 147.139.128.0/17
    - 147.139.128.0/18
    - 147.139.155.0/24
    - 147.139.192.0/18
    - 147.139.64.0/18
    - 149.129.0.0/20
    - 149.129.0.0/21
    - 149.129.16.0/23
    - 149.129.192.0/18
    - 149.129.192.0/19
    - 149.129.224.0/19
    - 149.129.32.0/19
    - 149.129.64.0/18
    - 149.129.64.0/19
    - 149.129.8.0/21
    - 149.129.96.0/19
    - 156.227.20.0/24
    - 156.236.12.0/24
    - 156.236.17.0/24
    - 156.240.76.0/23
    - 156.245.1.0/24
    - 161.117.0.0/16
    - 161.117.0.0/17
    - 161.117.126.0/24
    - 161.117.127.0/24
    - 161.117.128.0/17
    - 161.117.128.0/24
    - 161.117.129.0/24
    - 161.117.138.0/24
    - 161.117.143.0/24
    - 170.33.104.0/24
    - 170.33.105.0/24
    - 170.33.106.0/24
    - 170.33.107.0/24
    - 170.33.136.0/24
    - 170.33.137.0/24
    - 170.33.138.0/24
    - 170.33.20.0/24
    - 170.33.21.0/24
    - 170.33.22.0/24
    - 170.33.23.0/24
    - 170.33.24.0/24
    - 170.33.29.0/24
    - 170.33.30.0/24
    - 170.33.31.0/24
    - 170.33.32.0/24
    - 170.33.33.0/24
    - 170.33.34.0/24
    - 170.33.35.0/24
    - 170.33.64.0/24
    - 170.33.65.0/24
    - 170.33.66.0/24
    - 170.33.68.0/24
    - 170.33.69.0/24
    - 170.33.72.0/24
    - 170.33.73.0/24
    - 170.33.76.0/24
    - 170.33.77.0/24
    - 170.33.78.0/24
    - 170.33.79.0/24
    - 170.33.80.0/24
    - 170.33.81.0/24
    - 170.33.82.0/24
    - 170.33.83.0/24
    - 170.33.84.0/24
    - 170.33.85.0/24
    - 170.33.86.0/24
    - 170.33.88.0/24
    - 170.33.90.0/24
    - 170.33.92.0/24
    - 170.33.93.0/24
    - 185.78.106.0/23
    - 198.11.128.0/18
    - 198.11.137.0/24
    - 198.11.184.0/21
    - 202.144.199.0/24
    - 203.107.64.0/24
    - 203.107.65.0/24
    - 203.107.66.0/24
    - 203.107.67.0/24
    - 203.107.68.0/24
    - 205.204.102.0/23
    - 205.204.111.0/24
    - 205.204.117.0/24
    - 205.204.125.0/24
    - 205.204.96.0/19
    - 223.5.5.0/24
    - 223.6.6.0/24
    - 2400:3200::/48
    - 2400:3200:baba::/48
    - 2400:b200:4100::/48
    - 2400:b200:4101::/48
    - 2400:b200:4102::/48
    - 2400:b200:4103::/48
    - 2401:8680:4100::/48
    - 2401:b180:4100::/48
    - 2404:2280:1000::/36
    - 2404:2280:1000::/37
    - 2404:2280:1800::/37
    - 2404:2280:2000::/36
    - 2404:2280:2000::/37
    - 2404:2280:2800::/37
    - 2404:2280:3000::/36
    - 2404:2280:3000::/37
    - 2404:2280:3800::/37
    - 2404:2280:4000::/36
    - 2404:2280:4000::/37
    - 2404:2280:4800::/37
    - 2408:4000:1000::/48
    - 2408:4009:500::/48
    - 240b:4000::/32
    - 240b:4000::/33
    - 240b:4000:8000::/33
    - 240b:4000:fffe::/48
    - 240b:4001::/32
    - 240b:4001::/33
    - 240b:4001:8000::/33
    - 240b:4002::/32
    - 240b:4002::/33
    - 240b:4002:8000::/33
    - 240b:4004::/32
    - 240b:4004::/33
    - 240b:4004:8000::/33
    - 240b:4005::/32
    - 240b:4005::/33
    - 240b:4005:8000::/33
    - 240b:4006::/48
    - 240b:4006:1000::/44
    - 240b:4006:1000::/45
    - 240b:4006:1000::/47
    - 240b:4006:1002::/47
    - 240b:4006:1008::/45
    - 240b:4006:1010::/44
    - 240b:4006:1010::/45
    - 240b:4006:1018::/45
    - 240b:4006:1020::/44
    - 240b:4006:1020::/45
    - 240b:4006:1028::/45
    - 240b:4007::/32
    - 240b:4007::/33
    - 240b:4007:8000::/33
    - 240b:4009::/32
    - 240b:4009::/33
    - 240b:4009:8000::/33
    - 240b:400b::/32
    - 240b:400b::/33
    - 240b:400b:8000::/33
    - 240b:400c::/32
    - 240b:400c::/33
    - 240b:400c::/40
    - 240b:400c::/41
    - 240b:400c:100::/40
    - 240b:400c:100::/41
    - 240b:400c:180::/41
    - 240b:400c:80::/41
    - 240b:400c:8000::/33
    - 240b:400c:f00::/48
    - 240b:400c:f01::/48
    - 240b:400c:ffff::/48
    - 240b:400d::/32
    - 240b:400d::/33
    - 240b:400d:8000::/33
    - 240b:400e::/32
    - 240b:400e::/33
    - 240b:400e:8000::/33
    - 240b:400f::/32
    - 240b:400f::/33
    - 240b:400f:8000::/33
    - 240b:4011::/32
    - 240b:4011::/33
    - 240b:4011:8000::/33
    - 240b:4012::/48
    - 240b:4013::/32
    - 240b:4013::/33
    - 240b:4013:8000::/33
    - 240b:4014::/32
    - 240b:4014::/33
    - 240b:4014:8000::/33
    - 43.100.0.0/15
    - 43.100.0.0/16
    - 43.101.0.0/16
    - 43.102.0.0/20
    - 43.102.112.0/20
    - 43.102.16.0/20
    - 43.102.32.0/20
    - 43.102.48.0/20
    - 43.102.64.0/20
    - 43.102.80.0/20
    - 43.102.96.0/20
    - 43.103.0.0/17
    - 43.103.0.0/18
    - 43.103.64.0/18
    - 43.104.0.0/15
    - 43.104.0.0/16
    - 43.105.0.0/16
    - 43.108.0.0/17
    - 43.108.0.0/18
    - 43.108.64.0/18
    - 43.91.0.0/16
    - 43.91.0.0/17
    - 43.91.128.0/17
    - 43.96.10.0/24
    - 43.96.100.0/24
    - 43.96.101.0/24
    - 43.96.102.0/24
    - 43.96.104.0/24
    - 43.96.11.0/24
    - 43.96.20.0/24
    - 43.96.21.0/24
    - 43.96.23.0/24
    - 43.96.24.0/24
    - 43.96.25.0/24
    - 43.96.3.0/24
    - 43.96.32.0/24
    - 43.96.33.0/24
    - 43.96.34.0/24
    - 43.96.35.0/24
    - 43.96.4.0/24
    - 43.96.40.0/24
    - 43.96.5.0/24
    - 43.96.52.0/24
    - 43.96.6.0/24
    - 43.96.66.0/24
    - 43.96.67.0/24
    - 43.96.68.0/24
    - 43.96.69.0/24
    - 43.96.7.0/24
    - 43.96.70.0/24
    - 43.96.71.0/24
    - 43.96.72.0/24
    - 43.96.73.0/24
    - 43.96.74.0/24
    - 43.96.75.0/24
    - 43.96.8.0/24
    - 43.96.80.0/24
    - 43.96.81.0/24
    - 43.96.84.0/24
    - 43.96.85.0/24
    - 43.96.86.0/24
    - 43.96.88.0/24
    - 43.96.9.0/24
    - 43.96.96.0/24
    - 43.98.0.0/16
    - 43.98.0.0/17
    - 43.98.128.0/17
    - 43.99.0.0/16
    - 43.99.0.0/17
    - 43.99.128.0/17
    - 45.199.179.0/24
    - 47.235.0.0/22
    - 47.235.0.0/23
    - 47.235.1.0/24
    - 47.235.10.0/23
    - 47.235.10.0/24
    - 47.235.11.0/24
    - 47.235.12.0/23
    - 47.235.12.0/24
    - 47.235.13.0/24
    - 47.235.16.0/23
    - 47.235.16.0/24
    - 47.235.18.0/23
    - 47.235.18.0/24
    - 47.235.19.0/24
    - 47.235.2.0/23
    - 47.235.20.0/24
    - 47.235.21.0/24
    - 47.235.22.0/24
    - 47.235.23.0/24
    - 47.235.24.0/22
    - 47.235.24.0/23
    - 47.235.26.0/23
    - 47.235.28.0/23
    - 47.235.28.0/24
    - 47.235.29.0/24
    - 47.235.30.0/24
    - 47.235.31.0/24
    - 47.235.4.0/24
    - 47.235.5.0/24
    - 47.235.6.0/23
    - 47.235.6.0/24
    - 47.235.7.0/24
    - 47.235.8.0/24
    - 47.235.9.0/24
    - 47.236.0.0/15
    - 47.236.0.0/16
    - 47.237.0.0/16
    - 47.237.32.0/20
    - 47.237.34.0/24
    - 47.238.0.0/15
    - 47.238.0.0/16
    - 47.239.0.0/16
    - 47.240.0.0/16
    - 47.240.0.0/17
    - 47.240.128.0/17
    - 47.241.0.0/16
    - 47.241.0.0/17
    - 47.241.128.0/17
    - 47.242.0.0/15
    - 47.242.0.0/16
    - 47.243.0.0/16
    - 47.244.0.0/16
    - 47.244.0.0/17
    - 47.244.128.0/17
    - 47.244.73.0/24
    - 47.245.0.0/18
    - 47.245.0.0/19
    - 47.245.128.0/17
    - 47.245.128.0/18
    - 47.245.192.0/18
    - 47.245.32.0/19
    - 47.245.64.0/18
    - 47.245.64.0/19
    - 47.245.96.0/19
    - 47.246.100.0/22
    - 47.246.104.0/21
    - 47.246.104.0/22
    - 47.246.108.0/22
    - 47.246.120.0/24
    - 47.246.122.0/24
    - 47.246.123.0/24
    - 47.246.124.0/24
    - 47.246.125.0/24
    - 47.246.128.0/22
    - 47.246.128.0/23
    - 47.246.130.0/23
    - 47.246.132.0/22
    - 47.246.132.0/23
    - 47.246.134.0/23
    - 47.246.136.0/21
    - 47.246.136.0/22
    - 47.246.140.0/22
    - 47.246.144.0/23
    - 47.246.144.0/24
    - 47.246.145.0/24
    - 47.246.146.0/23
    - 47.246.146.0/24
    - 47.246.147.0/24
    - 47.246.150.0/23
    - 47.246.150.0/24
    - 47.246.151.0/24
    - 47.246.152.0/23
    - 47.246.152.0/24
    - 47.246.153.0/24
    - 47.246.154.0/24
    - 47.246.155.0/24
    - 47.246.156.0/22
    - 47.246.156.0/23
    - 47.246.158.0/23
    - 47.246.160.0/20
    - 47.246.160.0/21
    - 47.246.168.0/21
    - 47.246.176.0/20
    - 47.246.176.0/21
    - 47.246.184.0/21
    - 47.246.192.0/22
    - 47.246.192.0/23
    - 47.246.194.0/23
    - 47.246.196.0/22
    - 47.246.196.0/23
    - 47.246.198.0/23
    - 47.246.32.0/22
    - 47.246.66.0/24
    - 47.246.67.0/24
    - 47.246.68.0/23
    - 47.246.68.0/24
    - 47.246.69.0/24
    - 47.246.72.0/21
    - 47.246.72.0/22
    - 47.246.76.0/22
    - 47.246.80.0/24
    - 47.246.82.0/23
    - 47.246.82.0/24
    - 47.246.83.0/24
    - 47.246.84.0/22
    - 47.246.84.0/23
    - 47.246.86.0/23
    - 47.246.88.0/22
    - 47.246.88.0/23
    - 47.246.90.0/23
    - 47.246.92.0/23
    - 47.246.92.0/24
    - 47.246.93.0/24
    - 47.246.96.0/21
    - 47.246.96.0/22
    - 47.250.0.0/17
    - 47.250.0.0/18
    - 47.250.128.0/17
    - 47.250.128.0/18
    - 47.250.192.0/18
    - 47.250.64.0/18
    - 47.250.99.0/24
    - 47.251.0.0/16
    - 47.251.0.0/17
    - 47.251.128.0/17
    - 47.251.224.0/22
    - 47.252.0.0/17
    - 47.252.0.0/18
    - 47.252.128.0/17
    - 47.252.128.0/18
    - 47.252.192.0/18
    - 47.252.64.0/18
    - 47.252.67.0/24
    - 47.253.0.0/16
    - 47.253.0.0/17
    - 47.253.128.0/17
    - 47.254.0.0/17
    - 47.254.0.0/18
    - 47.254.113.0/24
    - 47.254.128.0/18
    - 47.254.128.0/19
    - 47.254.160.0/19
    - 47.254.192.0/18
    - 47.254.192.0/19
    - 47.254.224.0/19
    - 47.254.64.0/18
    - 47.52.0.0/16
    - 47.52.0.0/17
    - 47.52.128.0/17
    - 47.56.0.0/15
    - 47.56.0.0/16
    - 47.57.0.0/16
    - 47.74.0.0/18
    - 47.74.0.0/19
    - 47.74.0.0/21
    - 47.74.128.0/17
    - 47.74.128.0/18
    - 47.74.192.0/18
    - 47.74.32.0/19
    - 47.74.64.0/18
    - 47.74.64.0/19
    - 47.74.96.0/19
    - 47.75.0.0/16
    - 47.75.0.0/17
    - 47.75.128.0/17
    - 47.76.0.0/16
    - 47.76.0.0/17
    - 47.76.128.0/17
    - 47.77.0.0/22
    - 47.77.0.0/23
    - 47.77.104.0/21
    - 47.77.12.0/22
    - 47.77.128.0/17
    - 47.77.128.0/18
    - 47.77.128.0/21
    - 47.77.136.0/21
    - 47.77.144.0/21
    - 47.77.152.0/21
    - 47.77.16.0/21
    - 47.77.16.0/22
    - 47.77.192.0/18
    - 47.77.2.0/23
    - 47.77.20.0/22
    - 47.77.24.0/22
    - 47.77.24.0/23
    - 47.77.26.0/23
    - 47.77.32.0/19
    - 47.77.32.0/20
    - 47.77.4.0/22
    - 47.77.4.0/23
    - 47.77.48.0/20
    - 47.77.6.0/23
    - 47.77.64.0/19
    - 47.77.64.0/20
    - 47.77.8.0/21
    - 47.77.8.0/22
    - 47.77.80.0/20
    - 47.77.96.0/20
    - 47.77.96.0/21
    - 47.78.0.0/17
    - 47.78.128.0/17
    - 47.79.0.0/20
    - 47.79.0.0/21
    - 47.79.104.0/21
    - 47.79.112.0/20
    - 47.79.128.0/19
    - 47.79.128.0/20
    - 47.79.144.0/20
    - 47.79.16.0/20
    - 47.79.16.0/21
    - 47.79.192.0/18
    - 47.79.192.0/19
    - 47.79.224.0/19
    - 47.79.24.0/21
    - 47.79.32.0/20
    - 47.79.32.0/21
    - 47.79.40.0/21
    - 47.79.48.0/20
    - 47.79.48.0/21
    - 47.79.52.0/23
    - 47.79.54.0/23
    - 47.79.56.0/21
    - 47.79.56.0/23
    - 47.79.58.0/23
    - 47.79.60.0/23
    - 47.79.62.0/23
    - 47.79.64.0/20
    - 47.79.64.0/21
    - 47.79.72.0/21
    - 47.79.8.0/21
    - 47.79.80.0/20
    - 47.79.80.0/21
    - 47.79.83.0/24
    - 47.79.88.0/21
    - 47.79.96.0/19
    - 47.79.96.0/20
    - 47.80.0.0/18
    - 47.80.0.0/19
    - 47.80.128.0/17
    - 47.80.128.0/18
    - 47.80.192.0/18
    - 47.80.32.0/19
    - 47.80.64.0/18
    - 47.80.64.0/19
    - 47.80.96.0/19
    - 47.81.0.0/18
    - 47.81.0.0/19
    - 47.81.128.0/17
    - 47.81.128.0/18
    - 47.81.192.0/18
    - 47.81.32.0/19
    - 47.81.64.0/18
    - 47.81.64.0/19
    - 47.81.96.0/19
    - 47.82.0.0/18
    - 47.82.0.0/19
    - 47.82.10.0/23
    - 47.82.12.0/23
    - 47.82.128.0/17
    - 47.82.128.0/18
    - 47.82.14.0/23
    - 47.82.192.0/18
    - 47.82.32.0/19
    - 47.82.32.0/21
    - 47.82.40.0/21
    - 47.82.48.0/21
    - 47.82.56.0/21
    - 47.82.64.0/18
    - 47.82.64.0/19
    - 47.82.8.0/23
    - 47.82.96.0/19
    - 47.83.0.0/16
    - 47.83.0.0/17
    - 47.83.128.0/17
    - 47.83.32.0/21
    - 47.83.40.0/21
    - 47.83.48.0/21
    - 47.83.56.0/21
    - 47.84.0.0/16
    - 47.84.0.0/17
    - 47.84.128.0/17
    - 47.84.144.0/21
    - 47.84.152.0/21
    - 47.84.160.0/21
    - 47.84.168.0/21
    - 47.85.0.0/16
    - 47.85.0.0/17
    - 47.85.112.0/22
    - 47.85.112.0/23
    - 47.85.114.0/23
    - 47.85.128.0/17
    - 47.86.0.0/16
    - 47.86.0.0/17
    - 47.86.128.0/17
    - 47.87.0.0/18
    - 47.87.0.0/19
    - 47.87.128.0/18
    - 47.87.128.0/19
    - 47.87.160.0/19
    - 47.87.192.0/22
    - 47.87.192.0/23
    - 47.87.194.0/23
    - 47.87.196.0/22
    - 47.87.196.0/23
    - 47.87.198.0/23
    - 47.87.200.0/22
    - 47.87.200.0/23
    - 47.87.202.0/23
    - 47.87.204.0/22
    - 47.87.204.0/23
    - 47.87.206.0/23
    - 47.87.208.0/22
    - 47.87.208.0/23
    - 47.87.210.0/23
    - 47.87.212.0/22
    - 47.87.212.0/23
    - 47.87.214.0/23
    - 47.87.216.0/22
    - 47.87.216.0/23
    - 47.87.218.0/23
    - 47.87.220.0/22
    - 47.87.220.0/23
    - 47.87.222.0/23
    - 47.87.224.0/22
    - 47.87.224.0/23
    - 47.87.226.0/23
    - 47.87.228.0/22
    - 47.87.228.0/23
    - 47.87.230.0/23
    - 47.87.232.0/22
    - 47.87.232.0/23
    - 47.87.234.0/23
    - 47.87.236.0/22
    - 47.87.236.0/23
    - 47.87.238.0/23
    - 47.87.240.0/22
    - 47.87.240.0/23
    - 47.87.242.0/23
    - 47.87.32.0/19
    - 47.87.64.0/18
    - 47.87.64.0/19
    - 47.87.96.0/19
    - 47.88.0.0/17
    - 47.88.0.0/18
    - 47.88.109.0/24
    - 47.88.128.0/17
    - 47.88.128.0/18
    - 47.88.135.0/24
    - 47.88.192.0/18
    - 47.88.41.0/24
    - 47.88.42.0/24
    - 47.88.43.0/24
    - 47.88.64.0/18
    - 47.89.0.0/18
    - 47.89.0.0/19
    - 47.89.100.0/24
    - 47.89.101.0/24
    - 47.89.102.0/24
    - 47.89.103.0/24
    - 47.89.104.0/21
    - 47.89.104.0/22
    - 47.89.108.0/22
    - 47.89.122.0/24
    - 47.89.123.0/24
    - 47.89.124.0/23
    - 47.89.124.0/24
    - 47.89.125.0/24
    - 47.89.128.0/18
    - 47.89.128.0/19
    - 47.89.160.0/19
    - 47.89.192.0/18
    - 47.89.192.0/19
    - 47.89.221.0/24
    - 47.89.224.0/19
    - 47.89.32.0/19
    - 47.89.72.0/22
    - 47.89.72.0/23
    - 47.89.74.0/23
    - 47.89.76.0/22
    - 47.89.76.0/23
    - 47.89.78.0/23
    - 47.89.80.0/23
    - 47.89.82.0/23
    - 47.89.84.0/24
    - 47.89.88.0/22
    - 47.89.88.0/23
    - 47.89.90.0/23
    - 47.89.92.0/22
    - 47.89.92.0/23
    - 47.89.94.0/23
    - 47.89.96.0/24
    - 47.89.97.0/24
    - 47.89.98.0/23
    - 47.89.99.0/24
    - 47.90.0.0/17
    - 47.90.0.0/18
    - 47.90.128.0/17
    - 47.90.128.0/18
    - 47.90.172.0/24
    - 47.90.173.0/24
    - 47.90.174.0/24
    - 47.90.175.0/24
    - 47.90.192.0/18
    - 47.90.64.0/18
    - 47.91.0.0/19
    - 47.91.0.0/20
    - 47.91.112.0/20
    - 47.91.128.0/17
    - 47.91.128.0/18
    - 47.91.16.0/20
    - 47.91.192.0/18
    - 47.91.32.0/19
    - 47.91.32.0/20
    - 47.91.48.0/20
    - 47.91.64.0/19
    - 47.91.64.0/20
    - 47.91.80.0/20
    - 47.91.96.0/19
    - 47.91.96.0/20
    - 5.181.224.0/23
    - 59.82.136.0/23
    - 8.208.0.0/16
    - 8.208.0.0/17
    - 8.208.0.0/18
    - 8.208.0.0/19
    - 8.208.128.0/17
    - 8.208.141.0/24
    - 8.208.32.0/19
    - 8.209.0.0/19
    - 8.209.0.0/20
    - 8.209.128.0/18
    - 8.209.128.0/19
    - 8.209.16.0/20
    - 8.209.160.0/19
    - 8.209.192.0/18
    - 8.209.192.0/19
    - 8.209.224.0/19
    - 8.209.36.0/23
    - 8.209.36.0/24
    - 8.209.37.0/24
    - 8.209.38.0/23
    - 8.209.38.0/24
    - 8.209.39.0/24
    - 8.209.40.0/22
    - 8.209.40.0/23
    - 8.209.42.0/23
    - 8.209.44.0/22
    - 8.209.44.0/23
    - 8.209.46.0/23
    - 8.209.48.0/20
    - 8.209.48.0/21
    - 8.209.56.0/21
    - 8.209.64.0/18
    - 8.209.64.0/19
    - 8.209.96.0/19
    - 8.210.0.0/16
    - 8.210.0.0/17
    - 8.210.128.0/17
    - 8.210.240.0/24
    - 8.211.0.0/17
    - 8.211.0.0/18
    - 8.211.104.0/21
    - 8.211.128.0/18
    - 8.211.128.0/19
    - 8.211.160.0/19
    - 8.211.192.0/18
    - 8.211.192.0/19
    - 8.211.224.0/19
    - 8.211.226.0/24
    - 8.211.64.0/18
    - 8.211.80.0/21
    - 8.211.88.0/21
    - 8.211.96.0/21
    - 8.212.0.0/17
    - 8.212.0.0/18
    - 8.212.128.0/18
    - 8.212.128.0/19
    - 8.212.160.0/19
    - 8.212.192.0/18
    - 8.212.192.0/19
    - 8.212.224.0/19
    - 8.212.64.0/18
    - 8.213.0.0/17
    - 8.213.0.0/18
    - 8.213.128.0/19
    - 8.213.128.0/20
    - 8.213.144.0/20
    - 8.213.160.0/21
    - 8.213.160.0/22
    - 8.213.164.0/22
    - 8.213.176.0/20
    - 8.213.176.0/21
    - 8.213.184.0/21
    - 8.213.192.0/18
    - 8.213.192.0/19
    - 8.213.224.0/19
    - 8.213.251.0/24
    - 8.213.252.0/24
    - 8.213.253.0/24
    - 8.213.64.0/18
    - 8.214.0.0/16
    - 8.214.0.0/17
    - 8.214.128.0/17
    - 8.215.0.0/16
    - 8.215.0.0/17
    - 8.215.128.0/17
    - 8.215.160.0/24
    - 8.215.162.0/23
    - 8.215.168.0/24
    - 8.215.169.0/24
    - 8.216.0.0/17
    - 8.216.0.0/18
    - 8.216.128.0/17
    - 8.216.128.0/18
    - 8.216.148.0/24
    - 8.216.192.0/18
    - 8.216.64.0/18
    - 8.216.69.0/24
    - 8.216.74.0/24
    - 8.217.0.0/16
    - 8.217.0.0/17
    - 8.217.128.0/17
    - 8.218.0.0/16
    - 8.218.0.0/17
    - 8.218.128.0/17
    - 8.219.0.0/16
    - 8.219.0.0/17
    - 8.219.128.0/17
    - 8.219.40.0/21
    - 8.220.116.0/24
    - 8.220.128.0/18
    - 8.220.128.0/19
    - 8.220.147.0/24
    - 8.220.160.0/19
    - 8.220.192.0/18
    - 8.220.192.0/19
    - 8.220.224.0/19
    - 8.220.229.0/24
    - 8.220.64.0/18
    - 8.220.64.0/19
    - 8.220.96.0/19
    - 8.221.0.0/17
    - 8.221.0.0/18
    - 8.221.0.0/21
    - 8.221.128.0/17
    - 8.221.128.0/18
    - 8.221.184.0/22
    - 8.221.188.0/22
    - 8.221.192.0/18
    - 8.221.192.0/21
    - 8.221.200.0/21
    - 8.221.208.0/21
    - 8.221.216.0/21
    - 8.221.48.0/21
    - 8.221.56.0/21
    - 8.221.64.0/18
    - 8.221.8.0/21
    - 8.222.0.0/20
    - 8.222.0.0/21
    - 8.222.112.0/20
    - 8.222.128.0/17
    - 8.222.128.0/18
    - 8.222.16.0/20
    - 8.222.16.0/21
    - 8.222.192.0/18
    - 8.222.24.0/21
    - 8.222.32.0/20
    - 8.222.32.0/21
    - 8.222.40.0/21
    - 8.222.48.0/20
    - 8.222.48.0/21
    - 8.222.56.0/21
    - 8.222.64.0/20
    - 8.222.64.0/21
    - 8.222.72.0/21
    - 8.222.8.0/21
    - 8.222.80.0/20
    - 8.222.80.0/21
    - 8.222.88.0/21
    - 8.222.96.0/19
    - 8.222.96.0/20
    - 8.223.0.0/17
    - 8.223.0.0/18
    - 8.223.128.0/17
    - 8.223.128.0/18
    - 8.223.192.0/18
    - 8.223.64.0/18


================================================
FILE: data/crawlers/applebot.yaml
================================================
# Indexing for search and Siri
# https://support.apple.com/en-us/119829
- name: applebot
  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"
Download .txt
gitextract_t1amwk0u/

├── .air.toml
├── .devcontainer/
│   ├── Dockerfile
│   ├── README.md
│   ├── devcontainer.json
│   ├── docker-compose.yaml
│   └── poststart.sh
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   ├── config.yml
│   │   └── feature_request.yaml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── actions/
│   │   └── spelling/
│   │       ├── README.md
│   │       ├── advice.md
│   │       ├── allow.txt
│   │       ├── candidate.patterns
│   │       ├── excludes.txt
│   │       ├── expect.txt
│   │       ├── line_forbidden.patterns
│   │       ├── patterns.txt
│   │       └── reject.txt
│   ├── dependabot.yml
│   ├── workflows/
│   │   ├── asset-verification.yml
│   │   ├── dco-check.yaml
│   │   ├── docker-pr.yml
│   │   ├── docker.yml
│   │   ├── docs-deploy.yml
│   │   ├── docs-test.yml
│   │   ├── go-mod-tidy-check.yml
│   │   ├── go.yml
│   │   ├── lint-pr-title.yaml
│   │   ├── package-builds-stable.yml
│   │   ├── package-builds-unstable.yml
│   │   ├── smoke-tests.yml
│   │   ├── spelling.yml
│   │   ├── ssh-ci-runner-cron.yml
│   │   ├── ssh-ci.yml
│   │   └── zizmor.yml
│   └── zizmor.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── .ko.yaml
├── .prettierignore
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── AGENTS.md
├── Brewfile
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── VERSION
├── anubis.go
├── cmd/
│   ├── containerbuild/
│   │   ├── .gitignore
│   │   └── main.go
│   └── robots2policy/
│       ├── batch/
│       │   └── batch_process.go
│       ├── main.go
│       ├── robots2policy_test.go
│       └── testdata/
│           ├── blacklist.robots.txt
│           ├── blacklist.yaml
│           ├── complex.robots.txt
│           ├── complex.yaml
│           ├── consecutive.robots.txt
│           ├── consecutive.yaml
│           ├── custom-name.yaml
│           ├── deny-action.yaml
│           ├── empty.robots.txt
│           ├── empty.yaml
│           ├── simple.json
│           ├── simple.robots.txt
│           ├── simple.yaml
│           ├── wildcards.robots.txt
│           └── wildcards.yaml
├── data/
│   ├── apps/
│   │   ├── allow-api-routes.yaml
│   │   ├── bookstack-saml.yaml
│   │   ├── gitea-rss-feeds.yaml
│   │   ├── qualys-ssl-labs.yml
│   │   └── searx-checker.yml
│   ├── botPolicies.yaml
│   ├── bots/
│   │   ├── _deny-pathological.yaml
│   │   ├── aggressive-brazilian-scrapers.yaml
│   │   ├── ai-catchall.yaml
│   │   ├── ai-robots-txt.yaml
│   │   ├── cloudflare-workers.yaml
│   │   ├── custom-async-http-client.yaml
│   │   ├── headless-browsers.yaml
│   │   ├── irc-bots/
│   │   │   ├── archlinux-phrik.yaml
│   │   │   └── gentoo-chat.yaml
│   │   └── us-ai-scraper.yaml
│   ├── clients/
│   │   ├── ai.yaml
│   │   ├── docker-client.yaml
│   │   ├── git.yaml
│   │   ├── go-get.yaml
│   │   ├── mistral-mistralai-user.yaml
│   │   ├── openai-chatgpt-user.yaml
│   │   ├── perplexity-user.yaml
│   │   ├── small-internet-browsers/
│   │   │   ├── _permissive.yaml
│   │   │   ├── netsurf.yaml
│   │   │   └── palemoon.yaml
│   │   ├── telegram-preview.yaml
│   │   ├── vk-preview.yaml
│   │   └── x-firefox-ai.yaml
│   ├── common/
│   │   ├── acts-like-browser.yaml
│   │   ├── allow-api-like.yaml
│   │   ├── allow-private-addresses.yaml
│   │   ├── json-api.yaml
│   │   ├── keep-internet-working.yaml
│   │   └── rfc-violations.yaml
│   ├── crawlers/
│   │   ├── _allow-good.yaml
│   │   ├── ai-search.yaml
│   │   ├── ai-training.yaml
│   │   ├── alibaba-cloud.yaml
│   │   ├── applebot.yaml
│   │   ├── bingbot.yaml
│   │   ├── commoncrawl.yaml
│   │   ├── duckduckbot.yaml
│   │   ├── googlebot.yaml
│   │   ├── huawei-cloud.yaml
│   │   ├── internet-archive.yaml
│   │   ├── kagibot.yaml
│   │   ├── marginalia.yaml
│   │   ├── mojeekbot.yaml
│   │   ├── openai-gptbot.yaml
│   │   ├── openai-searchbot.yaml
│   │   ├── perplexitybot.yaml
│   │   ├── qwantbot.yaml
│   │   ├── tencent-cloud.yaml
│   │   ├── wikimedia-citoid.yaml
│   │   └── yandexbot.yaml
│   ├── embed.go
│   ├── embed_test.go
│   ├── meta/
│   │   ├── README.md
│   │   ├── ai-block-aggressive.yaml
│   │   ├── ai-block-moderate.yaml
│   │   ├── ai-block-permissive.yaml
│   │   ├── default-config.yaml
│   │   └── messengers-preview.yaml
│   └── services/
│       ├── updown.yaml
│       └── uptime-robot.yaml
├── decaymap/
│   ├── decaymap.go
│   └── decaymap_test.go
├── docs/
│   ├── .dockerignore
│   ├── .gitignore
│   ├── Dockerfile
│   ├── README.md
│   ├── blog/
│   │   ├── 2025-06-16-welcome/
│   │   │   └── index.mdx
│   │   ├── 2025-06-27-release-1.20.0/
│   │   │   └── index.mdx
│   │   ├── 2025-07-09-incident-report/
│   │   │   └── index.mdx
│   │   ├── 2025-07-22-release-1.21.1/
│   │   │   └── index.mdx
│   │   ├── 2025-08-18-funding-update/
│   │   │   └── index.mdx
│   │   ├── 2025-08-28-cpu-core-odd/
│   │   │   ├── ProofOfWorkDiagram/
│   │   │   │   ├── index.jsx
│   │   │   │   └── styles.module.css
│   │   │   └── index.mdx
│   │   ├── 2025-10-31-file-abuse-reports/
│   │   │   └── index.mdx
│   │   └── authors.yml
│   ├── docs/
│   │   ├── CHANGELOG.md
│   │   ├── admin/
│   │   │   ├── _category_.json
│   │   │   ├── botstopper.mdx
│   │   │   ├── caveats-gitea-forgejo.mdx
│   │   │   ├── caveats-xff.mdx
│   │   │   ├── configuration/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── challenges/
│   │   │   │   │   ├── _category_.json
│   │   │   │   │   ├── index.mdx
│   │   │   │   │   ├── metarefresh.mdx
│   │   │   │   │   ├── preact.mdx
│   │   │   │   │   └── proof-of-work.mdx
│   │   │   │   ├── custom-status-codes.mdx
│   │   │   │   ├── expressions.mdx
│   │   │   │   ├── import.mdx
│   │   │   │   ├── impressum.mdx
│   │   │   │   ├── open-graph.mdx
│   │   │   │   ├── redirect-domains.mdx
│   │   │   │   ├── subrequest-auth.mdx
│   │   │   │   └── thresholds.mdx
│   │   │   ├── default-allow-behavior.mdx
│   │   │   ├── environments/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── apache.mdx
│   │   │   │   ├── caddy.mdx
│   │   │   │   ├── cloudflare.mdx
│   │   │   │   ├── docker-compose.mdx
│   │   │   │   ├── haproxy/
│   │   │   │   │   ├── advanced-config-policy.yml
│   │   │   │   │   ├── advanced-config.env
│   │   │   │   │   ├── advanced-haproxy.cfg
│   │   │   │   │   ├── simple-config.env
│   │   │   │   │   └── simple-haproxy.cfg
│   │   │   │   ├── haproxy.mdx
│   │   │   │   ├── kubernetes.mdx
│   │   │   │   ├── nginx/
│   │   │   │   │   ├── conf-anubis.inc
│   │   │   │   │   ├── server-anubistest-techaro-lol.conf
│   │   │   │   │   ├── server-mimi-techaro-lol.conf
│   │   │   │   │   └── upstream-anubis.conf
│   │   │   │   ├── nginx.mdx
│   │   │   │   └── traefik.mdx
│   │   │   ├── frameworks/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── htmx.mdx
│   │   │   │   └── wordpress.mdx
│   │   │   ├── honeypot/
│   │   │   │   ├── _category_.json
│   │   │   │   └── overview.mdx
│   │   │   ├── installation.mdx
│   │   │   ├── iplist2rule.mdx
│   │   │   ├── native-install.mdx
│   │   │   ├── policies.mdx
│   │   │   ├── robots2policy.mdx
│   │   │   ├── roles/
│   │   │   │   ├── _category_.json
│   │   │   │   └── oci-registry.mdx
│   │   │   └── thoth.mdx
│   │   ├── design/
│   │   │   ├── _category_.json
│   │   │   ├── how-anubis-works.mdx
│   │   │   └── why-proof-of-work.mdx
│   │   ├── developer/
│   │   │   ├── _category_.json
│   │   │   ├── ai-coding-policy.md
│   │   │   ├── building-anubis.md
│   │   │   ├── local-dev.md
│   │   │   └── signed-commits.md
│   │   ├── funding.md
│   │   ├── index.mdx
│   │   └── user/
│   │       ├── _category_.json
│   │       ├── frequently-asked-questions.mdx
│   │       ├── known-broken-extensions.md
│   │       ├── known-instances.md
│   │       └── why-see-challenge.md
│   ├── docusaurus.config.ts
│   ├── fly.toml
│   ├── manifest/
│   │   ├── 1password.yaml
│   │   ├── cfg/
│   │   │   ├── anubis/
│   │   │   │   └── botPolicies.yaml
│   │   │   └── nginx/
│   │   │       ├── mime.types
│   │   │       └── nginx.conf
│   │   ├── deployment.yaml
│   │   ├── ingress.yaml
│   │   ├── kustomization.yaml
│   │   ├── onionservice.yaml
│   │   ├── poddisruptionbudget.yaml
│   │   └── service.yaml
│   ├── package.json
│   ├── sidebars.ts
│   ├── src/
│   │   ├── components/
│   │   │   ├── EnterpriseOnly/
│   │   │   │   ├── index.jsx
│   │   │   │   └── styles.module.css
│   │   │   ├── HomepageFeatures/
│   │   │   │   ├── index.tsx
│   │   │   │   └── styles.module.css
│   │   │   └── RandomKey/
│   │   │       └── index.tsx
│   │   ├── css/
│   │   │   └── custom.css
│   │   └── pages/
│   │       ├── index.module.css
│   │       └── index.tsx
│   ├── static/
│   │   └── .nojekyll
│   └── tsconfig.json
├── go.mod
├── go.sum
├── internal/
│   ├── actorify/
│   │   └── actorify.go
│   ├── clampip.go
│   ├── clampip_test.go
│   ├── dns/
│   │   ├── cache.go
│   │   ├── dns.go
│   │   └── dns_test.go
│   ├── dnsbl/
│   │   ├── dnsbl.go
│   │   ├── dnsbl_test.go
│   │   └── droneblresponse_string.go
│   ├── glob/
│   │   ├── glob.go
│   │   └── glob_test.go
│   ├── gzip.go
│   ├── hash.go
│   ├── hash_bench_test.go
│   ├── headers.go
│   ├── health.go
│   ├── honeypot/
│   │   ├── honeypot.go
│   │   └── naive/
│   │       ├── 100bytes.css
│   │       ├── affirmations.txt
│   │       ├── naive.go
│   │       ├── page.templ
│   │       ├── page_templ.go
│   │       ├── spintext.txt
│   │       └── titles.txt
│   ├── ja4h.go
│   ├── listor.go
│   ├── listor_test.go
│   ├── log.go
│   ├── log_test.go
│   ├── mimetype.go
│   ├── ogtags/
│   │   ├── cache.go
│   │   ├── cache_test.go
│   │   ├── fetch.go
│   │   ├── fetch_test.go
│   │   ├── integration_test.go
│   │   ├── mem_test.go
│   │   ├── ogtags.go
│   │   ├── ogtags_fuzz_test.go
│   │   ├── ogtags_test.go
│   │   ├── parse.go
│   │   ├── parse_test.go
│   │   └── sni.go
│   ├── test/
│   │   ├── playwright_test.go
│   │   └── var/
│   │       └── .gitignore
│   ├── unbreakdocker.go
│   └── xff_test.go
├── lib/
│   ├── anubis.go
│   ├── anubis_test.go
│   ├── challenge/
│   │   ├── challenge.go
│   │   ├── challengetest/
│   │   │   ├── challengetest.go
│   │   │   └── challengetest_test.go
│   │   ├── error.go
│   │   ├── interface.go
│   │   ├── metarefresh/
│   │   │   ├── metarefresh.go
│   │   │   ├── metarefresh.templ
│   │   │   └── metarefresh_templ.go
│   │   ├── metrics.go
│   │   ├── preact/
│   │   │   ├── build.sh
│   │   │   ├── js/
│   │   │   │   ├── app.tsx
│   │   │   │   └── xeact.js
│   │   │   ├── preact.go
│   │   │   ├── preact.templ
│   │   │   ├── preact_templ.go
│   │   │   └── static/
│   │   │       └── .gitignore
│   │   └── proofofwork/
│   │       ├── proofofwork.go
│   │       ├── proofofwork.templ
│   │       ├── proofofwork_templ.go
│   │       └── proofofwork_test.go
│   ├── config/
│   │   ├── asn.go
│   │   ├── asn_test.go
│   │   ├── check.go
│   │   ├── config.go
│   │   ├── config_test.go
│   │   ├── expressionorlist.go
│   │   ├── expressionorlist_test.go
│   │   ├── geoip.go
│   │   ├── geoip_test.go
│   │   ├── impressum.go
│   │   ├── impressum_test.go
│   │   ├── logging.go
│   │   ├── logging_test.go
│   │   ├── opengraph.go
│   │   ├── opengraph_test.go
│   │   ├── store.go
│   │   ├── store_test.go
│   │   ├── testdata/
│   │   │   ├── bad/
│   │   │   │   ├── badregexes.json
│   │   │   │   ├── badregexes.yaml
│   │   │   │   ├── dns-ttl-custom.yaml
│   │   │   │   ├── import_and_bot.json
│   │   │   │   ├── import_and_bot.yaml
│   │   │   │   ├── import_invalid_file.json
│   │   │   │   ├── import_invalid_file.yaml
│   │   │   │   ├── impressum-no-footer.yaml
│   │   │   │   ├── impressum-no-page-contents.yaml
│   │   │   │   ├── invalid.json
│   │   │   │   ├── invalid.yaml
│   │   │   │   ├── logging-invalid-sink.yaml
│   │   │   │   ├── logging-no-parameters.yaml
│   │   │   │   ├── multiple_expression_types.json
│   │   │   │   ├── multiple_expression_types.yaml
│   │   │   │   ├── nobots.json
│   │   │   │   ├── nobots.yaml
│   │   │   │   ├── opengraph_bad_ttl.yaml
│   │   │   │   ├── regex_ends_newline.json
│   │   │   │   ├── regex_ends_newline.yaml
│   │   │   │   ├── status-codes-0.json
│   │   │   │   ├── status-codes-0.yaml
│   │   │   │   ├── threshold-challenge-without-challenge.yaml
│   │   │   │   ├── thresholds.yaml
│   │   │   │   ├── unparseable.json
│   │   │   │   └── unparseable.yaml
│   │   │   ├── good/
│   │   │   │   ├── allow_everyone.json
│   │   │   │   ├── allow_everyone.yaml
│   │   │   │   ├── block_cf_workers.json
│   │   │   │   ├── block_cf_workers.yaml
│   │   │   │   ├── challenge_cloudflare.yaml
│   │   │   │   ├── challengemozilla.json
│   │   │   │   ├── challengemozilla.yaml
│   │   │   │   ├── dns-ttl-custom.yaml
│   │   │   │   ├── entropy.yaml
│   │   │   │   ├── everything_blocked.json
│   │   │   │   ├── everything_blocked.yaml
│   │   │   │   ├── geoip_us.yaml
│   │   │   │   ├── git_client.json
│   │   │   │   ├── git_client.yaml
│   │   │   │   ├── import_filesystem.json
│   │   │   │   ├── import_filesystem.yaml
│   │   │   │   ├── import_keep_internet_working.json
│   │   │   │   ├── import_keep_internet_working.yaml
│   │   │   │   ├── impressum.yaml
│   │   │   │   ├── logging-file.yaml
│   │   │   │   ├── logging-stdio.yaml
│   │   │   │   ├── no-thresholds.yaml
│   │   │   │   ├── old_xesite.json
│   │   │   │   ├── opengraph_all_good.yaml
│   │   │   │   ├── simple-weight.yaml
│   │   │   │   ├── status-codes-paranoid.json
│   │   │   │   ├── status-codes-paranoid.yaml
│   │   │   │   ├── status-codes-rfc.json
│   │   │   │   ├── status-codes-rfc.yaml
│   │   │   │   ├── thresholds.yaml
│   │   │   │   └── weight-no-weight.yaml
│   │   │   ├── hack-test.json
│   │   │   └── hack-test.yaml
│   │   ├── threshold.go
│   │   ├── threshold_test.go
│   │   └── weight.go
│   ├── config.go
│   ├── config_test.go
│   ├── http.go
│   ├── http_test.go
│   ├── localization/
│   │   ├── locales/
│   │   │   ├── cs.json
│   │   │   ├── de.json
│   │   │   ├── en.json
│   │   │   ├── es.json
│   │   │   ├── et.json
│   │   │   ├── fi.json
│   │   │   ├── fil.json
│   │   │   ├── fr.json
│   │   │   ├── is.json
│   │   │   ├── it.json
│   │   │   ├── ja.json
│   │   │   ├── lt.json
│   │   │   ├── manifest.json
│   │   │   ├── nb.json
│   │   │   ├── nl.json
│   │   │   ├── nn.json
│   │   │   ├── pl.json
│   │   │   ├── pt-BR.json
│   │   │   ├── ru.json
│   │   │   ├── sv.json
│   │   │   ├── th.json
│   │   │   ├── tr.json
│   │   │   ├── uk.json
│   │   │   ├── vi.json
│   │   │   ├── zh-CN.json
│   │   │   └── zh-TW.json
│   │   ├── localization.go
│   │   └── localization_test.go
│   ├── policy/
│   │   ├── bot.go
│   │   ├── celchecker.go
│   │   ├── checker/
│   │   │   ├── checker.go
│   │   │   └── checker_test.go
│   │   ├── checker.go
│   │   ├── checker_test.go
│   │   ├── checkresult.go
│   │   ├── expressions/
│   │   │   ├── README.md
│   │   │   ├── environment.go
│   │   │   ├── environment_test.go
│   │   │   ├── http_headers.go
│   │   │   ├── http_headers_test.go
│   │   │   ├── loadavg.go
│   │   │   ├── url_values.go
│   │   │   └── url_values_test.go
│   │   ├── policy.go
│   │   ├── policy_test.go
│   │   ├── testdata/
│   │   │   ├── hack-test.json
│   │   │   └── hack-test.yaml
│   │   └── thresholds.go
│   ├── redirect_security_test.go
│   ├── store/
│   │   ├── actorifiedstore.go
│   │   ├── all/
│   │   │   └── all.go
│   │   ├── bbolt/
│   │   │   ├── bbolt.go
│   │   │   ├── bbolt_test.go
│   │   │   ├── factory.go
│   │   │   └── factory_test.go
│   │   ├── interface.go
│   │   ├── json_test.go
│   │   ├── memory/
│   │   │   ├── memory.go
│   │   │   └── memory_test.go
│   │   ├── registry.go
│   │   ├── s3api/
│   │   │   ├── factory.go
│   │   │   ├── s3api.go
│   │   │   └── s3api_test.go
│   │   ├── storetest/
│   │   │   └── storetest.go
│   │   └── valkey/
│   │       ├── factory.go
│   │       ├── valkey.go
│   │       └── valkey_test.go
│   ├── testdata/
│   │   ├── aggressive_403.yaml
│   │   ├── cloudflare-workers-cel.yaml
│   │   ├── cloudflare-workers-header.yaml
│   │   ├── hack-test.json
│   │   ├── hack-test.yaml
│   │   ├── invalid-challenge-method.yaml
│   │   ├── permissive.yaml
│   │   ├── rule_change.yaml
│   │   ├── test_config.yaml
│   │   ├── test_config_no_thresholds.yaml
│   │   ├── useragent.yaml
│   │   └── zero_difficulty.yaml
│   └── thoth/
│       ├── asnchecker.go
│       ├── asnchecker_test.go
│       ├── auth.go
│       ├── cachediptoasn.go
│       ├── context.go
│       ├── geoipchecker.go
│       ├── geoipchecker_test.go
│       ├── thoth.go
│       ├── thoth_test.go
│       └── thothmock/
│           ├── iptoasn.go
│           └── withthothmock.go
├── package.json
├── run/
│   ├── anubis.freebsd
│   ├── anubis@.service
│   ├── default.env
│   └── openrc/
│       ├── anubis.confd
│       └── anubis.initd
├── test/
│   ├── .gitignore
│   ├── anubis_configs/
│   │   └── aggressive_403.yaml
│   ├── caddy/
│   │   ├── Caddyfile
│   │   ├── Dockerfile
│   │   ├── docker-compose.yaml
│   │   └── start.sh
│   ├── cmd/
│   │   ├── cipra/
│   │   │   ├── internal/
│   │   │   │   ├── containerip.go
│   │   │   │   ├── getlanip.go
│   │   │   │   └── unbreakdocker.go
│   │   │   └── main.go
│   │   ├── httpdebug/
│   │   │   └── main.go
│   │   ├── relayd/
│   │   │   └── main.go
│   │   └── unixhttpd/
│   │       └── main.go
│   ├── default-config-macro/
│   │   ├── compare_bots.py
│   │   └── test.sh
│   ├── docker-registry/
│   │   ├── anubis.yaml
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── double_slash/
│   │   ├── anubis.yaml
│   │   ├── input.txt
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── forced-language/
│   │   ├── anubis.yaml
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── git-clone/
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── git-push/
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── go.mod
│   ├── go.sum
│   ├── healthcheck/
│   │   ├── docker-compose.yaml
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── i18n/
│   │   ├── anubis.yaml
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── k8s/
│   │   ├── cert-manager/
│   │   │   └── selfsigned-issuer.yaml
│   │   └── deps/
│   │       └── cert-manager.yaml
│   ├── lib/
│   │   └── lib.sh
│   ├── log-file/
│   │   ├── anubis.yaml
│   │   ├── input.txt
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── nginx/
│   │   ├── conf/
│   │   │   └── nginx/
│   │   │       ├── conf-anubis.inc
│   │   │       ├── conf.d/
│   │   │       │   ├── server-mimi-techaro-lol.conf
│   │   │       │   └── upstream-anubis.conf
│   │   │       ├── mime.types
│   │   │       └── nginx.conf
│   │   └── test.sh
│   ├── nginx-external-auth/
│   │   ├── conf.d/
│   │   │   └── default.conf
│   │   ├── deployment.yaml
│   │   ├── ingress.yaml
│   │   ├── kustomization.yaml
│   │   ├── service.yaml
│   │   └── start.sh
│   ├── palemoon/
│   │   ├── README.md
│   │   ├── amd64/
│   │   │   ├── docker-compose.yml
│   │   │   ├── test.sh
│   │   │   └── var/
│   │   │       └── .gitignore
│   │   ├── anubis/
│   │   │   └── anubis.yaml
│   │   ├── i386/
│   │   │   ├── docker-compose.yml
│   │   │   ├── test.sh
│   │   │   └── var/
│   │   │       └── .gitignore
│   │   └── scripts/
│   │       └── install-cert.sh
│   ├── pki/
│   │   └── .gitignore
│   ├── robots_txt/
│   │   ├── anubis.yaml
│   │   ├── test.mjs
│   │   ├── test.sh
│   │   └── var/
│   │       └── .gitignore
│   ├── shared/
│   │   └── www/
│   │       └── index.html
│   ├── ssh-ci/
│   │   ├── Dockerfile
│   │   ├── docker-bake.hcl
│   │   ├── in-container.sh
│   │   └── rigging.sh
│   └── unix-socket-xff/
│       ├── start.sh
│       └── test.mjs
├── utils/
│   └── cmd/
│       ├── backoff-retry/
│       │   └── main.go
│       └── iplist2rule/
│           ├── blocklist.go
│           └── main.go
├── var/
│   └── .gitignore
├── web/
│   ├── build.sh
│   ├── embed.go
│   ├── index.go
│   ├── index.templ
│   ├── index_templ.go
│   ├── index_test.go
│   ├── js/
│   │   ├── algorithms/
│   │   │   ├── fast.ts
│   │   │   └── index.ts
│   │   ├── bench.ts
│   │   ├── main.ts
│   │   └── worker/
│   │       ├── sha256-purejs.ts
│   │       └── sha256-webcrypto.ts
│   └── static/
│       ├── img/
│       │   └── ATTRIBUTIONS.txt
│       ├── js/
│       │   └── .gitignore
│       └── robots.txt
├── xess/
│   ├── .gitignore
│   ├── build.sh
│   ├── postcss.config.js
│   ├── static/
│   │   └── podkova.css
│   ├── xess.css
│   └── xess.go
└── yeetfile.js
Download .txt
SYMBOL INDEX (735 symbols across 164 files)

FILE: anubis.go
  constant CookieDefaultExpirationTime (line 21) | CookieDefaultExpirationTime = 7 * 24 * time.Hour
  constant StaticPath (line 30) | StaticPath = "/.within.website/x/cmd/anubis/"
  constant APIPrefix (line 33) | APIPrefix = "/.within.website/x/cmd/anubis/api/"
  constant DefaultDifficulty (line 37) | DefaultDifficulty = 4

FILE: cmd/containerbuild/main.go
  function main (line 27) | func main() {
  type image (line 115) | type image struct
  function parseImageList (line 120) | func parseImageList(imageList string) ([]image, error) {
  function run (line 146) | func run(command string) (string, error) {
  function setOutput (line 161) | func setOutput(key, val string) {

FILE: cmd/robots2policy/batch/batch_process.go
  function main (line 17) | func main() {

FILE: cmd/robots2policy/main.go
  type RobotsRule (line 32) | type RobotsRule struct
  type AnubisRule (line 40) | type AnubisRule struct
  function init (line 48) | func init() {
  function main (line 66) | func main() {
  function createRuleFromAccumulated (line 134) | func createRuleFromAccumulated(userAgents, disallows, allows []string, c...
  function parseRobotsTxt (line 147) | func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
  function parseIntSafe (line 222) | func parseIntSafe(s string) (int, error) {
  function convertToAnubisRules (line 228) | func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
  function buildPathCondition (line 369) | func buildPathCondition(robotsPath string) string {

FILE: cmd/robots2policy/robots2policy_test.go
  type TestCase (line 15) | type TestCase struct
  type TestOptions (line 22) | type TestOptions struct
  function TestDataFileConversion (line 30) | func TestDataFileConversion(t *testing.T) {
  function TestCaseInsensitiveParsing (line 205) | func TestCaseInsensitiveParsing(t *testing.T) {
  function TestVariousOutputFormats (line 238) | func TestVariousOutputFormats(t *testing.T) {
  function TestDifferentActions (line 275) | func TestDifferentActions(t *testing.T) {
  function TestPolicyNaming (line 306) | func TestPolicyNaming(t *testing.T) {
  function TestCrawlDelayWeights (line 340) | func TestCrawlDelayWeights(t *testing.T) {
  function TestBlacklistActions (line 384) | func TestBlacklistActions(t *testing.T) {
  function compareData (line 422) | func compareData(actual, expected any) bool {

FILE: data/embed_test.go
  function TestBotPoliciesEmbed (line 11) | func TestBotPoliciesEmbed(t *testing.T) {

FILE: decaymap/decaymap.go
  function Zilch (line 8) | func Zilch[T any]() T {
  type Impl (line 14) | type Impl struct
  type decayMapEntry (line 25) | type decayMapEntry struct
  type deleteReq (line 32) | type deleteReq struct
  function New (line 40) | func New[K comparable, V any]() *Impl[K, V] {
  method expire (line 52) | func (m *Impl[K, V]) expire(key K) bool {
  method Delete (line 69) | func (m *Impl[K, V]) Delete(key K) bool {
  method Get (line 83) | func (m *Impl[K, V]) Get(key K) (V, bool) {
  method Set (line 107) | func (m *Impl[K, V]) Set(key K, value V, ttl time.Duration) {
  method Cleanup (line 118) | func (m *Impl[K, V]) Cleanup() {
  method Len (line 131) | func (m *Impl[K, V]) Len() int {
  method Close (line 140) | func (m *Impl[K, V]) Close() {
  method cleanupWorker (line 146) | func (m *Impl[K, V]) cleanupWorker() {
  method applyDeletes (line 182) | func (m *Impl[K, V]) applyDeletes(batch []deleteReq[K]) {

FILE: decaymap/decaymap_test.go
  function TestImpl (line 8) | func TestImpl(t *testing.T) {
  function TestCleanup (line 47) | func TestCleanup(t *testing.T) {

FILE: docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx
  function sha256 (line 6) | async function sha256(message) {
  function App (line 66) | function App() {

FILE: docs/src/components/EnterpriseOnly/index.jsx
  function EnterpriseOnly (line 3) | function EnterpriseOnly({ link }) {

FILE: docs/src/components/HomepageFeatures/index.tsx
  type FeatureItem (line 6) | type FeatureItem = {
  function Feature (line 47) | function Feature({ title, description, imageURL }: FeatureItem) {
  function HomepageFeatures (line 61) | function HomepageFeatures(): ReactNode {

FILE: docs/src/components/RandomKey/index.tsx
  function toHex (line 6) | function toHex(buffer) {
  function RandomKey (line 18) | function RandomKey() {

FILE: docs/src/pages/index.tsx
  function HomepageHeader (line 11) | function HomepageHeader() {
  function Home (line 33) | function Home(): ReactNode {

FILE: internal/actorify/actorify.go
  function z (line 12) | func z[Z any]() Z {
  type Handler (line 23) | type Handler
  type Actor (line 29) | type Actor struct
  type message (line 34) | type message struct
  type reply (line 40) | type reply struct
  function New (line 47) | func New[Input, Output any](ctx context.Context, handler Handler[Input, ...
  method handle (line 58) | func (a *Actor[Input, Output]) handle(ctx context.Context) {
  method Call (line 89) | func (a *Actor[Input, Output]) Call(ctx context.Context, input Input) (O...

FILE: internal/clampip.go
  function ClampIP (line 5) | func ClampIP(addr netip.Addr) (netip.Prefix, bool) {

FILE: internal/clampip_test.go
  function TestClampIP (line 8) | func TestClampIP(t *testing.T) {
  function TestClampIPSuccess (line 132) | func TestClampIPSuccess(t *testing.T) {
  function TestClampIPZeroValue (line 175) | func TestClampIPZeroValue(t *testing.T) {
  function TestClampIPSpecialCases (line 198) | func TestClampIPSpecialCases(t *testing.T) {
  function BenchmarkClampIP (line 252) | func BenchmarkClampIP(b *testing.B) {

FILE: internal/dns/cache.go
  type DnsCache (line 12) | type DnsCache struct
  function NewDNSCache (line 19) | func NewDNSCache(forwardTTL int, reverseTTL int, backend store.Interface...
  method getCachedForward (line 34) | func (d *Dns) getCachedForward(host string) ([]string, bool) {
  method getCachedReverse (line 46) | func (d *Dns) getCachedReverse(addr string) ([]string, bool) {
  method forwardCachePut (line 58) | func (d *Dns) forwardCachePut(host string, entries []string) {
  method reverseCachePut (line 65) | func (d *Dns) reverseCachePut(addr string, entries []string) {

FILE: internal/dns/dns.go
  type Dns (line 20) | type Dns struct
    method ReverseDNS (line 33) | func (d *Dns) ReverseDNS(addr string) ([]string, error) {
    method LookupHost (line 62) | func (d *Dns) LookupHost(host string) ([]string, error) {
    method verifyFCrDNSInternal (line 86) | func (d *Dns) verifyFCrDNSInternal(addr string, names []string) bool {
    method VerifyFCrDNS (line 103) | func (d *Dns) VerifyFCrDNS(addr string, pattern *string) bool {
    method ArpaReverseIP (line 146) | func (d *Dns) ArpaReverseIP(addr string) (string, error) {
  function New (line 25) | func New(ctx context.Context, cache *DnsCache) *Dns {

FILE: internal/dns/dns_test.go
  function newTestDNS (line 15) | func newTestDNS(forwardTTL int, reverseTTL int) *Dns {
  function mockLookupAddr (line 23) | func mockLookupAddr(addr string) ([]string, error) {
  function mockLookupHost (line 41) | func mockLookupHost(host string) ([]string, error) {
  function TestMain (line 56) | func TestMain(m *testing.M) {
  function TestDns_ArpaReverseIP (line 77) | func TestDns_ArpaReverseIP(t *testing.T) {
  function TestDns_ReverseDNS (line 104) | func TestDns_ReverseDNS(t *testing.T) {
  function TestDns_LookupHost (line 175) | func TestDns_LookupHost(t *testing.T) {
  function TestDns_VerifyFCrDNS (line 238) | func TestDns_VerifyFCrDNS(t *testing.T) {

FILE: internal/dnsbl/dnsbl.go
  type DroneBLResponse (line 12) | type DroneBLResponse
  constant AllGood (line 15) | AllGood               DroneBLResponse = 0
  constant IRCDrone (line 16) | IRCDrone              DroneBLResponse = 3
  constant Bottler (line 17) | Bottler               DroneBLResponse = 5
  constant UnknownSpambotOrDrone (line 18) | UnknownSpambotOrDrone DroneBLResponse = 6
  constant DDOSDrone (line 19) | DDOSDrone             DroneBLResponse = 7
  constant SOCKSProxy (line 20) | SOCKSProxy            DroneBLResponse = 8
  constant HTTPProxy (line 21) | HTTPProxy             DroneBLResponse = 9
  constant ProxyChain (line 22) | ProxyChain            DroneBLResponse = 10
  constant OpenProxy (line 23) | OpenProxy             DroneBLResponse = 11
  constant OpenDNSResolver (line 24) | OpenDNSResolver       DroneBLResponse = 12
  constant BruteForceAttackers (line 25) | BruteForceAttackers   DroneBLResponse = 13
  constant OpenWingateProxy (line 26) | OpenWingateProxy      DroneBLResponse = 14
  constant CompromisedRouter (line 27) | CompromisedRouter     DroneBLResponse = 15
  constant AutoRootingWorms (line 28) | AutoRootingWorms      DroneBLResponse = 16
  constant AutoDetectedBotIP (line 29) | AutoDetectedBotIP     DroneBLResponse = 17
  constant Unknown (line 30) | Unknown               DroneBLResponse = 255
  function Reverse (line 33) | func Reverse(ip net.IP) string {
  function reverse4 (line 41) | func reverse4(ip net.IP) string {
  function reverse6 (line 52) | func reverse6(ip net.IP) string {
  function Lookup (line 68) | func Lookup(ipStr string) (DroneBLResponse, error) {

FILE: internal/dnsbl/dnsbl_test.go
  function TestReverse4 (line 10) | func TestReverse4(t *testing.T) {
  function TestReverse6 (line 28) | func TestReverse6(t *testing.T) {
  function TestLookup (line 49) | func TestLookup(t *testing.T) {

FILE: internal/dnsbl/droneblresponse_string.go
  function _ (line 7) | func _() {
  constant _DroneBLResponse_name_0 (line 30) | _DroneBLResponse_name_0 = "AllGood"
  constant _DroneBLResponse_name_1 (line 31) | _DroneBLResponse_name_1 = "IRCDrone"
  constant _DroneBLResponse_name_2 (line 32) | _DroneBLResponse_name_2 = "BottlerUnknownSpambotOrDroneDDOSDroneSOCKSPro...
  constant _DroneBLResponse_name_3 (line 33) | _DroneBLResponse_name_3 = "Unknown"
  method String (line 40) | func (i DroneBLResponse) String() string {

FILE: internal/glob/glob.go
  constant GLOB (line 5) | GLOB = "*"
  constant maxGlobParts (line 7) | maxGlobParts = 5
  function Glob (line 12) | func Glob(pattern, subj string) bool {

FILE: internal/glob/glob_test.go
  function TestGlob_EqualityAndEmpty (line 5) | func TestGlob_EqualityAndEmpty(t *testing.T) {
  function TestGlob_LeadingAndTrailing (line 28) | func TestGlob_LeadingAndTrailing(t *testing.T) {
  function TestGlob_MiddleAndOrder (line 53) | func TestGlob_MiddleAndOrder(t *testing.T) {
  function TestGlob_ConsecutiveStarsAndEmptyParts (line 80) | func TestGlob_ConsecutiveStarsAndEmptyParts(t *testing.T) {
  function TestGlob_MaxPartsLimit (line 103) | func TestGlob_MaxPartsLimit(t *testing.T) {
  function TestGlob_CaseSensitivity (line 137) | func TestGlob_CaseSensitivity(t *testing.T) {
  function TestGlob_EmptySubjectInteractions (line 154) | func TestGlob_EmptySubjectInteractions(t *testing.T) {
  function BenchmarkGlob (line 172) | func BenchmarkGlob(b *testing.B) {

FILE: internal/gzip.go
  function GzipMiddleware (line 9) | func GzipMiddleware(level int, next http.Handler) http.Handler {
  type gzipResponseWriter (line 28) | type gzipResponseWriter struct
    method Write (line 33) | func (w gzipResponseWriter) Write(b []byte) (int, error) {

FILE: internal/hash.go
  function SHA256sum (line 13) | func SHA256sum(text string) string {
  function FastHash (line 22) | func FastHash(text string) string {

FILE: internal/hash_bench_test.go
  function XXHash64sum (line 10) | func XXHash64sum(text string) string {
  function BenchmarkSHA256_PolicyInputs (line 59) | func BenchmarkSHA256_PolicyInputs(b *testing.B) {
  function BenchmarkXXHash_PolicyInputs (line 67) | func BenchmarkXXHash_PolicyInputs(b *testing.B) {
  function BenchmarkSHA256_ChallengeInputs (line 75) | func BenchmarkSHA256_ChallengeInputs(b *testing.B) {
  function BenchmarkXXHash_ChallengeInputs (line 83) | func BenchmarkXXHash_ChallengeInputs(b *testing.B) {
  function BenchmarkSHA256_BotRuleInputs (line 91) | func BenchmarkSHA256_BotRuleInputs(b *testing.B) {
  function BenchmarkXXHash_BotRuleInputs (line 99) | func BenchmarkXXHash_BotRuleInputs(b *testing.B) {
  function BenchmarkSHA256_CELInputs (line 107) | func BenchmarkSHA256_CELInputs(b *testing.B) {
  function BenchmarkXXHash_CELInputs (line 115) | func BenchmarkXXHash_CELInputs(b *testing.B) {
  function BenchmarkSHA256_ASNInputs (line 123) | func BenchmarkSHA256_ASNInputs(b *testing.B) {
  function BenchmarkXXHash_ASNInputs (line 131) | func BenchmarkXXHash_ASNInputs(b *testing.B) {
  function BenchmarkSHA256_PolicyList (line 140) | func BenchmarkSHA256_PolicyList(b *testing.B) {
  function BenchmarkXXHash_PolicyList (line 151) | func BenchmarkXXHash_PolicyList(b *testing.B) {
  function TestHashCollisions (line 163) | func TestHashCollisions(t *testing.T) {
  function TestXXHashFormat (line 231) | func TestXXHashFormat(t *testing.T) {

FILE: internal/headers.go
  type realIPKey (line 17) | type realIPKey struct
  function RealIP (line 19) | func RealIP(r *http.Request) (netip.Addr, bool) {
  type XFFComputePreferences (line 25) | type XFFComputePreferences struct
  function UnchangingCache (line 37) | func UnchangingCache(next http.Handler) http.Handler {
  function CustomRealIPHeader (line 53) | func CustomRealIPHeader(customRealIPHeaderValue string, next http.Handle...
  function RemoteXRealIP (line 67) | func RemoteXRealIP(useRemoteAddress bool, bindNetwork string, next http....
  function XForwardedForToXRealIP (line 97) | func XForwardedForToXRealIP(next http.Handler) http.Handler {
  function XForwardedForUpdate (line 114) | func XForwardedForUpdate(stripPrivate bool, next http.Handler) http.Hand...
  function computeXFFHeader (line 154) | func computeXFFHeader(remoteAddr string, origXFFHeader string, pref XFFC...
  function NoStoreCache (line 222) | func NoStoreCache(next http.Handler) http.Handler {
  function NoBrowsing (line 230) | func NoBrowsing(next http.Handler) http.Handler {

FILE: internal/health.go
  function SetHealth (line 12) | func SetHealth(svc string, status healthv1.HealthCheckResponse_ServingSt...
  function GetHealth (line 16) | func GetHealth(svc string) (healthv1.HealthCheckResponse_ServingStatus, ...

FILE: internal/honeypot/honeypot.go
  type Info (line 18) | type Info struct

FILE: internal/honeypot/naive/naive.go
  function New (line 39) | func New(st store.Interface, lg *slog.Logger) (*Impl, error) {
  type Impl (line 69) | type Impl struct
    method incrementUA (line 79) | func (i *Impl) incrementUA(ctx context.Context, userAgent string) int {
    method incrementNetwork (line 86) | func (i *Impl) incrementNetwork(ctx context.Context, network string) i...
    method CheckUA (line 93) | func (i *Impl) CheckUA() checker.Impl {
    method CheckNetwork (line 104) | func (i *Impl) CheckNetwork() checker.Impl {
    method Hash (line 115) | func (i *Impl) Hash() string {
    method makeAffirmations (line 119) | func (i *Impl) makeAffirmations() []string {
    method makeSpins (line 130) | func (i *Impl) makeSpins() []string {
    method makeTitle (line 141) | func (i *Impl) makeTitle() string {
    method ServeHTTP (line 145) | func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  type link (line 202) | type link struct

FILE: internal/honeypot/naive/page_templ.go
  function base (line 13) | func base(title string, body templ.Component) templ.Component {
  method maze (line 76) | func (i Impl) maze(body []string, links []link) templ.Component {

FILE: internal/ja4h.go
  function JA4H (line 9) | func JA4H(next http.Handler) http.Handler {

FILE: internal/listor.go
  type ListOr (line 10) | type ListOr
  method UnmarshalJSON (line 12) | func (lo *ListOr[T]) UnmarshalJSON(data []byte) error {

FILE: internal/listor_test.go
  function TestListOr_UnmarshalJSON (line 8) | func TestListOr_UnmarshalJSON(t *testing.T) {

FILE: internal/log.go
  function InitSlog (line 13) | func InitSlog(level string, sink io.Writer) *slog.Logger {
  function GetRequestLogger (line 31) | func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger {
  type ErrorLogFilter (line 50) | type ErrorLogFilter struct
    method Write (line 54) | func (elf *ErrorLogFilter) Write(p []byte) (n int, err error) {
  function GetFilteredHTTPLogger (line 68) | func GetFilteredHTTPLogger() *log.Logger {

FILE: internal/log_test.go
  function TestErrorLogFilter (line 12) | func TestErrorLogFilter(t *testing.T) {
  function TestGetRequestLogger (line 50) | func TestGetRequestLogger(t *testing.T) {

FILE: internal/mimetype.go
  function init (line 5) | func init() {

FILE: internal/ogtags/cache.go
  method GetOGTags (line 14) | func (c *OGTagCache) GetOGTags(ctx context.Context, url *url.URL, origin...
  method generateCacheKey (line 65) | func (c *OGTagCache) generateCacheKey(target string, originalHost string...
  method checkCache (line 77) | func (c *OGTagCache) checkCache(ctx context.Context, cacheKey string) ma...

FILE: internal/ogtags/cache_test.go
  function TestCacheReturnsDefault (line 17) | func TestCacheReturnsDefault(t *testing.T) {
  function TestCheckCache (line 50) | func TestCheckCache(t *testing.T) {
  function TestGetOGTags (line 87) | func TestGetOGTags(t *testing.T) {
  function TestGetOGTagsWithHostConsideration (line 181) | func TestGetOGTagsWithHostConsideration(t *testing.T) {

FILE: internal/ogtags/fetch.go
  method fetchHTMLDocumentWithCache (line 23) | func (c *OGTagCache) fetchHTMLDocumentWithCache(ctx context.Context, url...

FILE: internal/ogtags/fetch_test.go
  function TestFetchHTMLDocument (line 19) | func TestFetchHTMLDocument(t *testing.T) {
  function TestFetchHTMLDocumentInvalidURL (line 112) | func TestFetchHTMLDocumentInvalidURL(t *testing.T) {
  method fetchHTMLDocument (line 135) | func (c *OGTagCache) fetchHTMLDocument(ctx context.Context, urlStr strin...

FILE: internal/ogtags/integration_test.go
  function TestIntegrationGetOGTags (line 14) | func TestIntegrationGetOGTags(t *testing.T) {

FILE: internal/ogtags/mem_test.go
  function BenchmarkGetTarget (line 14) | func BenchmarkGetTarget(b *testing.B) {
  function BenchmarkExtractOGTags (line 51) | func BenchmarkExtractOGTags(b *testing.B) {
  function TestMemoryUsage (line 87) | func TestMemoryUsage(t *testing.T) {

FILE: internal/ogtags/ogtags.go
  constant maxContentLength (line 19) | maxContentLength = 8 << 20
  constant httpTimeout (line 20) | httpTimeout      = 5 * time.Second
  constant schemeSeparatorLength (line 22) | schemeSeparatorLength = 3
  constant querySeparatorLength (line 23) | querySeparatorLength  = 1
  type OGTagCache (line 26) | type OGTagCache struct
    method getTarget (line 136) | func (c *OGTagCache) getTarget(u *url.URL) string {
  type TargetOptions (line 48) | type TargetOptions struct
  function NewOGTagCache (line 54) | func NewOGTagCache(target string, conf config.OpenGraph, backend store.I...

FILE: internal/ogtags/ogtags_fuzz_test.go
  function FuzzGetTarget (line 17) | func FuzzGetTarget(f *testing.F) {
  function FuzzExtractOGTags (line 92) | func FuzzExtractOGTags(f *testing.F) {
  function FuzzGetTargetRoundTrip (line 180) | func FuzzGetTargetRoundTrip(f *testing.F) {
  function FuzzExtractMetaTagInfo (line 221) | func FuzzExtractMetaTagInfo(f *testing.F) {
  function BenchmarkFuzzedGetTarget (line 278) | func BenchmarkFuzzedGetTarget(b *testing.B) {

FILE: internal/ogtags/ogtags_test.go
  function TestNewOGTagCache (line 29) | func TestNewOGTagCache(t *testing.T) {
  function TestNewOGTagCache_UnixSocket (line 87) | func TestNewOGTagCache_UnixSocket(t *testing.T) {
  function TestGetTarget (line 129) | func TestGetTarget(t *testing.T) {
  function TestIntegrationGetOGTags_UnixSocket (line 198) | func TestIntegrationGetOGTags_UnixSocket(t *testing.T) {
  function TestGetOGTagsWithTargetHostOverride (line 286) | func TestGetOGTagsWithTargetHostOverride(t *testing.T) {
  function TestGetOGTagsWithInsecureSkipVerify (line 342) | func TestGetOGTagsWithInsecureSkipVerify(t *testing.T) {
  function TestGetOGTagsWithTargetSNI (line 380) | func TestGetOGTagsWithTargetSNI(t *testing.T) {
  function newSNIServer (line 456) | func newSNIServer(t *testing.T, body string) (*httptest.Server, *sniReco...
  function mustCertificateForHost (line 476) | func mustCertificateForHost(t *testing.T, host string) tls.Certificate {
  type sniRecorder (line 507) | type sniRecorder struct
    method record (line 512) | func (r *sniRecorder) record(name string) {
    method last (line 518) | func (r *sniRecorder) last() string {

FILE: internal/ogtags/parse.go
  method extractOGTags (line 11) | func (c *OGTagCache) extractOGTags(doc *html.Node) map[string]string {
  function isOGMetaTag (line 32) | func isOGMetaTag(n *html.Node) bool {
  method extractMetaTagInfo (line 40) | func (c *OGTagCache) extractMetaTagInfo(n *html.Node) (property, content...

FILE: internal/ogtags/parse_test.go
  function TestExtractOGTags (line 15) | func TestExtractOGTags(t *testing.T) {
  function TestIsOGMetaTag (line 127) | func TestIsOGMetaTag(t *testing.T) {
  function TestExtractMetaTagInfo (line 196) | func TestExtractMetaTagInfo(t *testing.T) {

FILE: internal/ogtags/sni.go
  method clientForSNI (line 9) | func (c *OGTagCache) clientForSNI(serverName string) *http.Client {

FILE: internal/test/playwright_test.go
  constant actionAllow (line 97) | actionAllow     action = "ALLOW"
  constant actionDeny (line 98) | actionDeny      action = "DENY"
  constant actionChallenge (line 99) | actionChallenge action = "CHALLENGE"
  constant placeholderIP (line 101) | placeholderIP     = "fd11:5ee:bad:c0de::"
  constant playwrightVersion (line 102) | playwrightVersion = "1.52.0"
  type action (line 105) | type action
  type testCase (line 107) | type testCase struct
  function doesCommandExist (line 115) | func doesCommandExist(t *testing.T, command string) {
  function run (line 123) | func run(t *testing.T, command string) string {
  function daemonize (line 147) | func daemonize(t *testing.T, command string) {
  function startPlaywright (line 171) | func startPlaywright(t *testing.T) {
  function TestPlaywrightBrowser (line 211) | func TestPlaywrightBrowser(t *testing.T) {
  function TestPlaywrightWithBasePrefix (line 291) | func TestPlaywrightWithBasePrefix(t *testing.T) {
  function buildBrowserConnect (line 421) | func buildBrowserConnect(name string) string {
  function executeTestCase (line 431) | func executeTestCase(t *testing.T, tc testCase, typ playwright.BrowserTy...
  function checkImage (line 507) | func checkImage(t *testing.T, tc testCase, deadline time.Time, page play...
  function pwFail (line 526) | func pwFail(t *testing.T, page playwright.Page, format string, args ...a...
  function pwTimeout (line 533) | func pwTimeout(tc testCase, deadline time.Time) *float64 {
  function saveScreenshot (line 546) | func saveScreenshot(t *testing.T, page playwright.Page) {
  function setupPlaywright (line 571) | func setupPlaywright(t *testing.T) *playwright.Playwright {
  function spawnAnubis (line 586) | func spawnAnubis(t *testing.T) string {
  function spawnAnubisWithOptions (line 590) | func spawnAnubisWithOptions(t *testing.T, basePrefix string) string {

FILE: internal/unbreakdocker.go
  function UnbreakDocker (line 8) | func UnbreakDocker() {

FILE: internal/xff_test.go
  function TestXForwardedForUpdateIgnoreUnix (line 10) | func TestXForwardedForUpdateIgnoreUnix(t *testing.T) {
  function TestXForwardedForUpdateAddToChain (line 37) | func TestXForwardedForUpdateAddToChain(t *testing.T) {
  function TestComputeXFFHeader (line 66) | func TestComputeXFFHeader(t *testing.T) {

FILE: lib/anubis.go
  type Server (line 69) | type Server struct
    method getTokenKeyfunc (line 81) | func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
    method getChallenge (line 94) | func (s *Server) getChallenge(r *http.Request) (*challenge.Challenge, ...
    method issueChallenge (line 103) | func (s *Server) issueChallenge(ctx context.Context, r *http.Request, ...
    method hydrateChallengeRule (line 149) | func (s *Server) hydrateChallengeRule(rule *policy.Bot, chall *challen...
    method maybeReverseProxyHttpStatusOnly (line 187) | func (s *Server) maybeReverseProxyHttpStatusOnly(w http.ResponseWriter...
    method maybeReverseProxyOrPage (line 191) | func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *htt...
    method maybeReverseProxy (line 195) | func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Requ...
    method checkRules (line 298) | func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr...
    method handleDNSBL (line 340) | func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, i...
    method MakeChallenge (line 368) | func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
    method PassChallenge (line 441) | func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
    method check (line 607) | func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.Check...
  function cr (line 598) | func cr(name string, rule config.Rule, weight int) policy.CheckResult {

FILE: lib/anubis_test.go
  type TLogWriter (line 30) | type TLogWriter struct
    method Write (line 40) | func (w *TLogWriter) Write(p []byte) (n int, err error) {
  function NewTLogWriter (line 35) | func NewTLogWriter(t *testing.T) io.Writer {
  function loadPolicies (line 50) | func loadPolicies(t *testing.T, fname string, difficulty int) *policy.Pa...
  function spawnAnubis (line 69) | func spawnAnubis(t *testing.T, opts Options) *Server {
  type challengeResp (line 89) | type challengeResp struct
  function makeChallenge (line 94) | func makeChallenge(t *testing.T, ts *httptest.Server, cli *http.Client) ...
  function handleChallengeZeroDifficulty (line 120) | func handleChallengeZeroDifficulty(t *testing.T, ts *httptest.Server, cl...
  function handleChallengeInvalidProof (line 155) | func handleChallengeInvalidProof(t *testing.T, ts *httptest.Server, cli ...
  type loggingCookieJar (line 179) | type loggingCookieJar struct
    method Cookies (line 185) | func (lcj *loggingCookieJar) Cookies(u *url.URL) []*http.Cookie {
    method SetCookies (line 204) | func (lcj *loggingCookieJar) SetCookies(u *url.URL, cookies []*http.Co...
  type userAgentRoundTripper (line 216) | type userAgentRoundTripper struct
    method RoundTrip (line 220) | func (u *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Re...
  function httpClient (line 228) | func httpClient(t *testing.T) *http.Client {
  function TestLoadPolicies (line 244) | func TestLoadPolicies(t *testing.T) {
  function TestCVE2025_24369 (line 261) | func TestCVE2025_24369(t *testing.T) {
  function TestCookieCustomExpiration (line 282) | func TestCookieCustomExpiration(t *testing.T) {
  function TestCookieSettings (line 320) | func TestCookieSettings(t *testing.T) {
  function TestCookieSettingsSameSiteNoneModeDowngradedToLaxWhenUnsecure (line 376) | func TestCookieSettingsSameSiteNoneModeDowngradedToLaxWhenUnsecure(t *te...
  function TestCheckDefaultDifficultyMatchesPolicy (line 432) | func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
  function TestBasePrefix (line 471) | func TestBasePrefix(t *testing.T) {
  function TestCustomStatusCodes (line 638) | func TestCustomStatusCodes(t *testing.T) {
  function TestCloudflareWorkersRule (line 682) | func TestCloudflareWorkersRule(t *testing.T) {
  function TestRuleChange (line 740) | func TestRuleChange(t *testing.T) {
  function TestStripBasePrefixFromRequest (line 766) | func TestStripBasePrefixFromRequest(t *testing.T) {
  function TestChallengeFor_ErrNotFound (line 867) | func TestChallengeFor_ErrNotFound(t *testing.T) {
  function TestPassChallengeXSS (line 929) | func TestPassChallengeXSS(t *testing.T) {
  function TestPassChallengeNilRuleChallengeFallback (line 1053) | func TestPassChallengeNilRuleChallengeFallback(t *testing.T) {
  function TestXForwardedForNoDoubleComma (line 1106) | func TestXForwardedForNoDoubleComma(t *testing.T) {

FILE: lib/challenge/challenge.go
  type Challenge (line 6) | type Challenge struct

FILE: lib/challenge/challengetest/challengetest.go
  function New (line 13) | func New(t *testing.T) *challenge.Challenge {

FILE: lib/challenge/challengetest/challengetest_test.go
  function TestNew (line 5) | func TestNew(t *testing.T) {

FILE: lib/challenge/error.go
  function NewError (line 16) | func NewError(verb, publicReason string, privateReason error) *Error {
  type Error (line 25) | type Error struct
    method Error (line 32) | func (e *Error) Error() string {
    method Unwrap (line 36) | func (e *Error) Unwrap() error {

FILE: lib/challenge/interface.go
  function Register (line 21) | func Register(name string, impl Impl) {
  function Get (line 28) | func Get(name string) (Impl, bool) {
  function Methods (line 35) | func Methods() []string {
  type IssueInput (line 46) | type IssueInput struct
    method Valid (line 54) | func (in *IssueInput) Valid() error {
  type ValidateInput (line 70) | type ValidateInput struct
    method Valid (line 76) | func (in *ValidateInput) Valid() error {
  type Impl (line 92) | type Impl interface

FILE: lib/challenge/metarefresh/metarefresh.go
  function init (line 18) | func init() {
  type Impl (line 22) | type Impl struct
    method Setup (line 24) | func (i *Impl) Setup(mux *http.ServeMux) {}
    method Issue (line 26) | func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog....
    method Validate (line 55) | func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challeng...

FILE: lib/challenge/metarefresh/metarefresh_templ.go
  function page (line 18) | func page(redir string, difficulty int, showMeta bool, loc *localization...

FILE: lib/challenge/preact/js/app.tsx
  function toHexString (line 9) | function toHexString(arr: Uint8Array) {
  type PreactInfo (line 15) | interface PreactInfo {

FILE: lib/challenge/preact/preact.go
  function renderAppJS (line 26) | func renderAppJS(ctx context.Context, out io.Writer) error {
  function init (line 33) | func init() {
  type impl (line 37) | type impl struct
    method Setup (line 39) | func (i *impl) Setup(mux *http.ServeMux) {}
    method Issue (line 41) | func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog....
    method Validate (line 63) | func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challeng...

FILE: lib/challenge/preact/preact_templ.go
  function page (line 16) | func page(redir, challenge string, difficulty int, loc *localization.Sim...

FILE: lib/challenge/proofofwork/proofofwork.go
  function init (line 19) | func init() {
  type Impl (line 24) | type Impl struct
    method Setup (line 28) | func (i *Impl) Setup(mux *http.ServeMux) {}
    method Issue (line 30) | func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog....
    method Validate (line 35) | func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.Va...

FILE: lib/challenge/proofofwork/proofofwork_templ.go
  function page (line 16) | func page(localizer *localization.SimpleLocalizer) templ.Component {

FILE: lib/challenge/proofofwork/proofofwork_test.go
  function mkRequest (line 15) | func mkRequest(t *testing.T, values map[string]string) *http.Request {
  function TestValidateNilRuleChallenge (line 40) | func TestValidateNilRuleChallenge(t *testing.T) {
  function TestBasic (line 89) | func TestBasic(t *testing.T) {

FILE: lib/config.go
  type Options (line 30) | type Options struct
  function LoadPoliciesOrDefault (line 58) | func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDif...
  function New (line 101) | func New(opts Options) (*Server, error) {

FILE: lib/config/asn.go
  type ASNs (line 12) | type ASNs struct
    method Valid (line 16) | func (a *ASNs) Valid() error {
  function isPrivateASN (line 35) | func isPrivateASN(asn uint32) bool {

FILE: lib/config/asn_test.go
  function TestASNsValid (line 9) | func TestASNsValid(t *testing.T) {
  function TestIsPrivateASN (line 39) | func TestIsPrivateASN(t *testing.T) {

FILE: lib/config/check.go
  type AllChecks (line 18) | type AllChecks struct
  type AnyChecks (line 22) | type AnyChecks struct
  type Check (line 26) | type Check struct
    method Valid (line 31) | func (c *Check) Valid(ctx context.Context) error {

FILE: lib/config/config.go
  type Rule (line 36) | type Rule
    method Valid (line 47) | func (r Rule) Valid() error {
  constant RuleUnknown (line 39) | RuleUnknown   Rule = ""
  constant RuleAllow (line 40) | RuleAllow     Rule = "ALLOW"
  constant RuleDeny (line 41) | RuleDeny      Rule = "DENY"
  constant RuleChallenge (line 42) | RuleChallenge Rule = "CHALLENGE"
  constant RuleWeigh (line 43) | RuleWeigh     Rule = "WEIGH"
  constant RuleBenchmark (line 44) | RuleBenchmark Rule = "DEBUG_BENCHMARK"
  constant DefaultAlgorithm (line 56) | DefaultAlgorithm = "fast"
  type BotConfig (line 58) | type BotConfig struct
    method Zero (line 75) | func (b BotConfig) Zero() bool {
    method Valid (line 95) | func (b *BotConfig) Valid() error {
  type ChallengeRules (line 191) | type ChallengeRules struct
    method Valid (line 203) | func (cr ChallengeRules) Valid() error {
  type ImportStatement (line 225) | type ImportStatement struct
    method open (line 230) | func (is *ImportStatement) open() (fs.File, error) {
    method load (line 240) | func (is *ImportStatement) load() error {
    method Valid (line 279) | func (is *ImportStatement) Valid() error {
  type BotOrImport (line 283) | type BotOrImport struct
    method Valid (line 288) | func (boi *BotOrImport) Valid() error {
  type StatusCodes (line 304) | type StatusCodes struct
    method Valid (line 309) | func (sc StatusCodes) Valid() error {
  type fileConfig (line 327) | type fileConfig struct
    method Valid (line 339) | func (c *fileConfig) Valid() error {
  function Load (line 385) | func Load(fin io.Reader, fname string) (*Config, error) {
  type DnsTTL (line 478) | type DnsTTL struct
    method Valid (line 483) | func (sc DnsTTL) Valid() error {
  type Config (line 501) | type Config struct
    method Valid (line 513) | func (c Config) Valid() error {

FILE: lib/config/config_test.go
  function p (line 14) | func p[V any](v V) *V { return &v }
  function TestBotValid (line 16) | func TestBotValid(t *testing.T) {
  function TestConfigValidKnownGood (line 211) | func TestConfigValidKnownGood(t *testing.T) {
  function TestImportStatement (line 241) | func TestImportStatement(t *testing.T) {
  function TestConfigValidBad (line 297) | func TestConfigValidBad(t *testing.T) {
  function TestBotConfigZero (line 321) | func TestBotConfigZero(t *testing.T) {

FILE: lib/config/expressionorlist.go
  type ExpressionOrList (line 17) | type ExpressionOrList struct
    method String (line 23) | func (eol ExpressionOrList) String() string {
    method Equal (line 49) | func (eol ExpressionOrList) Equal(rhs *ExpressionOrList) bool {
    method MarshalYAML (line 65) | func (eol *ExpressionOrList) MarshalYAML() (any, error) {
    method MarshalJSON (line 83) | func (eol *ExpressionOrList) MarshalJSON() ([]byte, error) {
    method UnmarshalJSON (line 102) | func (eol *ExpressionOrList) UnmarshalJSON(data []byte) error {
    method Valid (line 121) | func (eol *ExpressionOrList) Valid() error {

FILE: lib/config/expressionorlist_test.go
  function TestExpressionOrListMarshalJSON (line 12) | func TestExpressionOrListMarshalJSON(t *testing.T) {
  function TestExpressionOrListMarshalYAML (line 75) | func TestExpressionOrListMarshalYAML(t *testing.T) {
  function TestExpressionOrListUnmarshalJSON (line 144) | func TestExpressionOrListUnmarshalJSON(t *testing.T) {
  function TestExpressionOrListString (line 217) | func TestExpressionOrListString(t *testing.T) {

FILE: lib/config/geoip.go
  type GeoIP (line 16) | type GeoIP struct
    method Valid (line 20) | func (g *GeoIP) Valid() error {

FILE: lib/config/geoip_test.go
  function TestGeoIPValid (line 8) | func TestGeoIPValid(t *testing.T) {

FILE: lib/config/impressum.go
  type Impressum (line 12) | type Impressum struct
    method Render (line 17) | func (i Impressum) Render(_ context.Context, w io.Writer) error {
    method Valid (line 24) | func (i Impressum) Valid() error {
  type ImpressumPage (line 42) | type ImpressumPage struct
    method Render (line 47) | func (ip ImpressumPage) Render(_ context.Context, w io.Writer) error {
    method Valid (line 55) | func (ip ImpressumPage) Valid() error {

FILE: lib/config/impressum_test.go
  function TestImpressumValid (line 9) | func TestImpressumValid(t *testing.T) {

FILE: lib/config/logging.go
  type Logging (line 16) | type Logging struct
    method Valid (line 27) | func (l *Logging) Valid() error {
    method Default (line 52) | func (Logging) Default() *Logging {
  constant LogSinkStdio (line 23) | LogSinkStdio = "stdio"
  constant LogSinkFile (line 24) | LogSinkFile  = "file"
  type LoggingFileConfig (line 58) | type LoggingFileConfig struct
    method Valid (line 67) | func (lfc *LoggingFileConfig) Valid() error {
    method Zero (line 98) | func (lfc LoggingFileConfig) Zero() bool {
    method Default (line 115) | func (LoggingFileConfig) Default() *LoggingFileConfig {

FILE: lib/config/logging_test.go
  function TestLoggingValid (line 8) | func TestLoggingValid(t *testing.T) {

FILE: lib/config/opengraph.go
  type openGraphFileConfig (line 15) | type openGraphFileConfig struct
    method Valid (line 29) | func (og *openGraphFileConfig) Valid() error {
  type OpenGraph (line 22) | type OpenGraph struct

FILE: lib/config/opengraph_test.go
  function TestOpenGraphFileConfigValid (line 8) | func TestOpenGraphFileConfigValid(t *testing.T) {

FILE: lib/config/store.go
  type Store (line 17) | type Store struct
    method Valid (line 22) | func (s *Store) Valid() error {

FILE: lib/config/store_test.go
  function TestStoreValid (line 13) | func TestStoreValid(t *testing.T) {

FILE: lib/config/threshold.go
  type Threshold (line 32) | type Threshold struct
    method Valid (line 39) | func (t Threshold) Valid() error {

FILE: lib/config/threshold_test.go
  function TestThresholdValid (line 11) | func TestThresholdValid(t *testing.T) {
  function TestDefaultThresholdsValid (line 85) | func TestDefaultThresholdsValid(t *testing.T) {
  function TestLoadActuallyLoadsThresholds (line 95) | func TestLoadActuallyLoadsThresholds(t *testing.T) {

FILE: lib/config/weight.go
  type Weight (line 3) | type Weight struct

FILE: lib/config_test.go
  function TestInvalidChallengeMethod (line 14) | func TestInvalidChallengeMethod(t *testing.T) {
  function TestBadConfigs (line 20) | func TestBadConfigs(t *testing.T) {
  function TestGoodConfigs (line 37) | func TestGoodConfigs(t *testing.T) {

FILE: lib/http.go
  function matchRedirectDomain (line 38) | func matchRedirectDomain(allowed []string, host string) bool {
  type CookieOpts (line 55) | type CookieOpts struct
  method SetCookie (line 63) | func (s *Server) SetCookie(w http.ResponseWriter, cookieOpts CookieOpts) {
  method ClearCookie (line 101) | func (s *Server) ClearCookie(w http.ResponseWriter, cookieOpts CookieOpt...
  type UnixRoundTripper (line 136) | type UnixRoundTripper struct
    method RoundTrip (line 141) | func (t UnixRoundTripper) RoundTrip(req *http.Request) (*http.Response...
  function randomChance (line 151) | func randomChance(n int) bool {
  function rot13 (line 156) | func rot13(s string) string {
  function makeCode (line 171) | func makeCode(err error) string {
  method RenderIndex (line 192) | func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr ...
  method constructRedirectURL (line 294) | func (s *Server) constructRedirectURL(r *http.Request) (string, error) {
  method RenderBench (line 326) | func (s *Server) RenderBench(w http.ResponseWriter, r *http.Request) {
  method respondWithError (line 334) | func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request...
  method respondWithStatus (line 338) | func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Reques...
  method ServeHTTP (line 351) | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  method stripBasePrefixFromRequest (line 372) | func (s *Server) stripBasePrefixFromRequest(r *http.Request) *http.Reque...
  method ServeHTTPNext (line 398) | func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
  method signJWT (line 451) | func (s *Server) signJWT(claims jwt.MapClaims) (string, error) {

FILE: lib/http_test.go
  function TestSetCookie (line 14) | func TestSetCookie(t *testing.T) {
  function TestClearCookie (line 58) | func TestClearCookie(t *testing.T) {
  function TestClearCookieWithDomain (line 83) | func TestClearCookieWithDomain(t *testing.T) {
  function TestClearCookieWithDynamicDomain (line 108) | func TestClearCookieWithDynamicDomain(t *testing.T) {
  function TestRenderIndexRedirect (line 137) | func TestRenderIndexRedirect(t *testing.T) {
  function TestRenderIndexUnauthorized (line 177) | func TestRenderIndexUnauthorized(t *testing.T) {
  function TestNoCacheOnError (line 196) | func TestNoCacheOnError(t *testing.T) {

FILE: lib/localization/localization.go
  type LocalizationService (line 18) | type LocalizationService struct
    method GetLocalizer (line 72) | func (ls *LocalizationService) GetLocalizer(lang string) *i18n.Localiz...
    method GetLocalizerFromRequest (line 76) | func (ls *LocalizationService) GetLocalizerFromRequest(r *http.Request...
  function NewLocalizationService (line 27) | func NewLocalizationService() *LocalizationService {
  type SimpleLocalizer (line 109) | type SimpleLocalizer struct
    method T (line 114) | func (sl *SimpleLocalizer) T(messageID string) string {
    method GetLang (line 119) | func (sl *SimpleLocalizer) GetLang() string {
  function GetLocalizer (line 128) | func GetLocalizer(r *http.Request) *SimpleLocalizer {

FILE: lib/localization/localization_test.go
  function TestLocalizationService (line 13) | func TestLocalizationService(t *testing.T) {
  type manifest (line 77) | type manifest struct
  function loadManifest (line 81) | func loadManifest(t *testing.T) manifest {
  function TestComprehensiveTranslations (line 98) | func TestComprehensiveTranslations(t *testing.T) {
  function TestAcceptLanguageQualityFactors (line 143) | func TestAcceptLanguageQualityFactors(t *testing.T) {

FILE: lib/policy/bot.go
  type Bot (line 11) | type Bot struct
    method Hash (line 19) | func (b Bot) Hash() string {

FILE: lib/policy/celchecker.go
  type CELChecker (line 15) | type CELChecker struct
    method Hash (line 37) | func (cc *CELChecker) Hash() string {
    method Check (line 41) | func (cc *CELChecker) Check(r *http.Request) (bool, error) {
  function NewCELChecker (line 20) | func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns) (*CELC...
  type CELRequest (line 55) | type CELRequest struct
    method Parent (line 59) | func (cr *CELRequest) Parent() cel.Activation { return nil }
    method ResolveName (line 61) | func (cr *CELRequest) ResolveName(name string) (any, bool) {

FILE: lib/policy/checker.go
  type RemoteAddrChecker (line 20) | type RemoteAddrChecker struct
    method Check (line 43) | func (rac *RemoteAddrChecker) Check(r *http.Request) (bool, error) {
    method Hash (line 62) | func (rac *RemoteAddrChecker) Hash() string {
  function NewRemoteAddrChecker (line 25) | func NewRemoteAddrChecker(cidrs []string) (checker.Impl, error) {
  type HeaderMatchesChecker (line 66) | type HeaderMatchesChecker struct
    method Check (line 84) | func (hmc *HeaderMatchesChecker) Check(r *http.Request) (bool, error) {
    method Hash (line 92) | func (hmc *HeaderMatchesChecker) Hash() string {
  function NewUserAgentChecker (line 72) | func NewUserAgentChecker(rexStr string) (checker.Impl, error) {
  function NewHeaderMatchesChecker (line 76) | func NewHeaderMatchesChecker(header, rexStr string) (checker.Impl, error) {
  type PathChecker (line 96) | type PathChecker struct
    method Check (line 109) | func (pc *PathChecker) Check(r *http.Request) (bool, error) {
    method Hash (line 124) | func (pc *PathChecker) Hash() string {
  function NewPathChecker (line 101) | func NewPathChecker(rexStr string) (checker.Impl, error) {
  function NewHeaderExistsChecker (line 128) | func NewHeaderExistsChecker(key string) checker.Impl {
  type headerExistsChecker (line 132) | type headerExistsChecker struct
    method Check (line 136) | func (hec headerExistsChecker) Check(r *http.Request) (bool, error) {
    method Hash (line 144) | func (hec headerExistsChecker) Hash() string {
  function NewHeadersChecker (line 148) | func NewHeadersChecker(headermap map[string]string) (checker.Impl, error) {

FILE: lib/policy/checker/checker.go
  type Impl (line 12) | type Impl interface
  type Func (line 17) | type Func
    method Check (line 19) | func (f Func) Check(r *http.Request) (bool, error) {
    method Hash (line 23) | func (f Func) Hash() string { return internal.FastHash(fmt.Sprintf("%#...
  type List (line 25) | type List
    method Check (line 30) | func (l List) Check(r *http.Request) (bool, error) {
    method Hash (line 47) | func (l List) Hash() string {

FILE: lib/policy/checker/checker_test.go
  type Mock (line 10) | type Mock struct
    method Check (line 16) | func (m Mock) Check(r *http.Request) (bool, error) { return m.result, ...
    method Hash (line 17) | func (m Mock) Hash() string                        { return m.hash }
  function TestListCheck_AndSemantics (line 19) | func TestListCheck_AndSemantics(t *testing.T) {

FILE: lib/policy/checker_test.go
  function TestRemoteAddrChecker (line 9) | func TestRemoteAddrChecker(t *testing.T) {
  function TestHeaderMatchesChecker (line 101) | func TestHeaderMatchesChecker(t *testing.T) {
  function TestHeaderExistsChecker (line 174) | func TestHeaderExistsChecker(t *testing.T) {
  function TestPathChecker_XOriginalURI (line 216) | func TestPathChecker_XOriginalURI(t *testing.T) {

FILE: lib/policy/checkresult.go
  type CheckResult (line 9) | type CheckResult struct
    method LogValue (line 15) | func (cr CheckResult) LogValue() slog.Value {

FILE: lib/policy/expressions/environment.go
  function BotEnvironment (line 19) | func BotEnvironment(dnsObj *dns.Dns) (*cel.Env, error) {
  function ThresholdEnvironment (line 198) | func ThresholdEnvironment() (*cel.Env, error) {
  function New (line 204) | func New(opts ...cel.EnvOption) (*cel.Env, error) {
  function Compile (line 237) | func Compile(env *cel.Env, src string) (cel.Program, error) {

FILE: lib/policy/expressions/environment_test.go
  function newTestDNS (line 17) | func newTestDNS(forwardTTL int, reverseTTL int) *dns.Dns {
  function TestBotEnvironment (line 24) | func TestBotEnvironment(t *testing.T) {
  function TestThresholdEnvironment (line 594) | func TestThresholdEnvironment(t *testing.T) {
  function TestNewEnvironment (line 661) | func TestNewEnvironment(t *testing.T) {
  function boolPtr (line 750) | func boolPtr(b bool) *bool {

FILE: lib/policy/expressions/http_headers.go
  type HTTPHeaders (line 14) | type HTTPHeaders struct
    method ConvertToNative (line 18) | func (h HTTPHeaders) ConvertToNative(typeDesc reflect.Type) (any, erro...
    method ConvertToType (line 22) | func (h HTTPHeaders) ConvertToType(typeVal ref.Type) ref.Val {
    method Equal (line 33) | func (h HTTPHeaders) Equal(other ref.Val) ref.Val {
    method Type (line 37) | func (h HTTPHeaders) Type() ref.Type {
    method Value (line 41) | func (h HTTPHeaders) Value() any { return h }
    method Find (line 43) | func (h HTTPHeaders) Find(key ref.Val) (ref.Val, bool) {
    method Contains (line 56) | func (h HTTPHeaders) Contains(key ref.Val) ref.Val {
    method Get (line 61) | func (h HTTPHeaders) Get(key ref.Val) ref.Val {
    method Iterator (line 69) | func (h HTTPHeaders) Iterator() traits.Iterator { panic("TODO(Xe): imp...
    method IsZeroValue (line 71) | func (h HTTPHeaders) IsZeroValue() bool {
    method Size (line 75) | func (h HTTPHeaders) Size() ref.Val { return types.Int(len(h.Header)) }

FILE: lib/policy/expressions/http_headers_test.go
  function TestHTTPHeaders (line 10) | func TestHTTPHeaders(t *testing.T) {

FILE: lib/policy/expressions/loadavg.go
  type loadAvg (line 12) | type loadAvg struct
    method updateThread (line 17) | func (l *loadAvg) updateThread(ctx context.Context) {
    method update (line 33) | func (l *loadAvg) update() {
  function init (line 48) | func init() {
  function Load1 (line 53) | func Load1() float64 {
  function Load5 (line 59) | func Load5() float64 {
  function Load15 (line 65) | func Load15() float64 {

FILE: lib/policy/expressions/url_values.go
  type URLValues (line 17) | type URLValues struct
    method ConvertToNative (line 21) | func (u URLValues) ConvertToNative(typeDesc reflect.Type) (any, error) {
    method ConvertToType (line 25) | func (u URLValues) ConvertToType(typeVal ref.Type) ref.Val {
    method Equal (line 36) | func (u URLValues) Equal(other ref.Val) ref.Val {
    method Type (line 40) | func (u URLValues) Type() ref.Type {
    method Value (line 44) | func (u URLValues) Value() any { return u }
    method Find (line 46) | func (u URLValues) Find(key ref.Val) (ref.Val, bool) {
    method Contains (line 59) | func (u URLValues) Contains(key ref.Val) ref.Val {
    method Get (line 64) | func (u URLValues) Get(key ref.Val) ref.Val {
    method Iterator (line 72) | func (u URLValues) Iterator() traits.Iterator { panic("TODO(Xe): imple...
    method IsZeroValue (line 74) | func (u URLValues) IsZeroValue() bool {
    method Size (line 78) | func (u URLValues) Size() ref.Val { return types.Int(len(u.Values)) }

FILE: lib/policy/expressions/url_values_test.go
  function TestURLValues (line 10) | func TestURLValues(t *testing.T) {

FILE: lib/policy/policy.go
  type ParsedConfig (line 36) | type ParsedConfig struct
  function newParsedConfig (line 51) | func newParsedConfig(orig *config.Config) *ParsedConfig {
  function ParseConfig (line 59) | func ParseConfig(ctx context.Context, fin io.Reader, fname string, defau...

FILE: lib/policy/policy_test.go
  function TestDefaultPolicyMustParse (line 13) | func TestDefaultPolicyMustParse(t *testing.T) {
  function TestGoodConfigs (line 27) | func TestGoodConfigs(t *testing.T) {
  function TestBadConfigs (line 64) | func TestBadConfigs(t *testing.T) {

FILE: lib/policy/thresholds.go
  type Threshold (line 9) | type Threshold struct
  function ParsedThresholdFromConfig (line 14) | func ParsedThresholdFromConfig(t config.Threshold) (*Threshold, error) {
  type ThresholdRequest (line 34) | type ThresholdRequest struct
    method Parent (line 38) | func (tr *ThresholdRequest) Parent() cel.Activation { return nil }
    method ResolveName (line 40) | func (tr *ThresholdRequest) ResolveName(name string) (any, bool) {

FILE: lib/redirect_security_test.go
  function TestRedirectSecurity (line 14) | func TestRedirectSecurity(t *testing.T) {

FILE: lib/store/actorifiedstore.go
  type unit (line 10) | type unit struct
  type ActorifiedStore (line 12) | type ActorifiedStore struct
    method Close (line 42) | func (a *ActorifiedStore) Close() { a.cancel() }
    method Delete (line 44) | func (a *ActorifiedStore) Delete(ctx context.Context, key string) error {
    method Get (line 52) | func (a *ActorifiedStore) Get(ctx context.Context, key string) ([]byte...
    method Set (line 56) | func (a *ActorifiedStore) Set(ctx context.Context, key string, value [...
    method actorDelete (line 68) | func (a *ActorifiedStore) actorDelete(ctx context.Context, key string)...
    method actorSet (line 76) | func (a *ActorifiedStore) actorSet(ctx context.Context, req *actorSetR...
  type actorSetReq (line 21) | type actorSetReq struct
  function NewActorifiedStore (line 27) | func NewActorifiedStore(backend Interface) *ActorifiedStore {

FILE: lib/store/bbolt/bbolt.go
  type Store (line 38) | type Store struct
    method Delete (line 43) | func (s *Store) Delete(ctx context.Context, key string) error {
    method Get (line 60) | func (s *Store) Get(ctx context.Context, key string) ([]byte, error) {
    method Set (line 103) | func (s *Store) Set(ctx context.Context, key string, value []byte, exp...
    method cleanup (line 124) | func (s *Store) cleanup(ctx context.Context) error {
    method IsPersistent (line 152) | func (s *Store) IsPersistent() bool {
    method cleanupThread (line 156) | func (s *Store) cleanupThread(ctx context.Context) {

FILE: lib/store/bbolt/bbolt_test.go
  function TestImpl (line 11) | func TestImpl(t *testing.T) {

FILE: lib/store/bbolt/factory.go
  function init (line 20) | func init() {
  type Factory (line 26) | type Factory struct
    method Build (line 30) | func (Factory) Build(ctx context.Context, data json.RawMessage) (store...
    method Valid (line 56) | func (Factory) Valid(data json.RawMessage) error {
  type Config (line 70) | type Config struct
    method Valid (line 76) | func (c Config) Valid() error {

FILE: lib/store/bbolt/factory_test.go
  function TestFactoryValid (line 9) | func TestFactoryValid(t *testing.T) {

FILE: lib/store/interface.go
  type Interface (line 31) | type Interface interface
  function z (line 47) | func z[T any]() T { return *new(T) }
  type JSON (line 49) | type JSON struct
  method Delete (line 54) | func (j *JSON[T]) Delete(ctx context.Context, key string) error {
  method Get (line 62) | func (j *JSON[T]) Get(ctx context.Context, key string) (T, error) {
  method Set (line 80) | func (j *JSON[T]) Set(ctx context.Context, key string, value T, expiry t...
  method IsPersistent (line 97) | func (j *JSON[T]) IsPersistent() bool {

FILE: lib/store/json_test.go
  function TestJSON (line 11) | func TestJSON(t *testing.T) {

FILE: lib/store/memory/memory.go
  type factory (line 13) | type factory struct
    method Build (line 15) | func (factory) Build(ctx context.Context, _ json.RawMessage) (store.In...
    method Valid (line 19) | func (factory) Valid(json.RawMessage) error { return nil }
  function init (line 21) | func init() {
  type impl (line 25) | type impl struct
    method Delete (line 29) | func (i *impl) Delete(_ context.Context, key string) error {
    method Get (line 37) | func (i *impl) Get(_ context.Context, key string) ([]byte, error) {
    method Set (line 46) | func (i *impl) Set(_ context.Context, key string, value []byte, expiry...
    method IsPersistent (line 51) | func (i *impl) IsPersistent() bool {
    method cleanupThread (line 55) | func (i *impl) cleanupThread(ctx context.Context) {
  function New (line 70) | func New(ctx context.Context) store.Interface {

FILE: lib/store/memory/memory_test.go
  function TestImpl (line 9) | func TestImpl(t *testing.T) {

FILE: lib/store/registry.go
  type Factory (line 15) | type Factory interface
  function Register (line 20) | func Register(name string, impl Factory) {
  function Get (line 27) | func Get(name string) (Factory, bool) {
  function Methods (line 34) | func Methods() []string {

FILE: lib/store/s3api/factory.go
  function init (line 21) | func init() {
  type S3API (line 26) | type S3API interface
  type Factory (line 35) | type Factory struct
    method Build (line 39) | func (f Factory) Build(ctx context.Context, data json.RawMessage) (sto...
    method Valid (line 77) | func (Factory) Valid(data json.RawMessage) error {
  type Config (line 90) | type Config struct
    method Valid (line 95) | func (c Config) Valid() error {

FILE: lib/store/s3api/s3api.go
  type Store (line 16) | type Store struct
    method Delete (line 21) | func (s *Store) Delete(ctx context.Context, key string) error {
    method Get (line 33) | func (s *Store) Get(ctx context.Context, key string) ([]byte, error) {
    method Set (line 58) | func (s *Store) Set(ctx context.Context, key string, value []byte, exp...
    method IsPersistent (line 78) | func (Store) IsPersistent() bool { return true }

FILE: lib/store/s3api/s3api_test.go
  type mockS3 (line 20) | type mockS3 struct
    method PutObject (line 27) | func (m *mockS3) PutObject(ctx context.Context, in *s3.PutObjectInput,...
    method GetObject (line 46) | func (m *mockS3) GetObject(ctx context.Context, in *s3.GetObjectInput,...
    method DeleteObject (line 60) | func (m *mockS3) DeleteObject(ctx context.Context, in *s3.DeleteObject...
    method HeadObject (line 68) | func (m *mockS3) HeadObject(ctx context.Context, in *s3.HeadObjectInpu...
  function TestImpl (line 77) | func TestImpl(t *testing.T) {
  function TestKeyNormalization (line 88) | func TestKeyNormalization(t *testing.T) {

FILE: lib/store/storetest/storetest.go
  function Common (line 13) | func Common(t *testing.T, f store.Factory, config json.RawMessage) {

FILE: lib/store/valkey/factory.go
  function init (line 16) | func init() {
  type Config (line 31) | type Config struct
    method Valid (line 38) | func (c Config) Valid() error {
  type Sentinel (line 65) | type Sentinel struct
    method Valid (line 73) | func (s Sentinel) Valid() error {
  type redisClient (line 104) | type redisClient interface
  type Factory (line 111) | type Factory struct
    method Valid (line 113) | func (Factory) Valid(data json.RawMessage) error {
    method Build (line 121) | func (Factory) Build(ctx context.Context, data json.RawMessage) (store...

FILE: lib/store/valkey/valkey.go
  type Store (line 12) | type Store struct
    method Get (line 18) | func (s *Store) Get(ctx context.Context, key string) ([]byte, error) {
    method Set (line 29) | func (s *Store) Set(ctx context.Context, key string, value []byte, exp...
    method Delete (line 33) | func (s *Store) Delete(ctx context.Context, key string) error {
    method IsPersistent (line 45) | func (s *Store) IsPersistent() bool {

FILE: lib/store/valkey/valkey_test.go
  function TestImpl (line 14) | func TestImpl(t *testing.T) {
  function TestFactoryValid (line 50) | func TestFactoryValid(t *testing.T) {

FILE: lib/thoth/asnchecker.go
  method ASNCheckerFor (line 17) | func (c *Client) ASNCheckerFor(asns []uint32) checker.Impl {
  type ASNChecker (line 33) | type ASNChecker struct
    method Check (line 39) | func (asnc *ASNChecker) Check(r *http.Request) (bool, error) {
    method Hash (line 67) | func (asnc *ASNChecker) Hash() string {

FILE: lib/thoth/asnchecker_test.go
  function TestASNChecker (line 15) | func TestASNChecker(t *testing.T) {
  function BenchmarkWithCache (line 66) | func BenchmarkWithCache(b *testing.B) {

FILE: lib/thoth/auth.go
  function authUnaryClientInterceptor (line 10) | func authUnaryClientInterceptor(token string) grpc.UnaryClientInterceptor {
  function authStreamClientInterceptor (line 26) | func authStreamClientInterceptor(token string) grpc.StreamClientIntercep...

FILE: lib/thoth/cachediptoasn.go
  type IPToASNWithCache (line 15) | type IPToASNWithCache struct
    method Lookup (line 53) | func (ip2asn *IPToASNWithCache) Lookup(ctx context.Context, lr *iptoas...
  function NewIpToASNWithCache (line 20) | func NewIpToASNWithCache(next iptoasnv1.IpToASNServiceClient) *IPToASNWi...

FILE: lib/thoth/context.go
  type ctxKey (line 5) | type ctxKey struct
  function With (line 7) | func With(ctx context.Context, cli *Client) context.Context {
  function FromContext (line 11) | func FromContext(ctx context.Context) (*Client, bool) {

FILE: lib/thoth/geoipchecker.go
  method GeoIPCheckerFor (line 16) | func (c *Client) GeoIPCheckerFor(countries []string) checker.Impl {
  type GeoIPChecker (line 32) | type GeoIPChecker struct
    method Check (line 38) | func (gipc *GeoIPChecker) Check(r *http.Request) (bool, error) {
    method Hash (line 66) | func (gipc *GeoIPChecker) Hash() string {

FILE: lib/thoth/geoipchecker_test.go
  function TestGeoIPChecker (line 14) | func TestGeoIPChecker(t *testing.T) {

FILE: lib/thoth/thoth.go
  type Client (line 20) | type Client struct
    method Close (line 70) | func (c *Client) Close() error {
    method WithIPToASNService (line 77) | func (c *Client) WithIPToASNService(impl iptoasnv1.IpToASNServiceClien...
  function New (line 26) | func New(ctx context.Context, thothURL, apiToken string, plaintext bool)...

FILE: lib/thoth/thoth_test.go
  function loadSecrets (line 12) | func loadSecrets(t testing.TB) *thoth.Client {
  function TestNew (line 30) | func TestNew(t *testing.T) {

FILE: lib/thoth/thothmock/iptoasn.go
  function MockIpToASNService (line 13) | func MockIpToASNService() *IpToASNService {
  type IpToASNService (line 43) | type IpToASNService struct
    method Lookup (line 48) | func (ip2asn *IpToASNService) Lookup(ctx context.Context, lr *iptoasnv...

FILE: lib/thoth/thothmock/withthothmock.go
  function WithMockThoth (line 10) | func WithMockThoth(t *testing.T) context.Context {

FILE: test/cmd/cipra/internal/containerip.go
  function GetContainerIPAddress (line 12) | func GetContainerIPAddress(containerName string) (string, error) {

FILE: test/cmd/cipra/internal/getlanip.go
  function GetLANIP (line 9) | func GetLANIP() (net.IP, error) {

FILE: test/cmd/cipra/internal/unbreakdocker.go
  function UnbreakDocker (line 14) | func UnbreakDocker(networkName string) error {

FILE: test/cmd/cipra/main.go
  function main (line 27) | func main() {
  function RunScript (line 83) | func RunScript(ctx context.Context, args ...string) error {

FILE: test/cmd/httpdebug/main.go
  function main (line 15) | func main() {

FILE: test/cmd/relayd/main.go
  function main (line 31) | func main() {

FILE: test/cmd/unixhttpd/main.go
  function init (line 24) | func init() {
  function main (line 33) | func main() {

FILE: test/default-config-macro/compare_bots.py
  function load_yaml (line 15) | def load_yaml(file_path):
  function normalize_yaml (line 24) | def normalize_yaml(data):
  function get_repo_root (line 29) | def get_repo_root():
  function main (line 38) | def main():

FILE: test/double_slash/test.mjs
  function getPage (line 4) | async function getPage(path) {

FILE: test/forced-language/test.mjs
  function getChallengePage (line 1) | async function getChallengePage() {

FILE: test/i18n/test.mjs
  function fetchLanguages (line 1) | async function fetchLanguages() {
  function getChallengePage (line 14) | async function getChallengePage(lang) {

FILE: test/log-file/test.mjs
  function getPage (line 3) | async function getPage(path) {
  function getFileSize (line 18) | async function getFileSize(filePath) {

FILE: test/robots_txt/test.mjs
  function getRobotsTxt (line 1) | async function getRobotsTxt() {

FILE: test/unix-socket-xff/test.mjs
  function testWithUserAgent (line 1) | async function testWithUserAgent(userAgent) {

FILE: utils/cmd/backoff-retry/main.go
  function main (line 18) | func main() {

FILE: utils/cmd/iplist2rule/blocklist.go
  function FetchBlocklist (line 17) | func FetchBlocklist(url string) ([]string, error) {

FILE: utils/cmd/iplist2rule/main.go
  type Rule (line 17) | type Rule struct
  function init (line 24) | func init() {
  function main (line 45) | func main() {

FILE: web/index.go
  function Base (line 15) | func Base(title string, body templ.Component, impressum *config.Impressu...
  function BaseWithChallengeAndOGTags (line 19) | func BaseWithChallengeAndOGTags(title string, body templ.Component, impr...
  function ErrorPage (line 29) | func ErrorPage(msg, mail, code string, localizer *localization.SimpleLoc...
  function Bench (line 33) | func Bench(localizer *localization.SimpleLocalizer) templ.Component {
  function honeypotLink (line 37) | func honeypotLink(href string) templ.Component {

FILE: web/index_templ.go
  function base (line 20) | func base(title string, body templ.Component, impressum *config.Impressu...
  function errorPage (line 295) | func errorPage(message, mail, code string, localizer *localization.Simpl...
  function StaticHappy (line 449) | func StaticHappy(localizer *localization.SimpleLocalizer) templ.Component {
  function bench (line 505) | func bench(localizer *localization.SimpleLocalizer) templ.Component {

FILE: web/index_test.go
  function TestBasePrefixInLinks (line 15) | func TestBasePrefixInLinks(t *testing.T) {

FILE: web/js/algorithms/fast.ts
  type ProgressCallback (line 1) | type ProgressCallback = (nonce: number) => void;
  type ProcessOptions (line 3) | interface ProcessOptions {
  function process (line 13) | function process(

FILE: web/js/main.ts
  function onDetailsExpand (line 247) | function onDetailsExpand() {

FILE: web/js/worker/sha256-purejs.ts
  function toHexString (line 9) | function toHexString(arr: Uint8Array): string {

FILE: xess/xess.go
  function init (line 23) | func init() {
  function Mount (line 35) | func Mount(mux *http.ServeMux) {
Condensed preview — 608 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,649K chars).
[
  {
    "path": ".air.toml",
    "chars": 337,
    "preview": "root = \".\"\ntmp_dir = \"var\"\n\n[build]\ncmd = \"go build -o ./var/main ./cmd/anubis\"\nbin = \"./var/main\"\nargs = [\"--use-remote"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "chars": 349,
    "preview": "FROM ghcr.io/xe/devcontainer-base/pre/go\n\nWORKDIR /app\n\nCOPY go.mod go.sum package.json package-lock.json ./\nRUN apt-get"
  },
  {
    "path": ".devcontainer/README.md",
    "chars": 1102,
    "preview": "# Anubis Dev Container\n\nAnubis offers a [development container](https://containers.dev/) image in order to make it easie"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 1028,
    "preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.co"
  },
  {
    "path": ".devcontainer/docker-compose.yaml",
    "chars": 639,
    "preview": "services:\n  playwright:\n    image: mcr.microsoft.com/playwright:v1.52.0-noble\n    init: true\n    network_mode: service:w"
  },
  {
    "path": ".devcontainer/poststart.sh",
    "chars": 88,
    "preview": "#!/usr/bin/env bash\n\npwd\n\nnpm ci &\ngo mod download &\ngo install ./utils/cmd/... &\n\nwait\n"
  },
  {
    "path": ".gitattributes",
    "chars": 38,
    "preview": "**/*_templ.go linguist-generated=true\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 40,
    "preview": "patreon: cadey\ngithub: xe\nliberapay: Xe\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "chars": 1619,
    "preview": "name: Bug report\ndescription: Create a report to help us improve\n\nbody:\n  - type: textarea\n    id: description-of-bug\n  "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 173,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Security\n    url: https://techaro.lol/contact\n    about: Do not fil"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "chars": 1316,
    "preview": "name: Feature request\ndescription: Suggest an idea for this project\ntitle: \"[Feature request] \"\n\nbody:\n  - type: textare"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 643,
    "preview": "<!--\ndelete me and describe your change here, give enough context for a maintainer to understand what and why\n\nSee https"
  },
  {
    "path": ".github/actions/spelling/README.md",
    "chars": 4341,
    "preview": "# check-spelling/check-spelling configuration\n\n| File                                               | Purpose           "
  },
  {
    "path": ".github/actions/spelling/advice.md",
    "chars": 1414,
    "preview": "<!-- See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice --> <!-- markdownlint-di"
  },
  {
    "path": ".github/actions/spelling/allow.txt",
    "chars": 301,
    "preview": "github\nhttps\nssh\nubuntu\nworkarounds\nrjack\nmsgbox\nxeact\nABee\ntencent\nmaintnotifications\nazurediamond\ncooldown\nverifyfcrdn"
  },
  {
    "path": ".github/actions/spelling/candidate.patterns",
    "chars": 21094,
    "preview": "# Repeated letters\n#\\b([a-z])\\g{-1}{2,}\\b\n\n# marker to ignore all code on line\n^.*/\\* #no-spell-check-line \\*/.*$\n# mark"
  },
  {
    "path": ".github/actions/spelling/excludes.txt",
    "chars": 1480,
    "preview": "# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes\n(?:^|/)(?i)COPYRIGHT\n(?:^|/"
  },
  {
    "path": ".github/actions/spelling/expect.txt",
    "chars": 3406,
    "preview": "acs\nActorified\nactorifiedstore\nactorify\nagentic\nAibrew\nalibaba\nalrest\namazonbot\nanexia\nanthro\nanubis\nanubistest\napnic\nAP"
  },
  {
    "path": ".github/actions/spelling/line_forbidden.patterns",
    "chars": 12673,
    "preview": "# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere\n# see [fprime](https://github.com/"
  },
  {
    "path": ".github/actions/spelling/patterns.txt",
    "chars": 4014,
    "preview": "# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns\n\n# Automatically suggested "
  },
  {
    "path": ".github/actions/spelling/reject.txt",
    "chars": 232,
    "preview": "^attache$\n^bellows?$\nbenefitting\noccurences?\n^dependan.*\n^develope$\n^developement$\n^developpe\n^Devers?$\n^devex\n^devide\n^"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 570,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: weekly\n    grou"
  },
  {
    "path": ".github/workflows/asset-verification.yml",
    "chars": 2112,
    "preview": "name: Asset Build Verification\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n"
  },
  {
    "path": ".github/workflows/dco-check.yaml",
    "chars": 177,
    "preview": "name: DCO Check\n\non: [pull_request]\n\njobs:\n  dco_check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: tisonkun/act"
  },
  {
    "path": ".github/workflows/docker-pr.yml",
    "chars": 1520,
    "preview": "name: Docker image builds (pull requests)\n\non:\n  pull_request:\n    branches: [\"main\"]\n\nenv:\n  DOCKER_METADATA_SET_OUTPUT"
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 2029,
    "preview": "name: Docker image builds\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n    tags: [\"v*\"]\n\nenv:\n  DOCKER_METAD"
  },
  {
    "path": ".github/workflows/docs-deploy.yml",
    "chars": 2008,
    "preview": "name: Docs deploy\n\non:\n  workflow_dispatch:\n  push:\n    branches: [\"main\"]\n\npermissions:\n  contents: read\n  packages: wr"
  },
  {
    "path": ".github/workflows/docs-test.yml",
    "chars": 1119,
    "preview": "name: Docs test build\n\non:\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  contents: read\n  actions: write\n\njobs:"
  },
  {
    "path": ".github/workflows/go-mod-tidy-check.yml",
    "chars": 2337,
    "preview": "name: Go Mod Tidy Check\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  conte"
  },
  {
    "path": ".github/workflows/go.yml",
    "chars": 1628,
    "preview": "name: Go\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  contents: read\n  act"
  },
  {
    "path": ".github/workflows/lint-pr-title.yaml",
    "chars": 403,
    "preview": "name: \"Lint PR\"\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\n\njobs:\n  lint_p"
  },
  {
    "path": ".github/workflows/package-builds-stable.yml",
    "chars": 1276,
    "preview": "name: Package builds (stable)\n\non:\n  workflow_dispatch:\n  # release:\n  #   types: [published]\n\npermissions:\n  contents: "
  },
  {
    "path": ".github/workflows/package-builds-unstable.yml",
    "chars": 1099,
    "preview": "name: Package builds (unstable)\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:"
  },
  {
    "path": ".github/workflows/smoke-tests.yml",
    "chars": 1617,
    "preview": "name: Smoke tests\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  contents: r"
  },
  {
    "path": ".github/workflows/spelling.yml",
    "chars": 4636,
    "preview": "name: Check Spelling\n\n# Comment management is handled through a secondary job, for details see:\n# https://github.com/che"
  },
  {
    "path": ".github/workflows/ssh-ci-runner-cron.yml",
    "chars": 1036,
    "preview": "name: Regenerate ssh ci runner image\n\non:\n  # pull_request:\n  #   branches: [\"main\"]\n  schedule:\n    - cron: \"0 0 1,8,15"
  },
  {
    "path": ".github/workflows/ssh-ci.yml",
    "chars": 1163,
    "preview": "name: SSH CI\n\non:\n  push:\n    branches: [\"main\"]\n  # pull_request:\n  #   branches: [\"main\"]\n\npermissions:\n  contents: re"
  },
  {
    "path": ".github/workflows/zizmor.yml",
    "chars": 921,
    "preview": "name: zizmor\n\non:\n  push:\n    paths:\n      - \".github/workflows/*.ya?ml\"\n  pull_request:\n    paths:\n      - \".github/wor"
  },
  {
    "path": ".github/zizmor.yml",
    "chars": 84,
    "preview": "rules:\n  unpinned-uses:\n    config:\n      policies:\n        Homebrew/actions/*: any\n"
  },
  {
    "path": ".gitignore",
    "chars": 231,
    "preview": ".env\n*.deb\n*.rpm\n\n# Additional package locks\npnpm-lock.yaml\nyarn.lock\n\n# Go binaries and test artifacts\nmain\n*.test\n\nnod"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 303,
    "preview": "npx --no-install commitlint --edit \"$1\"\n\n# Check if commit message contains Signed-off-by line\nif ! grep -q \"^Signed-off"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 26,
    "preview": "npm run lint\nnpm run test\n"
  },
  {
    "path": ".ko.yaml",
    "chars": 279,
    "preview": "defaultBaseImage: cgr.dev/chainguard/static\ndefaultPlatforms:\n  - linux/arm64\n  - linux/amd64\n  - linux/arm/v7\n\nbuilds:\n"
  },
  {
    "path": ".prettierignore",
    "chars": 51,
    "preview": "lib/config/testdata/bad/*\n*.inc\nAGENTS.md\nCLAUDE.md"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 232,
    "preview": "{\n  \"recommendations\": [\n    \"esbenp.prettier-vscode\",\n    \"ms-azuretools.vscode-containers\",\n    \"golang.go\",\n    \"unif"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 679,
    "preview": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  //"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 740,
    "preview": "{\n  \"github.copilot.enable\": {\n    \"*\": false,\n    \"plaintext\": false,\n    \"markdown\": false,\n    \"mdx\": false,\n    \"jso"
  },
  {
    "path": "AGENTS.md",
    "chars": 2423,
    "preview": "# Agent instructions\n\nPrimary agent documentation is in `CONTRIBUTING.md`. You MUST read this file before proceeding.\n\n#"
  },
  {
    "path": "Brewfile",
    "chars": 101,
    "preview": "# programming languages\nbrew \"go@1.24\"\nbrew \"node\"\nbrew \"ko\"\nbrew \"esbuild\"\nbrew \"zstd\"\nbrew \"brotli\""
  },
  {
    "path": "CLAUDE.md",
    "chars": 28,
    "preview": "@AGENTS.md\n@CONTRIBUTING.md\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 6482,
    "preview": "# Contributing to Anubis\n\nAnubis is a Web AI Firewall Utility (WAIFU) written in Go. It uses sha256 proof-of-work challe"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "Copyright (c) 2025 Xe Iaso <me@xeiaso.net>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "Makefile",
    "chars": 761,
    "preview": "VERSION= $(shell cat ./VERSION)\nGO?= go\nNPM?= npm\n\n.PHONY: build assets deps lint prebaked-build test\n\nall: build\n\ndeps:"
  },
  {
    "path": "README.md",
    "chars": 5858,
    "preview": "# Anubis\n\n<center>\n<img width=256 src=\"./web/static/img/happy.webp\" alt=\"A smiling chibi dark-skinned anthro jackal with"
  },
  {
    "path": "SECURITY.md",
    "chars": 569,
    "preview": "# Security Policy\n\nTecharo follows the [Semver 2.0 scheme](https://semver.org/).\n\n## Supported Versions\n\nTecharo strives"
  },
  {
    "path": "VERSION",
    "chars": 7,
    "preview": "1.25.0\n"
  },
  {
    "path": "anubis.go",
    "chars": 1635,
    "preview": "// Package anubis contains the version number of Anubis.\npackage anubis\n\nimport \"time\"\n\n// Version is the current versio"
  },
  {
    "path": "cmd/containerbuild/.gitignore",
    "chars": 6,
    "preview": "images"
  },
  {
    "path": "cmd/containerbuild/main.go",
    "chars": 4560,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/Tech"
  },
  {
    "path": "cmd/robots2policy/batch/batch_process.go",
    "chars": 1869,
    "preview": "/*\nBatch process robots.txt files from archives like https://github.com/nrjones8/robots-dot-txt-archive-bot/tree/master/"
  },
  {
    "path": "cmd/robots2policy/main.go",
    "chars": 11310,
    "preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n\t\"slices\"\n\t\"str"
  },
  {
    "path": "cmd/robots2policy/robots2policy_test.go",
    "chars": 10962,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gopkg.in/yaml."
  },
  {
    "path": "cmd/robots2policy/testdata/blacklist.robots.txt",
    "chars": 219,
    "preview": "# Test with blacklisted user agents\nUser-agent: *\nDisallow: /admin\nCrawl-delay: 10\n\nUser-agent: BadBot\nDisallow: /\n\nUser"
  },
  {
    "path": "cmd/robots2policy/testdata/blacklist.yaml",
    "chars": 796,
    "preview": "- action: WEIGH\n  expression: \"true\"\n  name: robots-txt-policy-crawl-delay-1\n  weight:\n    adjust: 3\n- action: CHALLENGE"
  },
  {
    "path": "cmd/robots2policy/testdata/complex.robots.txt",
    "chars": 477,
    "preview": "# Complex real-world example\nUser-agent: *\nDisallow: /admin/\nDisallow: /private/\nDisallow: /api/internal/\nAllow: /api/pu"
  },
  {
    "path": "cmd/robots2policy/testdata/complex.yaml",
    "chars": 1905,
    "preview": "- action: WEIGH\n  expression: \"true\"\n  name: robots-txt-policy-crawl-delay-1\n  weight:\n    adjust: 5\n- action: CHALLENGE"
  },
  {
    "path": "cmd/robots2policy/testdata/consecutive.robots.txt",
    "chars": 578,
    "preview": "# Test consecutive user agents that should be grouped into any: blocks\nUser-agent: *\nDisallow: /admin\nCrawl-delay: 10\n\n#"
  },
  {
    "path": "cmd/robots2policy/testdata/consecutive.yaml",
    "chars": 1190,
    "preview": "- action: WEIGH\n  expression: \"true\"\n  name: robots-txt-policy-crawl-delay-1\n  weight:\n    adjust: 3\n- action: CHALLENGE"
  },
  {
    "path": "cmd/robots2policy/testdata/custom-name.yaml",
    "chars": 195,
    "preview": "- action: CHALLENGE\n  expression: path.startsWith(\"/admin/\")\n  name: my-custom-policy-disallow-1\n- action: CHALLENGE\n  e"
  },
  {
    "path": "cmd/robots2policy/testdata/deny-action.yaml",
    "chars": 187,
    "preview": "- action: DENY\n  expression: path.startsWith(\"/admin/\")\n  name: robots-txt-policy-disallow-1\n- action: DENY\n  expression"
  },
  {
    "path": "cmd/robots2policy/testdata/empty.robots.txt",
    "chars": 52,
    "preview": "# Empty robots.txt (comments only)\n# No actual rules"
  },
  {
    "path": "cmd/robots2policy/testdata/empty.yaml",
    "chars": 3,
    "preview": "[]\n"
  },
  {
    "path": "cmd/robots2policy/testdata/simple.json",
    "chars": 262,
    "preview": "[\n  {\n    \"expression\": \"path.startsWith(\\\"/admin/\\\")\",\n    \"name\": \"robots-txt-policy-disallow-1\",\n    \"action\": \"CHALL"
  },
  {
    "path": "cmd/robots2policy/testdata/simple.robots.txt",
    "chars": 90,
    "preview": "# Simple robots.txt test\nUser-agent: *\nDisallow: /admin/\nDisallow: /private\nAllow: /public"
  },
  {
    "path": "cmd/robots2policy/testdata/simple.yaml",
    "chars": 197,
    "preview": "- action: CHALLENGE\n  expression: path.startsWith(\"/admin/\")\n  name: robots-txt-policy-disallow-1\n- action: CHALLENGE\n  "
  },
  {
    "path": "cmd/robots2policy/testdata/wildcards.robots.txt",
    "chars": 132,
    "preview": "# Test wildcard patterns\nUser-agent: *\nDisallow: /search*\nDisallow: /*/private\nDisallow: /file?.txt\nDisallow: /admin/*?a"
  },
  {
    "path": "cmd/robots2policy/testdata/wildcards.yaml",
    "chars": 411,
    "preview": "- action: CHALLENGE\n  expression: path.matches(\"^/search.*\")\n  name: robots-txt-policy-disallow-1\n- action: CHALLENGE\n  "
  },
  {
    "path": "data/apps/allow-api-routes.yaml",
    "chars": 146,
    "preview": "- name: allow-api-routes\n  action: ALLOW\n  expression:\n    all:\n      - '!(method == \"HEAD\" || method == \"GET\")'\n      -"
  },
  {
    "path": "data/apps/bookstack-saml.yaml",
    "chars": 547,
    "preview": "# Make SASL login work on bookstack with Anubis\n# https://www.bookstackapp.com/docs/admin/saml2-auth/\n- name: allow-book"
  },
  {
    "path": "data/apps/gitea-rss-feeds.yaml",
    "chars": 294,
    "preview": "# By Aibrew: https://github.com/TecharoHQ/anubis/discussions/261#discussioncomment-12821065\n- name: gitea-feed-atom\n  ac"
  },
  {
    "path": "data/apps/qualys-ssl-labs.yml",
    "chars": 310,
    "preview": "# This policy allows Qualys SSL Labs to fully work. (https://www.ssllabs.com/ssltest)\n# IP ranges are taken from: https:"
  },
  {
    "path": "data/apps/searx-checker.yml",
    "chars": 377,
    "preview": "# This policy allows SearXNG's instance tracker to work. (https://searx.space)\n# IPs are taken from `check.searx.space` "
  },
  {
    "path": "data/botPolicies.yaml",
    "chars": 8677,
    "preview": "## Anubis has the ability to let you import snippets of configuration into the main\n## configuration file. This allows y"
  },
  {
    "path": "data/bots/_deny-pathological.yaml",
    "chars": 273,
    "preview": "- import: (data)/bots/cloudflare-workers.yaml\n- import: (data)/bots/headless-browsers.yaml\n- import: (data)/bots/us-ai-s"
  },
  {
    "path": "data/bots/aggressive-brazilian-scrapers.yaml",
    "chars": 941,
    "preview": "- name: deny-aggressive-brazilian-scrapers\n  action: WEIGH\n  weight:\n    adjust: 20\n  expression:\n    any:\n      # Inter"
  },
  {
    "path": "data/bots/ai-catchall.yaml",
    "chars": 1392,
    "preview": "# Extensive list of AI-affiliated agents based on https://github.com/ai-robots-txt/ai.robots.txt\n# Add new/undocumented "
  },
  {
    "path": "data/bots/ai-robots-txt.yaml",
    "chars": 1590,
    "preview": "# Warning: Contains user agents that _must_ be blocked in robots.txt, or the opt-out will have no effect.\n# Note: Blocks"
  },
  {
    "path": "data/bots/cloudflare-workers.yaml",
    "chars": 103,
    "preview": "- name: cloudflare-workers\n  headers_regex:\n    CF-Worker: .*\n  action: WEIGH\n  weight:\n    adjust: 15\n"
  },
  {
    "path": "data/bots/custom-async-http-client.yaml",
    "chars": 121,
    "preview": "- name: \"custom-async-http-client\"\n  user_agent_regex: \"Custom-AsyncHttpClient\"\n  action: WEIGH\n  weight:\n    adjust: 10"
  },
  {
    "path": "data/bots/headless-browsers.yaml",
    "chars": 222,
    "preview": "- name: lightpanda\n  user_agent_regex: ^LightPanda/.*$\n  action: DENY\n- name: headless-chrome\n  user_agent_regex: Headle"
  },
  {
    "path": "data/bots/irc-bots/archlinux-phrik.yaml",
    "chars": 348,
    "preview": "# phrik in the Arch Linux IRC channels\n- name: archlinux-phrik\n  action: ALLOW\n  expression:\n    all:\n      - remoteAddr"
  },
  {
    "path": "data/bots/irc-bots/gentoo-chat.yaml",
    "chars": 306,
    "preview": "# chat in the gentoo IRC channels\n- name: gentoo-chat\n  action: ALLOW\n  expression:\n    all:\n      - remoteAddress == \"4"
  },
  {
    "path": "data/bots/us-ai-scraper.yaml",
    "chars": 136,
    "preview": "- name: us-artificial-intelligence-scraper\n  user_agent_regex: \\+https\\://github\\.com/US-Artificial-Intelligence/scraper"
  },
  {
    "path": "data/clients/ai.yaml",
    "chars": 350,
    "preview": "# User agents that act on behalf of humans in AI tools, e.g. searching the web.\n# Each entry should have a positive/ALLO"
  },
  {
    "path": "data/clients/docker-client.yaml",
    "chars": 1396,
    "preview": "- name: allow-docker-client\n  action: ALLOW\n  expression:\n    all:\n      - path.startsWith(\"/v2/\")\n      - userAgent.con"
  },
  {
    "path": "data/clients/git.yaml",
    "chars": 618,
    "preview": "- name: allow-git-clients\n  action: ALLOW\n  expression:\n    all:\n      - >\n        (  \n          userAgent.startsWith(\"g"
  },
  {
    "path": "data/clients/go-get.yaml",
    "chars": 161,
    "preview": "- name: go-get\n  action: ALLOW\n  expression:\n    all:\n      - userAgent.startsWith(\"Go-http-client/\")\n      - '\"go-get\" "
  },
  {
    "path": "data/clients/mistral-mistralai-user.yaml",
    "chars": 298,
    "preview": "# Acts on behalf of user requests\n# https://docs.mistral.ai/robots/\n- name: mistral-mistralai-user\n  user_agent_regex: M"
  },
  {
    "path": "data/clients/openai-chatgpt-user.yaml",
    "chars": 2576,
    "preview": "# Acts on behalf of user requests\n# https://platform.openai.com/docs/bots/overview-of-openai-crawlers\n- name: openai-cha"
  },
  {
    "path": "data/clients/perplexity-user.yaml",
    "chars": 350,
    "preview": "# Acts on behalf of user requests\n# https://docs.perplexity.ai/guides/bots\n- name: perplexity-user\n  user_agent_regex: P"
  },
  {
    "path": "data/clients/small-internet-browsers/_permissive.yaml",
    "chars": 125,
    "preview": "- import: (data)/clients/small-internet-browsers/netsurf.yaml\n- import: (data)/clients/small-internet-browsers/palemoon."
  },
  {
    "path": "data/clients/small-internet-browsers/netsurf.yaml",
    "chars": 103,
    "preview": "- name: \"reduce-weight-netsurf\"\n  user_agent_regex: \"NetSurf\"\n  action: WEIGH\n  weight:\n    adjust: -5\n"
  },
  {
    "path": "data/clients/small-internet-browsers/palemoon.yaml",
    "chars": 105,
    "preview": "- name: \"reduce-weight-palemoon\"\n  user_agent_regex: \"PaleMoon\"\n  action: WEIGH\n  weight:\n    adjust: -5\n"
  },
  {
    "path": "data/clients/telegram-preview.yaml",
    "chars": 161,
    "preview": "- name: telegrambot\n  action: ALLOW\n  expression:\n    all:\n      - userAgent.matches(\"TelegramBot\")\n      - verifyFCrDNS"
  },
  {
    "path": "data/clients/vk-preview.yaml",
    "chars": 196,
    "preview": "- name: vkbot\n  action: ALLOW\n  expression:\n    all:\n      - userAgent.matches(\"vkShare[^+]+\\\\+http\\\\://vk\\\\.com/dev/Sha"
  },
  {
    "path": "data/clients/x-firefox-ai.yaml",
    "chars": 217,
    "preview": "# https://connect.mozilla.org/t5/firefox-labs/try-out-link-previews-in-firefox-labs-138-and-share-your/td-p/92012\n- name"
  },
  {
    "path": "data/common/acts-like-browser.yaml",
    "chars": 1618,
    "preview": "# Assert behaviour that only genuine browsers display. This ensures that modern Chrome\n# or Firefox versions will get th"
  },
  {
    "path": "data/common/allow-api-like.yaml",
    "chars": 146,
    "preview": "- name: allow-api-routes\n  action: ALLOW\n  expression:\n    all:\n      - '!(method == \"HEAD\" || method == \"GET\")'\n      -"
  },
  {
    "path": "data/common/allow-private-addresses.yaml",
    "chars": 280,
    "preview": "- name: ipv4-rfc-1918\n  action: ALLOW\n  remote_addresses:\n    - 10.0.0.0/8\n    - 172.16.0.0/12\n    - 192.168.0.0/16\n    "
  },
  {
    "path": "data/common/json-api.yaml",
    "chars": 181,
    "preview": "- name: allow-api-requests\n  action: ALLOW\n  expression:\n    all:\n      - '\"Accept\" in headers'\n      - 'headers[\"Accept"
  },
  {
    "path": "data/common/keep-internet-working.yaml",
    "chars": 328,
    "preview": "# Common \"keeping the internet working\" routes\n- name: well-known\n  path_regex: ^/\\.well-known/.*$\n  action: ALLOW\n- nam"
  },
  {
    "path": "data/common/rfc-violations.yaml",
    "chars": 74,
    "preview": "- name: no-user-agent-string\n  action: DENY\n  expression: userAgent == \"\"\n"
  },
  {
    "path": "data/crawlers/_allow-good.yaml",
    "chars": 505,
    "preview": "- import: (data)/crawlers/googlebot.yaml\n- import: (data)/crawlers/applebot.yaml\n- import: (data)/crawlers/bingbot.yaml\n"
  },
  {
    "path": "data/crawlers/ai-search.yaml",
    "chars": 339,
    "preview": "# User agents that index exclusively for search in for AI systems.\n# Each entry should have a positive/ALLOW entry creat"
  },
  {
    "path": "data/crawlers/ai-training.yaml",
    "chars": 292,
    "preview": "# User agents that crawl for training AI/LLM systems\n# Each entry should have a positive/ALLOW entry created as well, wi"
  },
  {
    "path": "data/crawlers/alibaba-cloud.yaml",
    "chars": 18492,
    "preview": "- name: alibaba-cloud\n  action: DENY\n  # Updated 2025-08-20 from IP addresses for AS45102\n  remote_addresses:\n    - 103."
  },
  {
    "path": "data/crawlers/applebot.yaml",
    "chars": 518,
    "preview": "# Indexing for search and Siri\n# https://support.apple.com/en-us/119829\n- name: applebot\n  user_agent_regex: Applebot\n  "
  },
  {
    "path": "data/crawlers/bingbot.yaml",
    "chars": 862,
    "preview": "- name: bingbot\n  user_agent_regex: \\+http\\://www\\.bing\\.com/bingbot\\.htm\n  action: ALLOW\n  # https://www.bing.com/toolb"
  },
  {
    "path": "data/crawlers/commoncrawl.yaml",
    "chars": 271,
    "preview": "- name: common-crawl\n  user_agent_regex: CCBot\n  action: ALLOW\n  # https://index.commoncrawl.org/ccbot.json\n  remote_add"
  },
  {
    "path": "data/crawlers/duckduckbot.yaml",
    "chars": 7148,
    "preview": "- name: duckduckbot\n  user_agent_regex: DuckDuckBot/1\\.1; \\(\\+http\\://duckduckgo\\.com/duckduckbot\\.html\\)\n  action: ALLO"
  },
  {
    "path": "data/crawlers/googlebot.yaml",
    "chars": 7487,
    "preview": "- name: googlebot\n  user_agent_regex: \\+http\\://www\\.google\\.com/bot\\.html\n  action: ALLOW\n  # https://developers.google"
  },
  {
    "path": "data/crawlers/huawei-cloud.yaml",
    "chars": 13792,
    "preview": "- name: huawei-cloud\n  action: DENY\n  # Updated 2025-08-20 from IP addresses for AS136907\n  remote_addresses:\n    - 1.17"
  },
  {
    "path": "data/crawlers/internet-archive.yaml",
    "chars": 148,
    "preview": "- name: internet-archive\n  action: ALLOW\n  # https://ipinfo.io/AS7941\n  remote_addresses: [\"207.241.224.0/20\", \"208.70.2"
  },
  {
    "path": "data/crawlers/kagibot.yaml",
    "chars": 240,
    "preview": "- name: kagibot\n  user_agent_regex: \\+https\\://kagi\\.com/bot\n  action: ALLOW\n  # https://kagi.com/bot\n  remote_addresses"
  },
  {
    "path": "data/crawlers/marginalia.yaml",
    "chars": 273,
    "preview": "- name: marginalia\n  user_agent_regex: search\\.marginalia\\.nu\n  action: ALLOW\n  # Received directly over email\n  remote_"
  },
  {
    "path": "data/crawlers/mojeekbot.yaml",
    "chars": 168,
    "preview": "- name: mojeekbot\n  user_agent_regex: \\+https\\://www\\.mojeek\\.com/bot\\.html\n  action: ALLOW\n  # https://www.mojeek.com/b"
  },
  {
    "path": "data/crawlers/openai-gptbot.yaml",
    "chars": 465,
    "preview": "# Collects AI training data\n# https://platform.openai.com/docs/bots/overview-of-openai-crawlers\n- name: openai-gptbot\n  "
  },
  {
    "path": "data/crawlers/openai-searchbot.yaml",
    "chars": 435,
    "preview": "# Indexing for search, does not collect training data\n# https://platform.openai.com/docs/bots/overview-of-openai-crawler"
  },
  {
    "path": "data/crawlers/perplexitybot.yaml",
    "chars": 495,
    "preview": "# Indexing for search, does not collect training data\n# https://docs.perplexity.ai/guides/bots\n- name: perplexitybot\n  u"
  },
  {
    "path": "data/crawlers/qwantbot.yaml",
    "chars": 202,
    "preview": "- name: qwantbot\n  user_agent_regex: \\+https\\://help\\.qwant\\.com/bot/\n  action: ALLOW\n  # https://help.qwant.com/wp-cont"
  },
  {
    "path": "data/crawlers/tencent-cloud.yaml",
    "chars": 3514,
    "preview": "# Tencent Cloud crawler IP ranges\n- name: tencent-cloud\n  action: DENY\n  remote_addresses:\n    - 101.32.0.0/17\n    - 101"
  },
  {
    "path": "data/crawlers/wikimedia-citoid.yaml",
    "chars": 410,
    "preview": "# Wikimedia Foundation citation services\n# https://www.mediawiki.org/wiki/Citoid\n\n- name: wikimedia-citoid\n  user_agent_"
  },
  {
    "path": "data/crawlers/yandexbot.yaml",
    "chars": 184,
    "preview": "- name: yandexbot\n  action: ALLOW\n  expression:\n    all:\n      - userAgent.matches(\"\\\\+http\\\\://yandex\\\\.com/bots\")\n    "
  },
  {
    "path": "data/embed.go",
    "chars": 165,
    "preview": "package data\n\nimport \"embed\"\n\nvar (\n\t//go:embed botPolicies.yaml all:apps all:bots all:clients all:common all:crawlers a"
  },
  {
    "path": "data/embed_test.go",
    "chars": 920,
    "preview": "package data\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// TestBotPoliciesEmbed ensures all YAML files in the d"
  },
  {
    "path": "data/meta/README.md",
    "chars": 215,
    "preview": "# meta policies\n\nContains policies that exclusively reference policies in _multiple_ other data folders.\n\nAkin to \"stanc"
  },
  {
    "path": "data/meta/ai-block-aggressive.yaml",
    "chars": 350,
    "preview": "# Blocks all AI/LLM associated user agents, regardless of purpose or human agency\n# Warning: To completely block some AI"
  },
  {
    "path": "data/meta/ai-block-moderate.yaml",
    "chars": 497,
    "preview": "# Blocks all AI/LLM bots used for training or unknown/undocumented purposes.\n# Permits user agents with explicitly docum"
  },
  {
    "path": "data/meta/ai-block-permissive.yaml",
    "chars": 405,
    "preview": "# Permits all well documented AI/LLM user agents with published IP allowlists.\n- import: (data)/bots/ai-catchall.yaml\n- "
  },
  {
    "path": "data/meta/default-config.yaml",
    "chars": 2683,
    "preview": "- # Pathological bots to deny\n  # This correlates to data/bots/_deny-pathological.yaml in the source tree\n  # https://gi"
  },
  {
    "path": "data/meta/messengers-preview.yaml",
    "chars": 88,
    "preview": "- import: (data)/clients/telegram-preview.yaml\n- import: (data)/clients/vk-preview.yaml\n"
  },
  {
    "path": "data/services/updown.yaml",
    "chars": 678,
    "preview": "# https://updown.io/about\n- name: updown\n  user_agent_regex: updown.io\n  action: ALLOW\n  remote_addresses: [\n    \"45.32."
  },
  {
    "path": "data/services/uptime-robot.yaml",
    "chars": 7338,
    "preview": "- name: uptime-robot\n  user_agent_regex: UptimeRobot\n  action: ALLOW\n  # https://api.uptimerobot.com/meta/ips\n  remote_a"
  },
  {
    "path": "decaymap/decaymap.go",
    "chars": 4387,
    "preview": "package decaymap\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nfunc Zilch[T any]() T {\n\tvar zero T\n\treturn zero\n}\n\n// Impl is a lazy key-"
  },
  {
    "path": "decaymap/decaymap_test.go",
    "chars": 1779,
    "preview": "package decaymap\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestImpl(t *testing.T) {\n\tdm := New[string, string]()\n\tt.Cleanup(d"
  },
  {
    "path": "docs/.dockerignore",
    "chars": 234,
    "preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
  },
  {
    "path": "docs/.gitignore",
    "chars": 233,
    "preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
  },
  {
    "path": "docs/Dockerfile",
    "chars": 274,
    "preview": "FROM docker.io/library/node:lts AS build\n\nWORKDIR /app\nCOPY . .\n\nRUN npm ci && npm run build\n\nFROM ghcr.io/xe/nginx-micr"
  },
  {
    "path": "docs/README.md",
    "chars": 768,
    "preview": "# Website\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Ins"
  },
  {
    "path": "docs/blog/2025-06-16-welcome/index.mdx",
    "chars": 410,
    "preview": "---\nslug: welcome\ntitle: Welcome to the Anubis blog!\nauthors: [xe]\ntags: [intro]\n---\n\nHello, world!\n\nAt Techaro, we've b"
  },
  {
    "path": "docs/blog/2025-06-27-release-1.20.0/index.mdx",
    "chars": 16162,
    "preview": "---\nslug: release/v1.20.0\ntitle: Anubis v1.20.0 is now available!\nauthors: [xe]\ntags: [release]\nimage: sunburst.webp\n---"
  },
  {
    "path": "docs/blog/2025-07-09-incident-report/index.mdx",
    "chars": 8117,
    "preview": "---\nslug: incident/TI-20250709-0001\ntitle: \"TI-20250709-0001: IPv4 traffic failures for Techaro services\"\nauthors: [xe]\n"
  },
  {
    "path": "docs/blog/2025-07-22-release-1.21.1/index.mdx",
    "chars": 23592,
    "preview": "---\nslug: release/v1.21.1\ntitle: Anubis v1.21.1 is now available!\nauthors: [xe]\ntags: [release]\nimage: anubis-i18n.webp\n"
  },
  {
    "path": "docs/blog/2025-08-18-funding-update/index.mdx",
    "chars": 4111,
    "preview": "---\nslug: 2025/funding-update\ntitle: Funding update\nauthors: [xe]\ntags: [funding]\nimage: around-the-bend.webp\n---\n\n![](."
  },
  {
    "path": "docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx",
    "chars": 8968,
    "preview": "import React, { useState, useEffect, useMemo } from \"react\";\nimport styles from \"./styles.module.css\";\n\n// A helper func"
  },
  {
    "path": "docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css",
    "chars": 5769,
    "preview": "/* Main container styles */\n.container {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-con"
  },
  {
    "path": "docs/blog/2025-08-28-cpu-core-odd/index.mdx",
    "chars": 9608,
    "preview": "---\nslug: 2025/cpu-core-odd\ntitle: Sometimes CPU cores are odd\ndescription: \"TL;DR: all the assumptions you have about p"
  },
  {
    "path": "docs/blog/2025-10-31-file-abuse-reports/index.mdx",
    "chars": 6325,
    "preview": "---\nslug: 2025/file-abuse-reports\ntitle: Taking steps to end abusive traffic from cloud providers\ndescription: \"Learn ho"
  },
  {
    "path": "docs/blog/authors.yml",
    "chars": 174,
    "preview": "xe:\n  name: Xe Iaso\n  title: CEO @ Techaro\n  url: https://github.com/Xe\n  image_url: https://github.com/Xe.png\n  email: "
  },
  {
    "path": "docs/docs/CHANGELOG.md",
    "chars": 57346,
    "preview": "---\nsidebar_position: 999\n---\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe fo"
  },
  {
    "path": "docs/docs/admin/_category_.json",
    "chars": 203,
    "preview": "{\n  \"label\": \"Administrative guides\",\n  \"position\": 40,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"description\": \"T"
  },
  {
    "path": "docs/docs/admin/botstopper.mdx",
    "chars": 11365,
    "preview": "---\ntitle: \"Commercial support and an unbranded version\"\n---\n\nIf you want to use Anubis but organizational policies prev"
  },
  {
    "path": "docs/docs/admin/caveats-gitea-forgejo.mdx",
    "chars": 923,
    "preview": "---\ntitle: When using Caddy with Gitea/Forgejo\n---\n\nGitea/Forgejo relies on the reverse proxy setting the `X-Real-Ip` he"
  },
  {
    "path": "docs/docs/admin/caveats-xff.mdx",
    "chars": 1786,
    "preview": "# Client IP Headers\n\nCurrently Anubis will always flatten the `X-Forwarded-For` when it contains multiple IP addresses. "
  },
  {
    "path": "docs/docs/admin/configuration/_category_.json",
    "chars": 174,
    "preview": "{\n  \"label\": \"Configuration\",\n  \"position\": 10,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"description\": \"Detailed "
  },
  {
    "path": "docs/docs/admin/configuration/challenges/_category_.json",
    "chars": 62,
    "preview": "{\n  \"label\": \"Challenges\",\n  \"position\": 10,\n  \"link\": null\n}\n"
  },
  {
    "path": "docs/docs/admin/configuration/challenges/index.mdx",
    "chars": 228,
    "preview": "# Challenge Methods\n\nAnubis supports multiple challenge methods:\n\n- [Meta Refresh](./metarefresh.mdx)\n- [Preact](./preac"
  },
  {
    "path": "docs/docs/admin/configuration/challenges/metarefresh.mdx",
    "chars": 748,
    "preview": "# Meta Refresh (No JavaScript)\n\nThe `metarefresh` challenge sends a browser a much simpler challenge that makes it refre"
  },
  {
    "path": "docs/docs/admin/configuration/challenges/preact.mdx",
    "chars": 639,
    "preview": "# Preact\n\nThe `preact` challenge sends the browser a simple challenge that makes it run very lightweight JavaScript that"
  },
  {
    "path": "docs/docs/admin/configuration/challenges/proof-of-work.mdx",
    "chars": 538,
    "preview": "# Proof of Work (JavaScript)\n\nWhen Anubis is configured to use the `fast` or `slow` challenge methods, clients will be s"
  },
  {
    "path": "docs/docs/admin/configuration/custom-status-codes.mdx",
    "chars": 740,
    "preview": "# Custom status codes for Anubis errors\n\nOut of the box, Anubis will reply with `HTTP 200` for challenge and denial page"
  },
  {
    "path": "docs/docs/admin/configuration/expressions.mdx",
    "chars": 17785,
    "preview": "# Expression-based rule matching\n\nMost of the Anubis matchers let you match individual parts of a request and only those"
  },
  {
    "path": "docs/docs/admin/configuration/import.mdx",
    "chars": 4449,
    "preview": "# Importing configuration rules\n\nimport Tabs from \"@theme/Tabs\";\nimport TabItem from \"@theme/TabItem\";\n\nAnubis has the a"
  },
  {
    "path": "docs/docs/admin/configuration/impressum.mdx",
    "chars": 2887,
    "preview": "# Imprint / Impressum configuration\n\nSome jurisdictions (such as the European Union and specifically Germany) [must have"
  },
  {
    "path": "docs/docs/admin/configuration/open-graph.mdx",
    "chars": 4732,
    "preview": "---\nid: open-graph\ntitle: Open Graph Configuration\n---\n\n# Open Graph Configuration\n\nThis page provides detailed informat"
  },
  {
    "path": "docs/docs/admin/configuration/redirect-domains.mdx",
    "chars": 2876,
    "preview": "---\ntitle: Redirect Domain Configuration\n---\n\nimport Tabs from \"@theme/Tabs\";\nimport TabItem from \"@theme/TabItem\";\n\nAnu"
  },
  {
    "path": "docs/docs/admin/configuration/subrequest-auth.mdx",
    "chars": 5813,
    "preview": "---\ntitle: Subrequest Authentication\n---\n\nimport Tabs from \"@theme/Tabs\";\nimport TabItem from \"@theme/TabItem\";\n\nAnubis "
  },
  {
    "path": "docs/docs/admin/configuration/thresholds.mdx",
    "chars": 2909,
    "preview": "# Weight Threshold Configuration\n\nAnubis offers the ability to assign \"weight\" to requests. This is a custom level of su"
  },
  {
    "path": "docs/docs/admin/default-allow-behavior.mdx",
    "chars": 1921,
    "preview": "---\ntitle: Default allow behavior\n---\n\nimport Tabs from \"@theme/Tabs\";\nimport TabItem from \"@theme/TabItem\";\n\n# Default "
  },
  {
    "path": "docs/docs/admin/environments/_category_.json",
    "chars": 238,
    "preview": "{\n  \"label\": \"Environments\",\n  \"position\": 20,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"description\": \"Detailed i"
  },
  {
    "path": "docs/docs/admin/environments/apache.mdx",
    "chars": 4904,
    "preview": "# Apache\n\nimport Tabs from \"@theme/Tabs\";\nimport TabItem from \"@theme/TabItem\";\n\nAnubis is intended to be a filter proxy"
  },
  {
    "path": "docs/docs/admin/environments/caddy.mdx",
    "chars": 1857,
    "preview": "# Caddy\n\nTo use Anubis with Caddy, stick Anubis between Caddy and your backend. For example, consider this application s"
  },
  {
    "path": "docs/docs/admin/environments/cloudflare.mdx",
    "chars": 1218,
    "preview": "# Cloudflare\n\nIf you are using Cloudflare, you should configure your server to use `CF-Connecting-IP` as the source of t"
  },
  {
    "path": "docs/docs/admin/environments/docker-compose.mdx",
    "chars": 954,
    "preview": "# Docker compose\n\nDocker compose is typically used in concert with other load balancers such as [Apache](./apache.mdx) o"
  },
  {
    "path": "docs/docs/admin/environments/haproxy/advanced-config-policy.yml",
    "chars": 175,
    "preview": "# /etc/anubis/challenge-any.yml\n\nbots:\n  - name: any\n    action: CHALLENGE\n    user_agent_regex: .*\n\nstatus_codes:\n  CHA"
  },
  {
    "path": "docs/docs/admin/environments/haproxy/advanced-config.env",
    "chars": 290,
    "preview": "# /etc/anubis/default.env\n\nBIND=/run/anubis/default.sock\nBIND_NETWORK=unix\nDIFFICULTY=4\nMETRICS_BIND=:9090\n# target is i"
  },
  {
    "path": "docs/docs/admin/environments/haproxy/advanced-haproxy.cfg",
    "chars": 2779,
    "preview": "# /etc/haproxy/haproxy.cfg\n\nfrontend FE-multiple-applications\n  mode http\n  bind :80\n  # ssl offloading on port 443 usin"
  },
  {
    "path": "docs/docs/admin/environments/haproxy/simple-config.env",
    "chars": 225,
    "preview": "# /etc/anubis/default.env\n\nBIND=/run/anubis/default.sock\nBIND_NETWORK=unix\nSOCKET_MODE=0666\nDIFFICULTY=4\nMETRICS_BIND=:9"
  },
  {
    "path": "docs/docs/admin/environments/haproxy/simple-haproxy.cfg",
    "chars": 708,
    "preview": "# /etc/haproxy/haproxy.cfg\n\nfrontend FE-application\n  mode http\n  bind :80\n  # ssl offloading on port 443 using a certif"
  },
  {
    "path": "docs/docs/admin/environments/haproxy.mdx",
    "chars": 3434,
    "preview": "# HAProxy\n\nimport CodeBlock from \"@theme/CodeBlock\";\n\nTo use Anubis with HAProxy, you have two variants:\n  - simple - st"
  },
  {
    "path": "docs/docs/admin/environments/kubernetes.mdx",
    "chars": 4250,
    "preview": "# Kubernetes\n\n:::note\nLeave the `PUBLIC_URL` environment variable unset in this sidecar/standalone setup. Setting it her"
  },
  {
    "path": "docs/docs/admin/environments/nginx/conf-anubis.inc",
    "chars": 160,
    "preview": "# /etc/nginx/conf-anubis.inc # Forward to anubis location / { proxy_set_header\nHost $host; proxy_set_header X-Real-IP $r"
  },
  {
    "path": "docs/docs/admin/environments/nginx/server-anubistest-techaro-lol.conf",
    "chars": 1243,
    "preview": "# /etc/nginx/conf.d/server-anubistest-techaro-lol.conf\n\n# HTTP - Redirect all HTTP traffic to HTTPS\nserver {\n  listen 80"
  },
  {
    "path": "docs/docs/admin/environments/nginx/server-mimi-techaro-lol.conf",
    "chars": 598,
    "preview": "# /etc/nginx/conf.d/server-mimi-techaro-lol.conf\n\nserver {\n  # Listen on 443 with SSL\n  listen 443 ssl;\n  listen [::]:44"
  },
  {
    "path": "docs/docs/admin/environments/nginx/upstream-anubis.conf",
    "chars": 622,
    "preview": "# /etc/nginx/conf.d/upstream-anubis.conf\n\nupstream anubis {\n  # Make sure this matches the values you set for `BIND` and"
  },
  {
    "path": "docs/docs/admin/environments/nginx.mdx",
    "chars": 2790,
    "preview": "# Nginx\n\nimport CodeBlock from \"@theme/CodeBlock\";\n\nAnubis is intended to be a filter proxy. The way to integrate this w"
  },
  {
    "path": "docs/docs/admin/environments/traefik.mdx",
    "chars": 5087,
    "preview": "---\nid: traefik\ntitle: Traefik\n---\n\n:::note\n\nThis only talks about integration through Compose,\nbut it also applies to d"
  },
  {
    "path": "docs/docs/admin/frameworks/_category_.json",
    "chars": 191,
    "preview": "{\n  \"label\": \"Frameworks\",\n  \"position\": 30,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"description\": \"Information "
  },
  {
    "path": "docs/docs/admin/frameworks/htmx.mdx",
    "chars": 973,
    "preview": "# HTMX\n\nimport Tabs from \"@theme/Tabs\";\nimport TabItem from \"@theme/TabItem\";\n\n[HTMX](https://htmx.org) is a framework t"
  },
  {
    "path": "docs/docs/admin/frameworks/wordpress.mdx",
    "chars": 1020,
    "preview": "# WordPress\n\nWordPress is the most popular blog engine on the planet.\n\n## Using a multi-site setup with Anubis\n\nIf you h"
  }
]

// ... and 408 more files (download for full content)

About this extraction

This page contains the full source code of the TecharoHQ/anubis GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 608 files (1.4 MB), approximately 483.1k tokens, and a symbol index with 735 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!