Compare commits

...

19 Commits

Author SHA1 Message Date
Jason Cameron
978af9d5ff chore: add comments back to Challenge struct.
See #1284
and https://github.com/TecharoHQ/anubis/pull/1284#issuecomment-3784096905
2026-01-22 09:46:38 -05:00
Timon de Groot
57c0b2b22c Add IP mapped Perplexity user agents (#1393)
Perplexity has some proper documentation available for their crawlers,
with published IP addresses: https://docs.perplexity.ai/guides/bots.

Signed-off-by: Timon de Groot <timon.degroot@team.blue>
2026-01-15 19:57:31 -05:00
Thomas Arrow
186ffeb744 docs: clarify botstopper kubernetes instructions (#1404)
This makes it clear that when generating a kubernetes secret to pull the bot stopper image that:
- no email is required
- a user is required but the actual value of the username is not checked
- the GH token needs to be pasted in

Signed-off-by: Thomas Arrow <tarrow@users.noreply.github.com>
2026-01-15 11:13:10 +00:00
Xe Iaso
ff87aac4e7 fix(web): include base prefix in generated URLs (#1403)
* fix(web): include base prefix in generated URLs

Forgot to add the base prefix to these URLs. Committed a fix for this
and added a test to ensure this does not repeat. Oops!

Closes: #1402
Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs: update CHANGELOG

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-01-14 23:47:44 +00:00
Anton Kesy
3c76724aeb fix: correct typos (#1398) 2026-01-12 01:23:58 +00:00
Andrew Young
1db57e5d23 fix sponsor (Databento) logo size (#1395) 2026-01-09 23:42:03 +00:00
Xe Iaso
6fc2c3c857 docs: document how to import the default config (#1392)
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-01-08 16:14:52 +00:00
dependabot[bot]
149e864786 build(deps): bump preact from 10.28.0 to 10.28.1 in the npm group (#1387)
Bumps the npm group with 1 update: [preact](https://github.com/preactjs/preact).


Updates `preact` from 10.28.0 to 10.28.1
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](https://github.com/preactjs/preact/compare/10.28.0...10.28.1)

---
updated-dependencies:
- dependency-name: preact
  dependency-version: 10.28.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2026-01-05 21:44:28 -05:00
Jason Cameron
2aaee6c348 Revert "build(deps): bump the gomod group across 1 directory with 3 updates (…" (#1386) 2026-01-04 00:13:45 +00:00
dependabot[bot]
ebad69a4e1 build(deps): bump the gomod group across 1 directory with 3 updates (#1370)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Cameron <jason.cameron@stanwith.me>
2026-01-03 19:06:05 -05:00
lif
71147b4857 fix: respect Accept-Language quality factors in language detection (#1380)
The Accept-Language header parsing was not correctly handling quality
factors. When a browser sends "en-GB,de-DE;q=0.5", the expected behavior
is to prefer English (q=1.0 by default) over German (q=0.5).

The fix uses golang.org/x/text/language.ParseAcceptLanguage to properly
parse and sort language preferences by quality factor. It also adds base
language fallbacks (e.g., "en" for "en-GB") to ensure regional variants
match their parent languages when no exact match exists.

Fixes #1022

Signed-off-by: majiayu000 <1835304752@qq.com>
2026-01-02 08:01:43 -05:00
lif
cee7871ef8 fix: update SSL Labs IP addresses (#1377)
Signed-off-by: majiayu000 <1835304752@qq.com>
Co-authored-by: Jason Cameron <jason.cameron@stanwith.me>
2026-01-01 23:21:31 -05:00
Jason Cameron
26d258fb94 Update check-spelling metadata (#1379) 2026-01-01 23:02:15 +00:00
Xe Iaso
80a8e0a8ae chore: add Databento as diamond tier sponsor
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-12-30 10:56:58 -05:00
Xe Iaso
359613f35a feat: iplist2rule utility command (#1373)
* feat: iplist2rule utility command

Assisted-By: GLM 4.7 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs: update CHANGELOG

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: fix spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: fix spelling again

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(iplist2rule): add comment describing how rule was generated

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs: add iplist2rule docs

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: fix spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-12-29 17:10:17 +00:00
Xe Iaso
1d8e98c5ec test(nginx): fix tests to work in GHA (#1372)
* test(nginx): fix tests to work in GHA

Closes: #1371
Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(test): does this work lol

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(test): does this other thing work lol

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(test): pki folder location

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Jason Cameron <git@jasoncameron.dev>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-12-28 23:59:48 -05:00
Jason Cameron
880020095c fix(test): remove interactive flag from nginx smoke test docker run command (#1371) 2025-12-29 03:14:50 +00:00
dependabot[bot]
f5728e96a1 build(deps-dev): bump esbuild from 0.27.1 to 0.27.2 in the npm group (#1368)
Co-authored-by: Jason Cameron <git@jsn.cam>
2025-12-28 22:07:44 -05:00
dependabot[bot]
bcf525dbcf build(deps): bump the github-actions group with 3 updates (#1369)
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2025-12-28 22:04:16 -05:00
45 changed files with 1101 additions and 621 deletions

View File

@@ -18,3 +18,9 @@ clampip
pseudoprofound pseudoprofound
reimagining reimagining
iocaine iocaine
admins
fout
iplist
NArg
blocklists
rififi

View File

@@ -87,10 +87,14 @@
^docs/docs/user/known-instances.md$ ^docs/docs/user/known-instances.md$
^docs/manifest/.*$ ^docs/manifest/.*$
^docs/static/\.nojekyll$ ^docs/static/\.nojekyll$
^lib/policy/config/testdata/bad/unparseable\.json$
^internal/glob/glob_test.go$ ^internal/glob/glob_test.go$
^internal/honeypot/naive/affirmations\.txt$
^internal/honeypot/naive/spintext\.txt$
^internal/honeypot/naive/titles\.txt$
^lib/config/testdata/bad/unparseable\.json$
^lib/localization/.*_test.go$
^lib/localization/locales/.*\.json$
^lib/policy/config/testdata/bad/unparseable\.json$
^test/.*$
ignore$ ignore$
robots.txt robots.txt
^lib/localization/locales/.*\.json$
^lib/localization/.*_test.go$
^test/.*$

View File

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

View File

@@ -68,7 +68,7 @@ jobs:
SLOG_LEVEL: debug SLOG_LEVEL: debug
- name: Generate artifact attestation - name: Generate artifact attestation
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0 uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
with: with:
subject-name: ${{ env.IMAGE }} subject-name: ${{ env.IMAGE }}
subject-digest: ${{ steps.build.outputs.digest }} subject-digest: ${{ steps.build.outputs.digest }}

View File

@@ -22,7 +22,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Log into registry - name: Log into registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0

View File

@@ -18,7 +18,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Docker meta - name: Docker meta
id: meta id: meta

View File

@@ -30,7 +30,7 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Build and push - name: Build and push
run: | run: |
cd ./test/ssh-ci cd ./test/ssh-ci

View File

@@ -29,7 +29,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file - name: Upload SARIF file
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2 uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View File

@@ -20,6 +20,9 @@ Anubis is brought to you by sponsors and donors like:
<a href="https://www.raptorcs.com/content/base/products.html"> <a href="https://www.raptorcs.com/content/base/products.html">
<img src="./docs/static/img/sponsors/raptor-computing-logo.webp" alt="Raptor Computing Systems" height=64 /> <img src="./docs/static/img/sponsors/raptor-computing-logo.webp" alt="Raptor Computing Systems" height=64 />
</a> </a>
<a href="https://databento.com/?utm_source=anubis&utm_medium=sponsor&utm_campaign=anubis">
<img src="./docs/static/img/sponsors/databento-logo.webp" alt="Databento" height="64" />
</a>
### Gold Tier ### Gold Tier

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
# Acts on behalf of user requests
# https://docs.perplexity.ai/guides/bots
- name: perplexity-user
user_agent_regex: Perplexity-User/.+; \+https\://perplexity\.ai/perplexity-user
action: ALLOW
# https://www.perplexity.com/perplexity-user.json
remote_addresses: [
"44.208.221.197/32",
"34.193.163.52/32",
"18.97.21.0/30",
"18.97.43.80/29",
]

View File

@@ -0,0 +1,55 @@
# Assert behaviour that only genuine browsers display. This ensures that modern Chrome
# or Firefox versions will get through without a challenge.
#
# These rules have been known to be bypassed by some of the worst automated scrapers.
# Use at your own risk.
- name: realistic-browser-catchall
expression:
all:
- '"User-Agent" in headers'
- '( userAgent.contains("Firefox") ) || ( userAgent.contains("Chrome") ) || ( userAgent.contains("Safari") )'
- '"Accept" in headers'
- '"Sec-Fetch-Dest" in headers'
- '"Sec-Fetch-Mode" in headers'
- '"Sec-Fetch-Site" in headers'
- '"Accept-Encoding" in headers'
- '( headers["Accept-Encoding"].contains("zstd") || headers["Accept-Encoding"].contains("br") )'
- '"Accept-Language" in headers'
action: WEIGH
weight:
adjust: -10
# The Upgrade-Insecure-Requests header is typically sent by browsers, but not always
- name: upgrade-insecure-requests
expression: '"Upgrade-Insecure-Requests" in headers'
action: WEIGH
weight:
adjust: -2
# Chrome should behave like Chrome
- name: chrome-is-proper
expression:
all:
- userAgent.contains("Chrome")
- '"Sec-Ch-Ua" in headers'
- 'headers["Sec-Ch-Ua"].contains("Chromium")'
- '"Sec-Ch-Ua-Mobile" in headers'
- '"Sec-Ch-Ua-Platform" in headers'
action: WEIGH
weight:
adjust: -5
- name: should-have-accept
expression: '!("Accept" in headers)'
action: WEIGH
weight:
adjust: 5
# Generic catchall rule
- name: generic-browser
user_agent_regex: >-
Mozilla|Opera
action: WEIGH
weight:
adjust: 10

View File

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

View File

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

View File

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

View File

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

View File

@@ -226,7 +226,7 @@ So far Anubis supports the following languages:
- English (Simplified and Traditional) - English (Simplified and Traditional)
- French - French
- Portugese (Brazil) - Portuguese (Brazil)
- Spanish - Spanish
If you want to contribute translations, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new) with your language of choice or submit a pull request to [the `lib/localization/locales` folder](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). We are about to introduce features to the translation stack, so you may want to hold off a hot minute, but we welcome any and all contributions to making Anubis useful to a global audience. If you want to contribute translations, please [file an issue](https://github.com/TecharoHQ/anubis/issues/new) with your language of choice or submit a pull request to [the `lib/localization/locales` folder](https://github.com/TecharoHQ/anubis/tree/main/lib/localization/locales). We are about to introduce features to the translation stack, so you may want to hold off a hot minute, but we welcome any and all contributions to making Anubis useful to a global audience.

View File

@@ -69,7 +69,7 @@ I am waiting to hear back from NLNet on if Anubis was selected for funding or no
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: 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) - [Brazilian Portuguese](https://github.com/TecharoHQ/anubis/pull/726)
- [Chinese (Simplified)](https://github.com/TecharoHQ/anubis/pull/774) - [Chinese (Simplified)](https://github.com/TecharoHQ/anubis/pull/774)
- [Chinese (Traditional)](https://github.com/TecharoHQ/anubis/pull/759) - [Chinese (Traditional)](https://github.com/TecharoHQ/anubis/pull/759)
- [Czech](https://github.com/TecharoHQ/anubis/pull/849) - [Czech](https://github.com/TecharoHQ/anubis/pull/849)

View File

@@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- Add iplist2rule tool that lets admins turn an IP address blocklist into an Anubis ruleset.
- Add Polish locale ([#1292](https://github.com/TecharoHQ/anubis/pull/1309)) - Add Polish locale ([#1292](https://github.com/TecharoHQ/anubis/pull/1309))
- Fix honeypot and imprint links missing `BASE_PREFIX` when deployed behind a path prefix ([#1402](https://github.com/TecharoHQ/anubis/issues/1402))
<!-- This changes the project to: --> <!-- This changes the project to: -->

View File

@@ -51,9 +51,8 @@ If you are using Kubernetes, you will need to create an image pull secret:
kubectl create secret docker-registry \ kubectl create secret docker-registry \
techarohq-botstopper \ techarohq-botstopper \
--docker-server ghcr.io \ --docker-server ghcr.io \
--docker-username your-username \ --docker-username any-username \
--docker-password your-access-token \ --docker-password <your-access-token> \
--docker-email your@email.address
``` ```
Then attach it to your Deployment: Then attach it to your Deployment:
@@ -85,7 +84,7 @@ Follow [the upstream Docker compose directions](https://anubis.techaro.lol/docs/
OG_EXPIRY_TIME: "24h" OG_EXPIRY_TIME: "24h"
+ # botstopper config here + # botstopper config here
+ CHALLENGE_TITLE: "Doing math for your connnection!" + CHALLENGE_TITLE: "Doing math for your connection!"
+ ERROR_TITLE: "Something went wrong!" + ERROR_TITLE: "Something went wrong!"
+ OVERLAY_FOLDER: /assets + OVERLAY_FOLDER: /assets
+ volumes: + volumes:

View File

@@ -13,6 +13,8 @@ bots:
- # This correlates to data/bots/ai-catchall.yaml in the source tree - # This correlates to data/bots/ai-catchall.yaml in the source tree
import: (data)/bots/ai-catchall.yaml import: (data)/bots/ai-catchall.yaml
- import: (data)/bots/cloudflare-workers.yaml - import: (data)/bots/cloudflare-workers.yaml
# Import all the rules in the default configuration
- import: (data)/meta/default-config.yaml
``` ```
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. 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.
@@ -35,6 +37,33 @@ config.BotOrImport: rule definition is invalid, you must set either bot rules or
Paths can either be prefixed with `(data)` to import from the [the data folder in the Anubis source tree](https://github.com/TecharoHQ/anubis/tree/main/data) or anywhere on the filesystem. If you don't have access to the Anubis source tree, check /usr/share/docs/anubis/data or in the tarball you extracted Anubis from. Paths can either be prefixed with `(data)` to import from the [the data folder in the Anubis source tree](https://github.com/TecharoHQ/anubis/tree/main/data) or anywhere on the filesystem. If you don't have access to the Anubis source tree, check /usr/share/docs/anubis/data or in the tarball you extracted Anubis from.
## Importing the default configuration
If you want to base your configuration off of the default configuration, import `(data)/meta/default-config.yaml`:
```yaml
bots:
- import: (data)/meta/default-config.yaml
# Write your rules here
```
This will keep your configuration up to date as Anubis adapts to emerging threats.
## How do I exempt most modern browsers from Anubis challenges?
If you want to exempt most modern browsers from Anubis challenges, import `(data)/common/acts-like-browser.yaml`:
```yaml
bots:
- import: (data)/meta/default-config.yaml
- import: (data)/common/acts-like-browser.yaml
# Write your rules here
```
These rules will allow traffic that "looks like" it's from a modern copy of Edge, Safari, Chrome, or Firefox. These rules used to be enabled by default, however user reports have suggested that AI scraper bots have adapted to conform to these rules to scrape without regard for the infrastructure they are attacking.
Use these rules at your own risk.
## Importing from imports ## Importing from imports
You can also import from an imported file in case you want to import an entire folder of rules at once. You can also import from an imported file in case you want to import an entire folder of rules at once.

View File

@@ -0,0 +1,50 @@
---
title: iplist2rule CLI tool
---
The `iplist2rule` tool converts IP blocklists into Anubis challenge policies. It reads common IP block list formats and generates the appropriate Anubis policy file for IP address filtering.
## Installation
Install directly with Go
```bash
go install github.com/TecharoHQ/anubis/utils/cmd/iplist2rule@latest
```
## Usage
Basic conversion from URL:
```bash
iplist2rule https://raw.githubusercontent.com/7c/torfilter/refs/heads/main/lists/txt/torfilter-1m-flat.txt filter-tor.yaml
```
Explicitly allow every IP address on a list:
```bash
iplist2rule --action ALLOW https://raw.githubusercontent.com/7c/torfilter/refs/heads/main/lists/txt/torfilter-1m-flat.txt filter-tor.yaml
```
Add weight to requests matching IP addresses on a list:
```bash
iplist2rule --action WEIGH --weight 20 https://raw.githubusercontent.com/7c/torfilter/refs/heads/main/lists/txt/torfilter-1m-flat.txt filter-tor.yaml
```
## Options
| Flag | Description | Default |
| :------------ | :----------------------------------------------------------------------------------------------- | :-------------------------------- |
| `--action` | The Anubis action to take for the IP address in question, must be in ALL CAPS. | `DENY` (forbids traffic) |
| `--rule-name` | The name for the generated Anubis rule, should be in kebab-case. | (not set, inferred from filename) |
| `--weight` | When `--action=WEIGH`, how many weight points should be added or removed from matching requests? | 0 (not set) |
## Using the Generated Policy
Save the output and import it in your main policy file:
```yaml
bots:
- import: "./filter-tor.yaml"
```

View File

@@ -12,6 +12,7 @@ Install directly with Go:
```bash ```bash
go install github.com/TecharoHQ/anubis/cmd/robots2policy@latest go install github.com/TecharoHQ/anubis/cmd/robots2policy@latest
``` ```
## Usage ## Usage
Basic conversion from URL: Basic conversion from URL:
@@ -35,8 +36,8 @@ robots2policy -input robots.txt -action DENY -format json
## Options ## Options
| Flag | Description | Default | | Flag | Description | Default |
|-----------------------|--------------------------------------------------------------------|---------------------| | --------------------- | ------------------------------------------------------------------ | ------------------- |
| `-input` | robots.txt file path or URL (use `-` for stdin) | *required* | | `-input` | robots.txt file path or URL (use `-` for stdin) | _required_ |
| `-output` | Output file (use `-` for stdout) | stdout | | `-output` | Output file (use `-` for stdout) | stdout |
| `-format` | Output format: `yaml` or `json` | `yaml` | | `-format` | Output format: `yaml` or `json` | `yaml` |
| `-action` | Action for disallowed paths: `ALLOW`, `DENY`, `CHALLENGE`, `WEIGH` | `CHALLENGE` | | `-action` | Action for disallowed paths: `ALLOW`, `DENY`, `CHALLENGE`, `WEIGH` | `CHALLENGE` |
@@ -47,6 +48,7 @@ robots2policy -input robots.txt -action DENY -format json
## Example ## Example
Input robots.txt: Input robots.txt:
```txt ```txt
User-agent: * User-agent: *
Disallow: /admin/ Disallow: /admin/
@@ -57,6 +59,7 @@ Disallow: /
``` ```
Generated policy: Generated policy:
```yaml ```yaml
- name: robots-txt-policy-disallow-1 - name: robots-txt-policy-disallow-1
action: CHALLENGE action: CHALLENGE
@@ -77,8 +80,8 @@ Generated policy:
Save the output and import it in your main policy file: Save the output and import it in your main policy file:
```yaml ```yaml
import: bots:
- path: "./robots-policy.yaml" - import: "./robots-policy.yaml"
``` ```
The tool handles wildcard patterns, user-agent specific rules, and blacklisted bots automatically. The tool handles wildcard patterns, user-agent specific rules, and blacklisted bots automatically.

View File

@@ -29,6 +29,9 @@ Anubis is brought to you by sponsors and donors like:
height="64" height="64"
/> />
</a> </a>
<a href="https://databento.com/?utm_source=anubis&utm_medium=sponsor&utm_campaign=anubis">
<img src="/img/sponsors/databento-logo.webp" alt="Databento" height="64" />
</a>
### Gold Tier ### Gold Tier

View File

@@ -160,7 +160,7 @@ impressum:
<h2>How the Information is used</h2> <h2>How the Information is used</h2>
<p>The information is used to enhance the vistor's experience when using the website to display personalised content and possibly advertising.</p> <p>The information is used to enhance the visitor's experience when using the website to display personalised content and possibly advertising.</p>
<p>E-mail addresses will not be sold, rented or leased to 3rd parties.</p> <p>E-mail addresses will not be sold, rented or leased to 3rd parties.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -4,12 +4,12 @@ import "time"
// Challenge is the metadata about a single challenge issuance. // Challenge is the metadata about a single challenge issuance.
type Challenge struct { type Challenge struct {
IssuedAt time.Time `json:"issuedAt"` IssuedAt time.Time `json:"issuedAt"` // When the challenge was issued
Metadata map[string]string `json:"metadata"` Metadata map[string]string `json:"metadata"` // Challenge metadata such as IP address and user agent
ID string `json:"id"` ID string `json:"id"` // UUID identifying the challenge
Method string `json:"method"` Method string `json:"method"` // Challenge method
RandomData string `json:"randomData"` RandomData string `json:"randomData"` // The random data the client processes
PolicyRuleHash string `json:"policyRuleHash,omitempty"` PolicyRuleHash string `json:"policyRuleHash,omitempty"` // Hash of the policy rule that issued this challenge
Difficulty int `json:"difficulty,omitempty"` Difficulty int `json:"difficulty,omitempty"` // Difficulty that was in effect when issued
Spent bool `json:"spent"` Spent bool `json:"spent"` // Has the challenge already been solved?
} }

View File

@@ -52,7 +52,7 @@ func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.Validate
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond) wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
if time.Now().Before(wantTime) { if time.Now().Before(wantTime) {
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339))) return challenge.NewError("validate", "insufficient time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
} }
gotChallenge := r.FormValue("challenge") gotChallenge := r.FormValue("challenge")

View File

@@ -60,7 +60,7 @@ func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.Validate
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond) wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
if time.Now().Before(wantTime) { if time.Now().Before(wantTime) {
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339))) return challenge.NewError("validate", "insufficient time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
} }
got := r.FormValue("result") got := r.FormValue("result")

View File

@@ -15,7 +15,7 @@ var (
type Logging struct { type Logging struct {
Sink string `json:"sink"` // Logging sink, either "stdio" or "file" Sink string `json:"sink"` // Logging sink, either "stdio" or "file"
Level *slog.Level `json:"level"` // Log level, if set supercedes the level in flags Level *slog.Level `json:"level"` // Log level, if set supersedes the level in flags
Parameters *LoggingFileConfig `json:"parameters"` // Logging parameters, to be dynamic in the future Parameters *LoggingFileConfig `json:"parameters"` // Logging parameters, to be dynamic in the future
} }

View File

@@ -81,7 +81,28 @@ func (ls *LocalizationService) GetLocalizerFromRequest(r *http.Request) *i18n.Lo
return i18n.NewLocalizer(bundle, "en") return i18n.NewLocalizer(bundle, "en")
} }
acceptLanguage := r.Header.Get("Accept-Language") acceptLanguage := r.Header.Get("Accept-Language")
return i18n.NewLocalizer(ls.bundle, acceptLanguage, "en")
// Parse Accept-Language header to properly handle quality factors
// The language.ParseAcceptLanguage function returns tags sorted by quality
tags, _, err := language.ParseAcceptLanguage(acceptLanguage)
if err != nil || len(tags) == 0 {
return i18n.NewLocalizer(ls.bundle, "en")
}
// Convert parsed tags to strings for the localizer
// We include both the full tag and base language to ensure proper matching
langs := make([]string, 0, len(tags)*2+1)
for _, tag := range tags {
langs = append(langs, tag.String())
// Also add base language (e.g., "en" for "en-GB") to help matching
base, _ := tag.Base()
if base.String() != tag.String() {
langs = append(langs, base.String())
}
}
langs = append(langs, "en") // Always include English as fallback
return i18n.NewLocalizer(ls.bundle, langs...)
} }
// SimpleLocalizer wraps i18n.Localizer with a more convenient API // SimpleLocalizer wraps i18n.Localizer with a more convenient API

View File

@@ -3,6 +3,7 @@ package localization
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http/httptest"
"sort" "sort"
"testing" "testing"
@@ -138,3 +139,40 @@ func TestComprehensiveTranslations(t *testing.T) {
}) })
} }
} }
func TestAcceptLanguageQualityFactors(t *testing.T) {
service := NewLocalizationService()
testCases := []struct {
name string
acceptLanguage string
expectedLang string
}{
{"simple_en", "en", "en"},
{"simple_de", "de", "de"},
{"en_GB_with_lower_priority_de", "en-GB,de-DE;q=0.5", "en"},
{"en_GB_only", "en-GB", "en"},
{"de_with_lower_priority_en", "de,en;q=0.5", "de"},
{"de_DE_with_lower_priority_en", "de-DE,en;q=0.5", "de"},
{"fr_with_lower_priority_de", "fr,de;q=0.5", "fr"},
{"zh_CN_regional", "zh-CN", "zh-CN"},
{"zh_TW_regional", "zh-TW", "zh-TW"},
{"pt_BR_regional", "pt-BR", "pt-BR"},
{"complex_header", "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,de;q=0.5", "fr"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
req.Header.Set("Accept-Language", tc.acceptLanguage)
localizer := service.GetLocalizerFromRequest(req)
sl := &SimpleLocalizer{Localizer: localizer}
gotLang := sl.GetLang()
if gotLang != tc.expectedLang {
t.Errorf("Accept-Language %q: expected %s, got %s", tc.acceptLanguage, tc.expectedLang, gotLang)
}
})
}
}

228
package-lock.json generated
View File

@@ -1,21 +1,21 @@
{ {
"name": "@techaro/anubis", "name": "@techaro/anubis",
"version": "1.24.0-pre1", "version": "1.24.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@techaro/anubis", "name": "@techaro/anubis",
"version": "1.24.0-pre1", "version": "1.24.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/sha256-js": "^5.2.0",
"preact": "^10.28.0" "preact": "^10.28.1"
}, },
"devDependencies": { "devDependencies": {
"cssnano": "^7.1.2", "cssnano": "^7.1.2",
"cssnano-preset-advanced": "^7.0.10", "cssnano-preset-advanced": "^7.0.10",
"esbuild": "^0.27.1", "esbuild": "^0.27.2",
"playwright": "^1.52.0", "playwright": "^1.52.0",
"postcss-cli": "^11.0.1", "postcss-cli": "^11.0.1",
"postcss-import": "^16.1.1", "postcss-import": "^16.1.1",
@@ -62,9 +62,9 @@
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
"integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -79,9 +79,9 @@
} }
}, },
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
"integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -96,9 +96,9 @@
} }
}, },
"node_modules/@esbuild/android-arm64": { "node_modules/@esbuild/android-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
"integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -113,9 +113,9 @@
} }
}, },
"node_modules/@esbuild/android-x64": { "node_modules/@esbuild/android-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
"integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -130,9 +130,9 @@
} }
}, },
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
"integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -147,9 +147,9 @@
} }
}, },
"node_modules/@esbuild/darwin-x64": { "node_modules/@esbuild/darwin-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
"integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -164,9 +164,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-arm64": { "node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
"integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -181,9 +181,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-x64": { "node_modules/@esbuild/freebsd-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
"integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -198,9 +198,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm": { "node_modules/@esbuild/linux-arm": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
"integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -215,9 +215,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm64": { "node_modules/@esbuild/linux-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
"integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -232,9 +232,9 @@
} }
}, },
"node_modules/@esbuild/linux-ia32": { "node_modules/@esbuild/linux-ia32": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
"integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -249,9 +249,9 @@
} }
}, },
"node_modules/@esbuild/linux-loong64": { "node_modules/@esbuild/linux-loong64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
"integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -266,9 +266,9 @@
} }
}, },
"node_modules/@esbuild/linux-mips64el": { "node_modules/@esbuild/linux-mips64el": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
"integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
@@ -283,9 +283,9 @@
} }
}, },
"node_modules/@esbuild/linux-ppc64": { "node_modules/@esbuild/linux-ppc64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
"integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -300,9 +300,9 @@
} }
}, },
"node_modules/@esbuild/linux-riscv64": { "node_modules/@esbuild/linux-riscv64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
"integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -317,9 +317,9 @@
} }
}, },
"node_modules/@esbuild/linux-s390x": { "node_modules/@esbuild/linux-s390x": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
"integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -334,9 +334,9 @@
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
"integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -351,9 +351,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-arm64": { "node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
"integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -368,9 +368,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
"integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -385,9 +385,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-arm64": { "node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
"integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -402,9 +402,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
"integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -419,9 +419,9 @@
} }
}, },
"node_modules/@esbuild/openharmony-arm64": { "node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
"integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -436,9 +436,9 @@
} }
}, },
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
"integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -453,9 +453,9 @@
} }
}, },
"node_modules/@esbuild/win32-arm64": { "node_modules/@esbuild/win32-arm64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
"integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -470,9 +470,9 @@
} }
}, },
"node_modules/@esbuild/win32-ia32": { "node_modules/@esbuild/win32-ia32": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
"integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -487,9 +487,9 @@
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
"integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1156,9 +1156,9 @@
} }
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.27.1", "version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -1169,32 +1169,32 @@
"node": ">=18" "node": ">=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.1", "@esbuild/aix-ppc64": "0.27.2",
"@esbuild/android-arm": "0.27.1", "@esbuild/android-arm": "0.27.2",
"@esbuild/android-arm64": "0.27.1", "@esbuild/android-arm64": "0.27.2",
"@esbuild/android-x64": "0.27.1", "@esbuild/android-x64": "0.27.2",
"@esbuild/darwin-arm64": "0.27.1", "@esbuild/darwin-arm64": "0.27.2",
"@esbuild/darwin-x64": "0.27.1", "@esbuild/darwin-x64": "0.27.2",
"@esbuild/freebsd-arm64": "0.27.1", "@esbuild/freebsd-arm64": "0.27.2",
"@esbuild/freebsd-x64": "0.27.1", "@esbuild/freebsd-x64": "0.27.2",
"@esbuild/linux-arm": "0.27.1", "@esbuild/linux-arm": "0.27.2",
"@esbuild/linux-arm64": "0.27.1", "@esbuild/linux-arm64": "0.27.2",
"@esbuild/linux-ia32": "0.27.1", "@esbuild/linux-ia32": "0.27.2",
"@esbuild/linux-loong64": "0.27.1", "@esbuild/linux-loong64": "0.27.2",
"@esbuild/linux-mips64el": "0.27.1", "@esbuild/linux-mips64el": "0.27.2",
"@esbuild/linux-ppc64": "0.27.1", "@esbuild/linux-ppc64": "0.27.2",
"@esbuild/linux-riscv64": "0.27.1", "@esbuild/linux-riscv64": "0.27.2",
"@esbuild/linux-s390x": "0.27.1", "@esbuild/linux-s390x": "0.27.2",
"@esbuild/linux-x64": "0.27.1", "@esbuild/linux-x64": "0.27.2",
"@esbuild/netbsd-arm64": "0.27.1", "@esbuild/netbsd-arm64": "0.27.2",
"@esbuild/netbsd-x64": "0.27.1", "@esbuild/netbsd-x64": "0.27.2",
"@esbuild/openbsd-arm64": "0.27.1", "@esbuild/openbsd-arm64": "0.27.2",
"@esbuild/openbsd-x64": "0.27.1", "@esbuild/openbsd-x64": "0.27.2",
"@esbuild/openharmony-arm64": "0.27.1", "@esbuild/openharmony-arm64": "0.27.2",
"@esbuild/sunos-x64": "0.27.1", "@esbuild/sunos-x64": "0.27.2",
"@esbuild/win32-arm64": "0.27.1", "@esbuild/win32-arm64": "0.27.2",
"@esbuild/win32-ia32": "0.27.1", "@esbuild/win32-ia32": "0.27.2",
"@esbuild/win32-x64": "0.27.1" "@esbuild/win32-x64": "0.27.2"
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
@@ -2339,9 +2339,9 @@
} }
}, },
"node_modules/preact": { "node_modules/preact": {
"version": "10.28.0", "version": "10.28.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.1.tgz",
"integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", "integrity": "sha512-u1/ixq/lVQI0CakKNvLDEcW5zfCjUQfZdK9qqWuIJtsezuyG6pk9TWj75GMuI/EzRSZB/VAE43sNWWZfiy8psw==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",

View File

@@ -20,7 +20,7 @@
"devDependencies": { "devDependencies": {
"cssnano": "^7.1.2", "cssnano": "^7.1.2",
"cssnano-preset-advanced": "^7.0.10", "cssnano-preset-advanced": "^7.0.10",
"esbuild": "^0.27.1", "esbuild": "^0.27.2",
"playwright": "^1.52.0", "playwright": "^1.52.0",
"postcss-cli": "^11.0.1", "postcss-cli": "^11.0.1",
"postcss-import": "^16.1.1", "postcss-import": "^16.1.1",
@@ -29,6 +29,6 @@
}, },
"dependencies": { "dependencies": {
"@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/sha256-js": "^5.2.0",
"preact": "^10.28.0" "preact": "^10.28.1"
} }
} }

View File

@@ -14,7 +14,7 @@ services:
ports: ports:
- 3004:3004 - 3004:3004
volumes: volumes:
- ../pki/registry.local.cetacean.club:/etc/techaro/pki/registry.local.cetacean.club - ./pki/registry.local.cetacean.club:/etc/techaro/pki/registry.local.cetacean.club
anubis: anubis:
image: ko.local/anubis image: ko.local/anubis

View File

@@ -1,53 +1,56 @@
REPO_ROOT=$(git rev-parse --show-toplevel) REPO_ROOT=$(git rev-parse --show-toplevel)
(cd $REPO_ROOT && go install ./utils/cmd/...) (cd $REPO_ROOT && go install ./utils/cmd/...)
mkdir -p pki
echo '*' >>./pki/.gitignore
function cleanup() { function cleanup() {
set +e set +e
pkill -P $$ pkill -P $$
if [ -f "docker-compose.yaml" ]; then if [ -f "docker-compose.yaml" ]; then
docker compose down -t 1 || : docker compose down -t 1 || :
docker compose rm -f || : docker compose rm -f || :
fi fi
} }
trap cleanup EXIT SIGINT trap cleanup EXIT SIGINT
function build_anubis_ko() { function build_anubis_ko() {
( (
cd $REPO_ROOT && npm ci && npm run assets cd $REPO_ROOT && npm ci && npm run assets
) )
( (
cd $REPO_ROOT && cd $REPO_ROOT &&
VERSION=devel ko build \ VERSION=devel ko build \
--platform=all \ --platform=all \
--base-import-paths \ --base-import-paths \
--tags="latest" \ --tags="latest" \
--image-user=1000 \ --image-user=1000 \
--image-annotation="" \ --image-annotation="" \
--image-label="" \ --image-label="" \
./cmd/anubis \ ./cmd/anubis \
--local --local
) )
} }
function mint_cert() { function mint_cert() {
if [ "$#" -ne 1 ]; then if [ "$#" -ne 1 ]; then
echo "Usage: mint_cert <domain.name>" echo "Usage: mint_cert <domain.name>"
fi fi
domainName="$1" domainName="$1"
# If the transient local TLS certificate doesn't exist, mint a new one # If the transient local TLS certificate doesn't exist, mint a new one
if [ ! -f "${REPO_ROOT}/test/pki/${domainName}/cert.pem" ]; then if [ ! -f "./pki/${domainName}/cert.pem" ]; then
# Subshell to contain the directory change # Subshell to contain the directory change
( (
cd ${REPO_ROOT}/test/pki && cd ./pki &&
mkdir -p "${domainName}" && mkdir -p "${domainName}" &&
go tool minica -domains "${domainName}" && go tool minica -domains "${domainName}" &&
cd "${domainName}" && cd "${domainName}" &&
chmod 666 * chmod 666 *
) )
fi fi
} }

View File

@@ -1,24 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
export VERSION=$GITHUB_COMMIT-test
export KO_DOCKER_REPO=ko.local
source ../lib/lib.sh source ../lib/lib.sh
export KO_DOCKER_REPO=ko.local
set -euo pipefail set -euo pipefail
build_anubis_ko
mint_cert mimi.techaro.lol mint_cert mimi.techaro.lol
docker run --rm -it \ docker run --rm \
-v ./conf/nginx:/etc/nginx:ro \ -v $PWD/conf/nginx:/etc/nginx:ro \
-v ../pki:/techaro/pki:ro \ -v $PWD/pki:/techaro/pki:ro \
nginx \ nginx \
nginx -t nginx -t
docker compose up -d
docker compose down -t 1 || :
docker compose rm -f || :
exit 0 exit 0

View File

@@ -26,7 +26,7 @@ services:
KEY_FNAME: key.pem KEY_FNAME: key.pem
PROXY_TO: http://anubis:3000 PROXY_TO: http://anubis:3000
volumes: volumes:
- ../../pki/relayd:/techaro/pki:ro - ./pki/relayd:/techaro/pki:ro
# novnc: # novnc:
# image: geek1011/easy-novnc # image: geek1011/easy-novnc
@@ -42,7 +42,7 @@ services:
environment: environment:
DISPLAY: display:0 DISPLAY: display:0
volumes: volumes:
- ../../pki:/usr/local/share/ca-certificates/minica:ro - ./pki:/usr/local/share/ca-certificates/minica:ro
- ../scripts:/hack/scripts:ro - ../scripts:/hack/scripts:ro
depends_on: depends_on:
- anubis - anubis

View File

@@ -24,7 +24,7 @@ services:
KEY_FNAME: key.pem KEY_FNAME: key.pem
PROXY_TO: http://anubis:3000 PROXY_TO: http://anubis:3000
volumes: volumes:
- ../../pki/relayd:/techaro/pki:ro - ./pki/relayd:/techaro/pki:ro
# novnc: # novnc:
# image: geek1011/easy-novnc # image: geek1011/easy-novnc
@@ -40,5 +40,5 @@ services:
environment: environment:
DISPLAY: display:0 DISPLAY: display:0
volumes: volumes:
- ../../pki:/usr/local/share/ca-certificates/minica:ro - ./pki:/usr/local/share/ca-certificates/minica:ro
- ../scripts:/hack/scripts:ro - ../scripts:/hack/scripts:ro

View File

@@ -0,0 +1,57 @@
package main
import (
"bufio"
"fmt"
"io"
"net/http"
"net/netip"
"strings"
)
// FetchBlocklist reads the blocklist over HTTP and returns every non-commented
// line parsed as an IP address in CIDR notation. IPv4 addresses are returned as
// /32, IPv6 addresses as /128.
//
// This function was generated with GLM 4.7.
func FetchBlocklist(url string) ([]string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP request failed with status: %s", resp.Status)
}
var lines []string
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
// Skip empty lines and comments (lines starting with #)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
addr, err := netip.ParseAddr(line)
if err != nil {
// Skip lines that aren't valid IP addresses
continue
}
var cidr string
if addr.Is4() {
cidr = fmt.Sprintf("%s/32", addr.String())
} else {
cidr = fmt.Sprintf("%s/128", addr.String())
}
lines = append(lines, cidr)
}
if err := scanner.Err(); err != nil && err != io.EOF {
return nil, err
}
return lines, nil
}

View File

@@ -0,0 +1,103 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/TecharoHQ/anubis/lib/config"
"github.com/facebookgo/flagenv"
"sigs.k8s.io/yaml"
)
type Rule struct {
Name string `yaml:"name" json:"name"`
Action config.Rule `yaml:"action" json:"action"`
RemoteAddr []string `json:"remote_addresses,omitempty" yaml:"remote_addresses,omitempty"`
Weight *config.Weight `json:"weight,omitempty" yaml:"weight,omitempty"`
}
func init() {
flag.Usage = func() {
fmt.Printf(`Usage of %[1]s:
%[1]s [flags] <blocklist-url> <filename>
Grabs the contents of the blocklist, converts it to an Anubis ruleset, and writes it to filename.
Flags:
`, filepath.Base(os.Args[0]))
flag.PrintDefaults()
}
}
var (
action = flag.String("action", "DENY", "Anubis action to take (ALLOW / DENY / WEIGH)")
manualRuleName = flag.String("rule-name", "", "If set, prefer this name over inferring from filename")
weight = flag.Int("weight", 0, "If set to any number, add/subtract this many weight points when --action=WEIGH")
)
func main() {
flagenv.Parse()
flag.Parse()
if flag.NArg() != 2 {
flag.Usage()
os.Exit(2)
}
blocklistURL := flag.Arg(0)
foutName := flag.Arg(1)
ruleName := strings.TrimSuffix(foutName, filepath.Ext(foutName))
if *manualRuleName != "" {
ruleName = *manualRuleName
}
ruleAction := config.Rule(*action)
if err := ruleAction.Valid(); err != nil {
log.Fatalf("--action=%q is invalid: %v", *action, err)
}
result := &Rule{
Name: ruleName,
Action: ruleAction,
}
if *weight != 0 {
if ruleAction != config.RuleWeigh {
log.Fatalf("used --weight=%d but --action=%s", *weight, *action)
}
result.Weight = &config.Weight{
Adjust: *weight,
}
}
ips, err := FetchBlocklist(blocklistURL)
if err != nil {
log.Fatalf("can't fetch blocklist %s: %v", blocklistURL, err)
}
result.RemoteAddr = ips
fout, err := os.Create(foutName)
if err != nil {
log.Fatalf("can't create output file %q: %v", foutName, err)
}
defer fout.Close()
fmt.Fprintf(fout, "# Generated by %s on %s from %s\n\n", filepath.Base(os.Args[0]), time.Now().Format(time.RFC3339), blocklistURL)
data, err := yaml.Marshal([]*Rule{result})
if err != nil {
log.Fatalf("can't marshal yaml")
}
fout.Write(data)
}

View File

@@ -64,7 +64,7 @@ templ base(title string, body templ.Component, impressum *config.Impressum, chal
@templ.JSONScript("anubis_public_url", anubis.PublicUrl) @templ.JSONScript("anubis_public_url", anubis.PublicUrl)
</head> </head>
<body id="top"> <body id="top">
@honeypotLink(fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString())) @honeypotLink(anubis.BasePrefix + fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString()))
<main> <main>
<h1 id="title" class="centered-div">{ title }</h1> <h1 id="title" class="centered-div">{ title }</h1>
@body @body
@@ -79,7 +79,7 @@ templ base(title string, body templ.Component, impressum *config.Impressum, chal
if impressum != nil { if impressum != nil {
<p> <p>
@templ.Raw(impressum.Footer) @templ.Raw(impressum.Footer)
-- <a href={ templ.SafeURL(fmt.Sprintf("%simprint", anubis.APIPrefix)) }>Imprint</a> -- <a href={ templ.SafeURL(anubis.BasePrefix + fmt.Sprintf("%simprint", anubis.APIPrefix)) }>Imprint</a>
</p> </p>
} }
<p>{ localizer.T("version_info") } <code>{ anubis.Version }</code>.</p> <p>{ localizer.T("version_info") } <code>{ anubis.Version }</code>.</p>

6
web/index_templ.go generated
View File

@@ -137,7 +137,7 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = honeypotLink(fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString())).Render(ctx, templ_7745c5c3_Buffer) templ_7745c5c3_Err = honeypotLink(anubis.BasePrefix+fmt.Sprintf("%shoneypot/%s/init", anubis.APIPrefix, uuid.NewString())).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -245,9 +245,9 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var13 templ.SafeURL var templ_7745c5c3_Var13 templ.SafeURL
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("%simprint", anubis.APIPrefix))) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(anubis.BasePrefix + fmt.Sprintf("%simprint", anubis.APIPrefix)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 82, Col: 78} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 82, Col: 98}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

81
web/index_test.go Normal file
View File

@@ -0,0 +1,81 @@
package web
import (
"context"
"net/http/httptest"
"strings"
"testing"
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/lib/config"
"github.com/TecharoHQ/anubis/lib/localization"
"github.com/a-h/templ"
)
func TestBasePrefixInLinks(t *testing.T) {
tests := []struct {
name string
basePrefix string
wantInLink string
}{
{
name: "no prefix",
basePrefix: "",
wantInLink: "/.within.website/x/cmd/anubis/api/",
},
{
name: "with rififi prefix",
basePrefix: "/rififi",
wantInLink: "/rififi/.within.website/x/cmd/anubis/api/",
},
{
name: "with myapp prefix",
basePrefix: "/myapp",
wantInLink: "/myapp/.within.website/x/cmd/anubis/api/",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Save original BasePrefix and restore after test
origPrefix := anubis.BasePrefix
defer func() { anubis.BasePrefix = origPrefix }()
anubis.BasePrefix = tt.basePrefix
// Create test impressum
impressum := &config.Impressum{
Footer: "<p>Test footer</p>",
Page: config.ImpressumPage{
Title: "Test Imprint",
Body: "<p>Test imprint body</p>",
},
}
// Create localizer using a dummy request
req := httptest.NewRequest("GET", "/", nil)
localizer := &localization.SimpleLocalizer{}
localizer.Localizer = localization.NewLocalizationService().GetLocalizerFromRequest(req)
// Render the base template to a buffer
var buf strings.Builder
component := base(tt.name, templ.NopComponent, impressum, nil, nil, localizer)
err := component.Render(context.Background(), &buf)
if err != nil {
t.Fatalf("failed to render template: %v", err)
}
output := buf.String()
// Check that honeypot link includes the base prefix
if !strings.Contains(output, `href="`+tt.wantInLink+`honeypot/`) {
t.Errorf("honeypot link does not contain base prefix %q\noutput: %s", tt.wantInLink, output)
}
// Check that imprint link includes the base prefix
if !strings.Contains(output, `href="`+tt.wantInLink+`imprint`) {
t.Errorf("imprint link does not contain base prefix %q\noutput: %s", tt.wantInLink, output)
}
})
}
}