Compare commits

..

2 Commits

Author SHA1 Message Date
Xe Iaso
50e48df993 fix(Dockerfile): add HEALTHCHECK
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-13 15:24:42 -04:00
Xe Iaso
5e38c7d730 feat: build with docker buildx bake
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-10 09:00:57 -04:00
506 changed files with 5679 additions and 29073 deletions

View File

@@ -9,4 +9,4 @@ 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
# to change flags at runtime, prepend with -- e.g. $ air -- --target http://localhost:3000 --difficulty 20 --use-remote-address

View File

@@ -1,12 +0,0 @@
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"]

View File

@@ -1,13 +0,0 @@
# 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.

View File

@@ -1,34 +0,0 @@
// 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
}
}
}
}
}

View File

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

View File

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

25
.dockerignore Normal file
View File

@@ -0,0 +1,25 @@
.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/js/*
!web/static/js/.gitignore

2
.gitattributes vendored
View File

@@ -1 +1 @@
**/*_templ.go linguist-generated=true
web/index_templ.go linguist-generated

3
.github/FUNDING.yml vendored
View File

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

View File

@@ -1,61 +0,0 @@
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.

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Security
url: https://techaro.lol/contact
about: Do not file security reports here. Email security@techaro.lol.

View File

@@ -1,39 +0,0 @@
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

View File

@@ -9,4 +9,3 @@ Checklist:
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
- [ ] Added test cases to [the relevant parts of the codebase](https://anubis.techaro.lol/docs/developer/code-quality)
- [ ] 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)

View File

@@ -2,25 +2,4 @@ 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
workarounds

View File

@@ -83,18 +83,6 @@
^\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

View File

@@ -1,85 +1,59 @@
acs
Actorified
actorifiedstore
actorify
aeacus
Aibrew
alibaba
alrest
amazonbot
anthro
anubis
anubistest
apnic
APNICRANDNETAU
Applebot
archlinux
arpa
asnc
asnchecker
asns
aspirational
atuin
azuretools
badregexes
bbolt
bdba
berr
bezier
bingbot
Bitcoin
bitrate
bitcoin
blogging
Bluesky
blueskybot
boi
Bokm
botnet
botstopper
BPort
Brightbot
broked
buildah
byteslice
Bytespider
cachebuster
cachediptoasn
Caddyfile
caninetools
Cardyb
celchecker
celphase
CELPHASE
cerr
certresolver
cespare
CGNAT
cgr
chainguard
chall
challengemozilla
challengetest
checkpath
checkresult
chen
chibi
cidranger
ckie
cloudflare
Codespaces
confd
connnection
containerbuild
containerregistry
coreutils
Cotoyogi
Cromite
CRDs
crt
Cscript
daemonizing
databento
dayjob
DDOS
Debian
debrpm
decaymap
devcontainers
decompiling
Diffbot
discordapp
discordbot
@@ -87,19 +61,13 @@ distros
dnf
dnsbl
dnserr
DNSTTL
domainhere
dracula
dronebl
droneblresponse
dropin
dsilence
duckduckbot
eerror
ellenjoe
emacs
enbyware
etld
everyones
evilbot
evilsite
@@ -109,89 +77,55 @@ externalfetcher
extldflags
facebookgo
Factset
fahedouch
fastcgi
FCr
fcrdns
fediverse
ffprobe
financials
finfos
Firecrawl
flagenv
Fordola
forgejo
forwardauth
fsys
fullchain
gaissmai
Galvus
geoip
geoipchecker
gha
GHSA
Ghz
gipc
gitea
GLM
godotenv
goland
gomod
goodbot
googlebot
gopsutil
govulncheck
goyaml
GPG
GPT
gptbot
Graphene
grpcprom
grw
gzw
Hashcash
hashrate
headermap
healthcheck
healthz
hebis
hec
helpdesk
Hetzner
hmc
homelab
hostable
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
keikaku
keypair
KHTML
kinda
@@ -200,18 +134,17 @@ lcj
ldflags
letsencrypt
Lexentale
lfc
lgbt
licend
licstart
lightpanda
limsa
LIMSA
Linting
listor
linuxbrew
LLU
loadbalancer
lol
lominsa
LOMINSA
maintainership
malware
mcr
@@ -219,39 +152,25 @@ memes
metarefresh
metrix
mimi
Minfilia
minica
mistralai
mnt
Mojeek
mojeekbot
mozilla
myclient
mymaster
mypass
myuser
nbf
nepeat
netsurf
nginx
nicksnyder
nikandfor
nobots
NONINFRINGEMENT
nosleep
nullglob
oci
OCOB
ogtag
oklch
ogtags
omgili
omgilibot
onionservice
openai
opendns
opengraph
openrc
oswald
pag
pagegen
palemoon
Pangu
parseable
@@ -265,123 +184,89 @@ 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
relayd
reputational
Rhul
reqmeta
risc
ruleset
runlevels
RUnlock
runtimedir
runtimedirectory
Ryzen
sas
sasl
screenshots
Scumm
searchbot
searx
sebest
secretplans
selfsigned
Semrush
Seo
setsebool
shellcheck
shirou
shoneypot
shopt
Sidetrade
simprint
sitemap
sls
sni
snipster
Sourceware
Spambot
spammer
sparkline
spyderbot
srv
stackoverflow
startprecmd
stoppostcmd
storetest
subgrid
subr
subrequest
SVCNAME
tagline
tarballs
tarrif
taviso
tbn
tbr
techaro
techarohq
telegrambot
templ
templruntime
testarea
Thancred
thoth
thothmock
Tik
Timpibot
TLog
torproject
traefik
trunc
uberspace
Unbreak
unbreakdocker
unifiedjs
unixhttpd
unmarshal
unparseable
uvx
UXP
valkey
Varis
Velen
vendored
vhosts
vkbot
VKE
vnd
VPS
Vultr
videotest
waitloop
weblate
webmaster
webpage
websecure
websites
Webzio
whois
wildbase
withthothmock
wolfbeast
wordpress
workaround
Workaround
workdir
wpbot
XCircle
xcaddy
Xeact
xeiaso
xeserv
xesite
@@ -389,18 +274,13 @@ xess
xff
XForwarded
XNG
XOB
XOriginal
XReal
Y'shtola
yae
YAMLTo
Yda
yeet
yeetfile
yourdomain
yyz
yoursite
Zenos
zizmor
zombocom
zos

View File

@@ -273,6 +273,14 @@
# Most people only have two hands. Reword.
\b(?i)on the third hand\b
# Should be `Open Graph`
# unless talking about a specific Open Graph implementation:
# - Java
# - Node
# - Py
# - Ruby
\bOpenGraph\b
# Should be `OpenShift`
\bOpenshift\b

View File

@@ -131,8 +131,4 @@ go install(?:\s+[a-z]+\.[-@\w/.]+)+
# 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*
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*

View File

@@ -8,8 +8,6 @@ updates:
github-actions:
patterns:
- "*"
cooldown:
default-days: 7
- package-ecosystem: gomod
directory: /
@@ -19,8 +17,6 @@ updates:
gomod:
patterns:
- "*"
cooldown:
default-days: 7
- package-ecosystem: npm
directory: /
@@ -30,5 +26,3 @@ updates:
npm:
patterns:
- "*"
cooldown:
default-days: 7

View File

@@ -1,72 +0,0 @@
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24.11.0'
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- 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

View File

@@ -2,7 +2,7 @@ name: Docker image builds (pull requests)
on:
pull_request:
branches: ["main"]
branches: [ "main" ]
env:
DOCKER_METADATA_SET_OUTPUT_ENV: "true"
@@ -15,29 +15,39 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: build essential
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Setup Homebrew cellar cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
/home/linuxbrew/.linuxbrew/Cellar
/home/linuxbrew/.linuxbrew/bin
/home/linuxbrew/.linuxbrew/etc
/home/linuxbrew/.linuxbrew/include
/home/linuxbrew/.linuxbrew/lib
/home/linuxbrew/.linuxbrew/opt
/home/linuxbrew/.linuxbrew/sbin
/home/linuxbrew/.linuxbrew/share
/home/linuxbrew/.linuxbrew/var
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-homebrew-cellar-
- name: Install Brew dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24.11.0'
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
brew bundle
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ghcr.io/${{ github.repository }}

View File

@@ -3,8 +3,8 @@ name: Docker image builds
on:
workflow_dispatch:
push:
branches: ["main"]
tags: ["v*"]
branches: [ "main" ]
tags: [ "v*" ]
env:
DOCKER_METADATA_SET_OUTPUT_ENV: "true"
@@ -21,32 +21,42 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.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@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24.11.0'
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
- name: Setup Homebrew cellar cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
/home/linuxbrew/.linuxbrew/Cellar
/home/linuxbrew/.linuxbrew/bin
/home/linuxbrew/.linuxbrew/etc
/home/linuxbrew/.linuxbrew/include
/home/linuxbrew/.linuxbrew/lib
/home/linuxbrew/.linuxbrew/opt
/home/linuxbrew/.linuxbrew/sbin
/home/linuxbrew/.linuxbrew/share
/home/linuxbrew/.linuxbrew/var
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-homebrew-cellar-
- name: Log into registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
- name: Install Brew dependencies
run: |
brew bundle
- name: Log into registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -54,7 +64,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ${{ env.IMAGE }}
@@ -67,8 +77,9 @@ jobs:
DOCKER_REPO: ${{ env.IMAGE }}
SLOG_LEVEL: debug
- name: Generate artifact attestation
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }}

View File

@@ -17,15 +17,15 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Log into registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: techarohq
@@ -33,12 +33,9 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ghcr.io/techarohq/anubis/docs
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
main
- name: Build and push
id: build
@@ -52,15 +49,15 @@ jobs:
platforms: linux/amd64
push: true
- name: Apply k8s manifests to limsa lominsa
uses: actions-hub/kubectl@f6d776bd78f4523e36d6c74d34f9941c242b2213 # v1.35.0
- name: Apply k8s manifests to aeacus
uses: actions-hub/kubectl@f632a31512a74cb35940627c49c20f67723cbaaf # v1.33.1
env:
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
with:
args: apply -k docs/manifest
- name: Apply k8s manifests to limsa lominsa
uses: actions-hub/kubectl@f6d776bd78f4523e36d6c74d34f9941c242b2213 # v1.35.0
- name: Apply k8s manifests to aeacus
uses: actions-hub/kubectl@f632a31512a74cb35940627c49c20f67723cbaaf # v1.33.1
env:
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
with:

View File

@@ -2,7 +2,7 @@ name: Docs test build
on:
pull_request:
branches: ["main"]
branches: [ "main" ]
permissions:
contents: read
@@ -13,21 +13,18 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
with:
images: ghcr.io/techarohq/anubis/docs
tags: |
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
main
images: ghcr.io/${{ github.repository }}/docs
- name: Build and push
id: build

View File

@@ -1,76 +0,0 @@
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- 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"

View File

@@ -2,9 +2,9 @@ name: Go
on:
push:
branches: ["main"]
branches: [ "main" ]
pull_request:
branches: ["main"]
branches: [ "main" ]
permissions:
contents: read
@@ -15,50 +15,77 @@ jobs:
#runs-on: alrest-techarohq
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24.11.0'
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Cache playwright binaries
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
id: playwright-cache
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
- name: Setup Homebrew cellar cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
/home/linuxbrew/.linuxbrew/Cellar
/home/linuxbrew/.linuxbrew/bin
/home/linuxbrew/.linuxbrew/etc
/home/linuxbrew/.linuxbrew/include
/home/linuxbrew/.linuxbrew/lib
/home/linuxbrew/.linuxbrew/opt
/home/linuxbrew/.linuxbrew/sbin
/home/linuxbrew/.linuxbrew/share
/home/linuxbrew/.linuxbrew/var
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-homebrew-cellar-
- name: install node deps
run: |
npm ci
- name: Install Brew dependencies
run: |
brew bundle
- 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: Setup Golang caches
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-golang-
- name: Build
run: npm run build
- name: Cache playwright binaries
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
id: playwright-cache
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
- name: Test
run: npm run test
- name: install node deps
run: |
npm ci
- name: Lint with staticcheck
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
with:
version: "latest"
- 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: Govulncheck
run: |
go tool govulncheck ./...
- name: Build
run: npm run build
- name: Test
run: npm run test
- name: Lint with staticcheck
uses: dominikh/staticcheck-action@fe1dd0c3658873b46f8c9bb3291096a617310ca6 # v1.3.1
with:
version: "latest"
- name: Govulncheck
run: |
go tool govulncheck ./...

View File

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

View File

@@ -2,9 +2,9 @@ name: Package builds (unstable)
on:
push:
branches: ["main"]
branches: [ "main" ]
pull_request:
branches: ["main"]
branches: [ "main" ]
permissions:
contents: read
@@ -15,33 +15,60 @@ jobs:
#runs-on: alrest-techarohq
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
fetch-tags: true
fetch-depth: 0
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
fetch-tags: true
fetch-depth: 0
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- name: build essential
run: |
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24.11.0'
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: install node deps
run: |
npm ci
- name: Setup Homebrew cellar cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
/home/linuxbrew/.linuxbrew/Cellar
/home/linuxbrew/.linuxbrew/bin
/home/linuxbrew/.linuxbrew/etc
/home/linuxbrew/.linuxbrew/include
/home/linuxbrew/.linuxbrew/lib
/home/linuxbrew/.linuxbrew/opt
/home/linuxbrew/.linuxbrew/sbin
/home/linuxbrew/.linuxbrew/share
/home/linuxbrew/.linuxbrew/var
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
restore-keys: |
${{ runner.os }}-go-homebrew-cellar-
- name: Build Packages
run: |
go tool yeet
- name: Install Brew dependencies
run: |
brew bundle
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: packages
path: var/*
- name: Setup Golang caches
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-golang-
- name: install node deps
run: |
npm ci
- name: Build Packages
run: |
go tool yeet
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: packages
path: var/*

View File

@@ -1,64 +0,0 @@
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: "24.11.0"
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: "1.25.4"
- 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f
if: always()
with:
name: ${{ env.ARTIFACT_NAME }}
path: test/${{ matrix.test }}/var

View File

@@ -1,37 +0,0 @@
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Log into registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Build and push
run: |
cd ./test/ssh-ci
docker buildx bake --push

View File

@@ -1,45 +0,0 @@
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
strategy:
matrix:
host:
- riscv64
- ppc64le
- aarch64-4k
- aarch64-16k
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Install CI target SSH key
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0
with:
key: ${{ secrets.CI_SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
with:
go-version: '1.25.4'
- 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 }}

View File

@@ -16,12 +16,12 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- name: Run zizmor 🌈
run: uvx zizmor --format sarif . > results.sarif
@@ -29,7 +29,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
with:
sarif_file: results.sarif
category: zizmor

2
.gitignore vendored
View File

@@ -20,5 +20,3 @@ node_modules
# how does this get here
doc/VERSION
web/static/locales/*.json

View File

@@ -1,11 +0,0 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"ms-azuretools.vscode-containers",
"golang.go",
"unifiedjs.vscode-mdx",
"a-h.templ",
"redhat.vscode-yaml",
"streetsidesoftware.code-spell-checker"
]
}

27
.vscode/launch.json vendored
View File

@@ -1,27 +0,0 @@
{
// 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"
}
]
}

19
.vscode/settings.json vendored
View File

@@ -11,24 +11,5 @@
"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
}
}

30
Dockerfile Normal file
View File

@@ -0,0 +1,30 @@
ARG ALPINE_VERSION=edge
FROM --platform=${BUILDPLATFORM} alpine:${ALPINE_VERSION} AS build
ARG TARGETOS
ARG TARGETARCH
ARG COMPONENT=anubis
ARG VERSION=devel-docker
RUN apk -U add go nodejs git build-base git npm bash zstd brotli gzip
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache npm ci && npm run assets
RUN --mount=type=cache,target=/root/.cache GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 GOARM=7 go build -gcflags "all=-N -l" -o /app/bin/${COMPONENT} -ldflags "-s -w -extldflags -static -X github.com/TecharoHQ/anubis.Version=${VERSION}" ./cmd/${COMPONENT}
FROM alpine:${ALPINE_VERSION} AS run
WORKDIR /app
RUN apk -U add ca-certificates mailcap
COPY --from=build /app/bin/anubis /app/bin/anubis
CMD ["/app/bin/anubis"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD [ "/app/bin/anubis", "--healthcheck" ]
LABEL org.opencontainers.image.source="https://github.com/TecharoHQ/anubis"

View File

@@ -18,7 +18,6 @@ assets: deps
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
@@ -28,7 +27,6 @@ lint: assets
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 ./...

View File

@@ -9,7 +9,6 @@
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/TecharoHQ/anubis)
![language count](https://img.shields.io/github/languages/count/TecharoHQ/anubis)
![repo size](https://img.shields.io/github/repo-size/TecharoHQ/anubis)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/Xe)](https://github.com/sponsors/Xe)
## Sponsors
@@ -20,9 +19,6 @@ Anubis is brought to you by sponsors and donors like:
<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
@@ -44,20 +40,6 @@ Anubis is brought to you by sponsors and donors like:
<a href="https://wildbase.xyz/">
<img src="./docs/static/img/sponsors/wildbase-logo.webp" alt="Wildbase" height="64">
</a>
<a href="https://emma.pet">
<img
src="./docs/static/img/sponsors/nepeat-logo.webp"
alt="Cat eyes over the word Emma in a serif font"
height="64"
/>
</a>
<a href="https://fabulous.systems/">
<img
src="./docs/static/img/sponsors/fabulous-systems.webp"
alt="Cat eyes over the word Emma in a serif font"
height="64"
/>
</a>
## Overview
@@ -69,7 +51,7 @@ Anubis is a bit of a nuclear response. This will result in your website being bl
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).
If you want to try this out, connect to [anubis.techaro.lol](https://anubis.techaro.lol).
## Support

View File

@@ -1,13 +0,0 @@
# 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.

View File

@@ -1 +1 @@
1.24.0
1.19.1

View File

@@ -11,11 +11,12 @@ var Version = "devel"
// CookieName is the name of the cookie that Anubis uses in order to validate
// access.
var CookieName = "techaro.lol-anubis"
const CookieName = "techaro.lol-anubis-auth"
// 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"
// WithDomainCookieName is the name that is prepended to the per-domain cookie used when COOKIE_DOMAIN is set.
const WithDomainCookieName = "techaro.lol-anubis-auth-for-"
const TestCookieName = "techaro.lol-anubis-cookie-test-if-you-block-this-anubis-wont-work"
// CookieDefaultExpirationTime is the amount of time before the cookie/JWT expires.
const CookieDefaultExpirationTime = 7 * 24 * time.Hour
@@ -23,9 +24,6 @@ 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/"
@@ -35,10 +33,3 @@ 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

View File

@@ -31,14 +31,11 @@ import (
"github.com/TecharoHQ/anubis/data"
"github.com/TecharoHQ/anubis/internal"
libanubis "github.com/TecharoHQ/anubis/lib"
"github.com/TecharoHQ/anubis/lib/config"
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/thoth"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/web"
"github.com/facebookgo/flagenv"
_ "github.com/joho/godotenv/autoload"
"github.com/prometheus/client_golang/prometheus/promhttp"
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
)
var (
@@ -47,16 +44,8 @@ var (
bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge")
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
cookieDynamicDomain = flag.Bool("cookie-dynamic-domain", false, "if set, automatically set the cookie Domain value based on the request domain")
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
cookiePrefix = flag.String("cookie-prefix", anubis.CookieName, "prefix for browser cookies created by Anubis")
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
difficultyInJWT = flag.Bool("difficulty-in-jwt", false, "if true, adds a difficulty field in the JWT claims")
useSimplifiedExplanation = flag.Bool("use-simplified-explanation", false, "if true, replaces the text when clicking \"Why am I seeing this?\" with a more simplified text for a non-tech-savvy audience.")
forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header")
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies")
cookieSameSite = flag.String("cookie-same-site", "None", "sets the same site option on Anubis cookies, will auto-downgrade None to Lax if cookie-secure is false. Valid values are None, Lax, Strict, and Default.")
ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned")
ed25519PrivateKeyHexFile = flag.String("ed25519-private-key-hex-file", "", "file name containing value for ed25519-private-key-hex")
metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
@@ -66,12 +55,10 @@ var (
policyFname = flag.String("policy-fname", "", "full path to anubis policy document (defaults to a sensible built-in policy)")
redirectDomains = flag.String("redirect-domains", "", "list of domains separated by commas which anubis is allowed to redirect to. Leaving this unset allows any domain.")
slogLevel = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
stripBasePrefix = flag.Bool("strip-base-prefix", false, "if true, strips the base prefix from requests forwarded to the target server")
target = flag.String("target", "http://localhost:3923", "target to reverse proxy to, set to an empty string to disable proxying when only using auth request")
targetSNI = flag.String("target-sni", "", "if set, TLS handshake hostname when forwarding requests to the target, if set to auto, use Host header")
targetSNI = flag.String("target-sni", "", "if set, the value of the TLS handshake hostname when forwarding requests to the target")
targetHost = flag.String("target-host", "", "if set, the value of the Host header when forwarding requests to the target")
targetInsecureSkipVerify = flag.Bool("target-insecure-skip-verify", false, "if true, skips TLS validation for the backend")
targetDisableKeepAlive = flag.Bool("target-disable-keepalive", false, "if true, disables HTTP keep-alive for the backend")
healthcheck = flag.Bool("healthcheck", false, "run a health check against Anubis")
useRemoteAddress = flag.Bool("use-remote-address", false, "read the client's IP address from the network request, useful for debugging and running Anubis on bare metal")
debugBenchmarkJS = flag.Bool("debug-benchmark-js", false, "respond to every request with a challenge for benchmarking hashrate")
@@ -81,14 +68,7 @@ var (
extractResources = flag.String("extract-resources", "", "if set, extract the static resources to the specified folder")
webmasterEmail = flag.String("webmaster-email", "", "if set, displays webmaster's email on the reject page for appeals")
versionFlag = flag.Bool("version", false, "print Anubis version")
publicUrl = flag.String("public-url", "", "the externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for forwardAuth).")
xffStripPrivate = flag.Bool("xff-strip-private", true, "if set, strip private addresses from X-Forwarded-For")
customRealIPHeader = flag.String("custom-real-ip-header", "", "if set, read remote IP from header of this name (in case your environment doesn't set X-Real-IP header)")
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
jwtRestrictionHeader = flag.String("jwt-restriction-header", "X-Real-IP", "If set, the JWT is only valid if the current value of this header matched the value when the JWT was created")
)
func keyFromHex(value string) (ed25519.PrivateKey, error) {
@@ -105,7 +85,7 @@ func keyFromHex(value string) (ed25519.PrivateKey, error) {
}
func doHealthCheck() error {
resp, err := http.Get("http://localhost" + *metricsBind + "/healthz")
resp, err := http.Get("http://localhost" + *metricsBind + anubis.BasePrefix + "/metrics")
if err != nil {
return fmt.Errorf("failed to fetch metrics: %w", err)
}
@@ -118,57 +98,8 @@ func doHealthCheck() error {
return nil
}
// parseBindNetFromAddr determine bind network and address based on the given network and address.
func parseBindNetFromAddr(address string) (string, string) {
defaultScheme := "http://"
if !strings.Contains(address, "://") {
if strings.HasPrefix(address, ":") {
address = defaultScheme + "localhost" + address
} else {
address = defaultScheme + address
}
}
bindUri, err := url.Parse(address)
if err != nil {
log.Fatal(fmt.Errorf("failed to parse bind URL: %w", err))
}
switch bindUri.Scheme {
case "unix":
return "unix", bindUri.Path
case "tcp", "http", "https":
return "tcp", bindUri.Host
default:
log.Fatal(fmt.Errorf("unsupported network scheme %s in address %s", bindUri.Scheme, address))
}
return "", address
}
func parseSameSite(s string) http.SameSite {
switch strings.ToLower(s) {
case "none":
return http.SameSiteNoneMode
case "lax":
return http.SameSiteLaxMode
case "strict":
return http.SameSiteStrictMode
case "default":
return http.SameSiteDefaultMode
default:
log.Fatalf("invalid cookie same-site mode: %s, valid values are None, Lax, Strict, and Default", s)
}
return http.SameSiteDefaultMode
}
func setupListener(network string, address string) (net.Listener, string) {
formattedAddress := ""
if network == "" {
// keep compatibility
network, address = parseBindNetFromAddr(address)
}
switch network {
case "unix":
formattedAddress = "unix:" + address
@@ -208,7 +139,7 @@ func setupListener(network string, address string) (net.Listener, string) {
return listener, formattedAddress
}
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool, targetDisableKeepAlive bool) (http.Handler, error) {
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool) (http.Handler, error) {
targetUri, err := url.Parse(target)
if err != nil {
return nil, fmt.Errorf("failed to parse target URL: %w", err)
@@ -216,10 +147,6 @@ func makeReverseProxy(target string, targetSNI string, targetHost string, insecu
transport := http.DefaultTransport.(*http.Transport).Clone()
if targetDisableKeepAlive {
transport.DisableKeepAlives = true
}
// https://github.com/oauth2-proxy/oauth2-proxy/blob/4e2100a2879ef06aea1411790327019c1a09217c/pkg/upstream/http.go#L124
if targetUri.Scheme == "unix" {
// clean path up so we don't use the socket path in proxied requests
@@ -236,34 +163,43 @@ func makeReverseProxy(target string, targetSNI string, targetHost string, insecu
if insecureSkipVerify || targetSNI != "" {
transport.TLSClientConfig = &tls.Config{}
}
if insecureSkipVerify {
slog.Warn("TARGET_INSECURE_SKIP_VERIFY is set to true, TLS certificate validation will not be performed", "target", target)
transport.TLSClientConfig.InsecureSkipVerify = true
}
if targetSNI != "" && targetSNI != "auto" {
transport.TLSClientConfig.ServerName = targetSNI
if insecureSkipVerify {
slog.Warn("TARGET_INSECURE_SKIP_VERIFY is set to true, TLS certificate validation will not be performed", "target", target)
transport.TLSClientConfig.InsecureSkipVerify = true
}
if targetSNI != "" {
transport.TLSClientConfig.ServerName = targetSNI
}
}
rp := httputil.NewSingleHostReverseProxy(targetUri)
rp.Transport = transport
if targetHost != "" || targetSNI == "auto" {
if targetHost != "" {
originalDirector := rp.Director
rp.Director = func(req *http.Request) {
originalDirector(req)
if targetHost != "" {
req.Host = targetHost
}
if targetSNI == "auto" {
transport.TLSClientConfig.ServerName = req.Host
}
req.Host = targetHost
}
}
return rp, nil
}
func startDecayMapCleanup(ctx context.Context, s *libanubis.Server) {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.CleanupDecayMap()
case <-ctx.Done():
return
}
}
}
func main() {
flagenv.Parse()
flag.Parse()
@@ -273,18 +209,7 @@ func main() {
return
}
internal.SetHealth("anubis", healthv1.HealthCheckResponse_NOT_SERVING)
lg := internal.InitSlog(*slogLevel, os.Stderr)
lg.Info("starting up Anubis")
if *healthcheck {
log.Println("running healthcheck")
if err := doHealthCheck(); err != nil {
log.Fatal(err)
}
return
}
internal.InitSlog(*slogLevel)
if *extractResources != "" {
if err := extractEmbedFS(data.BotPolicies, ".", *extractResources); err != nil {
@@ -297,65 +222,20 @@ func main() {
return
}
// install signal handler
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
wg := new(sync.WaitGroup)
if *metricsBind != "" {
wg.Add(1)
go metricsServer(ctx, *lg.With("subsystem", "metrics"), wg.Done)
}
var rp http.Handler
// when using anubis via Systemd and environment variables, then it is not possible to set targe to an empty string but only to space
if strings.TrimSpace(*target) != "" {
var err error
rp, err = makeReverseProxy(*target, *targetSNI, *targetHost, *targetInsecureSkipVerify, *targetDisableKeepAlive)
rp, err = makeReverseProxy(*target, *targetSNI, *targetHost, *targetInsecureSkipVerify)
if err != nil {
log.Fatalf("can't make reverse proxy: %v", err)
}
}
if *cookieDomain != "" && *cookieDynamicDomain {
log.Fatalf("you can't set COOKIE_DOMAIN and COOKIE_DYNAMIC_DOMAIN at the same time")
}
// Thoth configuration
switch {
case *thothURL != "" && *thothToken == "":
lg.Warn("THOTH_URL is set but no THOTH_TOKEN is set")
case *thothURL == "" && *thothToken != "":
lg.Warn("THOTH_TOKEN is set but no THOTH_URL is set")
case *thothURL != "" && *thothToken != "":
lg.Debug("connecting to Thoth")
thothClient, err := thoth.New(ctx, *thothURL, *thothToken, *thothInsecure)
if err != nil {
log.Fatalf("can't dial thoth at %s: %v", *thothURL, err)
}
ctx = thoth.With(ctx, thothClient)
}
lg.Info("loading policy file", "fname", *policyFname)
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty, *slogLevel)
policy, err := libanubis.LoadPoliciesOrDefault(*policyFname, *challengeDifficulty)
if err != nil {
log.Fatalf("can't parse policy file: %v", err)
}
lg = policy.Logger
lg.Debug("swapped to new logger")
slog.SetDefault(lg)
// Warn if persistent storage is used without a configured signing key
if policy.Store.IsPersistent() {
if *hs512Secret == "" && *ed25519PrivateKeyHex == "" && *ed25519PrivateKeyHexFile == "" {
lg.Warn("[misconfiguration] persistent storage backend is configured, but no private key is set. " +
"Challenges will be invalidated when Anubis restarts. " +
"Set HS512_SECRET, ED25519_PRIVATE_KEY_HEX, or ED25519_PRIVATE_KEY_HEX_FILE to ensure challenges survive service restarts. " +
"See: https://anubis.techaro.lol/docs/admin/installation#key-generation")
}
}
ruleErrorIDs := make(map[string]string)
for _, rule := range policy.Bots {
@@ -380,20 +260,12 @@ func main() {
} else if strings.HasSuffix(*basePrefix, "/") {
log.Fatalf("[misconfiguration] base-prefix must not end with a slash")
}
if *stripBasePrefix && *basePrefix == "" {
log.Fatalf("[misconfiguration] strip-base-prefix is set to true, but base-prefix is not set, " +
"this may result in unexpected behavior")
}
var ed25519Priv ed25519.PrivateKey
if *hs512Secret != "" && (*ed25519PrivateKeyHex != "" || *ed25519PrivateKeyHexFile != "") {
log.Fatal("do not specify both HS512 and ED25519 secrets")
} else if *hs512Secret != "" {
ed25519Priv = ed25519.PrivateKey(*hs512Secret)
} else if *ed25519PrivateKeyHex != "" && *ed25519PrivateKeyHexFile != "" {
var priv ed25519.PrivateKey
if *ed25519PrivateKeyHex != "" && *ed25519PrivateKeyHexFile != "" {
log.Fatal("do not specify both ED25519_PRIVATE_KEY_HEX and ED25519_PRIVATE_KEY_HEX_FILE")
} else if *ed25519PrivateKeyHex != "" {
ed25519Priv, err = keyFromHex(*ed25519PrivateKeyHex)
priv, err = keyFromHex(*ed25519PrivateKeyHex)
if err != nil {
log.Fatalf("failed to parse and validate ED25519_PRIVATE_KEY_HEX: %v", err)
}
@@ -403,17 +275,17 @@ func main() {
log.Fatalf("failed to read ED25519_PRIVATE_KEY_HEX_FILE %s: %v", *ed25519PrivateKeyHexFile, err)
}
ed25519Priv, err = keyFromHex(string(bytes.TrimSpace(hexFile)))
priv, err = keyFromHex(string(bytes.TrimSpace(hexFile)))
if err != nil {
log.Fatalf("failed to parse and validate content of ED25519_PRIVATE_KEY_HEX_FILE: %v", err)
}
} else {
_, ed25519Priv, err = ed25519.GenerateKey(rand.Reader)
_, priv, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Fatalf("failed to generate ed25519 key: %v", err)
}
lg.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
slog.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
}
var redirectDomainsList []string
@@ -427,64 +299,49 @@ func main() {
redirectDomainsList = append(redirectDomainsList, strings.TrimSpace(domain))
}
} else {
lg.Warn("REDIRECT_DOMAINS is not set, Anubis will only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
}
anubis.CookieName = *cookiePrefix + "-auth"
anubis.TestCookieName = *cookiePrefix + "-cookie-verification"
anubis.ForcedLanguage = *forcedLanguage
anubis.UseSimplifiedExplanation = *useSimplifiedExplanation
// If OpenGraph configuration values are not set in the config file, use the
// values from flags / envvars.
if !policy.OpenGraph.Enabled {
policy.OpenGraph.Enabled = *ogPassthrough
policy.OpenGraph.ConsiderHost = *ogCacheConsiderHost
policy.OpenGraph.TimeToLive = *ogTimeToLive
policy.OpenGraph.Override = map[string]string{}
slog.Warn("REDIRECT_DOMAINS is not set, Anubis will only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
}
s, err := libanubis.New(libanubis.Options{
BasePrefix: *basePrefix,
StripBasePrefix: *stripBasePrefix,
Next: rp,
Policy: policy,
TargetHost: *targetHost,
TargetSNI: *targetSNI,
TargetInsecureSkipVerify: *targetInsecureSkipVerify,
ServeRobotsTXT: *robotsTxt,
ED25519PrivateKey: ed25519Priv,
HS512Secret: []byte(*hs512Secret),
CookieDomain: *cookieDomain,
CookieDynamicDomain: *cookieDynamicDomain,
CookieExpiration: *cookieExpiration,
CookiePartitioned: *cookiePartitioned,
RedirectDomains: redirectDomainsList,
Target: *target,
WebmasterEmail: *webmasterEmail,
OpenGraph: policy.OpenGraph,
CookieSecure: *cookieSecure,
CookieSameSite: parseSameSite(*cookieSameSite),
PublicUrl: *publicUrl,
JWTRestrictionHeader: *jwtRestrictionHeader,
Logger: policy.Logger.With("subsystem", "anubis"),
DifficultyInJWT: *difficultyInJWT,
BasePrefix: *basePrefix,
Next: rp,
Policy: policy,
ServeRobotsTXT: *robotsTxt,
PrivateKey: priv,
CookieDomain: *cookieDomain,
CookieExpiration: *cookieExpiration,
CookiePartitioned: *cookiePartitioned,
OGPassthrough: *ogPassthrough,
OGTimeToLive: *ogTimeToLive,
RedirectDomains: redirectDomainsList,
Target: *target,
WebmasterEmail: *webmasterEmail,
OGCacheConsidersHost: *ogCacheConsiderHost,
})
if err != nil {
log.Fatalf("can't construct libanubis.Server: %v", err)
}
wg := new(sync.WaitGroup)
// install signal handler
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if *metricsBind != "" {
wg.Add(1)
go metricsServer(ctx, wg.Done)
}
go startDecayMapCleanup(ctx, s)
var h http.Handler
h = s
h = internal.CustomRealIPHeader(*customRealIPHeader, h)
h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h)
h = internal.XForwardedForToXRealIP(h)
h = internal.XForwardedForUpdate(*xffStripPrivate, h)
h = internal.JA4H(h)
srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()}
listener, listenerUrl := setupListener(*bindNetwork, *bind)
lg.Info(
slog.Info(
"listening",
"url", listenerUrl,
"difficulty", *challengeDifficulty,
@@ -498,7 +355,6 @@ func main() {
"base-prefix", *basePrefix,
"cookie-expiration-time", *cookieExpiration,
"rule-error-ids", ruleErrorIDs,
"public-url", *publicUrl,
)
go func() {
@@ -510,41 +366,29 @@ func main() {
}
}()
internal.SetHealth("anubis", healthv1.HealthCheckResponse_SERVING)
if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
wg.Wait()
}
func metricsServer(ctx context.Context, lg slog.Logger, done func()) {
func metricsServer(ctx context.Context, done func()) {
defer done()
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
st, ok := internal.GetHealth("anubis")
if !ok {
slog.Error("health service anubis does not exist, file a bug")
}
switch st {
case healthv1.HealthCheckResponse_NOT_SERVING:
http.Error(w, "NOT OK", http.StatusInternalServerError)
return
case healthv1.HealthCheckResponse_SERVING:
fmt.Fprintln(w, "OK")
return
default:
http.Error(w, "UNKNOWN", http.StatusFailedDependency)
return
}
})
mux.Handle(anubis.BasePrefix+"/metrics", promhttp.Handler())
srv := http.Server{Handler: mux, ErrorLog: internal.GetFilteredHTTPLogger()}
listener, metricsUrl := setupListener(*metricsBindNetwork, *metricsBind)
lg.Debug("listening for metrics", "url", metricsUrl)
slog.Debug("listening for metrics", "url", metricsUrl)
if *healthcheck {
log.Println("running healthcheck")
if err := doHealthCheck(); err != nil {
log.Fatal(err)
}
return
}
go func() {
<-ctx.Done()

View File

@@ -28,7 +28,7 @@ func main() {
flagenv.Parse()
flag.Parse()
slog.SetDefault(internal.InitSlog(*slogLevel, os.Stderr))
internal.InitSlog(*slogLevel)
koDockerRepo := strings.TrimSuffix(*dockerRepo, "/"+filepath.Base(*dockerRepo))
@@ -46,11 +46,6 @@ func main() {
)
}
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")

View File

@@ -1,78 +0,0 @@
/*
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)
}

View File

@@ -1,384 +0,0 @@
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
"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 {
for _, disallow := range rules[i].Disallows {
if disallow == "/" {
rules[i].IsBlacklist = true
break
}
}
}
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)
}

View File

@@ -1,424 +0,0 @@
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 []interface{}
var expectedData []interface{}
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 []interface{}
var expectedData []interface{}
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 interface{}) bool {
return reflect.DeepEqual(actual, expected)
}

View File

@@ -1,15 +0,0 @@
# 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

View File

@@ -1,30 +0,0 @@
- 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

View File

@@ -1,30 +0,0 @@
# 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

View File

@@ -1,71 +0,0 @@
- 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

View File

@@ -1,25 +0,0 @@
# 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

View File

@@ -1,47 +0,0 @@
- 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

View File

@@ -1,6 +0,0 @@
- action: CHALLENGE
expression: path.startsWith("/admin/")
name: my-custom-policy-disallow-1
- action: CHALLENGE
expression: path.startsWith("/private")
name: my-custom-policy-disallow-2

View File

@@ -1,6 +0,0 @@
- action: DENY
expression: path.startsWith("/admin/")
name: robots-txt-policy-disallow-1
- action: DENY
expression: path.startsWith("/private")
name: robots-txt-policy-disallow-2

View File

@@ -1,2 +0,0 @@
# Empty robots.txt (comments only)
# No actual rules

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,12 +0,0 @@
[
{
"expression": "path.startsWith(\"/admin/\")",
"name": "robots-txt-policy-disallow-1",
"action": "CHALLENGE"
},
{
"expression": "path.startsWith(\"/private\")",
"name": "robots-txt-policy-disallow-2",
"action": "CHALLENGE"
}
]

View File

@@ -1,5 +0,0 @@
# Simple robots.txt test
User-agent: *
Disallow: /admin/
Disallow: /private
Allow: /public

View File

@@ -1,6 +0,0 @@
- action: CHALLENGE
expression: path.startsWith("/admin/")
name: robots-txt-policy-disallow-1
- action: CHALLENGE
expression: path.startsWith("/private")
name: robots-txt-policy-disallow-2

View File

@@ -1,6 +0,0 @@
# Test wildcard patterns
User-agent: *
Disallow: /search*
Disallow: /*/private
Disallow: /file?.txt
Disallow: /admin/*?action=delete

View File

@@ -1,12 +0,0 @@
- 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

View File

@@ -3,6 +3,5 @@
- name: qualys-ssl-labs
action: ALLOW
remote_addresses:
- 69.67.183.0/24
- 2600:C02:1020:4202::/64
- 2602:fdaa:c6:2::/64
- 64.41.200.0/24
- 2600:C02:1020:4202::/64

29
data/botPolicies.json Normal file
View File

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

View File

@@ -11,12 +11,9 @@
## /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
- # 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
@@ -50,51 +47,10 @@ bots:
# user_agent_regex: (?i:bot|crawler)
# action: CHALLENGE
# challenge:
# difficulty: 16 # impossible
# difficulty: 16 # impossible
# report_as: 4 # lie to the operator
# 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: >-
@@ -105,59 +61,6 @@ bots:
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
@@ -165,72 +68,3 @@ openGraph:
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

View File

@@ -1,6 +1,3 @@
- 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
- import: (data)/bots/us-ai-scraper.yaml

View File

@@ -7,5 +7,5 @@
# 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
AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|anthropic-ai|Brightbot 1.0|Bytespider|CCBot|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

View File

@@ -1,8 +1,6 @@
# 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
AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|bedrockbot|Brightbot 1.0|Bytespider|CCBot|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|FacebookBot|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User/1.0|NovaAct|OAI-SearchBot|omgili|omgilibot|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YandexAdditional|YandexAdditionalBot|YouBot
action: DENY

View File

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

View File

@@ -4,5 +4,5 @@
# - Claude-User: No published IP allowlist
- name: "ai-clients"
user_agent_regex: >-
ChatGPT-User|Claude-User|MistralAI-User|Perplexity-User
ChatGPT-User|Claude-User|MistralAI-User
action: DENY

View File

@@ -1,60 +0,0 @@
- 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/")

View File

@@ -2,19 +2,13 @@
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")
- >
(
userAgent.startsWith("git/") ||
userAgent.contains("libgit") ||
userAgent.startsWith("go-git") ||
userAgent.startsWith("JGit/") ||
userAgent.startsWith("JGit-")
)
- '"Git-Protocol" in headers'
- headers["Git-Protocol"] == "version=2"

View File

@@ -1,12 +0,0 @@
# 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",
]

View File

@@ -1,6 +0,0 @@
- name: telegrambot
action: ALLOW
expression:
all:
- userAgent.matches("TelegramBot")
- verifyFCrDNS(remoteAddress, "ptr\\.telegram\\.org$")

View File

@@ -1,6 +0,0 @@
- name: vkbot
action: ALLOW
expression:
all:
- userAgent.matches("vkShare[^+]+\\+http\\://vk\\.com/dev/Share")
- verifyFCrDNS(remoteAddress, "^snipster\\d+\\.go\\.mail\\.ru$")

View File

@@ -1,55 +0,0 @@
# 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

View File

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

View File

@@ -6,6 +6,4 @@
- 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/yandexbot.yaml
- import: (data)/crawlers/mojeekbot.yaml

View File

@@ -4,5 +4,5 @@
# - Claude-SearchBot: No published IP allowlist
- name: "ai-crawlers-search"
user_agent_regex: >-
OAI-SearchBot|Claude-SearchBot|PerplexityBot
OAI-SearchBot|Claude-SearchBot
action: DENY

View File

@@ -1,881 +0,0 @@
- 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

View File

@@ -1,12 +0,0 @@
- name: common-crawl
user_agent_regex: CCBot
action: ALLOW
# https://index.commoncrawl.org/ccbot.json
remote_addresses:
[
"2600:1f28:365:80b0::/60",
"18.97.9.168/29",
"18.97.14.80/29",
"18.97.14.88/30",
"98.85.178.216/32",
]

View File

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

View File

@@ -1,16 +0,0 @@
# Indexing for search, does not collect training data
# https://docs.perplexity.ai/guides/bots
- name: perplexitybot
user_agent_regex: PerplexityBot/.+; \+https\://perplexity\.ai/perplexitybot
action: ALLOW
# https://www.perplexity.com/perplexitybot.json
remote_addresses: [
"107.20.236.150/32",
"3.224.62.45/32",
"18.210.92.235/32",
"3.222.232.239/32",
"3.211.124.183/32",
"3.231.139.107/32",
"18.97.1.228/30",
"18.97.9.96/29",
]

View File

@@ -1,165 +0,0 @@
# Tencent Cloud crawler IP ranges
- name: tencent-cloud
action: DENY
remote_addresses:
- 101.32.0.0/17
- 101.32.176.0/20
- 101.32.192.0/18
- 101.33.116.0/22
- 101.33.120.0/21
- 101.33.16.0/20
- 101.33.2.0/23
- 101.33.32.0/19
- 101.33.4.0/22
- 101.33.64.0/19
- 101.33.8.0/21
- 101.33.96.0/20
- 119.28.28.0/24
- 119.29.29.0/24
- 124.156.0.0/16
- 129.226.0.0/18
- 129.226.128.0/18
- 129.226.224.0/19
- 129.226.96.0/19
- 150.109.0.0/18
- 150.109.128.0/20
- 150.109.160.0/19
- 150.109.192.0/18
- 150.109.64.0/20
- 150.109.80.0/21
- 150.109.88.0/22
- 150.109.96.0/19
- 162.14.60.0/22
- 162.62.0.0/18
- 162.62.128.0/20
- 162.62.144.0/21
- 162.62.152.0/22
- 162.62.172.0/22
- 162.62.176.0/20
- 162.62.192.0/19
- 162.62.255.0/24
- 162.62.80.0/20
- 162.62.96.0/19
- 170.106.0.0/16
- 43.128.0.0/14
- 43.132.0.0/22
- 43.132.12.0/22
- 43.132.128.0/17
- 43.132.16.0/22
- 43.132.28.0/22
- 43.132.32.0/22
- 43.132.40.0/22
- 43.132.52.0/22
- 43.132.60.0/24
- 43.132.64.0/22
- 43.132.69.0/24
- 43.132.70.0/23
- 43.132.72.0/21
- 43.132.80.0/21
- 43.132.88.0/22
- 43.132.92.0/23
- 43.132.96.0/19
- 43.133.0.0/16
- 43.134.0.0/16
- 43.135.0.0/17
- 43.135.128.0/18
- 43.135.192.0/19
- 43.152.0.0/21
- 43.152.11.0/24
- 43.152.12.0/22
- 43.152.128.0/22
- 43.152.133.0/24
- 43.152.134.0/23
- 43.152.136.0/21
- 43.152.144.0/20
- 43.152.160.0/22
- 43.152.16.0/21
- 43.152.164.0/23
- 43.152.166.0/24
- 43.152.168.0/21
- 43.152.178.0/23
- 43.152.180.0/22
- 43.152.184.0/21
- 43.152.192.0/18
- 43.152.24.0/22
- 43.152.31.0/24
- 43.152.32.0/23
- 43.152.35.0/24
- 43.152.36.0/22
- 43.152.40.0/21
- 43.152.48.0/20
- 43.152.74.0/23
- 43.152.76.0/22
- 43.152.80.0/22
- 43.152.8.0/23
- 43.152.92.0/23
- 43.153.0.0/16
- 43.154.0.0/15
- 43.156.0.0/15
- 43.158.0.0/16
- 43.159.0.0/20
- 43.159.128.0/17
- 43.159.64.0/23
- 43.159.70.0/23
- 43.159.72.0/21
- 43.159.81.0/24
- 43.159.82.0/23
- 43.159.85.0/24
- 43.159.86.0/23
- 43.159.88.0/21
- 43.159.96.0/19
- 43.160.0.0/15
- 43.162.0.0/16
- 43.163.0.0/17
- 43.163.128.0/18
- 43.163.192.255/32
- 43.163.193.0/24
- 43.163.194.0/23
- 43.163.196.0/22
- 43.163.200.0/21
- 43.163.208.0/20
- 43.163.224.0/19
- 43.164.0.0/18
- 43.164.128.0/17
- 43.165.0.0/16
- 43.166.128.0/18
- 43.166.224.0/19
- 43.168.0.0/20
- 43.168.16.0/21
- 43.168.24.0/22
- 43.168.255.0/24
- 43.168.32.0/19
- 43.168.64.0/20
- 43.168.80.0/22
- 43.169.0.0/16
- 43.170.0.0/16
- 43.174.0.0/18
- 43.174.128.0/17
- 43.174.64.0/22
- 43.174.68.0/23
- 43.174.71.0/24
- 43.174.74.0/23
- 43.174.76.0/22
- 43.174.80.0/20
- 43.174.96.0/19
- 43.175.0.0/20
- 43.175.113.0/24
- 43.175.114.0/23
- 43.175.116.0/22
- 43.175.120.0/21
- 43.175.128.0/18
- 43.175.16.0/22
- 43.175.192.0/20
- 43.175.20.0/23
- 43.175.208.0/21
- 43.175.216.0/22
- 43.175.220.0/23
- 43.175.22.0/24
- 43.175.222.0/24
- 43.175.224.0/20
- 43.175.25.0/24
- 43.175.26.0/23
- 43.175.28.0/22
- 43.175.32.0/19
- 43.175.64.0/19
- 43.175.96.0/20

View File

@@ -1,6 +0,0 @@
- name: yandexbot
action: ALLOW
expression:
all:
- userAgent.matches("\\+http\\://yandex\\.com/bots")
- verifyFCrDNS(remoteAddress, "^.*\\.yandex\\.(ru|com|net)$")

View File

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

View File

@@ -1,38 +0,0 @@
package data
import (
"path/filepath"
"strings"
"testing"
)
// TestBotPoliciesEmbed ensures all YAML files in the directory tree
// are accessible in the embedded BotPolicies filesystem.
func TestBotPoliciesEmbed(t *testing.T) {
yamlFiles, err := filepath.Glob("./**/*.yaml")
if err != nil {
t.Fatalf("Failed to glob YAML files: %v", err)
}
if len(yamlFiles) == 0 {
t.Fatal("No YAML files found in directory tree")
}
t.Logf("Found %d YAML files to verify", len(yamlFiles))
for _, filePath := range yamlFiles {
embeddedPath := strings.TrimPrefix(filePath, "./")
t.Run(embeddedPath, func(t *testing.T) {
content, err := BotPolicies.ReadFile(embeddedPath)
if err != nil {
t.Errorf("Failed to read %s from embedded filesystem: %v", embeddedPath, err)
return
}
if len(content) == 0 {
t.Errorf("File %s exists in embedded filesystem but is empty", embeddedPath)
}
})
}
}

View File

@@ -3,7 +3,5 @@
- import: (data)/bots/ai-catchall.yaml
- import: (data)/crawlers/ai-training.yaml
- import: (data)/crawlers/openai-searchbot.yaml
- import: (data)/crawlers/perplexitybot.yaml
- import: (data)/clients/openai-chatgpt-user.yaml
- import: (data)/clients/mistral-mistralai-user.yaml
- import: (data)/clients/perplexity-user.yaml
- import: (data)/clients/mistral-mistralai-user.yaml

View File

@@ -2,7 +2,5 @@
- import: (data)/bots/ai-catchall.yaml
- import: (data)/crawlers/openai-searchbot.yaml
- import: (data)/crawlers/openai-gptbot.yaml
- import: (data)/crawlers/perplexitybot.yaml
- import: (data)/clients/openai-chatgpt-user.yaml
- import: (data)/clients/mistral-mistralai-user.yaml
- import: (data)/clients/perplexity-user.yaml
- import: (data)/clients/mistral-mistralai-user.yaml

View File

@@ -1,88 +0,0 @@
- # 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

View File

@@ -1,2 +0,0 @@
- import: (data)/clients/telegram-preview.yaml
- import: (data)/clients/vk-preview.yaml

View File

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

View File

@@ -13,13 +13,7 @@ func Zilch[T any]() T {
// Impl is a lazy key->value map. It's a wrapper around a map and a mutex. If values exceed their time-to-live, they are pruned at Get time.
type Impl[K comparable, V any] struct {
data map[K]decayMapEntry[V]
// deleteCh receives decay-deletion requests from readers.
deleteCh chan deleteReq[K]
// stopCh stops the background cleanup worker.
stopCh chan struct{}
wg sync.WaitGroup
lock sync.RWMutex
lock sync.RWMutex
}
type decayMapEntry[V any] struct {
@@ -27,54 +21,31 @@ type decayMapEntry[V any] struct {
expiry time.Time
}
// deleteReq is a request to remove a key if its expiry timestamp still matches
// the observed one. This prevents racing with concurrent Set updates.
type deleteReq[K comparable] struct {
key K
expiry time.Time
}
// New creates a new DecayMap of key type K and value type V.
//
// Key types must be comparable to work with maps.
func New[K comparable, V any]() *Impl[K, V] {
m := &Impl[K, V]{
data: make(map[K]decayMapEntry[V]),
deleteCh: make(chan deleteReq[K], 1024),
stopCh: make(chan struct{}),
return &Impl[K, V]{
data: make(map[K]decayMapEntry[V]),
}
m.wg.Add(1)
go m.cleanupWorker()
return m
}
// expire forcibly expires a key by setting its time-to-live one second in the past.
func (m *Impl[K, V]) expire(key K) bool {
// Use a single write lock to avoid RUnlock->Lock convoy.
m.lock.Lock()
defer m.lock.Unlock()
m.lock.RLock()
val, ok := m.data[key]
m.lock.RUnlock()
if !ok {
return false
}
m.lock.Lock()
val.expiry = time.Now().Add(-1 * time.Second)
m.data[key] = val
return true
}
m.lock.Unlock()
// Delete a value from the DecayMap by key.
//
// If the value does not exist, return false. Return true after
// deletion.
func (m *Impl[K, V]) Delete(key K) bool {
// Use a single write lock to avoid RUnlock->Lock convoy.
m.lock.Lock()
defer m.lock.Unlock()
_, ok := m.data[key]
if ok {
delete(m.data, key)
}
return ok
return true
}
// Get gets a value from the DecayMap by key.
@@ -90,12 +61,13 @@ func (m *Impl[K, V]) Get(key K) (V, bool) {
}
if time.Now().After(value.expiry) {
// Defer decay deletion to the background worker to avoid convoy.
select {
case m.deleteCh <- deleteReq[K]{key: key, expiry: value.expiry}:
default:
// Channel full: drop request; a future Cleanup() or Get will retry.
m.lock.Lock()
// Since previously reading m.data[key], the value may have been updated.
// Delete the entry only if the expiry time is still the same.
if m.data[key].expiry.Equal(value.expiry) {
delete(m.data, key)
}
m.lock.Unlock()
return Zilch[V](), false
}
@@ -133,64 +105,3 @@ func (m *Impl[K, V]) Len() int {
defer m.lock.RUnlock()
return len(m.data)
}
// Close stops the background cleanup worker. It's optional to call; maps live
// for the process lifetime in many cases. Call in tests or when you know you no
// longer need the map to avoid goroutine leaks.
func (m *Impl[K, V]) Close() {
close(m.stopCh)
m.wg.Wait()
}
// cleanupWorker batches decay deletions to minimize lock contention.
func (m *Impl[K, V]) cleanupWorker() {
defer m.wg.Done()
batch := make([]deleteReq[K], 0, 64)
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
flush := func() {
if len(batch) == 0 {
return
}
m.applyDeletes(batch)
// reset batch without reallocating
batch = batch[:0]
}
for {
select {
case req := <-m.deleteCh:
batch = append(batch, req)
case <-ticker.C:
flush()
case <-m.stopCh:
// Drain any remaining requests then exit
for {
select {
case req := <-m.deleteCh:
batch = append(batch, req)
default:
flush()
return
}
}
}
}
}
func (m *Impl[K, V]) applyDeletes(batch []deleteReq[K]) {
now := time.Now()
m.lock.Lock()
for _, req := range batch {
entry, ok := m.data[req.key]
if !ok {
continue
}
// Only delete if the expiry is unchanged and already past.
if entry.expiry.Equal(req.expiry) && now.After(entry.expiry) {
delete(m.data, req.key)
}
}
m.lock.Unlock()
}

View File

@@ -7,7 +7,6 @@ import (
func TestImpl(t *testing.T) {
dm := New[string, string]()
t.Cleanup(dm.Close)
dm.Set("test", "hi", 5*time.Minute)
@@ -29,24 +28,10 @@ func TestImpl(t *testing.T) {
if ok {
t.Error("got value even though it was supposed to be expired")
}
// Deletion of expired entries after Get is deferred to a background worker.
// Assert it eventually disappears from the map.
deadline := time.Now().Add(200 * time.Millisecond)
for time.Now().Before(deadline) {
if dm.Len() == 0 {
break
}
time.Sleep(5 * time.Millisecond)
}
if dm.Len() != 0 {
t.Fatalf("expected background cleanup to remove expired key; len=%d", dm.Len())
}
}
func TestCleanup(t *testing.T) {
dm := New[string, string]()
t.Cleanup(dm.Close)
dm.Set("test1", "hi1", 1*time.Second)
dm.Set("test2", "hi2", 2*time.Second)

View File

@@ -1,12 +1,13 @@
variable "ALPINE_VERSION" { default = "3.22" }
variable "GITHUB_SHA" { default = "devel" }
group "default" {
targets = [
"ci-runner",
"anubis",
]
}
target "ci-runner" {
target "anubis" {
args = {
ALPINE_VERSION = "3.22"
}
@@ -21,6 +22,6 @@ target "ci-runner" {
]
pull = true
tags = [
"ghcr.io/techarohq/anubis/ci-runner:latest"
"ghcr.io/techarohq/anubis:${GITHUB_SHA}"
]
}

View File

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

View File

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

View File

@@ -1,14 +0,0 @@
---
slug: welcome
title: Welcome to the Anubis blog!
authors: [xe]
tags: [intro]
---
Hello, world!
At Techaro, we've been working on making Anubis even better, and in the process we want to share what we've done, how it works, and signal boost cool things the community has done. As things happen, we'll blog about them so that you can learn from our struggles.
More details to come soon!
{/* truncate */}

View File

@@ -1,248 +0,0 @@
---
slug: release/v1.20.0
title: Anubis v1.20.0 is now available!
authors: [xe]
tags: [release]
image: sunburst.webp
---
![](./sunburst.webp)
Hey all!
Today we released [Anubis v1.20.0: Thancred Waters](https://github.com/TecharoHQ/anubis/releases/tag/v1.20.0). This adds a lot of new and exciting features to Anubis, including but not limited to the `WEIGH` action, custom weight thresholds, Imprint/impressum support, and a no-JS challenge. Here's what you need to know so you can protect your websites in new and exciting ways!
{/* truncate */}
## Sponsoring the product
If you rely on Anubis to keep your website safe, please consider sponsoring the project on [GitHub Sponsors](https://github.com/sponsors/Xe) or [Patreon](https://patreon.com/cadey). Funding helps pay hosting bills and offset the time spent on making this project the best it can be. Every little bit helps and when enough money is raised, [I can make Anubis my full-time job](https://github.com/TecharoHQ/anubis/discussions/278).
I am waiting to hear back from NLNet on if Anubis was selected for funding or not. Let's hope it is!
## Deprecation warning: `DIFFICULTY`
Anubis v1.20.0 is the last version to support the `DIFFICULTY` flag in the exact way it currently does. In future versions, this will be ineffectual and you should use the [custom threshold system](/docs/admin/configuration/thresholds) instead.
If this becomes an imposition in practice, this will be reverted.
## Chrome won't show "invalid response" after "Success!"
There were a bunch of smaller fixes in Anubis this time around, but the biggest one was finally squashing the ["invalid response" after "Success!" issue](https://github.com/TecharoHQ/anubis/issues/564) that had been plaguing Chrome users. This was a really annoying issue to track down but it was discovered while we were working on better end-to-end / functional testing: [Chrome randomizes the `Accept-Language` header](https://github.com/explainers-by-googlers/reduce-accept-language) so that websites can't do fingerprinting as easily.
When Anubis issues a challenge, it grabs [information that the browser sends to the user](/docs/design/how-anubis-works#challenge-format) to create a challenge string. Anubis doesn't store these challenge strings anywhere, and when a solution is being checked it calculates the challenge string from the request. This means that they'd get a challenge on one end, compute the response for that challenge, and then the server would validate that against a different challenge. This server-side validation would fail, leading to the user seeing "invalid response" after the client reported success.
I suspect this was why Vanadium and Cromite were having sporadic issues as well.
## New Features
The biggest feature in Anubis is the "weight" subsystem. This allows administrators to make custom rules that change the suspicion level of a request without having to take immediate action. As an example, consider the self-hostable git forge [Gitea](https://about.gitea.com/). When you load a page in Gitea, it creates a session cookie that your browser sends with every request. Weight allows you to mark a request that includes a Gitea session token as _less_ suspicious:
```yaml
- name: gitea-session-token
action: WEIGH
expression:
all:
# Check if the request has a Cookie header
- '"Cookie" in headers'
# Check if the request's Cookie header contains the Gitea session token
- headers["Cookie"].contains("i_love_gitea=")
# Remove 5 weight points
weight:
adjust: -5
```
This is different from the past where you could only allow every request with a Gitea session token, meaning that the invention of lying would allow malicious clients to bypass protection.
Weight is added and removed whenever a `WEIGH` rule is encountered. When all rules are processed and the request doesn't match any `ALLOW`, `CHALLENGE`, or `DENY` rules, Anubis uses [weight thresholds](/docs/admin/configuration/thresholds) to figure out how to handle that request. Thresholds are defined in the [policy file](/docs/admin/policies) alongside your bot rules:
```yaml
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
report_as: 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
report_as: 2
# For clients that are browser like and have gained many points from custom rules
- name: extreme-suspicion
expression: weight >= 20
action: CHALLENGE
challenge:
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
algorithm: fast
difficulty: 4
report_as: 4
```
:::note
If you don't have thresholds defined in your Anubis policy file, Anubis will default to the "legacy" behaviour where browser-like clients get a challenge at the default difficulty.
:::
This lets most clients through if they pass a simple [proof of work challenge](/docs/admin/configuration/challenges/proof-of-work), but any clients that are less suspicious (like ones with a Gitea session token) are given the lightweight [Meta Refresh](/docs/admin/configuration/challenges/metarefresh) challenge instead.
Threshold expressions are like [Bot rule expressions](/docs/admin/configuration/expressions), but there's only one input: the request's weight. If no thresholds match, the request is allowed through.
### Imprint/Impressum Support
European countries like Germany [require an imprint/impressum](https://www.ionos.com/digitalguide/websites/digital-law/a-case-for-thinking-global-germanys-impressum-laws/) to be present in the footer of their website. This allows users to contact someone on the team behind a website in case they run into issues. This also must generally have a separate page where users can view an extended imprint with other information like a privacy policy or a copyright notice.
Anubis v1.20.0 and later [has support for showing imprints](/docs/admin/configuration/impressum). You can configure two kinds of imprints:
1. An imprint that is shown in the footer of every Anubis page.
2. An extended imprint / privacy policy that is shown when users click on the "Imprint" link. For example, [here's the imprint for the website you're looking at right now](https://anubis.techaro.lol/.within.website/x/cmd/anubis/api/imprint).
Imprints are configured in [the policy file](/docs/admin/policies/):
```yaml
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@zombocom.example">contact@zombocom.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>
<!-- ... -->
```
If this is insufficient, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new) with a link to the relevant legislation for your country so that this feature can be amended and improved.
### No-JS Challenge
One of the first issues in Anubis before it was moved to the [TecharoHQ org](https://github.com/TecharoHQ) was a request [to support challenging browsers without using JavaScript](https://github.com/Xe/x/issues/651). This is a pretty challenging thing to do without rethinking how Anubis works from a fundamentally low level, and with v1.20.0, [Anubis finally has support for running without client-side JavaScript](https://github.com/TecharoHQ/anubis/issues/95) thanks to the [Meta Refresh](/docs/admin/configuration/challenges/metarefresh) challenge.
When Anubis decides it needs to send a challenge to your browser, it sends a challenge page. Historically, this challenge page is [an HTML template](https://github.com/TecharoHQ/anubis/blob/main/web/index.templ) that kicks off some JavaScript, reads the challenge information out of the page body, and then solves it as fast as possible in order to let users see the website they want to visit.
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/admin/configuration/challenges/). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
In my testing, this has worked with every browser I have thrown it at (including CLI browsers, the browser embedded in emacs, etc.). The default configuration of Anubis does use the [meta refresh challenge](/docs/admin/configuration/challenges/metarefresh) for [clients with a very low suspicion](/docs/admin/configuration/thresholds), but by default clients will be sent an [easy proof of work challenge](/docs/admin/configuration/challenges/proof-of-work).
If the false positive rate of this challenge turns out to not be very high in practice, the meta refresh challenge will be enabled by default for browsers in future versions of Anubis.
### `robots2policy`
Anubis was created because crawler bots don't respect [`robots.txt` files](https://www.robotstxt.org/). Administrators have been working on refining and crafting their `robots.txt` files for years, and one common comment is that people don't know where to start crafting their own rules.
Anubis now ships with a [`robots2policy` tool](/docs/admin/robots2policy) that lets you convert your `robots.txt` file to an Anubis policy.
```text
robots2policy -input https://github.com/robots.txt
```
:::note
If you installed Anubis from [an OS package](/docs/admin/native-install), you may need to run `anubis-robots2policy` instead of `robots2policy`.
:::
We hope that this will help you get started with Anubis faster. We are working on a version of this that will run in the documentation via WebAssembly.
### Open Graph configuration is being moved to the policy file
Anubis supports reading [Open Graph tags](/docs/admin/configuration/open-graph) from target services and returning them in challenge pages. This makes the right metadata show up when linking services protected by Anubis in chat applications or on social media.
In order to test the migration of all of the configuration to the policy file, Open Graph configuration has been moved to the policy file. For more information, please read [the Open Graph configuration options](/docs/admin/configuration/open-graph#configuration-options).
You can also set default Open Graph tags:
```yaml
openGraph:
enabled: true
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!
```
## Improvements and optimizations
One of the biggest improvements we've made in v1.20.0 is replacing [SHA-256 with xxhash](https://github.com/TecharoHQ/anubis/pull/676). Anubis uses hashes all over the place to help with identifying clients, matching against rules when allowing traffic through, in error messages sent to users, and more. Historically these have been done with [SHA-256](https://en.wikipedia.org/wiki/SHA-2), however this has been having a mild performance impact in real-world use. As a result, we now use [xxhash](https://xxhash.com/) when possible. This makes policy matching 3x faster in some scenarios and reduces memory usage across the board.
Anubis now uses [bart](https://pkg.go.dev/github.com/gaissmai/bart) for doing IP address matching when you specify addresses in a `remote_address` check configuration or when you are matching against [advanced checks](/docs/admin/thoth). This uses the same kind of IP address routing configuration that your OS kernel does, making it very fast to query information about IP addresses. This makes IP address range matches anywhere from 3-14 times faster depending on the number of addresses it needs to match against. For more information and benchmarks, check out [@JasonLovesDoggo](https://github.com/JasonLovesDoggo)'s PR: [perf: replace cidranger with bart for significant performance improvements #675](https://github.com/TecharoHQ/anubis/pull/675).
## What's up next?
v1.21.0 is already shaping up to be a massive improvement as Anubis adds [internationalization](https://en.wikipedia.org/wiki/Internationalization) support, allowing your users to see its messages in the language they're most comfortable with.
So far Anubis supports the following languages:
- English (Simplified and Traditional)
- French
- Portuguese (Brazil)
- Spanish
If you want to contribute translations, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new) with your language of choice or submit a pull request to [the `lib/localization/locales` folder](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). We are about to introduce features to the translation stack, so you may want to hold off a hot minute, but we welcome any and all contributions to making Anubis useful to a global audience.
Other things we plan to do:
- Move configuration to the policy file
- Support reloading the policy file at runtime without having to restart Anubis
- Detecting if a client is "brand new"
- A [Valkey](https://valkey.io/)-backed store for sharing information between instances of Anubis
- Augmenting No-JS support in the paid product
- TLS fingerprinting
- Automated testing improvements in CI (FreeBSD CI support, better automated integration/functional testing, etc.)
## Conclusion
I hope that these features let you get the same Anubis power you've come to know and love and increases the things you can do with it! I've been really excited to ship [thresholds](/docs/admin/configuration/thresholds) and the cloud-based services for Anubis.
If you run into any problems, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new). Otherwise, have a good day and get back to making your communities great.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

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