mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-05 16:28:17 +00:00
Compare commits
249 Commits
Xe/checker
...
v1.23.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62c1b80189 | ||
|
|
7ed1753fcc | ||
|
|
3dab060bfa | ||
|
|
ab8b91fc0c | ||
|
|
168e72088f | ||
|
|
6b1cd6120f | ||
|
|
d7459de941 | ||
|
|
c96c229b68 | ||
|
|
b384ad03cb | ||
|
|
a4efcef1c9 | ||
|
|
2fc3765340 | ||
|
|
ec2981bf6f | ||
|
|
e3d3195bf2 | ||
|
|
25d677cbba | ||
|
|
00261d049e | ||
|
|
a12b4bb755 | ||
|
|
4dfc73abd1 | ||
|
|
ffbbdce3da | ||
|
|
c09c86778d | ||
|
|
9c47c180d0 | ||
|
|
d51d32726c | ||
|
|
ff33982ee9 | ||
|
|
ec90a8b87d | ||
|
|
5731477e0a | ||
|
|
714c85dbc4 | ||
|
|
75ea1b60d5 | ||
|
|
1cf03535a5 | ||
|
|
c3ed405dbc | ||
|
|
8cdf58c9e6 | ||
|
|
1c170988c8 | ||
|
|
9439466ff2 | ||
|
|
4787aeca51 | ||
|
|
fb3637df95 | ||
|
|
26076b8520 | ||
|
|
edb84f03b7 | ||
|
|
b2d525bba4 | ||
|
|
00679aed66 | ||
|
|
03299024c5 | ||
|
|
f745d37d90 | ||
|
|
d12993e31d | ||
|
|
88b3e457ee | ||
|
|
bb2b113b63 | ||
|
|
6c283d0cd9 | ||
|
|
0037e214a1 | ||
|
|
29ae2a4b87 | ||
|
|
401e18f29f | ||
|
|
63591866aa | ||
|
|
f79d36d21e | ||
|
|
f5b5243b5e | ||
|
|
2011b83a44 | ||
|
|
8ed89a6c6e | ||
|
|
9430d0e6a5 | ||
|
|
8b9dafac51 | ||
|
|
9997130a7c | ||
|
|
e239083944 | ||
|
|
abf6c8de57 | ||
|
|
7e1b5d9951 | ||
|
|
98945fb56f | ||
|
|
82099d9e05 | ||
|
|
87c2f1e0e6 | ||
|
|
f0199d014f | ||
|
|
75109f6b73 | ||
|
|
c43d7ca686 | ||
|
|
5d5c39e123 | ||
|
|
d35e47c655 | ||
|
|
48b49a0190 | ||
|
|
de94139789 | ||
|
|
fd011d19e2 | ||
|
|
489abb6b4d | ||
|
|
8da0771647 | ||
|
|
f0bcbe43af | ||
|
|
f6e077c907 | ||
|
|
2704ba95d0 | ||
|
|
f6a578787f | ||
|
|
31a654ecb6 | ||
|
|
1a4b5cadcb | ||
|
|
d5cdd21631 | ||
|
|
0e0847cbeb | ||
|
|
00afa72c4b | ||
|
|
eb50f59351 | ||
|
|
01f55cf552 | ||
|
|
99bd06b8c3 | ||
|
|
d6f1f24e1b | ||
|
|
6a5485fde9 | ||
|
|
582181f9b9 | ||
|
|
44264981b5 | ||
|
|
21c3e0c469 | ||
|
|
9ddc1eb840 | ||
|
|
c661bc37d1 | ||
|
|
fb8ce508ee | ||
|
|
573b0079fb | ||
|
|
d1d631a18a | ||
|
|
f3cd6c9ca4 | ||
|
|
23772fd3cb | ||
|
|
a7a61690fc | ||
|
|
f5afe8b6c8 | ||
|
|
61682e4987 | ||
|
|
b0fa256e3e | ||
|
|
ee55d857eb | ||
|
|
993ea8da1b | ||
|
|
6e4e471792 | ||
|
|
e8dfff6350 | ||
|
|
237a6a98e2 | ||
|
|
e43999f30c | ||
|
|
29d038835f | ||
|
|
39215457e4 | ||
|
|
ff691dfee8 | ||
|
|
83503525f2 | ||
|
|
a8b7b2ad7b | ||
|
|
87651f9506 | ||
|
|
100005ce70 | ||
|
|
0a68415c2e | ||
|
|
b3886752a1 | ||
|
|
0e9f831201 | ||
|
|
22ee227f20 | ||
|
|
adda60c163 | ||
|
|
e0a15bf4dc | ||
|
|
f6481b81a2 | ||
|
|
790bcbe773 | ||
|
|
7c80c23e90 | ||
|
|
2d8e942377 | ||
|
|
d5f01dbdb9 | ||
|
|
70bf58cc63 | ||
|
|
0dccf2e009 | ||
|
|
8d08de6d9c | ||
|
|
1f7fcf938b | ||
|
|
6ae386a11a | ||
|
|
963527fb60 | ||
|
|
b81c577106 | ||
|
|
987c1d7410 | ||
|
|
826433e8be | ||
|
|
4a4031450c | ||
|
|
8feacc78fc | ||
|
|
bca2e87e80 | ||
|
|
a735770c93 | ||
|
|
bf42014ac3 | ||
|
|
0ef3461816 | ||
|
|
7d7028d25c | ||
|
|
9affd2edf4 | ||
|
|
26b6d8a91a | ||
|
|
958992a69a | ||
|
|
221d9f2072 | ||
|
|
bb434a3351 | ||
|
|
45ff8f526e | ||
|
|
5700512da5 | ||
|
|
d40e9056bc | ||
|
|
21f570962c | ||
|
|
1cb1352a44 | ||
|
|
a4c08687cc | ||
|
|
1a19d7eee4 | ||
|
|
25af5a232f | ||
|
|
24d2501187 | ||
|
|
1dc9525427 | ||
|
|
24e3746b0b | ||
|
|
31184ccd5f | ||
|
|
e69fadddf1 | ||
|
|
5e8ebaeb5d | ||
|
|
3e1aaa6273 | ||
|
|
dce7ed2405 | ||
|
|
03758405d3 | ||
|
|
eb78ccc30c | ||
|
|
4156f84020 | ||
|
|
76dcd21582 | ||
|
|
6b639cd911 | ||
|
|
a0aba2d74a | ||
|
|
b485499125 | ||
|
|
300720f030 | ||
|
|
d6298adc6d | ||
|
|
1a9d8fb0cf | ||
|
|
36e25ff5f3 | ||
|
|
c59b7179c3 | ||
|
|
59515ed669 | ||
|
|
4d6b578f93 | ||
|
|
2915c1d209 | ||
|
|
68b653b099 | ||
|
|
509a4f3ce8 | ||
|
|
5c4d8480e6 | ||
|
|
132b2ed853 | ||
|
|
d28991ce8d | ||
|
|
0fd4bb81b8 | ||
|
|
603c68fd54 | ||
|
|
c8f2eb1185 | ||
|
|
f6b94dca98 | ||
|
|
6d8b98eb3d | ||
|
|
b9d8275234 | ||
|
|
c2cc1df172 | ||
|
|
735b2ceb14 | ||
|
|
2cb57fc247 | ||
|
|
61ce581f36 | ||
|
|
3f6750ac7d | ||
|
|
25d75b352a | ||
|
|
de17823bc7 | ||
|
|
29622e605d | ||
|
|
9fa1795db7 | ||
|
|
fbf69680f5 | ||
|
|
c74de19532 | ||
|
|
6dc726013a | ||
|
|
02304e8f3c | ||
|
|
607c9791d8 | ||
|
|
6b67be86a1 | ||
|
|
e02f017153 | ||
|
|
66b39f64af | ||
|
|
944fd25924 | ||
|
|
fa3fbfb0a5 | ||
|
|
3c739c1305 | ||
|
|
cc56baa5c7 | ||
|
|
053d29e0b6 | ||
|
|
a668095c22 | ||
|
|
1c4a1aec4a | ||
|
|
5b8b6d1c94 | ||
|
|
0cb6ef76e1 | ||
|
|
a900e98b8b | ||
|
|
e79cd93b61 | ||
|
|
d17fc6a174 | ||
|
|
95768cb70f | ||
|
|
ca61b8a05f | ||
|
|
1ea1157cd7 | ||
|
|
44ae5f2e2b | ||
|
|
ea2e76c6ee | ||
|
|
4ea0add50d | ||
|
|
289c802a0b | ||
|
|
543b942be1 | ||
|
|
edbe1dcfd6 | ||
|
|
94db16c0df | ||
|
|
c2f46907a1 | ||
|
|
6fa5b8e4e0 | ||
|
|
f98750b038 | ||
|
|
7d0c58d1a8 | ||
|
|
e870ede120 | ||
|
|
592d1e3dfc | ||
|
|
f6254b4b98 | ||
|
|
d19026d693 | ||
|
|
7b72c790ab | ||
|
|
719a1409ca | ||
|
|
890f21bf47 | ||
|
|
93bfe910d8 | ||
|
|
19d8de784b | ||
|
|
dff2176beb | ||
|
|
506d8817d5 | ||
|
|
d0fae02d05 | ||
|
|
845095c3f6 | ||
|
|
2f1e78cc6c | ||
|
|
7c0996448a | ||
|
|
d7a758f805 | ||
|
|
c121896f9c | ||
|
|
888b7d6e77 | ||
|
|
0e43138324 | ||
|
|
c981c23f7e | ||
|
|
9f0c5e974e |
@@ -3,10 +3,10 @@ FROM ghcr.io/xe/devcontainer-base/pre/go
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum package.json package-lock.json ./
|
||||
RUN go install github.com/a-h/templ/cmd/templ \
|
||||
&& npx --yes playwright@1.52.0 install --with-deps\
|
||||
&& apt-get update \
|
||||
&& apt-get -y install zstd brotli \
|
||||
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
|
||||
&& chown -R vscode:vscode /go
|
||||
|
||||
CMD ["/usr/bin/sleep", "infinity"]
|
||||
@@ -2,17 +2,15 @@
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
||||
{
|
||||
"name": "Dev",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"build": {
|
||||
"dockerfile": "./Dockerfile",
|
||||
"context": "..",
|
||||
"cacheFrom": [
|
||||
"type=registry,ref=ghcr.io/techarohq/anubis/devcontainer"
|
||||
]
|
||||
},
|
||||
"postStartCommand": "npm ci && go mod download",
|
||||
"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/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": {
|
||||
@@ -23,12 +21,14 @@
|
||||
"golang.go",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"a-h.templ",
|
||||
"redhat.vscode-yaml"
|
||||
]
|
||||
"redhat.vscode-yaml",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
],
|
||||
"settings": {
|
||||
"chat.instructionsFilesLocations": {
|
||||
".github/copilot-instructions.md": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"forwardPorts": [
|
||||
8923,
|
||||
3000
|
||||
]
|
||||
}
|
||||
}
|
||||
26
.devcontainer/docker-compose.yaml
Normal file
26
.devcontainer/docker-compose.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
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
|
||||
9
.devcontainer/poststart.sh
Normal file
9
.devcontainer/poststart.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
pwd
|
||||
|
||||
npm ci &
|
||||
go mod download &
|
||||
go install ./utils/cmd/... &
|
||||
|
||||
wait
|
||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
||||
patreon: cadey
|
||||
github: xe
|
||||
github: xe
|
||||
liberapay: Xe
|
||||
|
||||
61
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
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.
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security
|
||||
url: https://techaro.lol/contact
|
||||
about: Do not file security reports here. Email security@techaro.lol.
|
||||
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
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
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,3 +9,4 @@ 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)
|
||||
|
||||
5
.github/actions/spelling/allow.txt
vendored
5
.github/actions/spelling/allow.txt
vendored
@@ -3,3 +3,8 @@ https
|
||||
ssh
|
||||
ubuntu
|
||||
workarounds
|
||||
rjack
|
||||
msgbox
|
||||
xeact
|
||||
ABee
|
||||
tencent
|
||||
|
||||
3
.github/actions/spelling/excludes.txt
vendored
3
.github/actions/spelling/excludes.txt
vendored
@@ -84,10 +84,13 @@
|
||||
^\Q.github/workflows/spelling.yml\E$
|
||||
^data/crawlers/
|
||||
^docs/blog/tags\.yml$
|
||||
^docs/docs/user/known-instances.md$
|
||||
^docs/manifest/.*$
|
||||
^docs/static/\.nojekyll$
|
||||
^lib/policy/config/testdata/bad/unparseable\.json$
|
||||
^internal/glob/glob_test.go$
|
||||
ignore$
|
||||
robots.txt
|
||||
^lib/localization/locales/.*\.json$
|
||||
^lib/localization/.*_test.go$
|
||||
^test/.*$
|
||||
|
||||
95
.github/actions/spelling/expect.txt
vendored
95
.github/actions/spelling/expect.txt
vendored
@@ -1,12 +1,14 @@
|
||||
acs
|
||||
aeacus
|
||||
Actorified
|
||||
actorifiedstore
|
||||
actorify
|
||||
Aibrew
|
||||
alibaba
|
||||
alrest
|
||||
amazonbot
|
||||
anthro
|
||||
anubis
|
||||
anubistest
|
||||
apk
|
||||
Applebot
|
||||
archlinux
|
||||
asnc
|
||||
@@ -16,18 +18,23 @@ aspirational
|
||||
atuin
|
||||
azuretools
|
||||
badregexes
|
||||
bbolt
|
||||
bdba
|
||||
berr
|
||||
bezier
|
||||
bingbot
|
||||
bitcoin
|
||||
blogging
|
||||
Bitcoin
|
||||
bitrate
|
||||
Bluesky
|
||||
blueskybot
|
||||
boi
|
||||
Bokm
|
||||
botnet
|
||||
botstopper
|
||||
BPort
|
||||
Brightbot
|
||||
broked
|
||||
byteslice
|
||||
Bytespider
|
||||
cachebuster
|
||||
cachediptoasn
|
||||
@@ -44,6 +51,7 @@ cgr
|
||||
chainguard
|
||||
chall
|
||||
challengemozilla
|
||||
challengetest
|
||||
checkpath
|
||||
checkresult
|
||||
chibi
|
||||
@@ -52,18 +60,20 @@ ckie
|
||||
cloudflare
|
||||
Codespaces
|
||||
confd
|
||||
connnection
|
||||
containerbuild
|
||||
coreutils
|
||||
Cotoyogi
|
||||
CRDs
|
||||
Cromite
|
||||
crt
|
||||
Cscript
|
||||
daemonizing
|
||||
dayjob
|
||||
DDOS
|
||||
Debian
|
||||
debrpm
|
||||
decaymap
|
||||
devcontainers
|
||||
Diffbot
|
||||
discordapp
|
||||
discordbot
|
||||
@@ -75,6 +85,8 @@ domainhere
|
||||
dracula
|
||||
dronebl
|
||||
droneblresponse
|
||||
dropin
|
||||
dsilence
|
||||
duckduckbot
|
||||
eerror
|
||||
ellenjoe
|
||||
@@ -92,11 +104,14 @@ facebookgo
|
||||
Factset
|
||||
fastcgi
|
||||
fediverse
|
||||
ffprobe
|
||||
financials
|
||||
finfos
|
||||
Firecrawl
|
||||
flagenv
|
||||
Fordola
|
||||
forgejo
|
||||
forwardauth
|
||||
fsys
|
||||
fullchain
|
||||
gaissmai
|
||||
@@ -104,6 +119,8 @@ Galvus
|
||||
geoip
|
||||
geoipchecker
|
||||
gha
|
||||
GHSA
|
||||
Ghz
|
||||
gipc
|
||||
gitea
|
||||
godotenv
|
||||
@@ -111,47 +128,57 @@ goland
|
||||
gomod
|
||||
goodbot
|
||||
googlebot
|
||||
gopsutil
|
||||
govulncheck
|
||||
goyaml
|
||||
GPG
|
||||
GPT
|
||||
gptbot
|
||||
Graphene
|
||||
grpcprom
|
||||
grw
|
||||
gzw
|
||||
Hashcash
|
||||
hashrate
|
||||
headermap
|
||||
healthcheck
|
||||
hebis
|
||||
healthz
|
||||
hec
|
||||
Hetzner
|
||||
hmc
|
||||
homelab
|
||||
hostable
|
||||
htmlc
|
||||
htmx
|
||||
httpdebug
|
||||
Huawei
|
||||
huawei
|
||||
hypertext
|
||||
iaskspider
|
||||
iaso
|
||||
iat
|
||||
ifm
|
||||
Imagesift
|
||||
imgproxy
|
||||
impressum
|
||||
inbox
|
||||
inp
|
||||
internets
|
||||
IPTo
|
||||
iptoasn
|
||||
isp
|
||||
iss
|
||||
isset
|
||||
ivh
|
||||
Jenomis
|
||||
JGit
|
||||
jhjj
|
||||
joho
|
||||
journalctl
|
||||
jshelter
|
||||
JWTs
|
||||
kagi
|
||||
kagibot
|
||||
keikaku
|
||||
Keyfunc
|
||||
keypair
|
||||
KHTML
|
||||
@@ -165,13 +192,13 @@ lgbt
|
||||
licend
|
||||
licstart
|
||||
lightpanda
|
||||
LIMSA
|
||||
limsa
|
||||
Linting
|
||||
linuxbrew
|
||||
LLU
|
||||
loadbalancer
|
||||
lol
|
||||
LOMINSA
|
||||
lominsa
|
||||
maintainership
|
||||
malware
|
||||
mcr
|
||||
@@ -179,25 +206,29 @@ memes
|
||||
metarefresh
|
||||
metrix
|
||||
mimi
|
||||
minica
|
||||
Minfilia
|
||||
mistralai
|
||||
Mojeek
|
||||
mojeekbot
|
||||
mozilla
|
||||
nbf
|
||||
nepeat
|
||||
netsurf
|
||||
nginx
|
||||
nicksnyder
|
||||
nobots
|
||||
NONINFRINGEMENT
|
||||
nosleep
|
||||
nullglob
|
||||
OCOB
|
||||
ogtags
|
||||
ogtag
|
||||
oklch
|
||||
omgili
|
||||
omgilibot
|
||||
openai
|
||||
opengraph
|
||||
openrc
|
||||
oswald
|
||||
pag
|
||||
palemoon
|
||||
Pangu
|
||||
@@ -212,12 +243,14 @@ pipefail
|
||||
pki
|
||||
podkova
|
||||
podman
|
||||
poststart
|
||||
prebaked
|
||||
privkey
|
||||
promauto
|
||||
promhttp
|
||||
proofofwork
|
||||
publicsuffix
|
||||
purejs
|
||||
pwcmd
|
||||
pwuser
|
||||
qualys
|
||||
@@ -226,36 +259,37 @@ qwantbot
|
||||
rac
|
||||
rawler
|
||||
rcvar
|
||||
rdb
|
||||
redhat
|
||||
redir
|
||||
redirectscheme
|
||||
refactors
|
||||
relayd
|
||||
reputational
|
||||
reqmeta
|
||||
risc
|
||||
ruleset
|
||||
runlevels
|
||||
RUnlock
|
||||
runtimedir
|
||||
runtimedirectory
|
||||
Ryzen
|
||||
sas
|
||||
sasl
|
||||
Scumm
|
||||
screenshots
|
||||
searchbot
|
||||
searx
|
||||
sebest
|
||||
secretplans
|
||||
selfsigned
|
||||
Semrush
|
||||
Seo
|
||||
setsebool
|
||||
shellcheck
|
||||
shirou
|
||||
shopt
|
||||
Sidetrade
|
||||
simprint
|
||||
sitemap
|
||||
skopeo
|
||||
sls
|
||||
sni
|
||||
Sourceware
|
||||
Spambot
|
||||
sparkline
|
||||
spyderbot
|
||||
@@ -263,6 +297,7 @@ srv
|
||||
stackoverflow
|
||||
startprecmd
|
||||
stoppostcmd
|
||||
storetest
|
||||
subgrid
|
||||
subr
|
||||
subrequest
|
||||
@@ -270,6 +305,9 @@ SVCNAME
|
||||
tagline
|
||||
tarballs
|
||||
tarrif
|
||||
taviso
|
||||
tbn
|
||||
tbr
|
||||
techaro
|
||||
techarohq
|
||||
templ
|
||||
@@ -280,22 +318,25 @@ thoth
|
||||
thothmock
|
||||
Tik
|
||||
Timpibot
|
||||
TLog
|
||||
traefik
|
||||
trunc
|
||||
uberspace
|
||||
Unbreak
|
||||
unbreakdocker
|
||||
unifiedjs
|
||||
unixhttpd
|
||||
unmarshal
|
||||
unparseable
|
||||
uuidgen
|
||||
uvx
|
||||
uwu
|
||||
UXP
|
||||
Valkey
|
||||
valkey
|
||||
Varis
|
||||
Velen
|
||||
vendored
|
||||
vhosts
|
||||
videotest
|
||||
waitloop
|
||||
VKE
|
||||
Vultr
|
||||
weblate
|
||||
webmaster
|
||||
webpage
|
||||
@@ -304,12 +345,13 @@ websites
|
||||
Webzio
|
||||
wildbase
|
||||
withthothmock
|
||||
wolfbeast
|
||||
wordpress
|
||||
Workaround
|
||||
workaround
|
||||
workdir
|
||||
wpbot
|
||||
xcaddy
|
||||
Xeact
|
||||
XCircle
|
||||
xeiaso
|
||||
xeserv
|
||||
xesite
|
||||
@@ -318,15 +360,16 @@ xff
|
||||
XForwarded
|
||||
XNG
|
||||
XOB
|
||||
XOriginal
|
||||
XReal
|
||||
yae
|
||||
YAMLTo
|
||||
Yda
|
||||
yeet
|
||||
yeetfile
|
||||
yourdomain
|
||||
yoursite
|
||||
yyz
|
||||
Zenos
|
||||
zizmor
|
||||
zombocom
|
||||
Zonbocom
|
||||
zos
|
||||
|
||||
4
.github/actions/spelling/patterns.txt
vendored
4
.github/actions/spelling/patterns.txt
vendored
@@ -132,3 +132,7 @@ 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*
|
||||
47
.github/workflows/devcontainer.yml
vendored
47
.github/workflows/devcontainer.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: Dev container prebuild
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
tags: ["v*.*.*"]
|
||||
|
||||
jobs:
|
||||
devcontainer:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install skopeo
|
||||
|
||||
- name: Log into registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: techarohq
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pre-build dev container image
|
||||
uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417
|
||||
with:
|
||||
imageName: ghcr.io/techarohq/anubis/devcontainer
|
||||
cacheFrom: ghcr.io/techarohq/anubis/devcontainer
|
||||
push: always
|
||||
platform: linux/amd64,linux/arm64
|
||||
6
.github/workflows/docker-pr.yml
vendored
6
.github/workflows/docker-pr.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
|
||||
10
.github/workflows/docker.yml
vendored
10
.github/workflows/docker.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
brew bundle
|
||||
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ${{ env.IMAGE }}
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
SLOG_LEVEL: debug
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
|
||||
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||
with:
|
||||
subject-name: ${{ env.IMAGE }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
17
.github/workflows/docs-deploy.yml
vendored
17
.github/workflows/docs-deploy.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: techarohq
|
||||
@@ -33,9 +33,12 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.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
|
||||
@@ -49,15 +52,15 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
|
||||
- name: Apply k8s manifests to aeacus
|
||||
uses: actions-hub/kubectl@d50394b7d704525f93faefce1e65a6329ff67271 # v1.33.2
|
||||
- name: Apply k8s manifests to limsa lominsa
|
||||
uses: actions-hub/kubectl@f14933a23bc8c582b5aa7d108defd8e2cb9fa86d # v1.34.1
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||
with:
|
||||
args: apply -k docs/manifest
|
||||
|
||||
- name: Apply k8s manifests to aeacus
|
||||
uses: actions-hub/kubectl@d50394b7d704525f93faefce1e65a6329ff67271 # v1.33.2
|
||||
- name: Apply k8s manifests to limsa lominsa
|
||||
uses: actions-hub/kubectl@f14933a23bc8c582b5aa7d108defd8e2cb9fa86d # v1.34.1
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||
with:
|
||||
|
||||
11
.github/workflows/docs-test.yml
vendored
11
.github/workflows/docs-test.yml
vendored
@@ -2,7 +2,7 @@ name: Docs test build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -22,9 +22,12 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}/docs
|
||||
images: ghcr.io/techarohq/anubis/docs
|
||||
tags: |
|
||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
|
||||
main
|
||||
|
||||
- name: Build and push
|
||||
id: build
|
||||
|
||||
10
.github/workflows/go.yml
vendored
10
.github/workflows/go.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
brew bundle
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
${{ runner.os }}-golang-
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
run: npm run test
|
||||
|
||||
- name: Lint with staticcheck
|
||||
uses: dominikh/staticcheck-action@fe1dd0c3658873b46f8c9bb3291096a617310ca6 # v1.3.1
|
||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
|
||||
117
.github/workflows/package-builds-stable.yml
vendored
117
.github/workflows/package-builds-stable.yml
vendored
@@ -1,8 +1,9 @@
|
||||
name: Package builds (stable)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -13,67 +14,67 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
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
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- 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: Setup Homebrew cellar cache
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
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: |
|
||||
brew bundle
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
|
||||
- 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: Setup Golang caches
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
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: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Build Packages
|
||||
run: |
|
||||
go tool yeet
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
brew bundle
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
run: |
|
||||
go tool yeet
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: packages
|
||||
path: var/*
|
||||
|
||||
61
.github/workflows/smoke-tests.yml
vendored
Normal file
61
.github/workflows/smoke-tests.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Smoke tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
smoke-test:
|
||||
strategy:
|
||||
matrix:
|
||||
test:
|
||||
- default-config-macro
|
||||
- double_slash
|
||||
- forced-language
|
||||
- git-clone
|
||||
- git-push
|
||||
- healthcheck
|
||||
- i18n
|
||||
- palemoon/amd64
|
||||
#- palemoon/i386
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||
|
||||
- name: Install utils
|
||||
run: |
|
||||
go install ./utils/cmd/...
|
||||
|
||||
- name: Run test
|
||||
run: |
|
||||
cd test/${{ matrix.test }}
|
||||
backoff-retry --try-count 10 ./test.sh
|
||||
|
||||
- name: Sanitize artifact name
|
||||
if: always()
|
||||
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
if: always()
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: test/${{ matrix.test }}/var
|
||||
4
.github/workflows/ssh-ci-runner-cron.yml
vendored
4
.github/workflows/ssh-ci-runner-cron.yml
vendored
@@ -18,13 +18,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
18
.github/workflows/ssh-ci.yml
vendored
18
.github/workflows/ssh-ci.yml
vendored
@@ -12,26 +12,34 @@ permissions:
|
||||
jobs:
|
||||
ssh:
|
||||
if: github.repository == 'TecharoHQ/anubis'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: alrest-techarohq
|
||||
strategy:
|
||||
matrix:
|
||||
host:
|
||||
- ubuntu@riscv64.techaro.lol
|
||||
- ci@ppc64le.techaro.lol
|
||||
- riscv64
|
||||
- ppc64le
|
||||
- aarch64-4k
|
||||
- aarch64-16k
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
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@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Run CI
|
||||
run: bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
||||
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
||||
env:
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
|
||||
6
.github/workflows/zizmor.yml
vendored
6
.github/workflows/zizmor.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
|
||||
|
||||
- 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@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1
|
||||
uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -5,6 +5,7 @@
|
||||
"golang.go",
|
||||
"unifiedjs.vscode-mdx",
|
||||
"a-h.templ",
|
||||
"redhat.vscode-yaml"
|
||||
"redhat.vscode-yaml",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
16
README.md
16
README.md
@@ -41,6 +41,20 @@ 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
|
||||
|
||||
@@ -52,7 +66,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, connect to [anubis.techaro.lol](https://anubis.techaro.lol).
|
||||
If you want to try this out, visit the Anubis documentation site at [anubis.techaro.lol](https://anubis.techaro.lol).
|
||||
|
||||
## Support
|
||||
|
||||
|
||||
13
SECURITY.md
Normal file
13
SECURITY.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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.
|
||||
12
anubis.go
12
anubis.go
@@ -11,7 +11,7 @@ var Version = "devel"
|
||||
|
||||
// CookieName is the name of the cookie that Anubis uses in order to validate
|
||||
// access.
|
||||
var CookieName = "techaro.lol-anubis-auth"
|
||||
var CookieName = "techaro.lol-anubis"
|
||||
|
||||
// TestCookieName is the name of the cookie that Anubis uses in order to check
|
||||
// if cookies are enabled on the client's browser.
|
||||
@@ -23,6 +23,9 @@ 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/"
|
||||
|
||||
@@ -32,3 +35,10 @@ 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
|
||||
|
||||
@@ -30,14 +30,15 @@ import (
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/data"
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
"github.com/TecharoHQ/anubis/internal/thoth"
|
||||
libanubis "github.com/TecharoHQ/anubis/lib"
|
||||
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
"github.com/TecharoHQ/anubis/lib/thoth"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/facebookgo/flagenv"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -48,10 +49,14 @@ var (
|
||||
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
|
||||
cookieDynamicDomain = flag.Bool("cookie-dynamic-domain", false, "if set, automatically set the cookie Domain value based on the request domain")
|
||||
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
||||
cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis")
|
||||
cookiePrefix = flag.String("cookie-prefix", anubis.CookieName, "prefix for browser cookies created by Anubis")
|
||||
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
||||
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")
|
||||
@@ -63,9 +68,10 @@ var (
|
||||
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, the value of the TLS handshake hostname when forwarding requests to the target")
|
||||
targetSNI = flag.String("target-sni", "", "if set, TLS handshake hostname when forwarding requests to the target, if set to auto, use Host header")
|
||||
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")
|
||||
@@ -75,11 +81,14 @@ 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")
|
||||
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) {
|
||||
@@ -96,7 +105,7 @@ func keyFromHex(value string) (ed25519.PrivateKey, error) {
|
||||
}
|
||||
|
||||
func doHealthCheck() error {
|
||||
resp, err := http.Get("http://localhost" + *metricsBind + anubis.BasePrefix + "/metrics")
|
||||
resp, err := http.Get("http://localhost" + *metricsBind + "/healthz")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch metrics: %w", err)
|
||||
}
|
||||
@@ -136,6 +145,22 @@ func parseBindNetFromAddr(address string) (string, string) {
|
||||
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 := ""
|
||||
|
||||
@@ -183,7 +208,7 @@ func setupListener(network string, address string) (net.Listener, string) {
|
||||
return listener, formattedAddress
|
||||
}
|
||||
|
||||
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool) (http.Handler, error) {
|
||||
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool, targetDisableKeepAlive bool) (http.Handler, error) {
|
||||
targetUri, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse target URL: %w", err)
|
||||
@@ -191,6 +216,10 @@ 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
|
||||
@@ -207,43 +236,34 @@ 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 != "" {
|
||||
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 != "" && targetSNI != "auto" {
|
||||
transport.TLSClientConfig.ServerName = targetSNI
|
||||
}
|
||||
|
||||
rp := httputil.NewSingleHostReverseProxy(targetUri)
|
||||
rp.Transport = transport
|
||||
|
||||
if targetHost != "" {
|
||||
if targetHost != "" || targetSNI == "auto" {
|
||||
originalDirector := rp.Director
|
||||
rp.Director = func(req *http.Request) {
|
||||
originalDirector(req)
|
||||
req.Host = targetHost
|
||||
if targetHost != "" {
|
||||
req.Host = targetHost
|
||||
}
|
||||
if targetSNI == "auto" {
|
||||
transport.TLSClientConfig.ServerName = req.Host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -254,6 +274,15 @@ func main() {
|
||||
}
|
||||
|
||||
internal.InitSlog(*slogLevel)
|
||||
internal.SetHealth("anubis", healthv1.HealthCheckResponse_NOT_SERVING)
|
||||
|
||||
if *healthcheck {
|
||||
log.Println("running healthcheck")
|
||||
if err := doHealthCheck(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if *extractResources != "" {
|
||||
if err := extractEmbedFS(data.BotPolicies, ".", *extractResources); err != nil {
|
||||
@@ -266,11 +295,22 @@ 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, 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)
|
||||
rp, err = makeReverseProxy(*target, *targetSNI, *targetHost, *targetInsecureSkipVerify, *targetDisableKeepAlive)
|
||||
if err != nil {
|
||||
log.Fatalf("can't make reverse proxy: %v", err)
|
||||
}
|
||||
@@ -280,8 +320,6 @@ func main() {
|
||||
log.Fatalf("you can't set COOKIE_DOMAIN and COOKIE_DYNAMIC_DOMAIN at the same time")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Thoth configuration
|
||||
switch {
|
||||
case *thothURL != "" && *thothToken == "":
|
||||
@@ -303,6 +341,16 @@ func main() {
|
||||
log.Fatalf("can't parse policy file: %v", err)
|
||||
}
|
||||
|
||||
// Warn if persistent storage is used without a configured signing key
|
||||
if policy.Store.IsPersistent() {
|
||||
if *hs512Secret == "" && *ed25519PrivateKeyHex == "" && *ed25519PrivateKeyHexFile == "" {
|
||||
slog.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 {
|
||||
if rule.Action != config.RuleDeny {
|
||||
@@ -378,6 +426,8 @@ func main() {
|
||||
|
||||
anubis.CookieName = *cookiePrefix + "-auth"
|
||||
anubis.TestCookieName = *cookiePrefix + "-cookie-verification"
|
||||
anubis.ForcedLanguage = *forcedLanguage
|
||||
anubis.UseSimplifiedExplanation = *useSimplifiedExplanation
|
||||
|
||||
// If OpenGraph configuration values are not set in the config file, use the
|
||||
// values from flags / envvars.
|
||||
@@ -389,43 +439,38 @@ func main() {
|
||||
}
|
||||
|
||||
s, err := libanubis.New(libanubis.Options{
|
||||
BasePrefix: *basePrefix,
|
||||
StripBasePrefix: *stripBasePrefix,
|
||||
Next: rp,
|
||||
Policy: policy,
|
||||
ServeRobotsTXT: *robotsTxt,
|
||||
ED25519PrivateKey: ed25519Priv,
|
||||
HS512Secret: []byte(*hs512Secret),
|
||||
CookieDomain: *cookieDomain,
|
||||
CookieDynamicDomain: *cookieDynamicDomain,
|
||||
CookieExpiration: *cookieExpiration,
|
||||
CookiePartitioned: *cookiePartitioned,
|
||||
RedirectDomains: redirectDomainsList,
|
||||
Target: *target,
|
||||
WebmasterEmail: *webmasterEmail,
|
||||
OpenGraph: policy.OpenGraph,
|
||||
CookieSecure: *cookieSecure,
|
||||
BasePrefix: *basePrefix,
|
||||
StripBasePrefix: *stripBasePrefix,
|
||||
Next: rp,
|
||||
Policy: policy,
|
||||
ServeRobotsTXT: *robotsTxt,
|
||||
ED25519PrivateKey: ed25519Priv,
|
||||
HS512Secret: []byte(*hs512Secret),
|
||||
CookieDomain: *cookieDomain,
|
||||
CookieDynamicDomain: *cookieDynamicDomain,
|
||||
CookieExpiration: *cookieExpiration,
|
||||
CookiePartitioned: *cookiePartitioned,
|
||||
RedirectDomains: redirectDomainsList,
|
||||
Target: *target,
|
||||
WebmasterEmail: *webmasterEmail,
|
||||
OpenGraph: policy.OpenGraph,
|
||||
CookieSecure: *cookieSecure,
|
||||
CookieSameSite: parseSameSite(*cookieSameSite),
|
||||
PublicUrl: *publicUrl,
|
||||
JWTRestrictionHeader: *jwtRestrictionHeader,
|
||||
DifficultyInJWT: *difficultyInJWT,
|
||||
})
|
||||
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)
|
||||
@@ -443,6 +488,7 @@ func main() {
|
||||
"base-prefix", *basePrefix,
|
||||
"cookie-expiration-time", *cookieExpiration,
|
||||
"rule-error-ids", ruleErrorIDs,
|
||||
"public-url", *publicUrl,
|
||||
)
|
||||
|
||||
go func() {
|
||||
@@ -454,6 +500,8 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
internal.SetHealth("anubis", healthv1.HealthCheckResponse_SERVING)
|
||||
|
||||
if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -464,20 +512,30 @@ func metricsServer(ctx context.Context, done func()) {
|
||||
defer done()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(anubis.BasePrefix+"/metrics", promhttp.Handler())
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
srv := http.Server{Handler: mux, ErrorLog: internal.GetFilteredHTTPLogger()}
|
||||
listener, metricsUrl := setupListener(*metricsBindNetwork, *metricsBind)
|
||||
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()
|
||||
c, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
|
||||
@@ -46,6 +46,11 @@ 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")
|
||||
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
)
|
||||
|
||||
type RobotsRule struct {
|
||||
UserAgent string
|
||||
UserAgents []string
|
||||
Disallows []string
|
||||
Allows []string
|
||||
CrawlDelay int
|
||||
@@ -130,10 +130,26 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
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 currentRule *RobotsRule
|
||||
var currentUserAgents []string
|
||||
var currentDisallows []string
|
||||
var currentAllows []string
|
||||
var currentCrawlDelay int
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
@@ -154,38 +170,42 @@ func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
|
||||
|
||||
switch directive {
|
||||
case "user-agent":
|
||||
// Start a new rule section
|
||||
if currentRule != nil {
|
||||
rules = append(rules, *currentRule)
|
||||
}
|
||||
currentRule = &RobotsRule{
|
||||
UserAgent: value,
|
||||
Disallows: make([]string, 0),
|
||||
Allows: make([]string, 0),
|
||||
// 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 currentRule != nil && value != "" {
|
||||
currentRule.Disallows = append(currentRule.Disallows, value)
|
||||
if len(currentUserAgents) > 0 && value != "" {
|
||||
currentDisallows = append(currentDisallows, value)
|
||||
}
|
||||
|
||||
case "allow":
|
||||
if currentRule != nil && value != "" {
|
||||
currentRule.Allows = append(currentRule.Allows, value)
|
||||
if len(currentUserAgents) > 0 && value != "" {
|
||||
currentAllows = append(currentAllows, value)
|
||||
}
|
||||
|
||||
case "crawl-delay":
|
||||
if currentRule != nil {
|
||||
if len(currentUserAgents) > 0 {
|
||||
if delay, err := parseIntSafe(value); err == nil {
|
||||
currentRule.CrawlDelay = delay
|
||||
currentCrawlDelay = delay
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last rule
|
||||
if currentRule != nil {
|
||||
rules = append(rules, *currentRule)
|
||||
// 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: /")
|
||||
@@ -211,10 +231,11 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
var anubisRules []AnubisRule
|
||||
ruleCounter := 0
|
||||
|
||||
// Process each robots rule individually
|
||||
for _, robotsRule := range robotsRules {
|
||||
userAgent := robotsRule.UserAgent
|
||||
userAgents := robotsRule.UserAgents
|
||||
|
||||
// Handle crawl delay as weight adjustment (do this first before any continues)
|
||||
// Handle crawl delay
|
||||
if robotsRule.CrawlDelay > 0 && *crawlDelay > 0 {
|
||||
ruleCounter++
|
||||
rule := AnubisRule{
|
||||
@@ -223,20 +244,32 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
Weight: &config.Weight{Adjust: *crawlDelay},
|
||||
}
|
||||
|
||||
if userAgent == "*" {
|
||||
if len(userAgents) == 1 && userAgents[0] == "*" {
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{"true"}, // Always applies
|
||||
}
|
||||
} else {
|
||||
} else if len(userAgents) == 1 {
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||
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 (complete deny/challenge)
|
||||
// Handle blacklisted user agents
|
||||
if robotsRule.IsBlacklist {
|
||||
ruleCounter++
|
||||
rule := AnubisRule{
|
||||
@@ -244,21 +277,36 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
Action: *userAgentDeny,
|
||||
}
|
||||
|
||||
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
|
||||
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{
|
||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||
Any: expressions,
|
||||
}
|
||||
}
|
||||
anubisRules = append(anubisRules, rule)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle specific disallow rules
|
||||
@@ -276,9 +324,33 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
// Build CEL expression
|
||||
var conditions []string
|
||||
|
||||
// Add user agent condition if not wildcard
|
||||
if userAgent != "*" {
|
||||
conditions = append(conditions, fmt.Sprintf("userAgent.contains(%q)", userAgent))
|
||||
// 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
|
||||
@@ -291,7 +363,6 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
|
||||
anubisRules = append(anubisRules, rule)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return anubisRules
|
||||
|
||||
@@ -78,6 +78,12 @@ func TestDataFileConversion(t *testing.T) {
|
||||
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 {
|
||||
|
||||
6
cmd/robots2policy/testdata/blacklist.yaml
vendored
6
cmd/robots2policy/testdata/blacklist.yaml
vendored
@@ -25,6 +25,6 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-7
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-7
|
||||
|
||||
24
cmd/robots2policy/testdata/complex.yaml
vendored
24
cmd/robots2policy/testdata/complex.yaml
vendored
@@ -20,8 +20,8 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search/")
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search/")
|
||||
name: robots-txt-policy-disallow-6
|
||||
- action: WEIGH
|
||||
expression: userAgent.contains("Bingbot")
|
||||
@@ -31,14 +31,14 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/search/")
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/search/")
|
||||
name: robots-txt-policy-disallow-8
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/admin/")
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/admin/")
|
||||
name: robots-txt-policy-disallow-9
|
||||
- action: DENY
|
||||
expression: userAgent.contains("BadBot")
|
||||
@@ -54,18 +54,18 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/.*/admin")
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/.*/admin")
|
||||
name: robots-txt-policy-disallow-13
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/temp.*\\.html")
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/temp.*\\.html")
|
||||
name: robots-txt-policy-disallow-14
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/file.\\.log")
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/file.\\.log")
|
||||
name: robots-txt-policy-disallow-15
|
||||
|
||||
25
cmd/robots2policy/testdata/consecutive.robots.txt
vendored
Normal file
25
cmd/robots2policy/testdata/consecutive.robots.txt
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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
|
||||
47
cmd/robots2policy/testdata/consecutive.yaml
vendored
Normal file
47
cmd/robots2policy/testdata/consecutive.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
- 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
|
||||
8
cmd/robots2policy/testdata/simple.json
vendored
8
cmd/robots2policy/testdata/simple.json
vendored
@@ -1,12 +1,12 @@
|
||||
[
|
||||
{
|
||||
"action": "CHALLENGE",
|
||||
"expression": "path.startsWith(\"/admin/\")",
|
||||
"name": "robots-txt-policy-disallow-1"
|
||||
"name": "robots-txt-policy-disallow-1",
|
||||
"action": "CHALLENGE"
|
||||
},
|
||||
{
|
||||
"action": "CHALLENGE",
|
||||
"expression": "path.startsWith(\"/private\")",
|
||||
"name": "robots-txt-policy-disallow-2"
|
||||
"name": "robots-txt-policy-disallow-2",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/_deny-pathological.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/meta/ai-block-aggressive.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/crawlers/_allow-good.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/bots/aggressive-brazilian-scrapers.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/common/keep-internet-working.yaml"
|
||||
},
|
||||
{
|
||||
"name": "generic-browser",
|
||||
"user_agent_regex": "Mozilla|Opera",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
],
|
||||
"dnsbl": false,
|
||||
"status_codes": {
|
||||
"CHALLENGE": 200,
|
||||
"DENY": 200
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,12 @@
|
||||
## /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
|
||||
|
||||
@@ -74,6 +77,69 @@ bots:
|
||||
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
|
||||
|
||||
# Assert behaviour that only genuine browsers display. This ensures that Chrome
|
||||
# or Firefox versions
|
||||
- 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: >-
|
||||
@@ -145,6 +211,14 @@ 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.
|
||||
#
|
||||
@@ -189,12 +263,23 @@ thresholds:
|
||||
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
|
||||
- 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
|
||||
report_as: 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
|
||||
report_as: 6
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
- import: (data)/bots/cloudflare-workers.yaml
|
||||
- import: (data)/bots/headless-browsers.yaml
|
||||
- import: (data)/bots/us-ai-scraper.yaml
|
||||
- import: (data)/bots/us-ai-scraper.yaml
|
||||
- import: (data)/bots/custom-async-http-client.yaml
|
||||
- import: (data)/crawlers/alibaba-cloud.yaml
|
||||
- import: (data)/crawlers/huawei-cloud.yaml
|
||||
- import: (data)/crawlers/tencent-cloud.yaml
|
||||
|
||||
@@ -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|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
|
||||
AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|anthropic-ai|Brightbot 1.0|Bytespider|Claude-Web|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|FacebookBot|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|GoogleOther|GoogleOther-Image|GoogleOther-Video|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|imgproxy|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|NovaAct|omgili|omgilibot|Operator|PanguBot|Perplexity-User|PerplexityBot|PetalBot|QualifiedBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YouBot
|
||||
action: DENY
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 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: >-
|
||||
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|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User/1.0|MyCentralAIScraperBot|NovaAct|OAI-SearchBot|omgili|omgilibot|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot|SemrushBot-BA|SemrushBot-CT|SemrushBot-OCOB|SemrushBot-SI|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YandexAdditional|YandexAdditionalBot|YouBot
|
||||
AddSearchBot|AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|Awario|bedrockbot|bigsur.ai|Brightbot 1.0|Bytespider|CCBot|ChatGPT Agent|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|CloudVertexBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Datenbank Crawler|Devin|Diffbot|DuckAssistBot|Echobot Bot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Gemini-Deep-Research|Google-CloudVertexBot|Google-Extended|GoogleAgent-Mariner|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|LinerBot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User|MistralAI-User/1.0|MyCentralAIScraperBot|netEstate Imprint Crawler|NovaAct|OAI-SearchBot|omgili|omgilibot|OpenAI|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|Thinkbot|TikTokSpider|Timpibot|VelenPublicWebCrawler|WARDBot|Webzio-Extended|wpbot|YaK|YandexAdditional|YandexAdditionalBot|YouBot
|
||||
action: DENY
|
||||
|
||||
5
data/bots/custom-async-http-client.yaml
Normal file
5
data/bots/custom-async-http-client.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
- name: "custom-async-http-client"
|
||||
user_agent_regex: "Custom-AsyncHttpClient"
|
||||
action: WEIGH
|
||||
weight:
|
||||
adjust: 10
|
||||
@@ -2,13 +2,19 @@
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- >
|
||||
(
|
||||
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"
|
||||
- >
|
||||
(
|
||||
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")
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Common "keeping the internet working" routes
|
||||
- name: well-known
|
||||
path_regex: ^/.well-known/.*$
|
||||
path_regex: ^/\.well-known/.*$
|
||||
action: ALLOW
|
||||
- name: favicon
|
||||
path_regex: ^/favicon.ico$
|
||||
path_regex: ^/favicon\.(?:ico|png|gif|jpg|jpeg|svg)$
|
||||
action: ALLOW
|
||||
- name: robots-txt
|
||||
path_regex: ^/robots.txt$
|
||||
path_regex: ^/robots\.txt$
|
||||
action: ALLOW
|
||||
- name: sitemap
|
||||
path_regex: ^/sitemap.xml$
|
||||
action: ALLOW
|
||||
path_regex: ^/sitemap\.xml$
|
||||
action: ALLOW
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
- import: (data)/crawlers/internet-archive.yaml
|
||||
- import: (data)/crawlers/kagibot.yaml
|
||||
- import: (data)/crawlers/marginalia.yaml
|
||||
- import: (data)/crawlers/mojeekbot.yaml
|
||||
- import: (data)/crawlers/mojeekbot.yaml
|
||||
- import: (data)/crawlers/commoncrawl.yaml
|
||||
|
||||
881
data/crawlers/alibaba-cloud.yaml
Normal file
881
data/crawlers/alibaba-cloud.yaml
Normal file
@@ -0,0 +1,881 @@
|
||||
- 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
|
||||
12
data/crawlers/commoncrawl.yaml
Normal file
12
data/crawlers/commoncrawl.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
- 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",
|
||||
]
|
||||
617
data/crawlers/huawei-cloud.yaml
Normal file
617
data/crawlers/huawei-cloud.yaml
Normal file
@@ -0,0 +1,617 @@
|
||||
- name: huawei-cloud
|
||||
action: DENY
|
||||
# Updated 2025-08-20 from IP addresses for AS136907
|
||||
remote_addresses:
|
||||
- 1.178.32.0/20
|
||||
- 1.178.48.0/20
|
||||
- 101.44.0.0/20
|
||||
- 101.44.144.0/20
|
||||
- 101.44.16.0/20
|
||||
- 101.44.160.0/20
|
||||
- 101.44.173.0/24
|
||||
- 101.44.176.0/20
|
||||
- 101.44.192.0/20
|
||||
- 101.44.208.0/22
|
||||
- 101.44.212.0/22
|
||||
- 101.44.216.0/22
|
||||
- 101.44.220.0/22
|
||||
- 101.44.224.0/22
|
||||
- 101.44.228.0/22
|
||||
- 101.44.232.0/22
|
||||
- 101.44.236.0/22
|
||||
- 101.44.240.0/22
|
||||
- 101.44.244.0/22
|
||||
- 101.44.248.0/22
|
||||
- 101.44.252.0/24
|
||||
- 101.44.253.0/24
|
||||
- 101.44.254.0/24
|
||||
- 101.44.255.0/24
|
||||
- 101.44.32.0/20
|
||||
- 101.44.48.0/20
|
||||
- 101.44.64.0/20
|
||||
- 101.44.80.0/20
|
||||
- 101.44.96.0/20
|
||||
- 101.46.0.0/20
|
||||
- 101.46.128.0/21
|
||||
- 101.46.136.0/21
|
||||
- 101.46.144.0/21
|
||||
- 101.46.152.0/21
|
||||
- 101.46.160.0/21
|
||||
- 101.46.168.0/21
|
||||
- 101.46.176.0/21
|
||||
- 101.46.184.0/21
|
||||
- 101.46.192.0/21
|
||||
- 101.46.200.0/21
|
||||
- 101.46.208.0/21
|
||||
- 101.46.216.0/21
|
||||
- 101.46.224.0/22
|
||||
- 101.46.232.0/22
|
||||
- 101.46.236.0/22
|
||||
- 101.46.240.0/22
|
||||
- 101.46.244.0/22
|
||||
- 101.46.248.0/22
|
||||
- 101.46.252.0/24
|
||||
- 101.46.253.0/24
|
||||
- 101.46.254.0/24
|
||||
- 101.46.255.0/24
|
||||
- 101.46.32.0/20
|
||||
- 101.46.48.0/20
|
||||
- 101.46.64.0/20
|
||||
- 101.46.80.0/20
|
||||
- 103.198.203.0/24
|
||||
- 103.215.0.0/24
|
||||
- 103.215.1.0/24
|
||||
- 103.215.3.0/24
|
||||
- 103.240.156.0/22
|
||||
- 103.240.157.0/24
|
||||
- 103.255.60.0/22
|
||||
- 103.255.60.0/24
|
||||
- 103.255.61.0/24
|
||||
- 103.255.62.0/24
|
||||
- 103.255.63.0/24
|
||||
- 103.40.100.0/23
|
||||
- 103.84.110.0/24
|
||||
- 110.238.100.0/22
|
||||
- 110.238.104.0/21
|
||||
- 110.238.112.0/21
|
||||
- 110.238.120.0/22
|
||||
- 110.238.124.0/22
|
||||
- 110.238.64.0/21
|
||||
- 110.238.72.0/21
|
||||
- 110.238.80.0/20
|
||||
- 110.238.96.0/24
|
||||
- 110.238.98.0/24
|
||||
- 110.238.99.0/24
|
||||
- 110.239.127.0/24
|
||||
- 110.239.184.0/22
|
||||
- 110.239.188.0/23
|
||||
- 110.239.190.0/23
|
||||
- 110.239.64.0/19
|
||||
- 110.239.96.0/19
|
||||
- 110.41.208.0/24
|
||||
- 110.41.209.0/24
|
||||
- 110.41.210.0/24
|
||||
- 111.119.192.0/20
|
||||
- 111.119.208.0/20
|
||||
- 111.119.224.0/20
|
||||
- 111.119.240.0/20
|
||||
- 111.91.0.0/20
|
||||
- 111.91.112.0/20
|
||||
- 111.91.16.0/20
|
||||
- 111.91.32.0/20
|
||||
- 111.91.48.0/20
|
||||
- 111.91.64.0/20
|
||||
- 111.91.80.0/20
|
||||
- 111.91.96.0/20
|
||||
- 114.119.128.0/19
|
||||
- 114.119.160.0/21
|
||||
- 114.119.168.0/24
|
||||
- 114.119.169.0/24
|
||||
- 114.119.170.0/24
|
||||
- 114.119.171.0/24
|
||||
- 114.119.172.0/22
|
||||
- 114.119.176.0/20
|
||||
- 115.30.32.0/20
|
||||
- 115.30.48.0/20
|
||||
- 119.12.160.0/20
|
||||
- 119.13.112.0/20
|
||||
- 119.13.160.0/24
|
||||
- 119.13.161.0/24
|
||||
- 119.13.162.0/23
|
||||
- 119.13.163.0/24
|
||||
- 119.13.164.0/22
|
||||
- 119.13.168.0/21
|
||||
- 119.13.168.0/24
|
||||
- 119.13.169.0/24
|
||||
- 119.13.170.0/24
|
||||
- 119.13.172.0/24
|
||||
- 119.13.173.0/24
|
||||
- 119.13.32.0/22
|
||||
- 119.13.36.0/22
|
||||
- 119.13.64.0/24
|
||||
- 119.13.65.0/24
|
||||
- 119.13.66.0/23
|
||||
- 119.13.68.0/22
|
||||
- 119.13.72.0/22
|
||||
- 119.13.76.0/22
|
||||
- 119.13.80.0/21
|
||||
- 119.13.88.0/22
|
||||
- 119.13.92.0/22
|
||||
- 119.13.96.0/20
|
||||
- 119.8.0.0/21
|
||||
- 119.8.128.0/24
|
||||
- 119.8.129.0/24
|
||||
- 119.8.130.0/23
|
||||
- 119.8.132.0/22
|
||||
- 119.8.136.0/21
|
||||
- 119.8.144.0/20
|
||||
- 119.8.160.0/19
|
||||
- 119.8.18.0/24
|
||||
- 119.8.192.0/20
|
||||
- 119.8.192.0/21
|
||||
- 119.8.200.0/21
|
||||
- 119.8.208.0/20
|
||||
- 119.8.21.0/24
|
||||
- 119.8.22.0/24
|
||||
- 119.8.224.0/24
|
||||
- 119.8.227.0/24
|
||||
- 119.8.228.0/22
|
||||
- 119.8.23.0/24
|
||||
- 119.8.232.0/21
|
||||
- 119.8.24.0/21
|
||||
- 119.8.240.0/23
|
||||
- 119.8.242.0/23
|
||||
- 119.8.244.0/24
|
||||
- 119.8.245.0/24
|
||||
- 119.8.246.0/24
|
||||
- 119.8.247.0/24
|
||||
- 119.8.248.0/24
|
||||
- 119.8.249.0/24
|
||||
- 119.8.250.0/24
|
||||
- 119.8.253.0/24
|
||||
- 119.8.254.0/23
|
||||
- 119.8.32.0/19
|
||||
- 119.8.4.0/24
|
||||
- 119.8.64.0/22
|
||||
- 119.8.68.0/24
|
||||
- 119.8.69.0/24
|
||||
- 119.8.70.0/24
|
||||
- 119.8.71.0/24
|
||||
- 119.8.72.0/21
|
||||
- 119.8.8.0/21
|
||||
- 119.8.80.0/20
|
||||
- 119.8.96.0/19
|
||||
- 121.91.152.0/21
|
||||
- 121.91.168.0/21
|
||||
- 121.91.200.0/21
|
||||
- 121.91.200.0/24
|
||||
- 121.91.201.0/24
|
||||
- 121.91.204.0/24
|
||||
- 121.91.205.0/24
|
||||
- 122.8.128.0/20
|
||||
- 122.8.144.0/20
|
||||
- 122.8.160.0/20
|
||||
- 122.8.176.0/21
|
||||
- 122.8.184.0/22
|
||||
- 122.8.188.0/22
|
||||
- 124.243.128.0/18
|
||||
- 124.243.156.0/24
|
||||
- 124.243.157.0/24
|
||||
- 124.243.158.0/24
|
||||
- 124.243.159.0/24
|
||||
- 124.71.248.0/24
|
||||
- 124.71.249.0/24
|
||||
- 124.71.250.0/24
|
||||
- 124.71.252.0/24
|
||||
- 124.71.253.0/24
|
||||
- 124.81.0.0/20
|
||||
- 124.81.112.0/20
|
||||
- 124.81.128.0/20
|
||||
- 124.81.144.0/20
|
||||
- 124.81.16.0/20
|
||||
- 124.81.160.0/20
|
||||
- 124.81.176.0/20
|
||||
- 124.81.192.0/20
|
||||
- 124.81.208.0/20
|
||||
- 124.81.224.0/20
|
||||
- 124.81.240.0/20
|
||||
- 124.81.32.0/20
|
||||
- 124.81.48.0/20
|
||||
- 124.81.64.0/20
|
||||
- 124.81.80.0/20
|
||||
- 124.81.96.0/20
|
||||
- 139.9.98.0/24
|
||||
- 139.9.99.0/24
|
||||
- 14.137.132.0/22
|
||||
- 14.137.136.0/22
|
||||
- 14.137.140.0/22
|
||||
- 14.137.152.0/24
|
||||
- 14.137.153.0/24
|
||||
- 14.137.154.0/24
|
||||
- 14.137.155.0/24
|
||||
- 14.137.156.0/24
|
||||
- 14.137.157.0/24
|
||||
- 14.137.161.0/24
|
||||
- 14.137.163.0/24
|
||||
- 14.137.169.0/24
|
||||
- 14.137.170.0/23
|
||||
- 14.137.172.0/22
|
||||
- 146.174.128.0/20
|
||||
- 146.174.144.0/20
|
||||
- 146.174.160.0/20
|
||||
- 146.174.176.0/20
|
||||
- 148.145.160.0/20
|
||||
- 148.145.192.0/20
|
||||
- 148.145.208.0/20
|
||||
- 148.145.224.0/23
|
||||
- 148.145.234.0/23
|
||||
- 148.145.236.0/23
|
||||
- 148.145.238.0/23
|
||||
- 149.232.128.0/20
|
||||
- 149.232.144.0/20
|
||||
- 150.40.128.0/20
|
||||
- 150.40.144.0/20
|
||||
- 150.40.160.0/20
|
||||
- 150.40.176.0/20
|
||||
- 150.40.182.0/24
|
||||
- 150.40.192.0/20
|
||||
- 150.40.208.0/20
|
||||
- 150.40.224.0/20
|
||||
- 150.40.240.0/20
|
||||
- 154.220.192.0/19
|
||||
- 154.81.16.0/20
|
||||
- 154.83.0.0/23
|
||||
- 154.86.32.0/20
|
||||
- 154.86.48.0/20
|
||||
- 154.93.100.0/23
|
||||
- 154.93.104.0/23
|
||||
- 156.227.22.0/23
|
||||
- 156.230.32.0/21
|
||||
- 156.230.40.0/21
|
||||
- 156.230.64.0/18
|
||||
- 156.232.16.0/20
|
||||
- 156.240.128.0/18
|
||||
- 156.249.32.0/20
|
||||
- 156.253.16.0/20
|
||||
- 157.254.211.0/24
|
||||
- 157.254.212.0/24
|
||||
- 159.138.0.0/20
|
||||
- 159.138.112.0/21
|
||||
- 159.138.114.0/24
|
||||
- 159.138.120.0/22
|
||||
- 159.138.124.0/24
|
||||
- 159.138.125.0/24
|
||||
- 159.138.126.0/23
|
||||
- 159.138.128.0/20
|
||||
- 159.138.144.0/20
|
||||
- 159.138.152.0/21
|
||||
- 159.138.16.0/22
|
||||
- 159.138.160.0/20
|
||||
- 159.138.176.0/23
|
||||
- 159.138.178.0/24
|
||||
- 159.138.179.0/24
|
||||
- 159.138.180.0/24
|
||||
- 159.138.181.0/24
|
||||
- 159.138.182.0/23
|
||||
- 159.138.188.0/23
|
||||
- 159.138.190.0/23
|
||||
- 159.138.192.0/20
|
||||
- 159.138.20.0/22
|
||||
- 159.138.208.0/21
|
||||
- 159.138.216.0/22
|
||||
- 159.138.220.0/23
|
||||
- 159.138.224.0/20
|
||||
- 159.138.24.0/21
|
||||
- 159.138.240.0/20
|
||||
- 159.138.32.0/20
|
||||
- 159.138.48.0/20
|
||||
- 159.138.64.0/21
|
||||
- 159.138.67.0/24
|
||||
- 159.138.76.0/24
|
||||
- 159.138.77.0/24
|
||||
- 159.138.78.0/24
|
||||
- 159.138.79.0/24
|
||||
- 159.138.80.0/20
|
||||
- 159.138.96.0/20
|
||||
- 166.108.192.0/20
|
||||
- 166.108.208.0/20
|
||||
- 166.108.224.0/20
|
||||
- 166.108.240.0/20
|
||||
- 176.52.128.0/20
|
||||
- 176.52.144.0/20
|
||||
- 180.87.192.0/20
|
||||
- 180.87.208.0/20
|
||||
- 180.87.224.0/20
|
||||
- 180.87.240.0/20
|
||||
- 182.160.0.0/20
|
||||
- 182.160.16.0/24
|
||||
- 182.160.17.0/24
|
||||
- 182.160.18.0/23
|
||||
- 182.160.20.0/22
|
||||
- 182.160.20.0/24
|
||||
- 182.160.24.0/21
|
||||
- 182.160.36.0/22
|
||||
- 182.160.49.0/24
|
||||
- 182.160.52.0/22
|
||||
- 182.160.56.0/21
|
||||
- 182.160.56.0/24
|
||||
- 182.160.57.0/24
|
||||
- 182.160.58.0/24
|
||||
- 182.160.59.0/24
|
||||
- 182.160.60.0/24
|
||||
- 182.160.61.0/24
|
||||
- 182.160.62.0/24
|
||||
- 183.87.112.0/20
|
||||
- 183.87.128.0/20
|
||||
- 183.87.144.0/20
|
||||
- 183.87.32.0/20
|
||||
- 183.87.48.0/20
|
||||
- 183.87.64.0/20
|
||||
- 183.87.80.0/20
|
||||
- 183.87.96.0/20
|
||||
- 188.119.192.0/20
|
||||
- 188.119.208.0/20
|
||||
- 188.119.224.0/20
|
||||
- 188.119.240.0/20
|
||||
- 188.239.0.0/20
|
||||
- 188.239.16.0/20
|
||||
- 188.239.32.0/20
|
||||
- 188.239.48.0/20
|
||||
- 189.1.192.0/20
|
||||
- 189.1.208.0/20
|
||||
- 189.1.224.0/20
|
||||
- 189.1.240.0/20
|
||||
- 189.28.112.0/20
|
||||
- 189.28.96.0/20
|
||||
- 190.92.192.0/19
|
||||
- 190.92.224.0/19
|
||||
- 190.92.248.0/24
|
||||
- 190.92.252.0/24
|
||||
- 190.92.253.0/24
|
||||
- 190.92.254.0/24
|
||||
- 201.77.32.0/20
|
||||
- 202.170.88.0/21
|
||||
- 202.76.128.0/20
|
||||
- 202.76.144.0/20
|
||||
- 202.76.160.0/20
|
||||
- 202.76.176.0/20
|
||||
- 203.123.80.0/20
|
||||
- 203.167.20.0/23
|
||||
- 203.167.22.0/24
|
||||
- 212.34.192.0/20
|
||||
- 212.34.208.0/20
|
||||
- 213.250.128.0/20
|
||||
- 213.250.144.0/20
|
||||
- 213.250.160.0/20
|
||||
- 213.250.176.0/21
|
||||
- 213.250.184.0/21
|
||||
- 219.83.0.0/20
|
||||
- 219.83.112.0/22
|
||||
- 219.83.116.0/23
|
||||
- 219.83.118.0/23
|
||||
- 219.83.121.0/24
|
||||
- 219.83.122.0/24
|
||||
- 219.83.123.0/24
|
||||
- 219.83.124.0/24
|
||||
- 219.83.16.0/20
|
||||
- 219.83.32.0/20
|
||||
- 219.83.76.0/23
|
||||
- 2404:a140:43::/48
|
||||
- 2405:f080::/39
|
||||
- 2405:f080:1::/48
|
||||
- 2405:f080:1000::/39
|
||||
- 2405:f080:1200::/39
|
||||
- 2405:f080:1400::/48
|
||||
- 2405:f080:1401::/48
|
||||
- 2405:f080:1402::/48
|
||||
- 2405:f080:1403::/48
|
||||
- 2405:f080:1500::/40
|
||||
- 2405:f080:1600::/48
|
||||
- 2405:f080:1602::/48
|
||||
- 2405:f080:1603::/48
|
||||
- 2405:f080:1800::/39
|
||||
- 2405:f080:1800::/44
|
||||
- 2405:f080:1810::/48
|
||||
- 2405:f080:1811::/48
|
||||
- 2405:f080:1812::/48
|
||||
- 2405:f080:1813::/48
|
||||
- 2405:f080:1814::/48
|
||||
- 2405:f080:1815::/48
|
||||
- 2405:f080:1900::/40
|
||||
- 2405:f080:1e02::/47
|
||||
- 2405:f080:1e04::/47
|
||||
- 2405:f080:1e06::/47
|
||||
- 2405:f080:1e1e::/47
|
||||
- 2405:f080:1e20::/47
|
||||
- 2405:f080:200::/48
|
||||
- 2405:f080:2000::/39
|
||||
- 2405:f080:201::/48
|
||||
- 2405:f080:202::/48
|
||||
- 2405:f080:2040::/48
|
||||
- 2405:f080:2200::/39
|
||||
- 2405:f080:2280::/48
|
||||
- 2405:f080:2281::/48
|
||||
- 2405:f080:2282::/48
|
||||
- 2405:f080:2283::/48
|
||||
- 2405:f080:2284::/48
|
||||
- 2405:f080:2285::/48
|
||||
- 2405:f080:2286::/48
|
||||
- 2405:f080:2287::/48
|
||||
- 2405:f080:2288::/48
|
||||
- 2405:f080:2289::/48
|
||||
- 2405:f080:228a::/48
|
||||
- 2405:f080:228b::/48
|
||||
- 2405:f080:228c::/48
|
||||
- 2405:f080:228d::/48
|
||||
- 2405:f080:228e::/48
|
||||
- 2405:f080:228f::/48
|
||||
- 2405:f080:2400::/39
|
||||
- 2405:f080:2600::/39
|
||||
- 2405:f080:2800::/48
|
||||
- 2405:f080:2a00::/48
|
||||
- 2405:f080:2e00::/47
|
||||
- 2405:f080:3000::/38
|
||||
- 2405:f080:3000::/40
|
||||
- 2405:f080:3100::/40
|
||||
- 2405:f080:3200::/48
|
||||
- 2405:f080:3201::/48
|
||||
- 2405:f080:3202::/48
|
||||
- 2405:f080:3203::/48
|
||||
- 2405:f080:3204::/48
|
||||
- 2405:f080:3205::/48
|
||||
- 2405:f080:3400::/38
|
||||
- 2405:f080:3400::/40
|
||||
- 2405:f080:3500::/40
|
||||
- 2405:f080:3600::/48
|
||||
- 2405:f080:3601::/48
|
||||
- 2405:f080:3602::/48
|
||||
- 2405:f080:3603::/48
|
||||
- 2405:f080:3604::/48
|
||||
- 2405:f080:3605::/48
|
||||
- 2405:f080:400::/39
|
||||
- 2405:f080:4000::/40
|
||||
- 2405:f080:4100::/48
|
||||
- 2405:f080:4102::/48
|
||||
- 2405:f080:4103::/48
|
||||
- 2405:f080:4104::/48
|
||||
- 2405:f080:4200::/40
|
||||
- 2405:f080:4300::/40
|
||||
- 2405:f080:600::/48
|
||||
- 2405:f080:800::/40
|
||||
- 2405:f080:810::/44
|
||||
- 2405:f080:a00::/39
|
||||
- 2405:f080:a11::/48
|
||||
- 2405:f080:e02::/48
|
||||
- 2405:f080:e03::/48
|
||||
- 2405:f080:e04::/47
|
||||
- 2405:f080:e05::/48
|
||||
- 2405:f080:e06::/48
|
||||
- 2405:f080:e07::/48
|
||||
- 2405:f080:e0e::/47
|
||||
- 2405:f080:e10::/47
|
||||
- 2405:f080:edff::/48
|
||||
- 27.106.0.0/20
|
||||
- 27.106.112.0/20
|
||||
- 27.106.16.0/20
|
||||
- 27.106.32.0/20
|
||||
- 27.106.48.0/20
|
||||
- 27.106.64.0/20
|
||||
- 27.106.80.0/20
|
||||
- 27.106.96.0/20
|
||||
- 27.255.0.0/23
|
||||
- 27.255.10.0/23
|
||||
- 27.255.12.0/23
|
||||
- 27.255.14.0/23
|
||||
- 27.255.16.0/23
|
||||
- 27.255.18.0/23
|
||||
- 27.255.2.0/23
|
||||
- 27.255.20.0/23
|
||||
- 27.255.22.0/23
|
||||
- 27.255.26.0/23
|
||||
- 27.255.28.0/23
|
||||
- 27.255.30.0/23
|
||||
- 27.255.32.0/23
|
||||
- 27.255.34.0/23
|
||||
- 27.255.36.0/23
|
||||
- 27.255.38.0/23
|
||||
- 27.255.4.0/23
|
||||
- 27.255.40.0/23
|
||||
- 27.255.42.0/23
|
||||
- 27.255.44.0/23
|
||||
- 27.255.46.0/23
|
||||
- 27.255.48.0/23
|
||||
- 27.255.50.0/23
|
||||
- 27.255.52.0/23
|
||||
- 27.255.54.0/23
|
||||
- 27.255.58.0/23
|
||||
- 27.255.6.0/23
|
||||
- 27.255.60.0/23
|
||||
- 27.255.62.0/23
|
||||
- 27.255.8.0/23
|
||||
- 42.201.128.0/20
|
||||
- 42.201.144.0/20
|
||||
- 42.201.160.0/20
|
||||
- 42.201.176.0/20
|
||||
- 42.201.192.0/20
|
||||
- 42.201.208.0/20
|
||||
- 42.201.224.0/20
|
||||
- 42.201.240.0/20
|
||||
- 43.225.140.0/22
|
||||
- 43.255.104.0/22
|
||||
- 45.194.104.0/21
|
||||
- 45.199.144.0/22
|
||||
- 45.202.128.0/19
|
||||
- 45.202.160.0/20
|
||||
- 45.202.176.0/21
|
||||
- 45.202.184.0/21
|
||||
- 45.203.40.0/21
|
||||
- 46.250.160.0/20
|
||||
- 46.250.176.0/20
|
||||
- 49.0.192.0/21
|
||||
- 49.0.200.0/21
|
||||
- 49.0.224.0/22
|
||||
- 49.0.228.0/22
|
||||
- 49.0.232.0/21
|
||||
- 49.0.240.0/20
|
||||
- 62.245.0.0/20
|
||||
- 62.245.16.0/20
|
||||
- 80.238.128.0/22
|
||||
- 80.238.132.0/22
|
||||
- 80.238.136.0/22
|
||||
- 80.238.140.0/22
|
||||
- 80.238.144.0/22
|
||||
- 80.238.148.0/22
|
||||
- 80.238.152.0/22
|
||||
- 80.238.156.0/22
|
||||
- 80.238.164.0/22
|
||||
- 80.238.164.0/24
|
||||
- 80.238.165.0/24
|
||||
- 80.238.168.0/22
|
||||
- 80.238.168.0/24
|
||||
- 80.238.169.0/24
|
||||
- 80.238.170.0/24
|
||||
- 80.238.171.0/24
|
||||
- 80.238.172.0/22
|
||||
- 80.238.176.0/22
|
||||
- 80.238.180.0/24
|
||||
- 80.238.181.0/24
|
||||
- 80.238.183.0/24
|
||||
- 80.238.184.0/24
|
||||
- 80.238.185.0/24
|
||||
- 80.238.186.0/24
|
||||
- 80.238.190.0/24
|
||||
- 80.238.192.0/20
|
||||
- 80.238.208.0/20
|
||||
- 80.238.224.0/20
|
||||
- 80.238.240.0/20
|
||||
- 83.101.0.0/21
|
||||
- 83.101.104.0/21
|
||||
- 83.101.16.0/21
|
||||
- 83.101.24.0/21
|
||||
- 83.101.32.0/21
|
||||
- 83.101.48.0/21
|
||||
- 83.101.56.0/23
|
||||
- 83.101.58.0/23
|
||||
- 83.101.64.0/21
|
||||
- 83.101.72.0/21
|
||||
- 83.101.8.0/23
|
||||
- 83.101.80.0/21
|
||||
- 83.101.88.0/24
|
||||
- 83.101.89.0/24
|
||||
- 83.101.96.0/21
|
||||
- 87.119.12.0/24
|
||||
- 89.150.192.0/20
|
||||
- 89.150.208.0/20
|
||||
- 94.244.128.0/20
|
||||
- 94.244.144.0/20
|
||||
- 94.244.160.0/20
|
||||
- 94.244.176.0/20
|
||||
- 94.45.160.0/19
|
||||
- 94.45.160.0/24
|
||||
- 94.45.161.0/24
|
||||
- 94.45.163.0/24
|
||||
- 94.74.112.0/21
|
||||
- 94.74.120.0/21
|
||||
- 94.74.64.0/20
|
||||
- 94.74.80.0/20
|
||||
- 94.74.96.0/20
|
||||
165
data/crawlers/tencent-cloud.yaml
Normal file
165
data/crawlers/tencent-cloud.yaml
Normal file
@@ -0,0 +1,165 @@
|
||||
# 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
|
||||
@@ -3,6 +3,6 @@ package data
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed botPolicies.yaml botPolicies.json all:apps all:bots all:clients all:common all:crawlers all:meta
|
||||
//go:embed botPolicies.yaml all:apps all:bots all:clients all:common all:crawlers all:meta
|
||||
BotPolicies embed.FS
|
||||
)
|
||||
|
||||
133
data/meta/default-config.yaml
Normal file
133
data/meta/default-config.yaml
Normal file
@@ -0,0 +1,133 @@
|
||||
- # 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
|
||||
# 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
|
||||
|
||||
# Assert behaviour that only genuine browsers display. This ensures that Chrome
|
||||
# or Firefox versions
|
||||
- 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
|
||||
223
data/services/uptime-robot.yaml
Normal file
223
data/services/uptime-robot.yaml
Normal file
@@ -0,0 +1,223 @@
|
||||
- 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",
|
||||
]
|
||||
@@ -14,6 +14,12 @@ func Zilch[T any]() T {
|
||||
type Impl[K comparable, V any] struct {
|
||||
data map[K]decayMapEntry[V]
|
||||
lock sync.RWMutex
|
||||
|
||||
// deleteCh receives decay-deletion requests from readers.
|
||||
deleteCh chan deleteReq[K]
|
||||
// stopCh stops the background cleanup worker.
|
||||
stopCh chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
type decayMapEntry[V any] struct {
|
||||
@@ -21,33 +27,56 @@ 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] {
|
||||
return &Impl[K, V]{
|
||||
data: make(map[K]decayMapEntry[V]),
|
||||
m := &Impl[K, V]{
|
||||
data: make(map[K]decayMapEntry[V]),
|
||||
deleteCh: make(chan deleteReq[K], 1024),
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
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 {
|
||||
m.lock.RLock()
|
||||
// Use a single write lock to avoid RUnlock->Lock convoy.
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
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
|
||||
m.lock.Unlock()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Get gets a value from the DecayMap by key.
|
||||
//
|
||||
// If a value has expired, forcibly delete it if it was not updated.
|
||||
@@ -61,13 +90,12 @@ func (m *Impl[K, V]) Get(key K) (V, bool) {
|
||||
}
|
||||
|
||||
if time.Now().After(value.expiry) {
|
||||
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)
|
||||
// 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.Unlock()
|
||||
|
||||
return Zilch[V](), false
|
||||
}
|
||||
@@ -105,3 +133,64 @@ 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()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
func TestImpl(t *testing.T) {
|
||||
dm := New[string, string]()
|
||||
t.Cleanup(dm.Close)
|
||||
|
||||
dm.Set("test", "hi", 5*time.Minute)
|
||||
|
||||
@@ -28,10 +29,24 @@ 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)
|
||||
|
||||
@@ -19,5 +19,3 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Kubernetes manifests
|
||||
/manifest
|
||||
@@ -5,6 +5,7 @@ COPY . .
|
||||
|
||||
RUN npm ci && npm run build
|
||||
|
||||
FROM docker.io/library/nginx:alpine
|
||||
COPY --from=build /app/build /usr/share/nginx/html
|
||||
FROM ghcr.io/xe/nginx-micro
|
||||
COPY --from=build /app/build /www
|
||||
COPY ./manifest/cfg/nginx/nginx.conf /conf
|
||||
LABEL org.opencontainers.image.source="https://github.com/TecharoHQ/anubis"
|
||||
@@ -20,9 +20,9 @@ If you rely on Anubis to keep your website safe, please consider sponsoring the
|
||||
|
||||
I am waiting to hear back from NLNet on if Anubis was selected for funding or not. Let's hope it is!
|
||||
|
||||
## Deprecation warning: `DEFAULT_DIFFICULTY`
|
||||
## Deprecation warning: `DIFFICULTY`
|
||||
|
||||
Anubis v1.20.0 is the last version to support the `DEFAULT_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.
|
||||
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.
|
||||
|
||||
@@ -161,7 +161,7 @@ One of the first issues in Anubis before it was moved to the [TecharoHQ org](htt
|
||||
|
||||
When Anubis decides it needs to send a challenge to your browser, it sends a challenge page. Historically, this challenge page is [an HTML template](https://github.com/TecharoHQ/anubis/blob/main/web/index.templ) that kicks off some JavaScript, reads the challenge information out of the page body, and then solves it as fast as possible in order to let users see the website they want to visit.
|
||||
|
||||
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/category/challenges). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
|
||||
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/admin/configuration/challenges/). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
|
||||
|
||||
In my testing, this has worked with every browser I have thrown it at (including CLI browsers, the browser embedded in emacs, etc.). The default configuration of Anubis does use the [meta refresh challenge](/docs/admin/configuration/challenges/metarefresh) for [clients with a very low suspicion](/docs/admin/configuration/thresholds), but by default clients will be sent an [easy proof of work challenge](/docs/admin/configuration/challenges/proof-of-work).
|
||||
|
||||
|
||||
105
docs/blog/2025-07-09-incident-report/index.mdx
Normal file
105
docs/blog/2025-07-09-incident-report/index.mdx
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
slug: incident/TI-20250709-0001
|
||||
title: "TI-20250709-0001: IPv4 traffic failures for Techaro services"
|
||||
authors: [xe]
|
||||
tags: [incident]
|
||||
image: ./window-portal.jpg
|
||||
---
|
||||
|
||||

|
||||
|
||||
Techaro services were down for IPv4 traffic on July 9th, 2025. This blogpost is a report of what happened, what actions were taken to resolve the situation, and what actions are being done in the near future to prevent this problem. Enjoy this incident report!
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
:::note
|
||||
|
||||
In other companies, this kind of documentation would be kept internal. At Techaro, we believe that you deserve radical candor and the truth. As such, we are proving our lofty words with actions by publishing details about how things go wrong publicly.
|
||||
|
||||
Everything past this point follows my standard incident root cause meeting template.
|
||||
|
||||
:::
|
||||
|
||||
This incident report will focus on the services affected, timeline of what happened at which stage of the incident, where we got lucky, the root cause analysis, and what action items are being planned or taken to prevent this from happening in the future.
|
||||
|
||||
## Timeline
|
||||
|
||||
All events take place on July 9th, 2025.
|
||||
|
||||
| Time (UTC) | Description |
|
||||
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 12:32 | Uptime Kuma reports that another unrelated website on the same cluster was timing out. |
|
||||
| 12:33 | Uptime Kuma reports that Thoth's production endpoint is failing gRPC health checks. |
|
||||
| 12:35 | Investigation begins, [announcement made on Xe's Bluesky](https://bsky.app/profile/xeiaso.net/post/3ltjtdczpwc2x) due to the impact including their personal blog. |
|
||||
| 12:39 | `nginx-ingress` logs on the production cluster show IPv6 traffic but an abrupt cutoff in IPv4 traffic around 12:32 UTC. Ticket is opened with the hosting provider. |
|
||||
| 12:41 | IPv4 traffic resumes long enough for Uptime Kuma to report uptime, but then immediately fails again. |
|
||||
| 12:46 | IPv4 traffic resumes long enough for Uptime Kuma to report uptime, but then immediately fails again. (repeat instances of this have been scrubbed, but it happened about every 5-10 minutes) |
|
||||
| 12:48 | First reply from the hosting provider. |
|
||||
| 12:57 | Reply to hosting provider, ask to reboot the load balancer. |
|
||||
| 13:00 | Incident responder because busy due to a meeting under the belief that the downtime was out of their control and that uptime monitoring software would let them know if it came back up. |
|
||||
| 13:20 | Incident responder ended meeting and went back to monitoring downtime and preparing this document. |
|
||||
| 13:34 | IPv4 traffic starts to show up in the `ingress-nginx` logs. |
|
||||
| 13:35 | All services start to report healthy. Incident status changes to monitoring. |
|
||||
| 13:48 | Incident closed. |
|
||||
| 14:07 | Incident re-opened. Issues seem to be manifesting as BGP issues in the upstream provider. |
|
||||
| 14:10 | IPv4 traffic resumes and then stops. |
|
||||
| 14:18 | IPv4 traffic resumes again. Incident status changes to monitoring. |
|
||||
| 14:40 | Incident closed. |
|
||||
|
||||
## Services affected
|
||||
|
||||
| Service name | User impact |
|
||||
| :-------------------------------------------------- | :----------------- |
|
||||
| [Anubis Docs](https://anubis.techaro.lol) (IPv4) | Connection timeout |
|
||||
| [Anubis Docs](https://anubis.techaro.lol) (IPv6) | None |
|
||||
| [Thoth](/docs/admin/thoth/) (IPv4) | Connection timeout |
|
||||
| [Thoth](/docs/admin/thoth/) (IPv6) | None |
|
||||
| Other websites colocated on the same cluster (IPv4) | Connection timeout |
|
||||
| Other websites colocated on the same cluster (IPv6) | None |
|
||||
|
||||
## Root cause analysis
|
||||
|
||||
In simplify server management, Techaro runs a [Kubernetes](https://kubernetes.io/) cluster on [Vultr VKE](https://www.vultr.com/kubernetes/) (Vultr Kubernetes Engine). When you do this, Vultr needs to provision a [load balancer](https://docs.vultr.com/how-to-use-a-vultr-load-balancer-with-vke) to bridge the gap between the outside world and the Kubernetes world, kinda like this:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Overall architecture
|
||||
---
|
||||
|
||||
flowchart LR
|
||||
UT(User Traffic)
|
||||
subgraph Provider Infrastructure
|
||||
LB[Load Balancer]
|
||||
end
|
||||
subgraph Kubernetes
|
||||
IN(ingress-nginx)
|
||||
TH(Thoth)
|
||||
AN(Anubis Docs)
|
||||
OS(Other sites)
|
||||
|
||||
IN --> TH
|
||||
IN --> AN
|
||||
IN --> OS
|
||||
end
|
||||
|
||||
UT --> LB --> IN
|
||||
```
|
||||
|
||||
Techaro controls everything inside the Kubernetes side of that diagram. Anything else is out of our control. That load balancer is routed to the public internet via [Border Gateway Protocol (BGP)](https://en.wikipedia.org/wiki/Border_Gateway_Protocol).
|
||||
|
||||
If there is an interruption with the BGP sessions in the upstream provider, this can manifest as things either not working or inconsistently working. This is made more difficult by the fact that the IPv4 and IPv6 internets are technically separate networks. With this in mind, it's very possible to have IPv4 traffic fail but not IPv6 traffic.
|
||||
|
||||
The root cause is that the hosting provider we use for production services had flapping IPv4 BGP sessions in its Toronto region. When this happens all we can do is open a ticket and wait for it to come back up.
|
||||
|
||||
## Where we got lucky
|
||||
|
||||
The Uptime Kuma instance that caught this incident runs on an IPv4-only network. If it was dual stack, this would not have been caught as quickly.
|
||||
|
||||
The `ingress-nginx` logs print IP addresses of remote clients to the log feed. If this was not the case, it would be much more difficult to find this error.
|
||||
|
||||
## Action items
|
||||
|
||||
- A single instance of downtime like this is not enough reason to move providers. Moving providers because of this is thus out of scope.
|
||||
- Techaro needs a status page hosted on a different cloud provider than is used for the production cluster (`TecharoHQ/TODO#6`).
|
||||
- Health checks for IPv4 and IPv6 traffic need to be created (`TecharoHQ/TODO#7`).
|
||||
- Remove the requirement for [Anubis to pass Thoth health checks before it can start if Thoth is enabled](https://github.com/TecharoHQ/anubis/pull/794).
|
||||
BIN
docs/blog/2025-07-09-incident-report/window-portal.jpg
Normal file
BIN
docs/blog/2025-07-09-incident-report/window-portal.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/blog/2025-07-22-release-1.21.1/anubis-i18n.webp
Normal file
BIN
docs/blog/2025-07-22-release-1.21.1/anubis-i18n.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
369
docs/blog/2025-07-22-release-1.21.1/index.mdx
Normal file
369
docs/blog/2025-07-22-release-1.21.1/index.mdx
Normal file
@@ -0,0 +1,369 @@
|
||||
---
|
||||
slug: release/v1.21.1
|
||||
title: Anubis v1.21.1 is now available!
|
||||
authors: [xe]
|
||||
tags: [release]
|
||||
image: anubis-i18n.webp
|
||||
---
|
||||
|
||||

|
||||
|
||||
Hey all!
|
||||
|
||||
Recently we released [Anubis v1.21.1: Minfilia Warde (Echo 1)](https://github.com/TecharoHQ/anubis/releases/tag/v1.21.1). This is a fairly meaty release and like [last time](../2025-06-27-release-1.20.0/index.mdx) this blogpost will tell you what you need to know before you update. Kick back, get some popcorn and let's dig into this!
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
In this release, Anubis becomes internationalized, gains the ability to use system load as input to issuing challenges, finally fixes the "invalid response" after "success" bug, and more! Please read these notes before upgrading as the changes are big enough that administrators should take action to ensure that the upgrade goes smoothly.
|
||||
|
||||
This release is brought to you by [FreeCAD](https://www.freecad.org/), an open-source computer aided design tool that lets you design things for the real world.
|
||||
|
||||
## What's in this release?
|
||||
|
||||
The biggest change is that the ["invalid response" after "success" bug](https://github.com/TecharoHQ/anubis/issues/564) is now finally fixed for good by totally rewriting how [Anubis' challenge issuance flow works](#challenge-flow-v2).
|
||||
|
||||
This release gives Anubis the following features:
|
||||
|
||||
- [Internationalization support](#internationalization), allowing Anubis to render its messages in the human language you speak.
|
||||
- Anubis now supports the [`missingHeader`](#missingHeader-function) function to assert the absence of headers in requests.
|
||||
- Anubis now has the ability to [store data persistently on the server](#persistent-data-storage).
|
||||
- Anubis can use [the system load average](#load-average-checks) as a factor to determine if it needs to filter traffic or not.
|
||||
- Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies)
|
||||
- Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained)
|
||||
- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape
|
||||
- Add `/healthz` metrics route for use in platform-based health checks.
|
||||
- Start exposing JA4H fingerprints for later use in CEL expressions.
|
||||
|
||||
And this release also fixes the following bugs:
|
||||
|
||||
- [Challenge issuance has been totally rewritten](#challenge-flow-v2) to finally squash the infamous ["invalid response" after "success" bug](https://github.com/TecharoHQ/anubis/issues/564) for good.
|
||||
- In order to reduce confusion, the "Success" interstitial that shows up when you pass a proof of work challenge has been removed.
|
||||
- Don't block Anubis starting up if [Thoth](/docs/admin/thoth/) health checks fail.
|
||||
- The "Try again" button on the error page has been fixed. Previously it meant "try the solution again" instead of "try the challenge again".
|
||||
- In certain cases, a user could be stuck with a test cookie that is invalid, locking them out of the service for up to half an hour. This has been fixed with better validation of this case and clearing the cookie.
|
||||
- "Proof of work" has been removed from the branding due to some users having extremely negative connotations with it.
|
||||
|
||||
We try to avoid introducing breaking changes as much as possible, but these are the changes that may be relevant for you as an administrator:
|
||||
|
||||
- The [challenge format](#challenge-format-change) has been changed in order to account for [the new challenge issuance flow](#challenge-flow-v2).
|
||||
- The [systemd service `RuntimeDirectory` has been changed](#breaking-change-systemd-runtimedirectory-change).
|
||||
|
||||
### Sponsoring the project
|
||||
|
||||
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).
|
||||
|
||||
Once this pie chart is at 100%, I can start to reduce my hours at my day job as most of my needs will be met (pre-tax):
|
||||
|
||||
```mermaid
|
||||
pie title Funding update
|
||||
"GitHub Sponsors" : 29
|
||||
"Patreon" : 14
|
||||
"Remaining" : 56
|
||||
```
|
||||
|
||||
I am waiting to hear back from NLNet on if Anubis was selected for funding or not. Let's hope it is!
|
||||
|
||||
## New features
|
||||
|
||||
### Internationalization
|
||||
|
||||
Anubis now supports localized responses. Locales can be added in [lib/localization/locales/](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). This release includes support for the following languages:
|
||||
|
||||
- [Brazilian Portugese](https://github.com/TecharoHQ/anubis/pull/726)
|
||||
- [Chinese (Simplified)](https://github.com/TecharoHQ/anubis/pull/774)
|
||||
- [Chinese (Traditional)](https://github.com/TecharoHQ/anubis/pull/759)
|
||||
- [Czech](https://github.com/TecharoHQ/anubis/pull/849)
|
||||
- English
|
||||
- [Estonian](https://github.com/TecharoHQ/anubis/pull/783)
|
||||
- [Filipino](https://github.com/TecharoHQ/anubis/pull/775)
|
||||
- [Finnish](https://github.com/TecharoHQ/anubis/pull/863)
|
||||
- [French](https://github.com/TecharoHQ/anubis/pull/716)
|
||||
- [German](https://github.com/TecharoHQ/anubis/pull/741)
|
||||
- [Japanese](https://github.com/TecharoHQ/anubis/pull/772)
|
||||
- [Icelandic](https://github.com/TecharoHQ/anubis/pull/780)
|
||||
- [Italian](https://github.com/TecharoHQ/anubis/pull/778)
|
||||
- [Norwegian](https://github.com/TecharoHQ/anubis/pull/855)
|
||||
- [Russian](https://github.com/TecharoHQ/anubis/pull/882)
|
||||
- [Spanish](https://github.com/TecharoHQ/anubis/pull/716)
|
||||
- [Turkish](https://github.com/TecharoHQ/anubis/pull/751)
|
||||
|
||||
If facts or local regulations demand, you can set Anubis default language with the `FORCED_LANGUAGE` environment variable or the `--forced-language` command line argument:
|
||||
|
||||
```sh
|
||||
FORCED_LANGUAGE=de
|
||||
```
|
||||
|
||||
## Big ticket bug fixes
|
||||
|
||||
These issues affect every user of Anubis. Administrators should upgrade Anubis as soon as possible to mitigate them.
|
||||
|
||||
### Fix event loop thrashing when solving a proof of work challenge
|
||||
|
||||
Anubis has a progress bar so that users can have something moving while it works. This gives users more confidence that something is happening and that the website is not being malicious with CPU usage. However, the way it was implemented way back in [#87](https://github.com/TecharoHQ/anubis/pull/87) had a subtle bug:
|
||||
|
||||
```js
|
||||
if (
|
||||
(nonce > oldNonce) | 1023 && // we've wrapped past 1024
|
||||
(nonce >> 10) % threads === threadId // and it's our turn
|
||||
) {
|
||||
postMessage(nonce);
|
||||
}
|
||||
```
|
||||
|
||||
The logic here looks fine but is subtly wrong as was reported in [#877](https://github.com/TecharoHQ/anubis/issues/877) by the main Pale Moon developer.
|
||||
|
||||
For context, `nonce` is a counter that increments by the worker count every loop. This is intended to spread the load between CPU cores as such:
|
||||
|
||||
| Iteration | Worker ID | Nonce |
|
||||
| :-------- | :-------- | :---- |
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 1 |
|
||||
| 2 | 0 | 2 |
|
||||
| 2 | 1 | 3 |
|
||||
|
||||
And so on. This makes the proof of work challenge as fast as it can possibly be so that Anubis quickly goes away and you can enjoy the service it is protecting.
|
||||
|
||||
The incorrect part of this is the boolean logic, specifically the part with the bitwise or `|`. I think the intent was to use a logical or (`||`), but this had the effect of making the `postMessage` handler fire on every iteration. The intent of this snippet (as the comment clearly indicates) is to make sure that the main event loop is only updated with the worker status every 1024 iterations per worker. This had the opposite effect, causing a lot of messages to be sent from workers to the parent JavaScript context.
|
||||
|
||||
This is bad for the event loop.
|
||||
|
||||
Instead, I have ripped out that statement and replaced it with a much simpler increment only counter that fires every 1024 iterations. Additionally, only the first thread communicates back to the parent process. This does mean that in theory the other workers could be ahead of the first thread (posting a message out of a worker has a nonzero cost), but in practice I don't think this will be as much of an issue as the current behaviour is.
|
||||
|
||||
The root cause of the stack exhaustion is likely the pressure caused by all of the postMessage futures piling up. Maybe the larger stack size in 64 bit environments is causing this to be fine there, maybe it's some combination of newer hardware in 64 bit systems making this not be as much of a problem due to it being able to handle events fast enough to keep up with the pressure.
|
||||
|
||||
Either way, thanks much to [@wolfbeast](https://github.com/wolfbeast) and the Pale Moon community for finding this. This will make Anubis faster for everyone!
|
||||
|
||||
### Fix potential memory leak when discovering a solution
|
||||
|
||||
In some cases, the parallel solution finder in Anubis could cause all of the worker promises to leak due to the fact the promises were being improperly terminated. A recursion bomb happens in the following scenario:
|
||||
|
||||
1. A worker sends a message indicating it found a solution to the proof of work challenge.
|
||||
2. The `onmessage` handler for that worker calls `terminate()`
|
||||
3. Inside `terminate()`, the parent process loops through all other workers and calls `w.terminate()` on them.
|
||||
4. It's possible that terminating a worker could lead to the `onerror` event handler.
|
||||
5. This would create a recursive loop of `onmessage` -> `terminate` -> `onerror` -> `terminate` -> `onerror` and so on.
|
||||
|
||||
This infinite recursion quickly consumes all available stack space, but this has never been noticed in development because all of my computers have at least 64Gi of ram provisioned to them under the axiom paying for more ram is cheaper than paying in my time spent having to work around not having enough ram. Additionally, ia32 has a smaller base stack size, which means that they will run into this issue much sooner than users on other CPU architectures will.
|
||||
|
||||
The fix adds a boolean `settled` flag to prevent termination from running more than once.
|
||||
|
||||
## Expressions features
|
||||
|
||||
Anubis v1.21.1 adds additional [expressions](/docs/admin/configuration/expressions) features so that you can make your request matching even more granular.
|
||||
|
||||
### `missingHeader` function
|
||||
|
||||
Anubis [expressions](/docs/admin/configuration/expressions) have [a few functions exposed](/docs/admin/configuration/expressions/#functions-exposed-to-anubis-expressions). Anubis v1.21.1 adds the `missingHeader` function, allowing you to assert the _absence_ of a header in requests.
|
||||
|
||||
Let's say you're getting a lot of requests from clients that are pretending to be Google Chrome. Google Chrome sends a few signals to web servers, the main one of them is the [`Sec-Ch-Ua`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-CH-UA). Sec-CH-UA is part of Google's [User Agent Client Hints](https://wicg.github.io/ua-client-hints/#sec-ch-ua) proposal, but it being present is a sign that the client is more likely Google Chrome than not. With the `missingHeader` function, you can write a rule to [add weight](/docs/admin/policies/#request-weight) to requests without `Sec-Ch-Ua` that claim to be Google Chrome.
|
||||
|
||||
```yaml
|
||||
# Adds weight clients that claim to be Google Chrome without setting Sec-Ch-Ua
|
||||
- name: old-chrome
|
||||
action: WEIGH
|
||||
weight:
|
||||
adjust: 10
|
||||
expression:
|
||||
all:
|
||||
- userAgent.matches("Chrome/[1-9][0-9]?\\.0\\.0\\.0")
|
||||
- missingHeader(headers, "Sec-Ch-Ua")
|
||||
```
|
||||
|
||||
When combined with [weight thresholds](/docs/admin/configuration/thresholds), this allows you to make requests that don't match the signature of Google Chrome more suspicious, which will make them have a more difficult challenge.
|
||||
|
||||
### Load average checks
|
||||
|
||||
Anubis can dynamically take action [based on the system load average](/docs/admin/configuration/expressions/#using-the-system-load-average), allowing you to write rules like this:
|
||||
|
||||
```yaml
|
||||
## System load based checks.
|
||||
# If the system is under high load for the last minute, 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 it is not for the last 15 minutes, 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
|
||||
```
|
||||
|
||||
Something to keep in mind about system load average is that it is not aware of the number of cores the system has. If you have a 16 core system that has 16 processes running but none of them is hogging the CPU, then you will get a load average below 16. If you are in doubt, make your "high load" metric at least two times the number of CPU cores and your "low load" metric at least half of the number of CPU cores. For example:
|
||||
|
||||
| Kind | Core count | Load threshold |
|
||||
| --------: | :--------- | :------------- |
|
||||
| high load | 4 | `8.0` |
|
||||
| low load | 4 | `2.0` |
|
||||
| high load | 16 | `32.0` |
|
||||
| low load | 16 | `8` |
|
||||
|
||||
Also keep in mind that this does not account for other kinds of latency like I/O latency or downstream API response latency. A system can have its web applications unresponsive due to high latency from a MySQL server but still have that web application server report a load near or at zero.
|
||||
|
||||
:::note
|
||||
|
||||
This does not work if you are using Kubernetes.
|
||||
|
||||
:::
|
||||
|
||||
When combined with [weight thresholds](/docs/admin/configuration/thresholds), this allows you to make incoming sessions "back off" while the server is under high load.
|
||||
|
||||
## Challenge flow v2
|
||||
|
||||
The main goal of Anubis is to weigh the risks of incoming requests in order to protect upstream resources against abusive clients like badly written scrapers. In order to separate "good" clients (like users wanting to learn from a website's content) from "bad" clients, Anubis issues [challenges](/docs/admin/configuration/challenges/).
|
||||
|
||||
Previously the Anubis challenge flow looked like this:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Old Anubis challenge flow
|
||||
---
|
||||
flowchart LR
|
||||
user(User Browser)
|
||||
subgraph Anubis
|
||||
mIC{Challenge?}
|
||||
ic(Issue Challenge)
|
||||
rp(Proxy to service)
|
||||
mIC -->|User needs a challenge| ic
|
||||
mIC -->|User does not need a challenge| rp
|
||||
end
|
||||
target(Target Service)
|
||||
rp --> target
|
||||
user --> mIC
|
||||
ic -->|Pass a challenge| user
|
||||
target -->|Site data| users
|
||||
```
|
||||
|
||||
In order to issue a challenge, Anubis generated a challenge string based on request metadata that we assumed wouldn't drastically change between requests, including but not limited to:
|
||||
|
||||
- The client's User-Agent string.
|
||||
- The client [`Accept-Language` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language) value.
|
||||
- The client's IP address.
|
||||
|
||||
Anubis also didn't store any information about challenges so that it can remain lightweight and handle the onslaught of requests from scrapers. The assumption was that the challenge string function was idempotent per client across time. What actually ended up happening was something like this:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Anubis challenge string idempotency
|
||||
---
|
||||
sequenceDiagram
|
||||
User->>+Anubis: GET /wiki/some-page
|
||||
Anubis->>+Make Challenge: Generate a challenge string
|
||||
Make Challenge->>-Anubis: Challenge string: taco salad
|
||||
Anubis->>-User: HTTP 401 solve a challenge
|
||||
User->>+Anubis: GET internal-api/pass-challenge
|
||||
Anubis->>+Make Challenge: Generate a challenge string
|
||||
Make Challenge->>-Anubis: Challenge string: burrito bar
|
||||
Anubis->>+User: Error: invalid response
|
||||
```
|
||||
|
||||
Various attempts were made to fix this. All of these ended up failing. Many difficulties were discovered including but not limited to:
|
||||
|
||||
- Removing `Accept-Language` from consideration because [Chrome randomizes the contents of `Accept-Language` to reduce fingerprinting](https://github.com/explainers-by-googlers/reduce-accept-language), a behaviour which [causes a lot of confusion](https://www.reddit.com/r/chrome/comments/nhpnez/google_chrome_is_randomly_switching_languages_on/) for users with multiple system languages selected.
|
||||
- [IPv6 privacy extensions](https://www.internetsociety.org/resources/deploy360/2014/privacy-extensions-for-ipv6-slaac/) mean that each request could be coming from a different IP address (at least one legitimate user in the wild has been observed to have a different IP address per TCP session across an entire `/48`).
|
||||
- Some [US mobile phone carriers make it too easy for your IP address to drastically change](https://news.ycombinator.com/item?id=32038215) without user input.
|
||||
- [Happy eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) means that some requests can come in over IPv4 and some requests can come in over IPv6.
|
||||
- To make things worse, you can't even assert that users are from the same [BGP autonomous system](<https://en.wikipedia.org/wiki/Autonomous_system_(Internet)>) because some users could have ISPs that are IPv4 only, forcing them to use a different IP address space to get IPv6 internet access. This sounds like it's rare enough, but I personally have to do this even though I pay for 8 gigabit fiber from my ISP and only get IPv4 service from them.
|
||||
|
||||
Amusingly enough, the only part of this that has survived is the assertion that a user hasn't changed their `User-Agent` string. Maybe [that one guy that sets his Chrome version to `150`](https://github.com/TecharoHQ/anubis/issues/239) would have issues, but so far I've not seen any evidence that a client randomly changing their user agent between challenge issuance and solving can possibly be legitimate.
|
||||
|
||||
As a result, the entire subsystem that generated challenges before had to be ripped out and rewritten from scratch.
|
||||
|
||||
It was replaced with a new flow that stores data on the server side, compares that data against what the client responds with, and then checks pass/fail that way:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: New challenge flow
|
||||
---
|
||||
sequenceDiagram
|
||||
User->>+Anubis: GET /wiki/some-page
|
||||
Anubis->>+Make Challenge: Generate a challenge string
|
||||
Make Challenge->>+Store: Store info for challenge 1234
|
||||
Make Challenge->>-Anubis: Challenge string: taco salad, ID 1234
|
||||
Anubis->>-User: HTTP 401 solve a challenge
|
||||
User->>+Anubis: GET internal-api/pass-challenge, challenge 1234
|
||||
Anubis->>+Validate Challenge: verify challenge 1234
|
||||
Validate Challenge->>+Store: Get info for challenge 1234
|
||||
Store->>-Validate Challenge: Here you go!
|
||||
Validate Challenge->>-Anubis: Valid ✅
|
||||
Anubis->>+User: Here's a cookie to get past Anubis
|
||||
```
|
||||
|
||||
As a result, the [challenge format](#challenge-format-change) had to change. Old cookies will still be validated, but the next minor version (v1.22.0) will include validation to ensure that all challenges are accounted for on the server side. This data is stored in the active [storage backend](/docs/admin/policies/#storage-backends) for up to 30 minutes. This also fixes [#746](https://github.com/TecharoHQ/anubis/issues/746) and other similar instances of this issue.
|
||||
|
||||
### Challenge format change
|
||||
|
||||
Previously Anubis did no accounting for challenges that it issued. This means that if Anubis restarted during a client, the client would be able to proceed once Anubis came back online.
|
||||
|
||||
During the upgrade to v1.21.0 and when v1.21.0 (or later) restarts with the [in-memory storage backend](/docs/admin/policies/#memory), you may see a higher rate of failed challenges than normal. If this persists beyond a few minutes, [open an issue](https://github.com/TecharoHQ/anubis/issues/new).
|
||||
|
||||
If you are using the in-memory storage backend, please consider using [a different storage backend](/docs/admin/policies/#storage-backends).
|
||||
|
||||
### Storage
|
||||
|
||||
Anubis offers a few different storage backends depending on your needs:
|
||||
|
||||
| Backend | Description |
|
||||
| :--------------------------------------- | :------------------------------------------------------------------------------------------------------------- |
|
||||
| [`memory`](/docs/admin/policies/#memory) | An in-memory hashmap that is cleared when Anubis is restarted. |
|
||||
| [`bbolt`](/docs/admin/policies/#bbolt) | A memory-mapped key/value store that can persist between Anubis restarts. |
|
||||
| [`valkey`](/docs/admin/policies/#valkey) | A networked key/value store that can persist between Anubis restarts and coordinate across multiple instances. |
|
||||
|
||||
Please review the documentation for each storage method to figure out the one best for your needs. If you aren't sure, consult this diagram:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: What storage backend do I need?
|
||||
---
|
||||
flowchart TD
|
||||
OneInstance{Do you only have
|
||||
one instance of
|
||||
Anubis?}
|
||||
Persistence{Do you have
|
||||
persistent disk
|
||||
access in your
|
||||
environment?}
|
||||
bbolt[(bbolt)]
|
||||
memory[(memory)]
|
||||
valkey[(valkey)]
|
||||
OneInstance -->|Yes| Persistence
|
||||
OneInstance -->|No| valkey
|
||||
Persistence -->|Yes| bbolt
|
||||
Persistence -->|No| memory
|
||||
```
|
||||
|
||||
## Breaking change: systemd `RuntimeDirectory` change
|
||||
|
||||
The following potentially breaking change applies to native installs with systemd only:
|
||||
|
||||
Each instance of systemd service template now has a unique `RuntimeDirectory`, as opposed to each instance of the service sharing a `RuntimeDirectory`. This change was made to avoid [the `RuntimeDirectory` getting nuked](https://github.com/TecharoHQ/anubis/issues/748) any time one of the Anubis instances restarts.
|
||||
|
||||
If you configured Anubis' unix sockets to listen on `/run/anubis/foo.sock` for instance `anubis@foo`, you will need to configure Anubis to listen on `/run/anubis/foo/foo.sock` and additionally configure your HTTP load balancer as appropriate.
|
||||
|
||||
If you need the legacy behaviour, install this [systemd unit dropin](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/):
|
||||
|
||||
```systemd
|
||||
# /etc/systemd/system/anubis@.service.d/50-runtimedir.conf
|
||||
[Service]
|
||||
RuntimeDirectory=anubis
|
||||
```
|
||||
|
||||
Just keep in mind that this will cause problems when Anubis restarts.
|
||||
|
||||
## What's up next?
|
||||
|
||||
The biggest things we want to do in the next release (in no particular order):
|
||||
|
||||
- A rewrite of bot checking rule configuration syntax to make it less ambiguous.
|
||||
- [JA4](https://blog.foxio.io/ja4+-network-fingerprinting) (and other forms of) fingerprinting and coordination with [Thoth](/docs/admin/thoth/) to allow clients with high aggregate pass rates through without seeing Anubis at all.
|
||||
- Advanced heuristics for [users of the unbranded variant of Anubis](/docs/admin/botstopper/).
|
||||
- Optimize the release flow so that releases can be triggered and executed by continuous integration tools. The ultimate goal is to make it possible to release Anubis in 15 minutes after pressing a single "mint release" button.
|
||||
- Add "hot reloading" support to Anubis, allowing administrators to update the rules without restarting the service.
|
||||
- Fix [multiple slash support](https://github.com/TecharoHQ/anubis/issues/754) for web applications that require optional path variables.
|
||||
- Add weight to "brand new" clients.
|
||||
- Implement a "maze" feature that tries to get crawlers ensnared in a maze of random links so that clients that are more than 20 links in can be reported to the home base.
|
||||
- Open [Thoth-based advanced checks](/docs/admin/thoth/) to more users with an easier onboarding flow.
|
||||
- More smoke tests including for browsers like [Pale Moon](https://www.palemoon.org/).
|
||||
BIN
docs/blog/2025-08-18-funding-update/around-the-bend.webp
Normal file
BIN
docs/blog/2025-08-18-funding-update/around-the-bend.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
60
docs/blog/2025-08-18-funding-update/index.mdx
Normal file
60
docs/blog/2025-08-18-funding-update/index.mdx
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
slug: 2025/funding-update
|
||||
title: Funding update
|
||||
authors: [xe]
|
||||
tags: [funding]
|
||||
image: around-the-bend.webp
|
||||
---
|
||||
|
||||

|
||||
|
||||
As we finish up work on [all of the features in the next release of Anubis](/docs/CHANGELOG#unreleased), I took a moment to add up the financials and here's an update on the recurring revenue of the project. Once I reach the [$5000 per month](https://github.com/TecharoHQ/anubis/discussions/278) mark, I can start reducing hours at my dayjob and start to make working on Anubis my full time job.
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
Note that this only counts _recurring_ revenue (subscriptions to [BotStopper](/docs/admin/botstopper) and monthly repeating donations). Every one of the one-time donations I get is a gift and I am grateful for them, but I cannot make critically important financial decisions off of sporadic one-time donations.
|
||||
|
||||
:::note
|
||||
|
||||
All currency figures in this article are USD (United States Dollars) unless denoted otherwise.
|
||||
|
||||
:::
|
||||
|
||||
Here's the funding breakdown by income stream:
|
||||
|
||||
```mermaid
|
||||
pie title Funding update August 2025
|
||||
"GitHub Sponsors" : 3500
|
||||
"Patreon" : 1500
|
||||
"Liberapay" : 100
|
||||
"Remaining" : 4800
|
||||
```
|
||||
|
||||
Assuming that some of my private support contracts and other sales effort go through, this will slightly change the shapes of this (a new pie chart segment will emerge for "Manual invoices"), but I am halfway there. This is a huge bar to pass and as it stands right now this is just enough income to pay for my monthly rent (not accounting for tax).
|
||||
|
||||
As a reminder, here's the rough plan for the phases I want to hit based on the _recurring_ donation totals:
|
||||
|
||||
| Monthly donations | Details |
|
||||
| :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| $0-5,000 per month | Anubis is a nights and weekends project based on how much spare time and energy I have. |
|
||||
| $5,000-10,000 per month | Anubis gets 1-2 days per week of my time put into it consistently and I go part-time at my dayjob. |
|
||||
| $10,000-15,000 per month | Anubis becomes my full time job. Features that are currently exclusive to [BotStopper](/docs/admin/botstopper/) start to trickle down to the open source version of Anubis. |
|
||||
| $15,000 per month and above | I start planning hiring for Techaro. |
|
||||
|
||||
If your organization benefits from Anubis, please consider donating to the project in order to make this sustainable. The fewer financial problems I have means the more that Anubis can become better.
|
||||
|
||||
## New funding platform: Liberapay
|
||||
|
||||
After many comments about the funding options, I have set up [Liberapay](https://liberapay.com/Xe/) as an option to receive donations. Additional funding targets will be added to Liberapay as soon as I hear back from my accountant with more information. All money received via Liberapay goes directly towards supporting the project.
|
||||
|
||||
## Next goals
|
||||
|
||||
Here's my short term goals for the immediate future:
|
||||
|
||||
1. Finish [Thoth](/docs/admin/thoth/) and run a backfill to mass issue API keys.
|
||||
2. Document and publish the writeup for the multi-region Google Cloud spot instance setup that Thoth is built upon.
|
||||
3. Release v1.22.0 of Anubis with Traefik support and other important fixes.
|
||||
4. Continue growing the project into a sustainable business.
|
||||
5. Work through the [blog backlog](https://github.com/TecharoHQ/anubis/issues?q=is%3Aissue%20state%3Aopen%20label%3Ablog) to document the thoughts behind Anubis and how parts of it work.
|
||||
|
||||
Thank you for supporting Anubis! It's only going to get better from here.
|
||||
214
docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx
Normal file
214
docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
// A helper function to perform SHA-256 hashing.
|
||||
// It takes a string, encodes it, hashes it, and returns a hex string.
|
||||
async function sha256(message) {
|
||||
try {
|
||||
const msgBuffer = new TextEncoder().encode(message);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
return hashHex;
|
||||
} catch (error) {
|
||||
console.error("Hashing failed:", error);
|
||||
return "Error hashing data";
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a random hex string of a given byte length
|
||||
const generateRandomHex = (bytes = 16) => {
|
||||
const buffer = new Uint8Array(bytes);
|
||||
crypto.getRandomValues(buffer);
|
||||
return Array.from(buffer)
|
||||
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
};
|
||||
|
||||
|
||||
// Icon components for better visual feedback
|
||||
const CheckIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconGreen} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const XCircleIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconRed} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Main Application Component
|
||||
export default function App() {
|
||||
// State for the challenge, initialized with a random 16-byte hex string.
|
||||
const [challenge, setChallenge] = useState(() => generateRandomHex(16));
|
||||
// State for the nonce, which is the variable we can change
|
||||
const [nonce, setNonce] = useState(0);
|
||||
// State to store the resulting hash
|
||||
const [hash, setHash] = useState('');
|
||||
// A flag to indicate if the current hash is the "winning" one
|
||||
const [isMining, setIsMining] = useState(false);
|
||||
const [isFound, setIsFound] = useState(false);
|
||||
|
||||
// The mining difficulty, i.e., the required number of leading zeros
|
||||
const difficulty = "00";
|
||||
|
||||
// Memoize the combined data to avoid recalculating on every render
|
||||
const combinedData = useMemo(() => `${challenge}${nonce}`, [challenge, nonce]);
|
||||
|
||||
// This effect hook recalculates the hash whenever the combinedData changes.
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const calculateHash = async () => {
|
||||
const calculatedHash = await sha256(combinedData);
|
||||
if (isMounted) {
|
||||
setHash(calculatedHash);
|
||||
setIsFound(calculatedHash.startsWith(difficulty));
|
||||
}
|
||||
};
|
||||
calculateHash();
|
||||
return () => { isMounted = false; };
|
||||
}, [combinedData, difficulty]);
|
||||
|
||||
// This effect handles the automatic mining process
|
||||
useEffect(() => {
|
||||
if (!isMining) return;
|
||||
|
||||
let miningNonce = nonce;
|
||||
let continueMining = true;
|
||||
|
||||
const mine = async () => {
|
||||
while (continueMining) {
|
||||
const currentData = `${challenge}${miningNonce}`;
|
||||
const currentHash = await sha256(currentData);
|
||||
|
||||
if (currentHash.startsWith(difficulty)) {
|
||||
setNonce(miningNonce);
|
||||
setIsMining(false);
|
||||
break;
|
||||
}
|
||||
|
||||
miningNonce++;
|
||||
// Update the UI periodically to avoid freezing the browser
|
||||
if (miningNonce % 100 === 0) {
|
||||
setNonce(miningNonce);
|
||||
await new Promise(resolve => setTimeout(resolve, 0)); // Yield to the browser
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mine();
|
||||
|
||||
return () => {
|
||||
continueMining = false;
|
||||
}
|
||||
}, [isMining, challenge, nonce, difficulty]);
|
||||
|
||||
|
||||
const handleMineClick = () => {
|
||||
setIsMining(true);
|
||||
}
|
||||
|
||||
const handleStopClick = () => {
|
||||
setIsMining(false);
|
||||
}
|
||||
|
||||
const handleResetClick = () => {
|
||||
setIsMining(false);
|
||||
setNonce(0);
|
||||
}
|
||||
|
||||
const handleNewChallengeClick = () => {
|
||||
setIsMining(false);
|
||||
setChallenge(generateRandomHex(16));
|
||||
setNonce(0);
|
||||
}
|
||||
|
||||
// Helper to render the hash with colored leading characters
|
||||
const renderHash = () => {
|
||||
if (!hash) return <span>...</span>;
|
||||
const prefix = hash.substring(0, difficulty.length);
|
||||
const suffix = hash.substring(difficulty.length);
|
||||
const prefixColor = isFound ? styles.hashPrefixGreen : styles.hashPrefixRed;
|
||||
return (
|
||||
<>
|
||||
<span className={`${prefixColor} ${styles.hashPrefix}`}>{prefix}</span>
|
||||
<span className={styles.hashSuffix}>{suffix}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.grid}>
|
||||
{/* Challenge Block */}
|
||||
<div className={styles.block}>
|
||||
<h2 className={styles.blockTitle}>1. Challenge</h2>
|
||||
<p className={styles.challengeText}>{challenge}</p>
|
||||
</div>
|
||||
|
||||
{/* Nonce Control Block */}
|
||||
<div className={styles.block}>
|
||||
<h2 className={styles.blockTitle}>2. Nonce</h2>
|
||||
<div className={styles.nonceControls}>
|
||||
<button onClick={() => setNonce(n => n - 1)} disabled={isMining} className={styles.nonceButton}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconSmall} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" /></svg>
|
||||
</button>
|
||||
<span className={styles.nonceValue}>{nonce}</span>
|
||||
<button onClick={() => setNonce(n => n + 1)} disabled={isMining} className={styles.nonceButton}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconSmall} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Combined Data Block */}
|
||||
<div className={styles.block}>
|
||||
<h2 className={styles.blockTitle}>3. Combined Data</h2>
|
||||
<p className={styles.combinedDataText}>{combinedData}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow pointing down */}
|
||||
<div className={styles.arrowContainer}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconGray} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Hash Output Block */}
|
||||
<div className={`${styles.hashContainer} ${isFound ? styles.hashContainerSuccess : styles.hashContainerError}`}>
|
||||
<div className={styles.hashContent}>
|
||||
<div className={styles.hashText}>
|
||||
<h2 className={styles.blockTitle}>4. Resulting Hash (SHA-256)</h2>
|
||||
<p className={styles.hashValue}>{renderHash()}</p>
|
||||
</div>
|
||||
<div className={styles.hashIcon}>
|
||||
{isFound ? <CheckIcon /> : <XCircleIcon />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mining Controls */}
|
||||
<div className={styles.buttonContainer}>
|
||||
{!isMining ? (
|
||||
<button onClick={handleMineClick} className={`${styles.button} ${styles.buttonCyan}`}>
|
||||
Auto-Mine
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={handleStopClick} className={`${styles.button} ${styles.buttonYellow}`}>
|
||||
Stop Mining
|
||||
</button>
|
||||
)}
|
||||
<button onClick={handleNewChallengeClick} className={`${styles.button} ${styles.buttonIndigo}`}>
|
||||
New Challenge
|
||||
</button>
|
||||
<button onClick={handleResetClick} className={`${styles.button} ${styles.buttonGray}`}>
|
||||
Reset Nonce
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/* Main container styles */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.innerContainer {
|
||||
width: 100%;
|
||||
max-width: 56rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header styles */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
color: rgb(34 211 238);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.125rem;
|
||||
color: rgb(156 163 175);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Grid layout styles */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Block styles */
|
||||
.block {
|
||||
background-color: rgb(31 41 55);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.blockTitle {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: rgb(34 211 238);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.challengeText {
|
||||
font-size: 0.875rem;
|
||||
color: rgb(209 213 219);
|
||||
word-break: break-all;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
.combinedDataText {
|
||||
font-size: 0.875rem;
|
||||
color: rgb(156 163 175);
|
||||
word-break: break-all;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
/* Nonce control styles */
|
||||
.nonceControls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nonceButton {
|
||||
background-color: rgb(55 65 81);
|
||||
border-radius: 9999px;
|
||||
padding: 0.5rem;
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
|
||||
.nonceButton:hover:not(:disabled) {
|
||||
background-color: rgb(34 211 238);
|
||||
}
|
||||
|
||||
.nonceButton:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.nonceValue {
|
||||
font-size: 1.5rem;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
width: 6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Icon styles */
|
||||
.icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.iconGreen {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.iconRed {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.iconSmall {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.iconGray {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
color: rgb(75 85 99);
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Arrow animation */
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.arrowContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
/* Hash output styles */
|
||||
.hashContainer {
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
transition: all 300ms;
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
.hashContainerSuccess {
|
||||
background-color: rgb(20 83 45 / 0.5);
|
||||
border-color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.hashContainerError {
|
||||
background-color: rgb(127 29 29 / 0.5);
|
||||
border-color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.hashContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hashText {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hashTextLg {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hashValue {
|
||||
font-size: 0.875rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.hashValueLg {
|
||||
font-size: 1rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.hashIcon {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.hashIconLg {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Hash highlighting */
|
||||
.hashPrefix {
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
.hashPrefixGreen {
|
||||
color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.hashPrefixRed {
|
||||
color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.hashSuffix {
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.buttonContainer {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-weight: 700;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: transform 150ms;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.buttonCyan {
|
||||
background-color: rgb(8 145 178);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonCyan:hover {
|
||||
background-color: rgb(6 182 212);
|
||||
}
|
||||
|
||||
.buttonYellow {
|
||||
background-color: rgb(202 138 4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonYellow:hover {
|
||||
background-color: rgb(245 158 11);
|
||||
}
|
||||
|
||||
.buttonIndigo {
|
||||
background-color: rgb(79 70 229);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonIndigo:hover {
|
||||
background-color: rgb(99 102 241);
|
||||
}
|
||||
|
||||
.buttonGray {
|
||||
background-color: rgb(55 65 81);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonGray:hover {
|
||||
background-color: rgb(75 85 99);
|
||||
}
|
||||
|
||||
/* Responsive styles */
|
||||
@media (min-width: 768px) {
|
||||
.title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hashContent {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.hashText {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hashValue {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hashIcon {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.block {
|
||||
background-color: oklch(93% 0.034 272.788);
|
||||
}
|
||||
|
||||
.challengeText {
|
||||
color: oklch(12.9% 0.042 264.695);
|
||||
}
|
||||
|
||||
.combinedDataText {
|
||||
color: oklch(12.9% 0.042 264.695);
|
||||
}
|
||||
|
||||
.nonceButton {
|
||||
background-color: oklch(88.2% 0.059 254.128);
|
||||
}
|
||||
|
||||
.nonceValue {
|
||||
color: oklch(12.9% 0.042 264.695);
|
||||
}
|
||||
|
||||
.blockTitle {
|
||||
color: oklch(45% 0.085 224.283);
|
||||
}
|
||||
|
||||
.hashContainerSuccess {
|
||||
background-color: oklch(95% 0.052 163.051);
|
||||
border-color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.hashContainerError {
|
||||
background-color: oklch(94.1% 0.03 12.58);
|
||||
border-color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.hashPrefixGreen {
|
||||
color: oklch(53.2% 0.157 131.589);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hashPrefixRed {
|
||||
color: oklch(45.5% 0.188 13.697);
|
||||
}
|
||||
|
||||
.hashSuffix {
|
||||
color: oklch(27.9% 0.041 260.031);
|
||||
}
|
||||
}
|
||||
129
docs/blog/2025-08-28-cpu-core-odd/index.mdx
Normal file
129
docs/blog/2025-08-28-cpu-core-odd/index.mdx
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
slug: 2025/cpu-core-odd
|
||||
title: Sometimes CPU cores are odd
|
||||
description: "TL;DR: all the assumptions you have about processor design are wrong and if you are unlucky you will never run into problems that users do through sheer chance."
|
||||
authors: [xe]
|
||||
tags:
|
||||
- bugfix
|
||||
- implementation
|
||||
image: parc-dsilence.webp
|
||||
---
|
||||
|
||||
import ProofOfWorkDiagram from "./ProofOfWorkDiagram";
|
||||
|
||||

|
||||
|
||||
One of the biggest lessons that I've learned in my career is that all software has bugs, and the more complicated your software gets the more complicated your bugs get. A lot of the time those bugs will be fairly obvious and easy to spot, validate, and replicate. Sometimes, the process of fixing it will uncover your core assumptions about how things work in ways that will leave you feeling like you just got trolled.
|
||||
|
||||
Today I'm going to talk about a single line fix that prevents people on a large number of devices from having weird irreproducible issues with Anubis rejecting people when it frankly shouldn't. Stick around, it's gonna be a wild ride.
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
## How this happened
|
||||
|
||||
Anubis is a web application firewall that tries to make sure that the client is a browser. It uses a few [challenge methods](/docs/admin/configuration/challenges/) to do this determination, but the main method is the [proof of work](/docs/admin/configuration/challenges/proof-of-work/) challenge which makes clients grind away at cryptographic checksums in order to rate limit clients from connecting too eagerly.
|
||||
|
||||
:::note
|
||||
|
||||
In retrospect implementing the proof of work challenge may have been a mistake and it's likely to be supplanted by things like [Proof of React](https://github.com/TecharoHQ/anubis/pull/1038) or other methods that have yet to be developed. Your patience and polite behaviour in the bug tracker is appreciated.
|
||||
|
||||
:::
|
||||
|
||||
In order to make sure the proof of work challenge screen _goes away as fast as possible_, the [worker code](https://github.com/TecharoHQ/anubis/tree/main/web/js/worker) is optimized within an inch of its digital life. One of the main ways that this code is optimized is with how it's run. Over the last 10-20 years, the main way that CPUs have gotten fast is via increasing multicore performance. Anubis tries to make sure that it can use as many cores as possible in order to take advantage of your device's CPU as much as it can.
|
||||
|
||||
This strategy sometimes has some issues though, for one Firefox seems to get _much slower_ if you have Anubis try to absolutely saturate all of the cores on the system. It also has a fairly high overhead between JavaScript JIT code and [WebCrypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). I did some testing and found out that Firefox's point of diminishing returns was about half of the CPU cores.
|
||||
|
||||
## Another "invalid response" bug
|
||||
|
||||
One of the complaints I've been getting from users and administrators using Anubis is that they've been running into issues where users get randomly rejected with an error message only saying "invalid response". This happens when the challenge validating process fails. This issue has been blocking the release of the next version of Anubis.
|
||||
|
||||
In order to demonstrate this better, I've made a little interactive diagram for the proof of work process:
|
||||
|
||||
<ProofOfWorkDiagram />
|
||||
|
||||
I've fixed a lot of the easy bugs in Anubis by this point. A lot of what's left is the hard bugs, but also specifically the kinds of hard bugs that involve weird hardware configurations. In order to try and catch these issues before software hits prod, I test Anubis against a bunch of hardware I have locally. Any issues I find and fix before software ships are issues that you don't hit in production.
|
||||
|
||||
Let's consider [the line of code](https://github.com/TecharoHQ/anubis/blob/main/web/js/algorithms/fast.mjs) that was causing this issue:
|
||||
|
||||
```js
|
||||
threads = Math.max(navigator.hardwareConcurrency / 2, 1),
|
||||
```
|
||||
|
||||
This is intended to make your browser spawn a proof of work worker for _half_ of your available CPU cores. If you only have one CPU core, you should only have one worker. Each thread is given this number of threads and uses that to increment the nonce so that each thread doesn't try to find a solution that another worker has already performed.
|
||||
|
||||
One of the subtle problems here is that all of the parts of this assume that the thread ID and nonce are integers without a decimal portion. Famously, [all JavaScript numbers are IEEE 754 floating point numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). Surely there wouldn't be a case where the thread count could be a _decimal_ number, right?
|
||||
|
||||
Here's all the devices I use to test Anubis _and their core counts_:
|
||||
|
||||
| Device Name | Core Count |
|
||||
| :--------------------------- | :--------- |
|
||||
| MacBook Pro M3 Max | 16 |
|
||||
| MacBook Pro M4 Max | 16 |
|
||||
| AMD Ryzen 9 7950x3D | 32 |
|
||||
| Google Pixel 9a (GrapheneOS) | 8 |
|
||||
| iPhone 15 Pro Max | 6 |
|
||||
| iPad Pro (M1) | 8 |
|
||||
| iPad mini | 6 |
|
||||
| Steam Deck | 8 |
|
||||
| Core i5 10600 (homelab) | 12 |
|
||||
| ROG Ally | 16 |
|
||||
|
||||
Notice something? All of those devices have an _even_ number of cores. Some devices such as the [Pixel 8 Pro](https://www.gsmarena.com/google_pixel_8_pro-12545.php) have an _odd_ number of cores. So what happens with that line of code as the JavaScript engine evaluates it?
|
||||
|
||||
Let's replace the [`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency) with the Pixel 8 Pro's 9 cores:
|
||||
|
||||
```js
|
||||
threads = Math.max(9 / 2, 1),
|
||||
```
|
||||
|
||||
Then divide it by two:
|
||||
|
||||
```js
|
||||
threads = Math.max(4.5, 1),
|
||||
```
|
||||
|
||||
Oops, that's not ideal. However `4.5` is bigger than `1`, so [`Math.max`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) returns that:
|
||||
|
||||
```js
|
||||
threads = 4.5,
|
||||
```
|
||||
|
||||
This means that each time the proof of work equation is calculated, there is a 50% chance that a valid solution would include a nonce with a decimal portion in it. If the client finds a solution with such a nonce, then it would think the client was successful and submit the solution to the server, but the server only expects whole numbers back so it rejects that as an invalid response.
|
||||
|
||||
I keep telling more junior people that when you have the weirdest, most inconsistent bugs in software that it's going to boil down to the dumbest possible thing you can possibly imagine. People don't believe me, then they encounter bugs like this. Then they suddenly believe me.
|
||||
|
||||
Here is the fix:
|
||||
|
||||
```js
|
||||
threads = Math.trunc(Math.max(navigator.hardwareConcurrency / 2, 1)),
|
||||
```
|
||||
|
||||
This uses [`Math.trunc`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc) to truncate away the decimal portion so that the Pixel 8 Pro has `4` workers instead of `4.5` workers.
|
||||
|
||||
## Today I learned this was possible
|
||||
|
||||
This was a total "today I learned" moment. I didn't actually think that hardware vendors shipped processors with an odd number of cores, however if you look at the core geometry of the Pixel 8 Pro, it has _three_ tiers of processor cores:
|
||||
|
||||
| Core type | Core model | Number |
|
||||
| :----------------- | :------------------- | :----- |
|
||||
| High performance | 3 Ghz Cortex X3 | 1 |
|
||||
| Medium performance | 2.45 Ghz Cortex A715 | 4 |
|
||||
| High efficiency | 2.15 Cortex A510 | 4 |
|
||||
| Total | | 9 |
|
||||
|
||||
I guess every assumption that developers have about CPU design is probably wrong.
|
||||
|
||||
This probably isn't helped by the fact that for most of my career, the core count in phones has been largely irrelevant and most of the desktop / laptop CPUs I've had (where core count does matter) uses [simultaneous multithreading](https://en.wikipedia.org/wiki/Simultaneous_multithreading) to "multiply" the core count by two.
|
||||
|
||||
The client side fix is a bit of an "emergency stop" button to try and mitigate the badness as early as possible. In general I'm quite aware of the terrible UX involved with this flow failing and I'm still noodling through ways to make that UX better and easier for users / administrators to debug.
|
||||
|
||||
I'm looking into the following:
|
||||
|
||||
1. This could have been prevented on the server side by doing less strict input validation in compliance with [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle). I feel nervous about making such a security-sensitive endpoint _more liberal_ with the inputs it can accept, but it may be fine? I need to consult with a security expert.
|
||||
2. Showing an encrypted error message on the "invalid response" page so that the user and administrator can work together to fix or report the issue. I remember Google doing this at least once, but I can't recall where I've seen it in the past. Either way, this is probably the most robust method even though it would require developing some additional tooling. I think it would be worth it.
|
||||
|
||||
I'm likely going to go with the second option. I will need to figure out a good flow for this. It's likely going to involve [age](https://github.com/FiloSottile/age). I'll say more about this when I have more to say.
|
||||
|
||||
In the meantime though, looks like I need to expense a used Pixel 8 Pro to add to the testing jungle for Anubis. If anyone has a deal out there, please let me know!
|
||||
|
||||
Thank you to the people that have been polite and helpful when trying to root cause and fix this issue.
|
||||
BIN
docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp
Normal file
BIN
docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -10,15 +10,336 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
<!-- This changes the project to: -->
|
||||
|
||||
## v1.23.0: Lyse Hext
|
||||
|
||||
- Add default tencent cloud DENY rule.
|
||||
- Added `(data)/meta/default-config.yaml` for importing the entire default configuration at once.
|
||||
- Add `-custom-real-ip-header` flag to get the original request IP from a different header than `x-real-ip`.
|
||||
- Add `contentLength` variable to bot expressions.
|
||||
- Add `COOKIE_SAME_SITE_MODE` to force anubis cookies SameSite value, and downgrade automatically from `None` to `Lax` if cookie is insecure.
|
||||
- Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)).
|
||||
- Fix lock convoy problem in bbolt by implementing the actor pattern ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)).
|
||||
- Remove bbolt actorify implementation due to causing production issues.
|
||||
- Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086)).
|
||||
- Add validation warning when persistent storage is used without setting signing keys.
|
||||
- Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925)).
|
||||
- Make the `fast` algorithm prefer purejs when running in an insecure context.
|
||||
- Add the [`s3api` storage backend](./admin/policies.mdx#s3api) to allow Anubis to use S3 API compatible object storage as its storage backend.
|
||||
- Fix a "stutter" in the cookie name prefix so the auth cookie is named `techaro.lol-anubis-auth` instead of `techaro.lol-anubis-auth-auth`.
|
||||
- Make `cmd/containerbuild` support commas for separating elements of the `--docker-tags` argument as well as newlines.
|
||||
- Add the `DIFFICULTY_IN_JWT` option, which allows one to add the `difficulty` field in the JWT claims which indicates the difficulty of the token ([#1063](https://github.com/TecharoHQ/anubis/pull/1063)).
|
||||
- Ported the client-side JS to TypeScript to avoid egregious errors in the future.
|
||||
- Fixes concurrency problems with very old browsers ([#1082](https://github.com/TecharoHQ/anubis/issues/1082)).
|
||||
- Randomly use the Refresh header instead of the meta refresh tag in the metarefresh challenge.
|
||||
- Update OpenRC service to truncate the runtime directory before starting Anubis.
|
||||
- Make the git client profile more strictly match how the git client behaves.
|
||||
- Make the default configuration reward users using normal browsers.
|
||||
- Allow multiple consecutive slashes in a row in application paths ([#754](https://github.com/TecharoHQ/anubis/issues/754)).
|
||||
- Add option to set `targetSNI` to special keyword 'auto' to indicate that it should be automatically set to the request Host name ([424](https://github.com/TecharoHQ/anubis/issues/424)).
|
||||
- The Preact challenge has been removed from the default configuration. It will be deprecated in the future.
|
||||
- An open redirect when in subrequest mode has been fixed.
|
||||
|
||||
### Potentially breaking changes
|
||||
|
||||
#### Multiple checks at once has and-like semantics instead of or-like semantics
|
||||
|
||||
Anubis lets you stack multiple checks at once with blocks like this:
|
||||
|
||||
```yaml
|
||||
name: allow-prometheus
|
||||
action: ALLOW
|
||||
user_agent_regex: ^prometheus-probe$
|
||||
remote_addresses:
|
||||
- 192.168.2.0/24
|
||||
```
|
||||
|
||||
Previously, this only returned ALLOW if _any one_ of the conditions matched. This behaviour has changed to only return ALLOW if _all_ of the conditions match. I expect this to have some issues with user configs, however this fix is grave enough that it's worth the risk of breaking configs. If this bites you, please let me know so we can make an escape hatch.
|
||||
|
||||
### Better error messages
|
||||
|
||||
In order to make it easier for legitimate clients to debug issues with their browser configuration and Anubis, Anubis will emit internal error detail in base 64 so that administrators can chase down issues. Future versions of this may also include a variant that encrypts the error detail messages.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
Sometimes the enhanced temporal assurance in [#1038](https://github.com/TecharoHQ/anubis/pull/1038) and [#1068](https://github.com/TecharoHQ/anubis/pull/1068) could backfire because Chromium and its ilk randomize the amount of time they wait in order to avoid a timing side channel attack. This has been fixed by both increasing the amount of time a client has to wait for the metarefresh and preact challenges as well as making the server side logic more permissive.
|
||||
|
||||
## v1.22.0: Yda Hext
|
||||
|
||||
> Someone has to make an effort at reconciliation if these conflicts are ever going to end.
|
||||
|
||||
In this release, we finally fix the odd number of CPU cores bug, pave the way for lighter weight challenges, make Anubis more adaptable, and more.
|
||||
|
||||
### Big ticket items
|
||||
|
||||
#### Proof of React challenge
|
||||
|
||||
A new ["proof of React"](./admin/configuration/challenges/preact.mdx) has been added. It runs a simple app in React that has several chained hooks. It is much more lightweight than the proof of work check.
|
||||
|
||||
#### Smaller features
|
||||
|
||||
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.
|
||||
- Added possibility to disable HTTP keep-alive to support backends not properly handling it.
|
||||
- When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917))
|
||||
- One of the biggest sources of lag in Firefox has been eliminated: the use of WebCrypto. Now whenever Anubis detects the client is using Firefox (or Pale Moon), it will swap over to a pure-JS implementation of SHA-256 for speed.
|
||||
- Proof of work solving has had a complete overhaul and rethink based on feedback from browser engine developers, frontend experts, and overall performance profiling.
|
||||
- Optimize the performance of the pure-JS Anubis solver.
|
||||
- Web Workers are stored as dedicated JavaScript files in `static/js/workers/*.mjs`.
|
||||
- Pave the way for non-SHA256 solver methods and eventually one that uses WebAssembly (or WebAssembly code compiled to JS for those that disable WebAssembly).
|
||||
- Legacy JavaScript code has been eliminated.
|
||||
- When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work.
|
||||
- The hard dependency on WebCrypto has been removed, allowing a proof of work challenge to work over plain (unencrypted) HTTP.
|
||||
- The Anubis version number is put in the footer of every page.
|
||||
- Add a default block rule for Huawei Cloud.
|
||||
- Add a default block rule for Alibaba Cloud.
|
||||
- Added support to use Traefik forwardAuth middleware.
|
||||
- Add X-Request-URI support so that Subrequest Authentication has path support.
|
||||
- Added glob matching for `REDIRECT_DOMAINS`. You can pass `*.bugs.techaro.lol` to allow redirecting to anything ending with `.bugs.techaro.lol`. There is a limit of 4 wildcards.
|
||||
|
||||
### Fixes
|
||||
|
||||
#### Odd numbers of CPU cores are properly supported
|
||||
|
||||
Some phones have an odd number of CPU cores. This caused [interesting issues](https://anubis.techaro.lol/blog/2025/cpu-core-odd). This was fixed by [using `Math.trunc` to convert the number of CPU cores back into an integer](https://github.com/TecharoHQ/anubis/issues/1043).
|
||||
|
||||
#### Smaller fixes
|
||||
|
||||
- A standard library HTTP server log message about HTTP pipelining not working has been filtered out of Anubis' logs. There is no action that can be taken about it.
|
||||
- Added a missing link to the Caddy installation environment in the installation documentation.
|
||||
- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
|
||||
- The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
|
||||
- [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)).
|
||||
- Add option for replacing the default explanation text with a custom one ([#747](https://github.com/TecharoHQ/anubis/pull/747))
|
||||
- The contact email in the LibreJS header has been changed.
|
||||
- Firefox for Android support has been fixed by embedding the challenge ID into the pass-challenge route. This also fixes some inconsistent issues with other mobile browsers.
|
||||
- The default `favicon` pattern in `data/common/keep-internet-working.yaml` has been updated to permit requests for png/gif/jpg/svg files as well as ico.
|
||||
- The `--cookie-prefix` flag has been fixed so that it is fully respected.
|
||||
- The default patterns in `data/common/keep-internet-working.yaml` have been updated to appropriately escape the '.' character in the regular expression patterns.
|
||||
- Add optional restrictions for JWT based on the value of a header ([#697](https://github.com/TecharoHQ/anubis/pull/697))
|
||||
- The word "hack" has been removed from the translation strings for Anubis due to incidents involving people misunderstanding that word and sending particularly horrible things to the project lead over email.
|
||||
- Bump AI-robots.txt to version 1.39
|
||||
- Inject adversarial input to break AI coding assistants.
|
||||
- Add better logging when using Subrequest Authentication.
|
||||
|
||||
### Security-relevant changes
|
||||
|
||||
- Add a server-side check for the meta-refresh challenge that makes sure clients have waited for at least 95% of the time that they should.
|
||||
|
||||
#### Fix potential double-spend for challenges
|
||||
|
||||
Anubis operates by issuing a challenge and having the client present a solution for that challenge. Challenges are identified by a unique UUID, which is stored in the database.
|
||||
|
||||
The problem is that a challenge could potentially be used twice by a dedicated attacker making a targeted attack against Anubis. Challenge records did not have a "spent" or "used" field. In total, a dedicated attacker could solve a challenge once and reuse that solution across multiple sessions in order to mint additional tokens.
|
||||
|
||||
This was fixed by adding a "spent" field to challenges in the data store. When a challenge is solved, that "spent" field gets set to `true`. If a future attempt to solve this challenge is observed, it gets rejected.
|
||||
|
||||
With the advent of store based challenge issuance in [#749](https://github.com/TecharoHQ/anubis/pull/749), this means that these challenge IDs are [only good for 30 minutes](https://github.com/TecharoHQ/anubis/blob/e8dfff635015d6c906dddd49cb0eaf591326092a/lib/anubis.go#L130-L135d). Websites using the most recent version of Anubis have limited exposure to this problem.
|
||||
|
||||
Websites using older versions of Anubis have a much more increased exposure to this problem and are encouraged to keep this software updated as often and as frequently as possible.
|
||||
|
||||
Thanks to [@taviso](https://github.com/taviso) for reporting this issue.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- The "slow" frontend solver has been removed in order to reduce maintenance burden. Any existing uses of it will still work, but issue a warning upon startup asking administrators to upgrade to the "fast" frontend solver.
|
||||
- The legacy JSON based policy file example has been removed and all documentation for how to write a policy file in JSON has been deleted. JSON based policy files will still work, but YAML is the superior option for Anubis configuration.
|
||||
|
||||
### New Locales
|
||||
|
||||
- Lithuanian [#972](https://github.com/TecharoHQ/anubis/pull/972)
|
||||
- Vietnamese [#926](https://github.com/TecharoHQ/anubis/pull/926)
|
||||
|
||||
## v1.21.3: Minfilia Warde - Echo 3
|
||||
|
||||
### Added
|
||||
|
||||
#### New locales
|
||||
|
||||
Anubis now supports these new languages:
|
||||
|
||||
- [Swedish](https://github.com/TecharoHQ/anubis/pull/913)
|
||||
|
||||
### Fixes
|
||||
|
||||
#### Fixes a problem with nonstandard URLs and redirects
|
||||
|
||||
Fixes [GHSA-jhjj-2g64-px7c](https://github.com/TecharoHQ/anubis/security/advisories/GHSA-jhjj-2g64-px7c).
|
||||
|
||||
This could allow an attacker to craft an Anubis pass-challenge URL that forces a redirect to nonstandard URLs, such as the `javascript:` scheme which executes arbitrary JavaScript code in a browser context when the user clicks the "Try again" button.
|
||||
|
||||
This has been fixed by disallowing any URLs without the scheme `http` or `https`.
|
||||
|
||||
Additionally, the "Try again" button has been fixed to completely ignore the user-supplied redirect location. It now redirects to the home page (`/`).
|
||||
|
||||
## v1.21.2: Minfilia Warde - Echo 2
|
||||
|
||||
This contained an incomplete fix for [GHSA-jhjj-2g64-px7c](https://github.com/TecharoHQ/anubis/security/advisories/GHSA-jhjj-2g64-px7c). Do not use this version.
|
||||
|
||||
## v1.21.1: Minfilia Warde - Echo 1
|
||||
|
||||
- Expired records are now properly removed from bbolt databases ([#848](https://github.com/TecharoHQ/anubis/pull/848)).
|
||||
- Fix hanging on service restart ([#853](https://github.com/TecharoHQ/anubis/issues/853))
|
||||
|
||||
### Added
|
||||
|
||||
Anubis now supports the [`missingHeader`](./admin/configuration/expressions.mdx#missingHeader) to assert the absence of headers in requests.
|
||||
|
||||
#### New locales
|
||||
|
||||
Anubis now supports these new languages:
|
||||
|
||||
- [Czech](https://github.com/TecharoHQ/anubis/pull/849)
|
||||
- [Finnish](https://github.com/TecharoHQ/anubis/pull/863)
|
||||
- [Norwegian Bokmål](https://github.com/TecharoHQ/anubis/pull/855)
|
||||
- [Norwegian Nynorsk](https://github.com/TecharoHQ/anubis/pull/855)
|
||||
- [Russian](https://github.com/TecharoHQ/anubis/pull/882)
|
||||
|
||||
### Fixes
|
||||
|
||||
#### Fix ["error: can't get challenge"](https://github.com/TecharoHQ/anubis/issues/869) when details about a challenge can't be found in the server side state
|
||||
|
||||
v1.21.0 changed the core challenge flow to maintain information about challenges on the server side instead of only doing them via stateless idempotent generation functions and relying on details to not change. There was a subtle bug introduced in this change: if a client has an unknown challenge ID set in its test cookie, Anubis will clear that cookie and then throw an HTTP 500 error.
|
||||
|
||||
This has been fixed by making Anubis throw a new challenge page instead.
|
||||
|
||||
#### Fix event loop thrashing when solving a proof of work challenge
|
||||
|
||||
Previously the "fast" proof of work solver had a fragment of JavaScript that attempted to only post an update about proof of work progress to the main browser window every 1024 iterations. This fragment of JavaScript was subtly incorrect in a way that passed review but actually made the workers send an update back to the main thread every iteration. This caused a pileup of unhandled async calls (similar to a socket accept() backlog pileup in Unix) that caused stack space exhaustion.
|
||||
|
||||
This has been fixed in the following ways:
|
||||
|
||||
1. The complicated boolean logic has been totally removed in favour of a worker-local iteration counter.
|
||||
2. The progress bar is updated by worker `0` instead of all workers.
|
||||
|
||||
Hopefully this should limit the event loop thrashing and let ia32 browsers (as well as any environment with a smaller stack size than amd64 and aarch64 seem to have) function normally when processing Anubis proof of work challenges.
|
||||
|
||||
#### Fix potential memory leak when discovering a solution
|
||||
|
||||
In some cases, the parallel solution finder in Anubis could cause all of the worker promises to leak due to the fact the promises were being improperly terminated. This was fixed by having Anubis debounce worker termination instead of allowing it to potentially recurse infinitely.
|
||||
|
||||
## v1.21.0: Minfilia Warde
|
||||
|
||||
> Please, be at ease. You are among friends here.
|
||||
|
||||
In this release, Anubis becomes internationalized, gains the ability to use system load as input to issuing challenges, finally fixes the "invalid response" after "success" bug, and more! Please read these notes before upgrading as the changes are big enough that administrators should take action to ensure that the upgrade goes smoothly.
|
||||
|
||||
### Big ticket changes
|
||||
|
||||
The biggest change is that the ["invalid response" after "success" bug](https://github.com/TecharoHQ/anubis/issues/564) is now finally fixed for good by totally rewriting how Anubis' challenge issuance flow works. Instead of generating challenge strings from request metadata (under the assumption that the values being compared against are stable), Anubis now generates random data for each challenge. This data is stored in the active [storage backend](./admin/policies.mdx#storage-backends) for up to 30 minutes. This also fixes [#746](https://github.com/TecharoHQ/anubis/issues/746) and other similar instances of this issue.
|
||||
|
||||
In order to reduce confusion, the "Success" interstitial that shows up when you pass a proof of work challenge has been removed.
|
||||
|
||||
#### Storage
|
||||
|
||||
Anubis now is able to store things persistently [in memory](./admin/policies.mdx#memory), [on the disk](./admin/policies.mdx#bbolt), or [in Valkey](./admin/policies.mdx#valkey) (this includes other compatible software). By default Anubis uses the in-memory backend. If you have an environment with mutable storage (even if it is temporary), be sure to configure the [`bbolt`](./admin/policies.mdx#bbolt) storage backend.
|
||||
|
||||
#### Localization
|
||||
|
||||
Anubis now supports localized responses. Locales can be added in [lib/localization/locales/](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). This release includes support for the following languages:
|
||||
|
||||
- [Brazilian Portugese](https://github.com/TecharoHQ/anubis/pull/726)
|
||||
- [Chinese (Simplified)](https://github.com/TecharoHQ/anubis/pull/774)
|
||||
- [Chinese (Traditional)](https://github.com/TecharoHQ/anubis/pull/759)
|
||||
- English
|
||||
- [Estonian](https://github.com/TecharoHQ/anubis/pull/783)
|
||||
- [Filipino](https://github.com/TecharoHQ/anubis/pull/775)
|
||||
- [French](https://github.com/TecharoHQ/anubis/pull/716)
|
||||
- [German](https://github.com/TecharoHQ/anubis/pull/741)
|
||||
- [Icelandic](https://github.com/TecharoHQ/anubis/pull/780)
|
||||
- [Italian](https://github.com/TecharoHQ/anubis/pull/778)
|
||||
- [Japanese](https://github.com/TecharoHQ/anubis/pull/772)
|
||||
- [Spanish](https://github.com/TecharoHQ/anubis/pull/716)
|
||||
- [Turkish](https://github.com/TecharoHQ/anubis/pull/751)
|
||||
|
||||
If facts or local regulations demand, you can set Anubis default language with the `FORCED_LANGUAGE` environment variable or the `--forced-language` command line argument:
|
||||
|
||||
```sh
|
||||
FORCED_LANGUAGE=de
|
||||
```
|
||||
|
||||
#### Load average
|
||||
|
||||
Anubis can dynamically take action [based on the system load average](./admin/configuration/expressions.mdx#using-the-system-load-average), allowing you to write rules like this:
|
||||
|
||||
```yaml
|
||||
## System load based checks.
|
||||
# If the system is under high load for the last minute, 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 it is not for the last 15 minutes, 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
|
||||
```
|
||||
|
||||
Something to keep in mind about system load average is that it is not aware of the number of cores the system has. If you have a 16 core system that has 16 processes running but none of them is hogging the CPU, then you will get a load average below 16. If you are in doubt, make your "high load" metric at least two times the number of CPU cores and your "low load" metric at least half of the number of CPU cores. For example:
|
||||
|
||||
| Kind | Core count | Load threshold |
|
||||
| --------: | :--------- | :------------- |
|
||||
| high load | 4 | `8.0` |
|
||||
| low load | 4 | `2.0` |
|
||||
| high load | 16 | `32.0` |
|
||||
| low load | 16 | `8` |
|
||||
|
||||
Also keep in mind that this does not account for other kinds of latency like I/O latency. A system can have its web applications unresponsive due to high latency from a MySQL server but still have that web application server report a load near or at zero.
|
||||
|
||||
### Other features and fixes
|
||||
|
||||
There are a bunch of other assorted features and fixes too:
|
||||
|
||||
- Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies)
|
||||
- Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained)
|
||||
|
||||
- Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)).
|
||||
- Implement localization system. Find locale files in lib/localization/locales/.
|
||||
- Implement a [development container](https://containers.dev/) manifest to make contributions easier.
|
||||
- Fix dynamic cookie domains functionality ([#731](https://github.com/TecharoHQ/anubis/pull/731))
|
||||
- Add option for custom cookie prefix ([#732](https://github.com/TecharoHQ/anubis/pull/732))
|
||||
- Make the [Open Graph](./admin/configuration/open-graph.mdx) subsystem and DNSBL subsystem use [storage backends](./admin/policies.mdx#storage-backends) instead of storing everything in memory by default.
|
||||
- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape
|
||||
- The [bbolt storage backend](./admin/policies.mdx#bbolt) now runs its cleanup every hour instead of every five minutes.
|
||||
- Don't block Anubis starting up if [Thoth](./admin/thoth.mdx) health checks fail.
|
||||
- A race condition involving [opening two challenge pages at once in different tabs](https://github.com/TecharoHQ/anubis/issues/832) causing one of them to fail has been fixed.
|
||||
- The "Try again" button on the error page has been fixed. Previously it meant "try the solution again" instead of "try the challenge again".
|
||||
- In certain cases, a user could be stuck with a test cookie that is invalid, locking them out of the service for up to half an hour. This has been fixed with better validation of this case and clearing the cookie.
|
||||
- Start exposing JA4H fingerprints for later use in CEL expressions.
|
||||
- Add `/healthz` route for use in platform-based health checks.
|
||||
|
||||
### Potentially breaking changes
|
||||
|
||||
We try to introduce breaking changes as much as possible, but these are the changes that may be relevant for you as an administrator:
|
||||
|
||||
#### Challenge format change
|
||||
|
||||
Previously Anubis did no accounting for challenges that it issued. This means that if Anubis restarted during a client, the client would be able to proceed once Anubis came back online.
|
||||
|
||||
During the upgrade to v1.21.0 and when v1.21.0 (or later) restarts with the [in-memory storage backend](./admin/policies.mdx#memory), you may see a higher rate of failed challenges than normal. If this persists beyond a few minutes, [open an issue](https://github.com/TecharoHQ/anubis/issues/new).
|
||||
|
||||
If you are using the in-memory storage backend, please consider using [a different storage backend](./admin/policies.mdx#storage-backends).
|
||||
|
||||
#### Systemd service changes
|
||||
|
||||
The following potentially breaking change applies to native installs with systemd only:
|
||||
|
||||
Each instance of systemd service template now has a unique `RuntimeDirectory`, as opposed to each instance of the service sharing a `RuntimeDirectory`. This change was made to avoid [the `RuntimeDirectory` getting nuked any time one of the Anubis instances restarts](https://github.com/TecharoHQ/anubis/issues/748).
|
||||
|
||||
If you configured Anubis' unix sockets to listen on `/run/anubis/foo.sock` for instance `anubis@foo`, you will need to configure Anubis to listen on `/run/anubis/foo/foo.sock` and additionally configure your HTTP load balancer as appropriate.
|
||||
|
||||
If you need the legacy behaviour, install this [systemd unit dropin](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/):
|
||||
|
||||
```systemd
|
||||
# /etc/systemd/system/anubis@.service.d/50-runtimedir.conf
|
||||
[Service]
|
||||
RuntimeDirectory=anubis
|
||||
```
|
||||
|
||||
Just keep in mind that this will cause problems when Anubis restarts.
|
||||
|
||||
## v1.20.0: Thancred Waters
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Proof-of-Work Algorithm Selection
|
||||
---
|
||||
|
||||
Anubis offers two proof-of-work algorithms:
|
||||
|
||||
- `"fast"`: highly optimized JavaScript that will run as fast as your computer lets it
|
||||
- `"slow"`: intentionally slow JavaScript that will waste time and memory
|
||||
|
||||
The fast algorithm is used by default to limit impacts on users' computers. Administrators may configure individual bot policy rules to use the slow algorithm in order to make known malicious clients waitloop and do nothing useful.
|
||||
|
||||
Generally, you should use the fast algorithm unless you have a good reason not to.
|
||||
341
docs/docs/admin/botstopper.mdx
Normal file
341
docs/docs/admin/botstopper.mdx
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
title: "Commercial support and an unbranded version"
|
||||
---
|
||||
|
||||
If you want to use Anubis but organizational policies prevent you from using the branding that the open source project ships, we offer a commercial version of Anubis named BotStopper. BotStopper builds off of the open source core of Anubis and offers organizations more control over the branding, including but not limited to:
|
||||
|
||||
- Custom images for different states of the challenge process (in process, success, failure)
|
||||
- Custom CSS and fonts
|
||||
- Custom titles for the challenge and error pages
|
||||
- "Anubis" replaced with "BotStopper" across the UI
|
||||
- A private bug tracker for issues
|
||||
|
||||
In the near future this will expand to:
|
||||
|
||||
- A private challenge implementation that does advanced fingerprinting to check if the client is a genuine browser or not
|
||||
- Advanced fingerprinting via [Thoth-based advanced checks](./thoth.mdx)
|
||||
|
||||
In order to sign up for BotStopper, please do one of the following:
|
||||
|
||||
- Sign up [on GitHub Sponsors](https://github.com/sponsors/Xe) at the $50 per month tier or higher
|
||||
- Email [sales@techaro.lol](mailto:sales@techaro.lol) with your requirements for invoicing, please note that custom invoicing will cost more than using GitHub Sponsors for understandable overhead reasons
|
||||
|
||||
## Installation
|
||||
|
||||
Install BotStopper like you would Anubis, but replace the image reference. EG:
|
||||
|
||||
```diff
|
||||
-ghcr.io/techarohq/anubis:latest
|
||||
+ghcr.io/techarohq/botstopper/anubis:latest
|
||||
```
|
||||
|
||||
### Binary packages
|
||||
|
||||
Binary packages are available [in the GitHub Releases page](https://github.com/TecharoHQ/botstopper/releases), the main difference is that the package name is `techaro-botstopper`, the systemd service is `techaro-botstopper@your-instance.service`, the binary is `/usr/bin/botstopper`, and the configuration is in `/etc/techaro-botstopper`. All other instructions in the [native package install guide](./native-install.mdx) apply.
|
||||
|
||||
### Docker / Podman
|
||||
|
||||
In order to pull the BotStopper image, you need to [authenticate with GitHub's Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry).
|
||||
|
||||
```text
|
||||
docker login ghcr.io -u your-username --password-stdin
|
||||
```
|
||||
|
||||
Then you can use the image as normal.
|
||||
|
||||
### Kubernetes
|
||||
|
||||
If you are using Kubernetes, you will need to create an image pull secret:
|
||||
|
||||
```text
|
||||
kubectl create secret docker-registry \
|
||||
techarohq-botstopper \
|
||||
--docker-server ghcr.io \
|
||||
--docker-username your-username \
|
||||
--docker-password your-access-token \
|
||||
--docker-email your@email.address
|
||||
```
|
||||
|
||||
Then attach it to your Deployment:
|
||||
|
||||
```diff
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
+ imagePullSecrets:
|
||||
+ - name: techarohq-botstopper
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Docker compose
|
||||
|
||||
Follow [the upstream Docker compose directions](https://anubis.techaro.lol/docs/admin/environments/docker-compose) with the following additional options:
|
||||
|
||||
```diff
|
||||
anubis:
|
||||
image: ghcr.io/techarohq/botstopper/anubis:latest
|
||||
environment:
|
||||
BIND: ":8080"
|
||||
DIFFICULTY: "4"
|
||||
METRICS_BIND: ":9090"
|
||||
SERVE_ROBOTS_TXT: "true"
|
||||
TARGET: "http://nginx"
|
||||
OG_PASSTHROUGH: "true"
|
||||
OG_EXPIRY_TIME: "24h"
|
||||
|
||||
+ # botstopper config here
|
||||
+ CHALLENGE_TITLE: "Doing math for your connnection!"
|
||||
+ ERROR_TITLE: "Something went wrong!"
|
||||
+ OVERLAY_FOLDER: /assets
|
||||
+ volumes:
|
||||
+ - "./your_folder:/assets"
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
There is an example in [docker-compose.yaml](https://github.com/TecharoHQ/botstopper/blob/main/docker-compose.yaml). Start the example with `docker compose up`:
|
||||
|
||||
```text
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
And then open [https://botstopper.local.cetacean.club:8443](https://botstopper.local.cetacean.club:8443) in your browser.
|
||||
|
||||
> [!NOTE]
|
||||
> This uses locally signed sacrificial TLS certificates stored in `./demo/pki`. Your browser will rightly reject these. Here is what the example looks like:
|
||||
>
|
||||
> 
|
||||
|
||||
## Custom images and CSS
|
||||
|
||||
Anubis uses an internal filesystem that contains CSS, JavaScript, and images. The BotStopper variant of Anubis lets you specify an overlay folder with the environment variable `OVERLAY_FOLDER`. The contents of this folder will be overlaid on top of Anubis' internal filesystem, allowing you to easily customize the images and CSS.
|
||||
|
||||
Your directory tree should look like this, assuming your data is in `./your_folder`:
|
||||
|
||||
```text
|
||||
./your_folder
|
||||
└── static
|
||||
├── css
|
||||
│ └── custom.css
|
||||
└── img
|
||||
├── happy.webp
|
||||
├── pensive.webp
|
||||
└── reject.webp
|
||||
```
|
||||
|
||||
For an example directory tree using some off-the-shelf images the Tango icon set, see the [testdata](https://github.com/TecharoHQ/botstopper/tree/main/testdata/static/img) folder.
|
||||
|
||||
### Header-based overlay dispatch
|
||||
|
||||
If you run BotStopper in a multi-tenant environment where each tenant needs its own branding, BotStopper supports the ability to use request header values to direct asset reads to different folders under your `OVERLAY_FOLDER`. One of the most common ways to do this is based on the HTTP Host of the request. For example, if you set `ASSET_LOOKUP_HEADER=Host` in BotStopper's environment:
|
||||
|
||||
```text
|
||||
$OVERLAY_FOLDER
|
||||
├── static
|
||||
│ ├── css
|
||||
│ │ ├── custom.css
|
||||
│ │ └── eyesore.css
|
||||
│ └── img
|
||||
│ ├── happy.webp
|
||||
│ ├── pensive.webp
|
||||
│ └── reject.webp
|
||||
└── test.anubis.techaro.lol
|
||||
└── static
|
||||
├── css
|
||||
│ └── custom.css
|
||||
└── img
|
||||
├── happy.webp
|
||||
├── pensive.webp
|
||||
└── reject.webp
|
||||
```
|
||||
|
||||
Requests to `test.anubis.techaro.lol` will load assets in `$OVERLAY_FOLDER/test.anubis.techaro.lol/static` and all other requests will load them from `$OVERLAY_FOLDER/static`.
|
||||
|
||||
For an example, look at [the testdata folder in the BotStopper repo](https://github.com/TecharoHQ/botstopper/tree/main/testdata).
|
||||
|
||||
### Custom CSS
|
||||
|
||||
CSS customization is done mainly with CSS variables. View [the example custom CSS file](https://github.com/TecharoHQ/botstopper/blob/main/testdata/static/css/custom.css) for more information about what can be customized.
|
||||
|
||||
### Custom fonts
|
||||
|
||||
If you want to add custom fonts, copy the `woff2` files alongside your `custom.css` file and then include them with the [`@font-face` CSS at-rule](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face):
|
||||
|
||||
```css
|
||||
@font-face {
|
||||
font-family: "Oswald";
|
||||
font-style: normal;
|
||||
font-weight: 200 900;
|
||||
font-display: swap;
|
||||
src: url("./fonts/oswald.woff2") format("woff2");
|
||||
}
|
||||
```
|
||||
|
||||
Then adjust your CSS variables accordingly:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--body-sans-font: Oswald, sans-serif;
|
||||
--body-preformatted-font: monospace;
|
||||
--body-title-font: serif;
|
||||
}
|
||||
```
|
||||
|
||||
To convert `.ttf` fonts to [Web-optimized woff2 fonts](https://www.w3.org/TR/WOFF2/), use the `woff2_compress` command from the `woff2` or `woff2-tools` package:
|
||||
|
||||
```console
|
||||
$ woff2_compress oswald.ttf
|
||||
Processing oswald.ttf => oswald.woff2
|
||||
Compressed 159517 to 70469.
|
||||
```
|
||||
|
||||
Then you can import and use it as normal.
|
||||
|
||||
### Customizing images
|
||||
|
||||
Anubis uses three images to visually communicate the state of the program. These are:
|
||||
|
||||
| Image name | Intended message | Example |
|
||||
| :------------- | :----------------------------------------------- | :-------------------------------- |
|
||||
| `happy.webp` | You have passed validation, all is good |  |
|
||||
| `pensive.webp` | Checking is running, hold steady until it's done |  |
|
||||
| `reject.webp` | Something went wrong, this is a terminal state |  |
|
||||
|
||||
To make your own images at the optimal quality, use the following ffmpeg command:
|
||||
|
||||
```text
|
||||
ffmpeg -i /path/to/image -vf scale=-1:384 happy.webp
|
||||
```
|
||||
|
||||
`ffprobe` should report something like this on the generated images:
|
||||
|
||||
```text
|
||||
Input #0, webp_pipe, from 'happy.webp':
|
||||
Duration: N/A, bitrate: N/A
|
||||
Stream #0:0: Video: webp, none, 25 fps, 25 tbr, 25 tbn
|
||||
```
|
||||
|
||||
In testing 384 by 384 pixels gives the best balance between filesize, quality, and clarity.
|
||||
|
||||
```text
|
||||
$ du -hs *
|
||||
4.0K happy.webp
|
||||
12K pensive.webp
|
||||
8.0K reject.webp
|
||||
```
|
||||
|
||||
## Custom HTML templates
|
||||
|
||||
If you need to completely control the HTML layout of all Anubis pages, you can customize the entire page with `USE_TEMPLATES=true`. This uses Go's standard library [html/template](https://pkg.go.dev/html/template) package to template HTML responses. Your templates can contain whatever HTML you want. The only catch is that you MUST include `{{ .Head }}` in the `<head>` element for challenge pages, and you MUST include `{{ .Body }}` in the `<body>` element for all pages.
|
||||
|
||||
In order to use this, you must define the following templates:
|
||||
|
||||
| Template path | Usage |
|
||||
| :----------------------------------------- | :---------------------------------------------- |
|
||||
| `$OVERLAY_FOLDER/templates/challenge.tmpl` | Challenge pages |
|
||||
| `$OVERLAY_FOLDER/templates/error.tmpl` | Error pages |
|
||||
| `$OVERLAY_FOLDER/templates/impressum.tmpl` | [Impressum](./configuration/impressum.mdx) page |
|
||||
|
||||
:::note
|
||||
|
||||
Currently HTML templates don't work together with [Header-based overlay dispatch](#header-based-overlay-dispatch). This is a known issue that will be fixed soon. If you enable header-based overlay dispatch, BotStopper will use the global `templates` folder instead of using the templates present in the overlay.
|
||||
|
||||
:::
|
||||
|
||||
Here are minimal (but working) examples for each template:
|
||||
|
||||
<details>
|
||||
<summary>`challenge.tmpl`</summary>
|
||||
|
||||
:::note
|
||||
|
||||
You **MUST** include the `{{.Head}}` segment in a `<head>` tag. It contains important information for challenges to execute. If you don't include this, no clients will be able to pass challenges.
|
||||
|
||||
:::
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}">
|
||||
<head>
|
||||
{{ .Head }}
|
||||
</head>
|
||||
<body>
|
||||
{{ .Body }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>`error.tmpl`</summary>
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}">
|
||||
<body>
|
||||
{{ .Body }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>`impressum.tmpl`</summary>
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}">
|
||||
<body>
|
||||
{{ .Body }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Template functions
|
||||
|
||||
In order to make life easier, the following template functions are defined:
|
||||
|
||||
#### `Asset`
|
||||
|
||||
Constructs the path for a static asset in the [overlay folder](#custom-images-and-css)'s `static` directory.
|
||||
|
||||
```go
|
||||
func Asset(string) string
|
||||
```
|
||||
|
||||
Usage:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="{{ Asset "css/example.css" }}" />
|
||||
```
|
||||
|
||||
Generates:
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/.within.website/x/cmd/anubis/static/css/example.css"
|
||||
/>
|
||||
```
|
||||
|
||||
## Customizing messages
|
||||
|
||||
You can customize messages using the following environment variables:
|
||||
|
||||
| Message | Environment variable | Default |
|
||||
| :------------------- | :------------------- | :----------------------------------------- |
|
||||
| Challenge page title | `CHALLENGE_TITLE` | `Ensuring the security of your connection` |
|
||||
| Error page title | `ERROR_TITLE` | `Error` |
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
# /etc/techaro-botstopper/gitea.env
|
||||
CHALLENGE_TITLE="Wait a moment please!"
|
||||
ERROR_TITLE="Client error"
|
||||
```
|
||||
27
docs/docs/admin/caveats-xff.mdx
Normal file
27
docs/docs/admin/caveats-xff.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
# Client IP Headers
|
||||
|
||||
Currently Anubis will always flatten the `X-Forwarded-For` when it contains multiple IP addresses. From right to left, the first IP address that is not in one of the following categories will be set as `X-Forwarded-For` in the request passed to the upstream.
|
||||
|
||||
- Private (`XFF_STRIP_PRIVATE`, enabled by default)
|
||||
- CGNAT (always stripped)
|
||||
- Link-local Unicast (always stripped)
|
||||
|
||||
```
|
||||
Incoming: X-Forwarded-For: 1.2.3.4, 5.6.7.8, 10.0.0.1
|
||||
Upstream: X-Forwarded-For: 5.6.7.8
|
||||
```
|
||||
|
||||
This behavior will cause problems if the proxy in front of Anubis is from a public IP, such as Cloudflare, because Anubis will use the Cloudflare IP instead of your client's real IP. You will likely see all requests from your browser being blocked and/or an infinite challenge loop.
|
||||
|
||||
```
|
||||
Incoming: X-Forwarded-For: REAL_CLIENT_IP, CF_IP
|
||||
Upstream: X-Forwarded-For: CF_IP
|
||||
```
|
||||
|
||||
As a workaround, you should configure your web server to parse an alternative source (such as `CF-Connecting-IP`), or pre-process the incoming `X-Forwarded-For` with your web server to ensure it only contains the real client IP address, then pass it to Anubis as `X-Forwarded-For`.
|
||||
|
||||
If you do not control the web server upstream of Anubis, the `custom-real-ip-header` command line flag accepts a header value that Anubis will read the real client IP address from. Anubis will set the `X-Real-IP` header to the IP address found in the custom header.
|
||||
|
||||
The `X-Real-IP` header will be automatically inferred from `X-Forwarded-For` if not set, setting it explicitly is not necessary as long as `X-Forwarded-For` contains only the real client IP. However setting it explicitly can eliminate spoofed values if your web server doesn't set this.
|
||||
|
||||
See [Cloudflare](environments/cloudflare.mdx) for an example configuration.
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"label": "Challenges",
|
||||
"position": 10,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "The different challenge methods that Anubis supports."
|
||||
}
|
||||
"link": null
|
||||
}
|
||||
9
docs/docs/admin/configuration/challenges/index.mdx
Normal file
9
docs/docs/admin/configuration/challenges/index.mdx
Normal file
@@ -0,0 +1,9 @@
|
||||
# Challenge Methods
|
||||
|
||||
Anubis supports multiple challenge methods:
|
||||
|
||||
- [Meta Refresh](./metarefresh.mdx)
|
||||
- [Preact](./preact.mdx)
|
||||
- [Proof of Work](./proof-of-work.mdx)
|
||||
|
||||
Read the documentation to know which method is best for you.
|
||||
19
docs/docs/admin/configuration/challenges/preact.mdx
Normal file
19
docs/docs/admin/configuration/challenges/preact.mdx
Normal file
@@ -0,0 +1,19 @@
|
||||
# Preact
|
||||
|
||||
The `preact` challenge sends the browser a simple challenge that makes it run very lightweight JavaScript that proves the client is able to execute client-side JavaScript. It uses [Preact](https://www.npmjs.com/package/preact) (a lightweight client side web framework in the vein of React) to do this.
|
||||
|
||||
To use it in your Anubis configuration:
|
||||
|
||||
```yaml
|
||||
# Generic catchall rule
|
||||
- name: generic-browser
|
||||
user_agent_regex: >-
|
||||
Mozilla|Opera
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
difficulty: 1 # Number of seconds to wait before refreshing the page
|
||||
report_as: 4 # Unused by this challenge method
|
||||
algorithm: preact
|
||||
```
|
||||
|
||||
This is the default challenge method for most clients.
|
||||
@@ -77,7 +77,7 @@ For example, consider this rule:
|
||||
|
||||
For this rule, if a request comes in from `8.8.8.8` or `1.1.1.1`, Anubis will deny the request and return an error page.
|
||||
|
||||
#### `all` blocks
|
||||
### `all` blocks
|
||||
|
||||
An `all` block that contains a list of expressions. If all expressions in the list return `true`, then the action specified in the rule will be taken. If any of the expressions in the list returns `false`, Anubis will move on to the next rule.
|
||||
|
||||
@@ -99,15 +99,19 @@ For this rule, if a request comes in matching [the signature of the `go get` com
|
||||
|
||||
Anubis exposes the following variables to expressions:
|
||||
|
||||
| Name | Type | Explanation | Example |
|
||||
| :-------------- | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
|
||||
| `headers` | `map[string, string]` | The [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) of the request being processed. | `{"User-Agent": "Mozilla/5.0 Gecko/20100101 Firefox/137.0"}` |
|
||||
| `host` | `string` | The [HTTP hostname](https://web.dev/articles/url-parts#host) the request is targeted to. | `anubis.techaro.lol` |
|
||||
| `method` | `string` | The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) in the request being processed. | `GET`, `POST`, `DELETE`, etc. |
|
||||
| `path` | `string` | The [path](https://web.dev/articles/url-parts#pathname) of the request being processed. | `/`, `/api/memes/create` |
|
||||
| `query` | `map[string, string]` | The [query parameters](https://web.dev/articles/url-parts#query) of the request being processed. | `?foo=bar` -> `{"foo": "bar"}` |
|
||||
| `remoteAddress` | `string` | The IP address of the client. | `1.1.1.1` |
|
||||
| `userAgent` | `string` | The [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent) string in the request being processed. | `Mozilla/5.0 Gecko/20100101 Firefox/137.0` |
|
||||
| Name | Type | Explanation | Example |
|
||||
| :-------------- | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
|
||||
| `headers` | `map[string, string]` | The [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) of the request being processed. | `{"User-Agent": "Mozilla/5.0 Gecko/20100101 Firefox/137.0"}` |
|
||||
| `host` | `string` | The [HTTP hostname](https://web.dev/articles/url-parts#host) the request is targeted to. | `anubis.techaro.lol` |
|
||||
| `contentLength` | `int64` | The numerical value of the `Content-Length` header. |
|
||||
| `load_1m` | `double` | The current system load average over the last one minute. This is useful for making [load-based checks](#using-the-system-load-average). |
|
||||
| `load_5m` | `double` | The current system load average over the last five minutes. This is useful for making [load-based checks](#using-the-system-load-average). |
|
||||
| `load_15m` | `double` | The current system load average over the last fifteen minutes. This is useful for making [load-based checks](#using-the-system-load-average). |
|
||||
| `method` | `string` | The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) in the request being processed. | `GET`, `POST`, `DELETE`, etc. |
|
||||
| `path` | `string` | The [path](https://web.dev/articles/url-parts#pathname) of the request being processed. | `/`, `/api/memes/create` |
|
||||
| `query` | `map[string, string]` | The [query parameters](https://web.dev/articles/url-parts#query) of the request being processed. | `?foo=bar` -> `{"foo": "bar"}` |
|
||||
| `remoteAddress` | `string` | The IP address of the client. | `1.1.1.1` |
|
||||
| `userAgent` | `string` | The [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent) string in the request being processed. | `Mozilla/5.0 Gecko/20100101 Firefox/137.0` |
|
||||
|
||||
Of note: in many languages when you look up a key in a map and there is nothing there, the language will return some "falsy" value like `undefined` in JavaScript, `None` in Python, or the zero value of the type in Go. In CEL, if you try to look up a value that does not exist, execution of the expression will fail and Anubis will return an error.
|
||||
|
||||
@@ -120,7 +124,7 @@ In order to avoid this, make sure the header or query parameter you are testing
|
||||
- 'path == "/index.php"'
|
||||
- '"title" in query'
|
||||
- '"action" in query'
|
||||
- 'query["action"] == "history"
|
||||
- 'query["action"] == "history"'
|
||||
```
|
||||
|
||||
This rule throws a challenge if and only if all of the following conditions are true:
|
||||
@@ -141,12 +145,74 @@ X-Real-Ip: 8.8.8.8
|
||||
|
||||
Anubis would return a challenge because all of those conditions are true.
|
||||
|
||||
### Using the system load average
|
||||
|
||||
In Unix-like systems (such as Linux), every process on the system has to wait its turn to be able to run. This means that as more processes on the system are running, they need to wait longer to be able to execute. The [load average](<https://en.wikipedia.org/wiki/Load_(computing)>) represents the number of processes that want to be able to run but can't run yet. This metric isn't the most reliable to identify a cause, but is great at helping to identify symptoms.
|
||||
|
||||
Anubis lets you use the system load average as an input to expressions so that you can make dynamic rules like "when the system is under a low amount of load, dial back the protection, but when it's under a lot of load, crank it up to the mix". This lets you get all of the blocking features of Anubis in the background but only really expose Anubis to users when the system is actively being attacked.
|
||||
|
||||
This is best combined with the [weight](../policies.mdx#request-weight) and [threshold](./thresholds.mdx) systems so that you can have Anubis dynamically respond to attacks. Consider these rules in the default configuration file:
|
||||
|
||||
```yaml
|
||||
## System load based checks.
|
||||
# If the system is under high load for the last minute, 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 it is not for the last 15 minutes, 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
|
||||
```
|
||||
|
||||
This combination of rules makes Anubis dynamically react to the system load and only kick in when the system is under attack.
|
||||
|
||||
Something to keep in mind about system load average is that it is not aware of the number of cores the system has. If you have a 16 core system that has 16 processes running but none of them is hogging the CPU, then you will get a load average below 16. If you are in doubt, make your "high load" metric at least two times the number of CPU cores and your "low load" metric at least half of the number of CPU cores. For example:
|
||||
|
||||
| Kind | Core count | Load threshold |
|
||||
| --------: | :--------- | :------------- |
|
||||
| high load | 4 | `8.0` |
|
||||
| low load | 4 | `2.0` |
|
||||
| high load | 16 | `32.0` |
|
||||
| low load | 16 | `8` |
|
||||
|
||||
Also keep in mind that this does not account for other kinds of latency like I/O latency. A system can have its web applications unresponsive due to high latency from a MySQL server but still have that web application server report a load near or at zero.
|
||||
|
||||
## Functions exposed to Anubis expressions
|
||||
|
||||
Anubis expressions can be augmented with the following functions:
|
||||
|
||||
### `missingHeader`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function missingHeader(headers: Record<string, string>, key: string) bool
|
||||
```
|
||||
|
||||
`missingHeader` returns `true` if the request does not contain a header. This is useful when you are trying to assert behavior such as:
|
||||
|
||||
```yaml
|
||||
# Adds weight to old versions of Chrome
|
||||
- name: old-chrome
|
||||
action: WEIGH
|
||||
weight:
|
||||
adjust: 10
|
||||
expression:
|
||||
all:
|
||||
- userAgent.matches("Chrome/[1-9][0-9]?\\.0\\.0\\.0")
|
||||
- missingHeader(headers, "Sec-Ch-Ua")
|
||||
```
|
||||
|
||||
### `randInt`
|
||||
|
||||
Available in all expressions.
|
||||
|
||||
```ts
|
||||
function randInt(n: int): int;
|
||||
```
|
||||
@@ -167,6 +233,39 @@ This is best applied when doing explicit block rules, eg:
|
||||
|
||||
It seems counter-intuitive to allow known bad clients through sometimes, but this allows you to confuse attackers by making Anubis' behavior random. Adjust the thresholds and numbers as facts and circumstances demand.
|
||||
|
||||
### `segments`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function segments(path: string): string[];
|
||||
```
|
||||
|
||||
`segments` returns the number of slash-separated path segments, ignoring the leading slash. Here is what it will return with some common paths:
|
||||
|
||||
| Input | Output |
|
||||
| :----------------------- | :--------------------- |
|
||||
| `segments("/")` | `[""]` |
|
||||
| `segments("/foo/bar")` | `["foo", "bar"] ` |
|
||||
| `segments("/users/xe/")` | `["users", "xe", ""] ` |
|
||||
|
||||
:::note
|
||||
|
||||
If the path ends with a `/`, then the last element of the result will be an empty string. This is because `/users/xe` and `/users/xe/` are semantically different paths.
|
||||
|
||||
:::
|
||||
|
||||
This is useful if you want to write rules that allow requests that have no query parameters only if they have less than two path segments:
|
||||
|
||||
```yaml
|
||||
- name: two-path-segments-no-query
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- size(query) == 0
|
||||
- size(segments(path)) < 2
|
||||
```
|
||||
|
||||
## Life advice
|
||||
|
||||
Expressions are very powerful. This is a benefit and a burden. If you are not careful with your expression targeting, you will be liable to get yourself into trouble. If you are at all in doubt, throw a `CHALLENGE` over a `DENY`. Legitimate users can easily work around a `CHALLENGE` result with a [proof of work challenge](../../design/why-proof-of-work.mdx). Bots are less likely to be able to do this.
|
||||
|
||||
@@ -7,25 +7,6 @@ Anubis has the ability to let you import snippets of configuration into the main
|
||||
|
||||
EG:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/ai-catchall.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/bots/cloudflare-workers.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
# Pathological bots to deny
|
||||
@@ -34,30 +15,8 @@ bots:
|
||||
- import: (data)/bots/cloudflare-workers.yaml
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Of note, a bot rule can either have inline bot configuration or import a bot config snippet. You cannot do both in a single bot rule.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/ai-catchall.yaml",
|
||||
"name": "generic-browser",
|
||||
"user_agent_regex": "Mozilla|Opera\n",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: (data)/bots/ai-catchall.yaml
|
||||
@@ -67,9 +26,6 @@ bots:
|
||||
action: CHALLENGE
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This will return an error like this:
|
||||
|
||||
```text
|
||||
@@ -83,30 +39,11 @@ Paths can either be prefixed with `(data)` to import from the [the data folder i
|
||||
|
||||
You can also import from an imported file in case you want to import an entire folder of rules at once.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/_deny-pathological.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: (data)/bots/_deny-pathological.yaml
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This lets you import an entire ruleset at once:
|
||||
|
||||
```yaml
|
||||
@@ -124,22 +61,6 @@ Snippets can be written in either JSON or YAML, with a preference for YAML. When
|
||||
|
||||
Here is an example snippet that allows [IPv6 Unique Local Addresses](https://en.wikipedia.org/wiki/Unique_local_address) through Anubis:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "ipv6-ula",
|
||||
"action": "ALLOW",
|
||||
"remote_addresses": ["fc00::/7"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
- name: ipv6-ula
|
||||
action: ALLOW
|
||||
@@ -147,9 +68,6 @@ Here is an example snippet that allows [IPv6 Unique Local Addresses](https://en.
|
||||
- fc00::/7
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Extracting Anubis' embedded filesystem
|
||||
|
||||
You can always extract the list of rules embedded into the Anubis binary with this command:
|
||||
|
||||
@@ -32,7 +32,7 @@ sequenceDiagram
|
||||
participant Validation
|
||||
participant Evil Site
|
||||
|
||||
Hacker->>+User: Click on yoursite.com with this solution
|
||||
Hacker->>+User: Click on example.org with this solution
|
||||
User->>+Validation: Here's a solution, send me to evilsite.com
|
||||
Validation->>+User: Here's a cookie, go to evilsite.com
|
||||
User->>+Evil Site: GET evilsite.com
|
||||
@@ -46,11 +46,14 @@ Redirect domain not allowed
|
||||
|
||||
## Configuring allowed redirect domains
|
||||
|
||||
By default, Anubis will limit redirects to be on the same HTTP Host that Anubis is running on (EG: requests to yoursite.com cannot redirect outside of yoursite.com). If you need to set more than one domain, fill the `REDIRECT_DOMAINS` environment variable with a comma-separated list of domain names that Anubis should allow redirects to.
|
||||
By default, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.
|
||||
One can restrict the domains that Anubis can redirect to when passing a challenge by setting up `REDIRECT_DOMAINS` environment variable.
|
||||
If you need to set more than one domain, fill the environment variable with a comma-separated list of domain names.
|
||||
There is also glob matching support. You can pass `*.bugs.techaro.lol` to allow redirecting to anything ending with `.bugs.techaro.lol`. There is a limit of 4 wildcards.
|
||||
|
||||
:::note
|
||||
|
||||
These domains are _an exact string match_, they do not support wildcard matches.
|
||||
If you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here.
|
||||
|
||||
:::
|
||||
|
||||
@@ -60,7 +63,7 @@ These domains are _an exact string match_, they do not support wildcard matches.
|
||||
```shell
|
||||
# anubis.env
|
||||
|
||||
REDIRECT_DOMAINS="yoursite.com,secretplans.yoursite.com"
|
||||
REDIRECT_DOMAINS="example.org,secretplans.example.org,*.test.example.org"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -72,7 +75,7 @@ services:
|
||||
anubis-nginx:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
environment:
|
||||
REDIRECT_DOMAINS: "yoursite.com,secretplans.yoursite.com"
|
||||
REDIRECT_DOMAINS: "example.org,secretplans.example.org,*.test.example.org"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -86,7 +89,7 @@ Inside your Deployment, StatefulSet, or Pod:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
env:
|
||||
- name: REDIRECT_DOMAINS
|
||||
value: "yoursite.com,secretplans.yoursite.com"
|
||||
value: "example.org,secretplans.example.org,*.test.example.org"
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Effectively you have one trip through Apache to do TLS termination, a detour thr
|
||||
|
||||
:::note
|
||||
|
||||
These examples assume that you are using a setup where your nginx configuration is made up of a bunch of files in `/etc/httpd/conf.d/*.conf`. This is not true for all deployments of Apache. If you are not in such an environment, append these snippets to your `/etc/httpd/conf/httpd.conf` file.
|
||||
These examples assume that you are using a setup where your Apache configuration is made up of a bunch of files in `/etc/httpd/conf.d/*.conf`. This is not true for all deployments of Apache. If you are not in such an environment, append these snippets to your `/etc/httpd/conf/httpd.conf` file.
|
||||
|
||||
:::
|
||||
|
||||
@@ -56,6 +56,7 @@ Assuming you are protecting `anubistest.techaro.lol`, you need the following ser
|
||||
</VirtualHost>
|
||||
|
||||
# HTTPS listener that forwards to Anubis
|
||||
<IfModule mod_proxy.c>
|
||||
<VirtualHost *:443>
|
||||
ServerAdmin your@email.here
|
||||
ServerName anubistest.techaro.lol
|
||||
|
||||
26
docs/docs/admin/environments/cloudflare.mdx
Normal file
26
docs/docs/admin/environments/cloudflare.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
# Cloudflare
|
||||
|
||||
If you are using Cloudflare, you should configure your server to use `CF-Connecting-IP` as the source of the real client IP, and pass that address to Anubis as `X-Forwarded-For`. Read [Client IP Headers](../caveats-xff.mdx) for details.
|
||||
|
||||
Example configuration with Caddy:
|
||||
|
||||
```Caddyfile
|
||||
{
|
||||
servers {
|
||||
# Cloudflare IP ranges from https://www.cloudflare.com/en-gb/ips/
|
||||
trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
|
||||
# Use CF-Connecting-IP to determine the client IP instead of XFF
|
||||
# https://caddyserver.com/docs/caddyfile/options#client-ip-headers
|
||||
client_ip_headers CF-Connecting-IP
|
||||
}
|
||||
}
|
||||
|
||||
example.com {
|
||||
reverse_proxy http://anubis:3000 {
|
||||
# Pass the client IP read from CF-Connecting-IP
|
||||
header_up X-Forwarded-For {client_ip}
|
||||
header_up X-Real-IP {client_ip}
|
||||
header_up X-Http-Version {http.request.proto}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -4,7 +4,7 @@ Docker compose is typically used in concert with other load balancers such as [A
|
||||
|
||||
```yaml
|
||||
services:
|
||||
anubis-nginx:
|
||||
anubis:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
environment:
|
||||
BIND: ":8080"
|
||||
@@ -15,10 +15,17 @@ services:
|
||||
POLICY_FNAME: "/data/cfg/botPolicy.yaml"
|
||||
OG_PASSTHROUGH: "true"
|
||||
OG_EXPIRY_TIME: "24h"
|
||||
healthcheck:
|
||||
test: ["CMD", "anubis", "--healthcheck"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 5
|
||||
start_period: 500ms
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- "./botPolicy.yaml:/data/cfg/botPolicy.yaml:ro"
|
||||
|
||||
nginx:
|
||||
image: nginx
|
||||
volumes:
|
||||
|
||||
@@ -79,6 +79,10 @@ server {
|
||||
root "/srv/http/anubistest.techaro.lol";
|
||||
index index.html;
|
||||
|
||||
# Get the visiting IP from the TLS termination server
|
||||
set_real_ip_from unix:;
|
||||
real_ip_header X-Real-IP;
|
||||
|
||||
# Your normal configuration can go here
|
||||
# location .php { fastcgi...} etc.
|
||||
}
|
||||
@@ -125,6 +129,8 @@ server {
|
||||
listen unix:/run/nginx/nginx.sock;
|
||||
|
||||
server_name mimi.techaro.lol;
|
||||
|
||||
port_in_redirect off;
|
||||
root "/srv/http/mimi.techaro.lol";
|
||||
index index.html;
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ but it also applies to docker cli options.
|
||||
|
||||
:::
|
||||
|
||||
Currently, Anubis doesn't have any Traefik middleware,
|
||||
so you need to manually route it between Traefik and your target service.
|
||||
This routing is done per labels in Traefik.
|
||||
|
||||
In this example, we will use 4 Containers:
|
||||
|
||||
- `traefik` - the Traefik instance
|
||||
@@ -21,12 +17,6 @@ In this example, we will use 4 Containers:
|
||||
- `target` - our service to protect (`traefik/whoami` in this case)
|
||||
- `target2` - a second service that isn't supposed to be protected (`traefik/whoami` in this case)
|
||||
|
||||
There are 3 steps we need to follow:
|
||||
|
||||
1. Create a new exclusive Traefik endpoint for Anubis
|
||||
2. Pass all unspecified requests to Anubis
|
||||
3. Let Anubis pass all verified requests back to Traefik on its exclusive endpoint
|
||||
|
||||
## Diagram of Flow
|
||||
|
||||
This is a small diagram depicting the flow.
|
||||
@@ -40,74 +30,16 @@ anubis[Anubis]
|
||||
target[Target]
|
||||
|
||||
user-->|:443 - Requesting Service|traefik
|
||||
traefik-->|:8080 - Passing to Anubis|anubis
|
||||
anubis-->|:3923 - Passing back to Traefik|traefik
|
||||
traefik-->|:8080 - Check authorization to Anubis|anubis
|
||||
anubis-->|redirect if failed|traefik
|
||||
user-->|:8080 - make the challenge|traefik
|
||||
anubis-->|redirect back to target|traefik
|
||||
traefik-->|:80 - Passing to the target|target
|
||||
```
|
||||
|
||||
## Create an Exclusive Anubis Endpoint in Traefik
|
||||
|
||||
There are 2 ways of registering a new endpoint in Traefik.
|
||||
Which one to use depends on how you configured your Traefik so far.
|
||||
|
||||
**CLI Options:**
|
||||
|
||||
```yml
|
||||
--entrypoints.anubis.address=:3923
|
||||
```
|
||||
|
||||
**traefik.yml:**
|
||||
|
||||
```yml
|
||||
entryPoints:
|
||||
anubis:
|
||||
address: ":3923"
|
||||
```
|
||||
|
||||
It is important that the specified port isn't actually reachable from the outside,
|
||||
but only exposed in the Docker network.
|
||||
Exposing the Anubis port on Traefik directly will allow direct unprotected access to all containers behind it.
|
||||
|
||||
## Passing all unspecified Web Requests to Anubis
|
||||
|
||||
There are cases where you want Traefik to still route some requests without protection, just like before.
|
||||
To achieve this, we can register Anubis as the default handler for non-protected requests.
|
||||
|
||||
We also don't want users to get SSL Errors during the checking phase,
|
||||
thus we also need to let Traefik provide SSL Certs for our endpoint.
|
||||
This example expects an TLS cert resolver called `le`.
|
||||
|
||||
We also expect there to be an endpoint called `websecure` for HTTPS in this example.
|
||||
|
||||
This is an example of the required labels to configure Traefik on the Anubis container:
|
||||
|
||||
```yml
|
||||
labels:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.anubis.priority=1 # Setting Anubis to the lowest priority, so it only takes the slack
|
||||
- traefik.http.routers.anubis.rule=PathRegexp(`.*`) # Wildcard match every path
|
||||
- traefik.http.routers.anubis.entrypoints=websecure # Listen on HTTPS
|
||||
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik to which port it should route requests
|
||||
- traefik.http.routers.anubis.service=anubis # Telling Traefik to use the above specified port
|
||||
- traefik.http.routers.anubis.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
|
||||
```
|
||||
|
||||
## Passing all Verified Requests Back Correctly to Traefik
|
||||
|
||||
To pass verified requests back to Traefik,
|
||||
we only need to configure Anubis using its environment variables:
|
||||
|
||||
```yml
|
||||
environment:
|
||||
- BIND=:8080
|
||||
- TARGET=http://traefik:3923
|
||||
```
|
||||
|
||||
## Full Example Config
|
||||
|
||||
Now that we know how to pass all requests back and forth, here is the example.
|
||||
This example contains 2 services: one that is protected and the other one that is not.
|
||||
This example contains 3 services: anubis, one that is protected and the other one that is not.
|
||||
|
||||
**compose.yml**
|
||||
|
||||
@@ -128,6 +60,8 @@ services:
|
||||
# Enable Traefik
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=traefik
|
||||
# Anubis middleware
|
||||
- traefik.http.middlewares.anubis.forwardauth.address=http://anubis:8080/.within.website/x/cmd/anubis/api/check
|
||||
# Redirect any HTTP to HTTPS
|
||||
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
|
||||
- traefik.http.routers.web.rule=PathPrefix(`/`)
|
||||
@@ -140,17 +74,22 @@ services:
|
||||
environment:
|
||||
# Telling Anubis, where to listen for Traefik
|
||||
- BIND=:8080
|
||||
# Telling Anubis to point to Traefik via the Docker network
|
||||
- TARGET=http://traefik:3923
|
||||
# Telling Anubis to do redirect — ensure there is a space after '='
|
||||
- 'TARGET= '
|
||||
# Specifies which domains Anubis is allowed to redirect to.
|
||||
- REDIRECT_DOMAINS=example.com
|
||||
# Should be the full external URL for Anubis (including scheme)
|
||||
- PUBLIC_URL=https://anubis.example.com
|
||||
# Should match your domain for proper cookie scoping
|
||||
- COOKIE_DOMAIN=example.com
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.anubis.priority=1 # Setting Anubis to the lowest priority, so it only takes the slack
|
||||
- traefik.http.routers.anubis.rule=PathRegexp(`.*`) # wildcard match anything
|
||||
- traefik.http.routers.anubis.rule=Host(`anubis.example.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.anubis.entrypoints=websecure # Listen on HTTPS
|
||||
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik to which port it should route requests
|
||||
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik where to receive requests
|
||||
- traefik.http.routers.anubis.service=anubis # Telling Traefik to use the above specified port
|
||||
- traefik.http.routers.anubis.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
|
||||
|
||||
@@ -163,9 +102,11 @@ services:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.target.rule=Host(`example.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.target.entrypoints=anubis # Listening on the exclusive Anubis Network
|
||||
- traefik.http.routers.target.entrypoints=websecure # Listening on the exclusive Anubis Network
|
||||
- traefik.http.services.target.loadbalancer.server.port=80 # Telling Traefik where to receive requests
|
||||
- traefik.http.routers.target.service=target # Telling Traefik to use the above specified port
|
||||
- traefik.http.routers.target.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
|
||||
- traefik.http.routers.target.middlewares=anubis@docker # Use the Anubis middleware
|
||||
|
||||
# Not Protected by Anubis
|
||||
target2:
|
||||
@@ -175,7 +116,7 @@ services:
|
||||
labels:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.target2.rule=Host(`another.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.target2.rule=Host(`another.example.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.target2.entrypoints=websecure # Listening on the exclusive Anubis Network
|
||||
- traefik.http.services.target2.loadbalancer.server.port=80 # Telling Traefik where to receive requests
|
||||
- traefik.http.routers.target2.service=target2 # Telling Traefik to use the above specified port
|
||||
@@ -198,9 +139,6 @@ entryPoints:
|
||||
address: ":80"
|
||||
websecure:
|
||||
address: ":443"
|
||||
# Anubis
|
||||
anubis:
|
||||
address: ":3923"
|
||||
|
||||
certificatesResolvers:
|
||||
le:
|
||||
|
||||
@@ -7,27 +7,6 @@ import TabItem from "@theme/TabItem";
|
||||
|
||||
To work around this, you can make a custom [expression](../configuration/expressions.mdx) rule that allows HTMX requests if the user has passed a challenge in the past:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "allow-htmx-iff-already-passed-challenge",
|
||||
"action": "ALLOW",
|
||||
"expression": {
|
||||
"all": [
|
||||
"\"Cookie\" in headers",
|
||||
"headers[\"Cookie\"].contains(\"anubis-auth\")",
|
||||
"\"Hx-Request\" in headers",
|
||||
"headers[\"Hx-Request\"] == \"true\""
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
- name: allow-htmx-iff-already-passed-challenge
|
||||
action: ALLOW
|
||||
@@ -39,7 +18,4 @@ To work around this, you can make a custom [expression](../configuration/express
|
||||
- 'headers["Hx-Request"] == "true"'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This will reduce some security because it does not assert the validity of the Anubis auth cookie, however in trade it improves the experience for existing users.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Wordpress
|
||||
# WordPress
|
||||
|
||||
Wordpress is the most popular blog engine on the planet.
|
||||
WordPress is the most popular blog engine on the planet.
|
||||
|
||||
## Using a multi-site setup with Anubis
|
||||
|
||||
@@ -27,7 +27,7 @@ flowchart LR
|
||||
US --> |whatever you're doing| B
|
||||
```
|
||||
|
||||
Wordpress may not realize that the underlying connection is being done over HTTPS. This could lead to a redirect loop in the `/wp-admin/` routes. In order to fix this, add the following to your `wp-config.php` file:
|
||||
WordPress may not realize that the underlying connection is being done over HTTPS. This could lead to a redirect loop in the `/wp-admin/` routes. In order to fix this, add the following to your `wp-config.php` file:
|
||||
|
||||
```php
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
||||
@@ -36,4 +36,4 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
|
||||
}
|
||||
```
|
||||
|
||||
This will make Wordpress think that your connection is over HTTPS instead of plain HTTP.
|
||||
This will make WordPress think that your connection is over HTTPS instead of plain HTTP.
|
||||
|
||||
@@ -2,8 +2,16 @@
|
||||
title: Setting up Anubis
|
||||
---
|
||||
|
||||
import EnterpriseOnly from "@site/src/components/EnterpriseOnly";
|
||||
import RandomKey from "@site/src/components/RandomKey";
|
||||
|
||||
export const EO = () => (
|
||||
<>
|
||||
<EnterpriseOnly link="./botstopper/" />
|
||||
<div style={{ marginBottom: "0.5rem" }} />
|
||||
</>
|
||||
);
|
||||
|
||||
Anubis is meant to sit between your reverse proxy (such as Nginx or Caddy) and your target service. One instance of Anubis must be used per service you are protecting.
|
||||
|
||||
<center>
|
||||
@@ -58,33 +66,46 @@ Currently the following settings are configurable via the policy file:
|
||||
|
||||
Anubis uses these environment variables for configuration:
|
||||
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :----------------------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints. For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
|
||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.<br/><br/>Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. |
|
||||
| `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. |
|
||||
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
||||
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. |
|
||||
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. |
|
||||
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
||||
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
||||
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||
| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||
| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
|
||||
| `REDIRECT_DOMAINS` | unset | If set, restrict the domains that Anubis can redirect to when passing a challenge.<br/><br/>If this is unset, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.<br/><br/>Note that if you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here. |
|
||||
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
|
||||
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
|
||||
| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. |
|
||||
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
|
||||
| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
|
||||
| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
|
||||
| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. |
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :----------------------------- | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ASSET_LOOKUP_HEADER` | unset | <EO /> If set, use the contents of this header in requests when looking up custom assets in `OVERLAY_FOLDER`. See [Header-based overlay dispatch](./botstopper.mdx#header-based-overlay-dispatch) for more details. |
|
||||
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
|
||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||
| `CHALLENGE_TITLE` | unset | <EO /> If set, override the translation stack to show a custom title for challenge pages such as "Making sure your connection is secure!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. |
|
||||
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.<br/><br/>Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. |
|
||||
| `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. |
|
||||
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
||||
| `CUSTOM_REAL_IP_HEADER` | unset | If set, Anubis will read the client's real IP address from this header, and set it in `X-Real-IP` header. |
|
||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
||||
| `COOKIE_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. |
|
||||
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
||||
| `COOKIE_SAME_SITE` | `None` | Controls the cookie’s [`SameSite` attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value). Allowed: `None`, `Lax`, `Strict`, `Default`. `None` permits cross-site use but modern browsers require it to be **Secure**—so if `COOKIE_SECURE=false` or you serve over plain HTTP, use `Lax` (recommended) or `Strict` or the cookie will be rejected. `Default` uses the Go runtime’s `SameSiteDefaultMode`. `None` will be downgraded to `Lax` automatically if cookie is set NOT to be secure. |
|
||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||
| `DIFFICULTY_IN_JWT` | `false` | If set to `true`, adds the `difficulty` field into JWT claims, which indicates the difficulty the token has been generated. This may be useful for statistics and debugging. |
|
||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. |
|
||||
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
||||
| `ERROR_TITLE` | unset | <EO /> If set, override the translation stack to show a custom title for error pages such as "Something went wrong!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. |
|
||||
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
|
||||
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
||||
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
||||
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||
| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||
| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||
| `OVERLAY_FOLDER` | unset | <EO /> If set, treat the given path as an [overlay folder](./botstopper.mdx#custom-images-and-css), allowing you to customize CSS, fonts, images, and add other assets to BotStopper deployments. |
|
||||
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
|
||||
| `PUBLIC_URL` | unset | The externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for Traefik forwardAuth). |
|
||||
| `REDIRECT_DOMAINS` | unset | Comma-separated list of domain names that Anubis should allow redirects to when passing a challenge. See [Redirect Domain Configuration](./configuration/redirect-domains) for more details. |
|
||||
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
|
||||
| `SLOG_LEVEL` | `INFO` | The log level for structured logging. Valid values are `DEBUG`, `INFO`, `WARN`, and `ERROR`. Set to `DEBUG` to see all requests, evaluations, and detailed diagnostic information. |
|
||||
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
|
||||
| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. |
|
||||
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
|
||||
| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
|
||||
| `USE_SIMPLIFIED_EXPLANATION` | false | If set to `true`, replaces the text when clicking "Why am I seeing this?" with a more simplified text for a non-tech-savvy audience. |
|
||||
| `USE_TEMPLATES` | false | <EO /> If set to `true`, enable [custom HTML template support](./botstopper.mdx#custom-html-templates), allowing you to completely rewrite how BotStopper renders its HTML pages. |
|
||||
| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
|
||||
| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. |
|
||||
|
||||
<details>
|
||||
<summary>Advanced configuration settings</summary>
|
||||
@@ -95,12 +116,14 @@ If you don't know or understand what these settings mean, ignore them. These are
|
||||
|
||||
:::
|
||||
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `TARGET_SNI` | unset | If set, overrides the TLS handshake hostname in requests forwarded to `TARGET`. |
|
||||
| `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. |
|
||||
| `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. |
|
||||
| `HS512_SECRET` | unset | Secret string for JWT HS512 algorithm. If this is not set, Anubis will use ED25519 as defined via the variables above. The longer the better; 128 chars should suffice. |
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FORCED_LANGUAGE` | unset | If set, forces Anubis to display challenge pages in the specified language instead of using the browser's Accept-Language header. Use ISO 639-1 language codes (e.g., `de` for German, `fr` for French). |
|
||||
| `HS512_SECRET` | unset | Secret string for JWT HS512 algorithm. If this is not set, Anubis will use ED25519 as defined via the variables above. The longer the better; 128 chars should suffice. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
||||
| `TARGET_DISABLE_KEEPALIVE` | `false` | If `true`, disables HTTP keep-alive for connections to the target backend. Useful for backends that don't handle keep-alive properly. |
|
||||
| `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. |
|
||||
| `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. |
|
||||
| `TARGET_SNI` | unset | If set, TLS handshake hostname when forwarding requests to the `TARGET`. If set to auto, use Host header. |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -175,6 +198,7 @@ Alternatively here is a key generated by your browser:
|
||||
To get Anubis filtering your traffic, you need to make sure it's added to your HTTP load balancer or platform configuration. See the [environments category](/docs/category/environments) for detailed information on individual environments.
|
||||
|
||||
- [Apache](./environments/apache.mdx)
|
||||
- [Caddy](./environments/caddy.mdx)
|
||||
- [Docker compose](./environments/docker-compose.mdx)
|
||||
- [Kubernetes](./environments/kubernetes.mdx)
|
||||
- [Nginx](./environments/nginx.mdx)
|
||||
|
||||
@@ -137,7 +137,7 @@ Test to make sure it's running with `curl`:
|
||||
curl http://localhost:8240/metrics
|
||||
```
|
||||
|
||||
Then set up your reverse proxy (Nginx, Caddy, etc.) to point to the Anubis port. Anubis will then reverse proxy all requests that meet the policies in `/etc/anubis/gitea.botPolicies.json` to the target service.
|
||||
Then set up your reverse proxy (Nginx, Caddy, etc.) to point to the Anubis port. Anubis will then reverse proxy all requests that meet the policies in `/etc/anubis/gitea.botPolicies.yaml` to the target service.
|
||||
|
||||
For more details on particular reverse proxies, see here:
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ import TabItem from "@theme/TabItem";
|
||||
|
||||
Out of the box, Anubis is pretty heavy-handed. It will aggressively challenge everything that might be a browser (usually indicated by having `Mozilla` in its user agent). However, some bots are smart enough to get past the challenge. Some things that look like bots may actually be fine (IE: RSS readers). Some resources need to be visible no matter what. Some resources and remotes are fine to begin with.
|
||||
|
||||
Anubis lets you customize its configuration with a Policy File. This is a YAML document that spells out what actions Anubis should take when evaluating requests. The [default configuration](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml) explains everything, but this page contains an overview of everything you can do with it.
|
||||
|
||||
## Bot Policies
|
||||
|
||||
Bot policies let you customize the rules that Anubis uses to allow, deny, or challenge incoming requests. Currently you can set policies by the following matches:
|
||||
|
||||
- Request path
|
||||
@@ -18,75 +22,18 @@ As of version v1.17.0 or later, configuration can be written in either JSON or Y
|
||||
|
||||
Here's an example rule that denies [Amazonbot](https://developer.amazon.com/en/amazonbot):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "amazonbot",
|
||||
"user_agent_regex": "Amazonbot",
|
||||
"action": "DENY"
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
- name: amazonbot
|
||||
user_agent_regex: Amazonbot
|
||||
action: DENY
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
When this rule is evaluated, Anubis will check the `User-Agent` string of the request. If it contains `Amazonbot`, Anubis will send an error page to the user saying that access is denied, but in such a way that makes scrapers think they have correctly loaded the webpage.
|
||||
|
||||
Right now the only kinds of policies you can write are bot policies. Other forms of policies will be added in the future.
|
||||
|
||||
Here is a minimal policy file that will protect against most scraper bots:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"name": "cloudflare-workers",
|
||||
"headers_regex": {
|
||||
"CF-Worker": ".*"
|
||||
},
|
||||
"action": "DENY"
|
||||
},
|
||||
{
|
||||
"name": "well-known",
|
||||
"path_regex": "^/.well-known/.*$",
|
||||
"action": "ALLOW"
|
||||
},
|
||||
{
|
||||
"name": "favicon",
|
||||
"path_regex": "^/favicon.ico$",
|
||||
"action": "ALLOW"
|
||||
},
|
||||
{
|
||||
"name": "robots-txt",
|
||||
"path_regex": "^/robots.txt$",
|
||||
"action": "ALLOW"
|
||||
},
|
||||
{
|
||||
"name": "generic-browser",
|
||||
"user_agent_regex": "Mozilla",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- name: cloudflare-workers
|
||||
@@ -107,22 +54,20 @@ bots:
|
||||
action: CHALLENGE
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.json) is a bit more cohesive, but this should be more than enough for most users.
|
||||
This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml) is a bit more cohesive, but this should be more than enough for most users.
|
||||
|
||||
If no rules match the request, it is allowed through. For more details on this default behavior and its implications, see [Default allow behavior](./default-allow-behavior.mdx).
|
||||
|
||||
## Writing your own rules
|
||||
### Writing your own rules
|
||||
|
||||
There are three actions that can be returned from a rule:
|
||||
There are four actions that can be returned from a rule:
|
||||
|
||||
| Action | Effects |
|
||||
| :---------- | :-------------------------------------------------------------------------------- |
|
||||
| `ALLOW` | Bypass all further checks and send the request to the backend. |
|
||||
| `DENY` | Deny the request and send back an error message that scrapers think is a success. |
|
||||
| `CHALLENGE` | Show a challenge page and/or validate that clients have passed a challenge. |
|
||||
| Action | Effects |
|
||||
| :---------- | :---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ALLOW` | Bypass all further checks and send the request to the backend. |
|
||||
| `DENY` | Deny the request and send back an error message that scrapers think is a success. |
|
||||
| `CHALLENGE` | Show a challenge page and/or validate that clients have passed a challenge. |
|
||||
| `WEIGH` | Change the [request weight](#request-weight) for this request. See the [request weight](#request-weight) docs for more information. |
|
||||
|
||||
Name your rules in lower case using kebab-case. Rule names will be exposed in Prometheus metrics.
|
||||
|
||||
@@ -130,27 +75,6 @@ Name your rules in lower case using kebab-case. Rule names will be exposed in Pr
|
||||
|
||||
Rules can also have their own challenge settings. These are customized using the `"challenge"` key. For example, here is a rule that makes challenges artificially hard for connections with the substring "bot" in their user agent:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
This rule has been known to have a high false positive rate in testing. Please use this with care.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "generic-bot-catchall",
|
||||
"user_agent_regex": "(?i:bot|crawler)",
|
||||
"action": "CHALLENGE",
|
||||
"challenge": {
|
||||
"difficulty": 16,
|
||||
"report_as": 4,
|
||||
"algorithm": "slow"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
This rule has been known to have a high false positive rate in testing. Please use this with care.
|
||||
|
||||
```yaml
|
||||
@@ -164,16 +88,13 @@ This rule has been known to have a high false positive rate in testing. Please u
|
||||
algorithm: slow # intentionally waste CPU cycles and time
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Challenges can be configured with these settings:
|
||||
|
||||
| Key | Example | Description |
|
||||
| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `difficulty` | `4` | The challenge difficulty (number of leading zeros) for proof-of-work. See [Why does Anubis use Proof-of-Work?](/docs/design/why-proof-of-work) for more details. |
|
||||
| `report_as` | `4` | What difficulty the UI should report to the user. Useful for messing with industrial-scale scraping efforts. |
|
||||
| `algorithm` | `"fast"` | The algorithm used on the client to run proof-of-work calculations. This must be set to `"fast"` or `"slow"`. See [Proof-of-Work Algorithm Selection](./algorithm-selection) for more details. |
|
||||
| Key | Example | Description |
|
||||
| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `difficulty` | `4` | The challenge difficulty (number of leading zeros) for proof-of-work. See [Why does Anubis use Proof-of-Work?](/docs/design/why-proof-of-work) for more details. |
|
||||
| `report_as` | `4` | What difficulty the UI should report to the user. Useful for messing with industrial-scale scraping efforts. |
|
||||
| `algorithm` | `"fast"` | The challenge method to use. See [the list of challenge methods](./configuration/challenges/) for more information. |
|
||||
|
||||
### Remote IP based filtering
|
||||
|
||||
@@ -181,21 +102,6 @@ The `remote_addresses` field of a Bot rule allows you to set the IP range that t
|
||||
|
||||
For example, you can allow a search engine to connect if and only if its IP address matches the ones they published:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "qwantbot",
|
||||
"user_agent_regex": "\\+https\\:\\/\\/help\\.qwant\\.com/bot/",
|
||||
"action": "ALLOW",
|
||||
"remote_addresses": ["91.242.162.0/24"]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
- name: qwantbot
|
||||
user_agent_regex: \+https\://help\.qwant\.com/bot/
|
||||
@@ -204,25 +110,8 @@ For example, you can allow a search engine to connect if and only if its IP addr
|
||||
remote_addresses: ["91.242.162.0/24"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This also works at an IP range level without any other checks:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "internal-network",
|
||||
"action": "ALLOW",
|
||||
"remote_addresses": ["100.64.0.0/10"]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
name: internal-network
|
||||
action: ALLOW
|
||||
@@ -230,13 +119,201 @@ remote_addresses:
|
||||
- 100.64.0.0/10
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Imprint / Impressum support
|
||||
|
||||
Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information.
|
||||
|
||||
## Storage backends
|
||||
|
||||
Anubis needs to store temporary data in order to determine if a user is legitimate or not. Administrators should choose a storage backend based on their infrastructure needs. Each backend has its own advantages and disadvantages.
|
||||
|
||||
Anubis offers the following storage backends:
|
||||
|
||||
- [`memory`](#memory) -- A simple in-memory hashmap
|
||||
- [`bbolt`](#bbolt) -- An on-disk key/value store backed by [bbolt](https://github.com/etcd-io/bbolt), an embedded key/value database for Go programs
|
||||
- [`valkey`](#valkey) -- A remote in-memory key/value database backed by [Valkey](https://valkey.io/) (or another database compatible with the [RESP](https://redis.io/docs/latest/develop/reference/protocol-spec/) protocol)
|
||||
|
||||
If no storage backend is set in the policy file, Anubis will use the [`memory`](#memory) backend by default. This is equivalent to the following in the policy file:
|
||||
|
||||
```yaml
|
||||
store:
|
||||
backend: memory
|
||||
parameters: {}
|
||||
```
|
||||
|
||||
### `memory`
|
||||
|
||||
The memory backend is an in-memory cache. This backend works best if you don't use multiple instances of Anubis or don't have mutable storage in the environment you're running Anubis in.
|
||||
|
||||
| Should I use this backend? | Yes/no |
|
||||
| :------------------------------------------------------------ | :----- |
|
||||
| Are you running only one instance of Anubis for this service? | ✅ Yes |
|
||||
| Does your service get a lot of traffic? | 🚫 No |
|
||||
| Do you want to store data persistently when Anubis restarts? | 🚫 No |
|
||||
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
|
||||
|
||||
The biggest downside is that there is not currently a limit to how much data can be stored in memory. This will be addressed at a later time.
|
||||
|
||||
:::warning
|
||||
|
||||
The in-memory backend exists mostly for validation, testing, and to ensure that the default configuration of Anubis works as expected. Do not use this persistently in production.
|
||||
|
||||
:::
|
||||
|
||||
#### Configuration
|
||||
|
||||
The memory backend does not require any configuration to use.
|
||||
|
||||
### `bbolt`
|
||||
|
||||
An on-disk storage layer powered by [bbolt](https://github.com/etcd-io/bbolt), a high performance embedded key/value database used by containerd, etcd, Kubernetes, and NATS. This backend works best if you're running Anubis on a single host and get a lot of traffic.
|
||||
|
||||
| Should I use this backend? | Yes/no |
|
||||
| :------------------------------------------------------------ | :----- |
|
||||
| Are you running only one instance of Anubis for this service? | ✅ Yes |
|
||||
| Does your service get a lot of traffic? | ✅ Yes |
|
||||
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
|
||||
| Do you run Anubis without mutable filesystem storage? | 🚫 No |
|
||||
|
||||
When Anubis opens a bbolt database, it takes an exclusive lock on that database. Other instances of Anubis or other tools cannot view the bbolt database while it is locked by another instance of Anubis. If you run multiple instances of Anubis for different services, give each its own `bbolt` configuration.
|
||||
|
||||
#### Configuration
|
||||
|
||||
The `bbolt` backend takes the following configuration options:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :----- | :--- | :----------------- | :--------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `path` | path | `/data/anubis.bdb` | The filesystem path for the Anubis bbolt database. Anubis requires write access to the folder containing the bbolt database. |
|
||||
|
||||
Example:
|
||||
|
||||
If you have persistent storage mounted to `/data`, then your store configuration could look like this:
|
||||
|
||||
```yaml
|
||||
store:
|
||||
backend: bbolt
|
||||
parameters:
|
||||
path: /data/anubis.bdb
|
||||
```
|
||||
|
||||
### `s3api`
|
||||
|
||||
A network-backed storage layer backed by [object storage](https://en.wikipedia.org/wiki/Object_storage), specifically using the [S3 API](https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_Reference.html). This can be backed by any S3-compatible object storage service such as:
|
||||
|
||||
- [AWS S3](https://aws.amazon.com/s3/)
|
||||
- [Cloudflare R2](https://www.cloudflare.com/developer-platform/products/r2/)
|
||||
- [Hetzner Object Storage](https://www.hetzner.com/storage/object-storage/)
|
||||
- [Minio](https://www.min.io/)
|
||||
- [Tigris](https://www.tigrisdata.com/)
|
||||
|
||||
If you are using a cloud platform, they likely provide an S3 compatible object storage service. If not, you may want to choose [one of the fastest options](https://www.tigrisdata.com/blog/benchmark-small-objects/).
|
||||
|
||||
| Should I use this backend? | Yes/no |
|
||||
| :------------------------------------------------------------ | :----- |
|
||||
| Are you running only one instance of Anubis for this service? | 🚫 No |
|
||||
| Does your service get a lot of traffic? | ✅ Yes |
|
||||
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
|
||||
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
|
||||
|
||||
:::note
|
||||
|
||||
Using this backend will cause a lot of S3 operations, at least one for creating challenges, one for invalidating challenges, one for updating challenges to prevent double-spends, and one for removing challenges.
|
||||
|
||||
:::
|
||||
|
||||
#### Configuration
|
||||
|
||||
The `s3api` backend takes the following configuration options:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :----------- | :------ | :------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `bucketName` | string | The name of the dedicated bucket for Anubis to store information in. |
|
||||
| `pathStyle` | boolean | `false` | If true, use path-style S3 API operations. Please consult your storage provider's documentation if you don't know what you should put here. |
|
||||
|
||||
:::note
|
||||
|
||||
You should probably enable a lifecycle expiration rule for buckets containing Anubis data. Here is an example policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"Rules": [
|
||||
{
|
||||
"Status": "Enabled",
|
||||
"Expiration": {
|
||||
"Days": 7
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Adjust this as facts and circumstances demand, but 7 days should be enough for anyone.
|
||||
|
||||
:::
|
||||
|
||||
Example:
|
||||
|
||||
Assuming your environment looks like this:
|
||||
|
||||
```sh
|
||||
# All of the following are fake credentials that look like real ones.
|
||||
AWS_ACCESS_KEY_ID=accordingToAllKnownRulesOfAviation
|
||||
AWS_SECRET_ACCESS_KEY=thereIsNoWayABeeShouldBeAbleToFly
|
||||
AWS_REGION=yow
|
||||
AWS_ENDPOINT_URL_S3=https://yow.s3.probably-not-malware.lol
|
||||
```
|
||||
|
||||
Then your configuration would look like this:
|
||||
|
||||
```yaml
|
||||
store:
|
||||
backend: s3api
|
||||
parameters:
|
||||
bucketName: techaro-prod-anubis
|
||||
pathStyle: false
|
||||
```
|
||||
|
||||
### `valkey`
|
||||
|
||||
[Valkey](https://valkey.io/) is an in-memory key/value store that clients access over the network. This allows multiple instances of Anubis to share information and does not require each instance of Anubis to have persistent filesystem storage.
|
||||
|
||||
:::note
|
||||
|
||||
You can also use [Redis](http://redis.io/) with Anubis.
|
||||
|
||||
:::
|
||||
|
||||
This backend is ideal if you are running multiple instances of Anubis in a worker pool (eg: Kubernetes Deployments with a copy of Anubis in each Pod).
|
||||
|
||||
| Should I use this backend? | Yes/no |
|
||||
| :------------------------------------------------------------ | :----- |
|
||||
| Are you running only one instance of Anubis for this service? | 🚫 No |
|
||||
| Does your service get a lot of traffic? | ✅ Yes |
|
||||
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
|
||||
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
|
||||
| Do you have Redis or Valkey installed? | ✅ Yes |
|
||||
|
||||
#### Configuration
|
||||
|
||||
The `valkey` backend takes the following configuration options:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :---- | :----- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `url` | string | `redis://valkey:6379/0` | The URL for the instance of Redis or Valkey that Anubis should store data in. This is in the same format as `REDIS_URL` in many cloud providers. |
|
||||
|
||||
Example:
|
||||
|
||||
If you have an instance of Valkey running with the hostname `valkey.int.techaro.lol`, then your store configuration could look like this:
|
||||
|
||||
```yaml
|
||||
store:
|
||||
backend: valkey
|
||||
parameters:
|
||||
url: "redis://valkey.int.techaro.lol:6379/0"
|
||||
```
|
||||
|
||||
This would have the Valkey client connect to host `valkey.int.techaro.lol` on port `6379` with database `0` (the default database).
|
||||
|
||||
## Risk calculation for downstream services
|
||||
|
||||
In case your service needs it for risk calculation reasons, Anubis exposes information about the rules that any requests match using a few headers:
|
||||
|
||||
@@ -102,18 +102,6 @@ When a client passes a challenge, Anubis sets an HTTP cookie named `"techaro.lol
|
||||
|
||||
This ensures that the token has enough metadata to prove that the token is valid (due to the token's signature), but also so that the server can independently prove the token is valid. This cookie is allowed to be set without triggering an EU cookie banner notification; but depending on facts and circumstances, you may wish to disclose this to your users.
|
||||
|
||||
## Challenge format
|
||||
|
||||
Challenges are formed by taking some user request metadata and using that to generate a SHA-256 checksum. The following request headers are used:
|
||||
|
||||
- `Accept-Encoding`: The content encodings that the requestor supports, such as gzip.
|
||||
- `X-Real-Ip`: The IP address of the requestor, as set by a reverse proxy server.
|
||||
- `User-Agent`: The user agent string of the requestor.
|
||||
- The current time in UTC rounded to the nearest week.
|
||||
- The fingerprint (checksum) of Anubis' private ED25519 key.
|
||||
|
||||
This forms a fingerprint of the requestor using metadata that any requestor already is sending. It also uses time as an input, which is known to both the server and requestor due to the nature of linear timelines. Depending on facts and circumstances, you may wish to disclose this to your users.
|
||||
|
||||
## JWT signing
|
||||
|
||||
Anubis uses an ed25519 keypair to sign the JWTs issued when challenges are passed. Anubis will generate a new ed25519 keypair every time it starts. At this time, there is no way to share this keypair between instance of Anubis, but that will be addressed in future versions.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Why does Anubis use Proof-of-Work?
|
||||
---
|
||||
|
||||
Anubis uses a [proof of work](https://en.wikipedia.org/wiki/Proof_of_work) in order to validate that clients are genuine. The reason Anubis does this was inspired by [Hashcash](https://en.wikipedia.org/wiki/Hashcash), a suggestion from the early 2000's about extending the email protocol to avoid spam. The idea is that genuine people sending emails will have to do a small math problem that is expensive to compute, but easy to verify such as hashing a string with a given number of leading zeroes. This will have basically no impact on individuals sending a few emails a week, but the company churning out industrial quantities of advertising will be required to do prohibitively expensive computation. This is also how Bitcoin's consensus algorithm works.
|
||||
Anubis uses [proof of work](https://en.wikipedia.org/wiki/Proof_of_work) in order to validate that clients are genuine. The reason Anubis does this was inspired by [Hashcash](https://en.wikipedia.org/wiki/Hashcash), a suggestion from the early 2000's about extending the email protocol to avoid spam. The idea is that genuine people sending emails will have to do a small math problem that is expensive to compute, but easy to verify such as hashing a string with a given number of leading zeroes. This will have basically no impact on individuals sending a few emails a week, but the company churning out industrial quantities of advertising will be required to do prohibitively expensive computation. This is also how Bitcoin's consensus algorithm works.
|
||||
|
||||
## How Anubis' proof of work scheme works
|
||||
|
||||
@@ -21,16 +21,3 @@ const hash = await sha256(`${challenge}${nonce}`);
|
||||
In order to pass a challenge, the `hash` has to have the right number of leading zeros (the "difficulty"). When a client requests to pass the challenge, they include the nonce they used. The server then only has to do one sha256 operation: the one that confirms that the challenge (generated from request metadata) and the nonce (provided by the client) match the difficulty number of leading zeroes.
|
||||
|
||||
Ultimately, this is a hack whose real purpose is to give a "good enough" placeholder solution so that more time can be spent on fingerprinting and identifying headless browsers (EG via how they do font rendering) so that the challenge proof of work page doesn't need to be presented to known legitimate users.
|
||||
|
||||
## Challenge format
|
||||
|
||||
Anubis generates challenges based on browser metadata, including but not limited to the following:
|
||||
|
||||
- The contents of your [`Accept-Language` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language)
|
||||
- The IP address of your client
|
||||
- Your browser's [`User-Agent` string](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent)
|
||||
- The date of the current week, rooted on Sundays
|
||||
- Anubis' ed25519 public signing key for [JSON web tokens](https://jwt.io/) (JWTs)
|
||||
- The challenge difficulty
|
||||
|
||||
This is intended to be a random value that is difficult for attackers to forge and guess, but also deterministic enough that it will naturally reset itself.
|
||||
|
||||
@@ -58,6 +58,20 @@ Anubis is brought to you by sponsors and donors like:
|
||||
<a href="https://wildbase.xyz/">
|
||||
<img src="/img/sponsors/wildbase-logo.webp" alt="Wildbase" height="64" />
|
||||
</a>
|
||||
<a href="https://emma.pet">
|
||||
<img
|
||||
src="/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="/img/sponsors/fabulous-systems.webp"
|
||||
alt="Cat eyes over the word Emma in a serif font"
|
||||
height="64"
|
||||
/>
|
||||
</a>
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -21,8 +21,4 @@ If you use a browser extension such as [JShelter](https://jshelter.org/), you wi
|
||||
|
||||
## Does Anubis mine Bitcoin?
|
||||
|
||||
No. Anubis does not mine Bitcoin.
|
||||
|
||||
In order to mine bitcoin, you need to download a copy of the blockchain (so you have the state required to do mining) and also broadcast your mined blocks to the network should you reach a hash with the right number of leading zeroes. You also need to continuously read for newly broadcasted transactions so you can batch them into a block. This requires gigabytes of data to be transferred from the server to the client.
|
||||
|
||||
Anubis transfers two digit numbers of kilobytes from the server to the client (which you can independently verify with your browser's Developer Tools feature). This is orders of magnitude below what is required to mine Bitcoin.
|
||||
No. Anubis does not mine Bitcoin or any other cryptocurrency.
|
||||
|
||||
@@ -45,6 +45,21 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
||||
- https://gitlab.postmarketos.org/
|
||||
- https://wiki.koha-community.org/
|
||||
- https://extensions.typo3.org/
|
||||
- https://ebird.org/
|
||||
- https://fabulous.systems/
|
||||
- https://coinhoards.org/
|
||||
- https://pluralpedia.org/
|
||||
- https://git.aya.so/
|
||||
- https://marginalia-search.com/
|
||||
- https://repositorio.ufrn.br/home/
|
||||
- https://mozillazine.org/
|
||||
- https://clew.se/
|
||||
- https://tumfatig.net/
|
||||
- https://rpmfusion.org/
|
||||
- https://wiki.freepascal.org/
|
||||
- https://azurlane.koumakan.jp/
|
||||
- https://lab.civicrm.org/
|
||||
- https://git.door43.org/
|
||||
- <details>
|
||||
<summary>FreeCAD</summary>
|
||||
- https://forum.freecad.org/
|
||||
@@ -82,3 +97,30 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
||||
- https://karla.hds.hebis.de/
|
||||
- and many more (see https://www.hebis.de/dienste/hebis-discovery-system/)
|
||||
</details>
|
||||
- <details>
|
||||
<summary>Duke University</summary>
|
||||
- https://repository.duke.edu/
|
||||
- https://archives.lib.duke.edu/
|
||||
- https://find.library.duke.edu/
|
||||
- https://nicholas.duke.edu/
|
||||
</details>
|
||||
- <details>
|
||||
<summary>Forschungszentrum Jülich</summary>
|
||||
- https://juser.fz-juelich.de/
|
||||
</details>
|
||||
- <details>
|
||||
<summary>archlinux32.org</summary>
|
||||
- https://www.archlinux32.org/packages/
|
||||
- https://bbs.archlinux32.org/
|
||||
- https://bugs.archlinux32.org/
|
||||
</details>
|
||||
- <details>
|
||||
<summary>HackLab.TO</summary>
|
||||
- https://hacklab.to/
|
||||
- https://knowledge.hacklab.to/
|
||||
</details>
|
||||
- <details>
|
||||
<summary>Slackware</summary>
|
||||
- https://git.slackware.nl/
|
||||
- https://git.liveslak.org/
|
||||
</details>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type * as Preset from '@docusaurus/preset-classic';
|
||||
|
||||
const config: Config = {
|
||||
title: 'Anubis',
|
||||
tagline: 'Weigh the soul of incoming HTTP requests using proof-of-work to stop AI crawlers',
|
||||
tagline: 'Weigh the soul of incoming HTTP requests to protect your website!',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// Set the production url of your site here
|
||||
@@ -40,27 +40,20 @@ const config: Config = {
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
docs: {
|
||||
sidebarPath: './sidebars.ts',
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/TecharoHQ/anubis/tree/main/docs/',
|
||||
},
|
||||
blog: {
|
||||
showReadingTime: true,
|
||||
feedOptions: {
|
||||
type: ['rss', 'atom', "json"],
|
||||
xslt: true,
|
||||
},
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
|
||||
// Useful options to enforce blogging best practices
|
||||
editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/',
|
||||
onInlineTags: 'warn',
|
||||
onInlineAuthors: 'warn',
|
||||
onUntruncatedBlogPosts: 'warn',
|
||||
onUntruncatedBlogPosts: 'throw',
|
||||
},
|
||||
docs: {
|
||||
sidebarPath: './sidebars.ts',
|
||||
editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/',
|
||||
},
|
||||
theme: {
|
||||
customCss: './src/css/custom.css',
|
||||
@@ -74,7 +67,7 @@ const config: Config = {
|
||||
respectPrefersColorScheme: true,
|
||||
},
|
||||
// Replace with your project's social card
|
||||
image: 'img/docusaurus-social-card.jpg',
|
||||
image: 'img/social-card.jpg',
|
||||
navbar: {
|
||||
title: 'Anubis',
|
||||
logo: {
|
||||
@@ -82,23 +75,28 @@ const config: Config = {
|
||||
src: 'img/favicon.webp',
|
||||
},
|
||||
items: [
|
||||
{ to: '/blog', label: 'Blog', position: 'left' },
|
||||
{
|
||||
type: 'docSidebar',
|
||||
sidebarId: 'tutorialSidebar',
|
||||
position: 'left',
|
||||
label: 'Docs',
|
||||
},
|
||||
{ to: '/blog', label: 'Blog', position: 'left' },
|
||||
{
|
||||
href: 'https://github.com/sponsors/Xe',
|
||||
label: "Sponsorship",
|
||||
position: 'left'
|
||||
to: '/docs/admin/botstopper',
|
||||
label: "Unbranded Version",
|
||||
position: "left"
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/TecharoHQ/anubis',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
{
|
||||
href: 'https://github.com/sponsors/Xe',
|
||||
label: "Sponsor the Project",
|
||||
position: 'right'
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
@@ -141,6 +139,10 @@ const config: Config = {
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/TecharoHQ/anubis',
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
href: 'https://techarohq.github.io/status/'
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
19
docs/fly.toml
Normal file
19
docs/fly.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
app = 'anubis-docs'
|
||||
primary_region = 'yyz'
|
||||
|
||||
[build]
|
||||
image = "ghcr.io/techarohq/anubis/docs:main"
|
||||
|
||||
[http_service]
|
||||
internal_port = 80
|
||||
force_https = true
|
||||
auto_stop_machines = true
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
|
||||
[[vm]]
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
||||
memory_mb = 256
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
## /usr/share/docs/anubis/data or in the tarball you extracted Anubis from.
|
||||
|
||||
bots:
|
||||
- import: (data)/crawlers/commoncrawl.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
|
||||
@@ -59,20 +60,95 @@ bots:
|
||||
- path.startsWith("/blog/rss.")
|
||||
|
||||
# Generic catchall rule
|
||||
- name: generic-browser
|
||||
user_agent_regex: >-
|
||||
Mozilla|Opera
|
||||
- name: base-weight
|
||||
expression: "true"
|
||||
action: WEIGH
|
||||
weight:
|
||||
adjust: 10
|
||||
|
||||
- name: http2-client-protocol
|
||||
expression:
|
||||
all:
|
||||
- '"X-Http-Protocol" in headers'
|
||||
- headers["X-Http-Protocol"] == "HTTP/2.0"
|
||||
action: WEIGH
|
||||
weight:
|
||||
adjust: -5
|
||||
|
||||
# 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:
|
||||
difficulty: 1 # Number of seconds to wait before refreshing the page
|
||||
report_as: 4 # Unused by this challenge method
|
||||
algorithm: metarefresh # Specify a non-JS challenge method
|
||||
# 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/preact
|
||||
#
|
||||
# This challenge proves the client can run a webapp written with Preact.
|
||||
# The preact webapp simply loads, calculates the SHA-256 checksum of the
|
||||
# challenge data, and forwards that to the client.
|
||||
algorithm: preact
|
||||
difficulty: 1
|
||||
report_as: 1
|
||||
- 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: 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 >= 30
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
|
||||
algorithm: fast
|
||||
difficulty: 4
|
||||
report_as: 4
|
||||
|
||||
dnsbl: false
|
||||
|
||||
impressum:
|
||||
footer: |
|
||||
This website is hosted by Techaro. If you have any complaints or notes about the service, please contact <a href="mailto:contact@techaro.lol">contact@techaro.lol</a> and we will assist you as soon as possible.
|
||||
This website is hosted by Techaro. If you have any complaints or notes about the service, please contact <a href="mailto:support@techaro.lol">support@techaro.lol</a> and we will assist you as soon as possible.
|
||||
|
||||
page:
|
||||
title: Privacy Policy
|
||||
@@ -126,3 +202,8 @@ impressum:
|
||||
status_codes:
|
||||
CHALLENGE: 200
|
||||
DENY: 200
|
||||
|
||||
store:
|
||||
backend: bbolt
|
||||
parameters:
|
||||
path: /xe/data/anubis/data.bdb
|
||||
|
||||
99
docs/manifest/cfg/nginx/mime.types
Normal file
99
docs/manifest/cfg/nginx/mime.types
Normal file
@@ -0,0 +1,99 @@
|
||||
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/avif avif;
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/wasm wasm;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
31
docs/manifest/cfg/nginx/nginx.conf
Normal file
31
docs/manifest/cfg/nginx/nginx.conf
Normal file
@@ -0,0 +1,31 @@
|
||||
user nginx;
|
||||
worker_processes 2;
|
||||
error_log /dev/stdout warn;
|
||||
pid /nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
access_log /dev/stdout;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
root /www;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user