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
(\\?['"]|")[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>





[](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
[](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"
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
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\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.