Compare commits

..

3 Commits

Author SHA1 Message Date
Xe Iaso
6b09ac9543 chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-09 10:14:10 -04:00
Xe Iaso
de602116d0 fix(thr1): update spec to respond to feedback and evaluation against a private dataset
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-09 10:12:34 -04:00
Xe Iaso
3a4b1086af docs: add THR1 spec
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-04 23:14:17 -04:00
61 changed files with 683 additions and 878 deletions

View File

@@ -1,25 +0,0 @@
.env
*.deb
*.rpm
# Additional package locks
pnpm-lock.yaml
yarn.lock
# Go binaries and test artifacts
main
*.test
node_modules
# MacOS
.DS_store
# Intellij
.idea
# how does this get here
doc/VERSION
web/static/js/*
!web/static/js/.gitignore

View File

@@ -9,7 +9,6 @@ anubistest
Applebot Applebot
archlinux archlinux
badregexes badregexes
bdba
berr berr
bingbot bingbot
bitcoin bitcoin
@@ -28,7 +27,6 @@ caninetools
Cardyb Cardyb
celchecker celchecker
CELPHASE CELPHASE
cerr
certresolver certresolver
CGNAT CGNAT
cgr cgr
@@ -68,6 +66,7 @@ duckduckbot
eerror eerror
ellenjoe ellenjoe
enbyware enbyware
enca
everyones everyones
evilbot evilbot
evilsite evilsite
@@ -78,6 +77,7 @@ extldflags
facebookgo facebookgo
Factset Factset
fastcgi fastcgi
fcf
fediverse fediverse
finfos finfos
Firecrawl Firecrawl
@@ -141,6 +141,7 @@ lightpanda
LIMSA LIMSA
Linting Linting
linuxbrew linuxbrew
lkey
LLU LLU
loadbalancer loadbalancer
lol lol
@@ -149,7 +150,6 @@ maintainership
malware malware
mcr mcr
memes memes
metarefresh
metrix metrix
mimi mimi
minica minica
@@ -158,7 +158,6 @@ Mojeek
mojeekbot mojeekbot
mozilla mozilla
nbf nbf
netsurf
nginx nginx
nobots nobots
NONINFRINGEMENT NONINFRINGEMENT
@@ -171,7 +170,6 @@ onionservice
openai openai
openrc openrc
pag pag
palemoon
Pangu Pangu
parseable parseable
passthrough passthrough
@@ -188,7 +186,6 @@ prebaked
privkey privkey
promauto promauto
promhttp promhttp
proofofwork
pwcmd pwcmd
pwuser pwuser
qualys qualys
@@ -239,6 +236,7 @@ techarohq
templ templ
templruntime templruntime
testarea testarea
thr
Tik Tik
Timpibot Timpibot
torproject torproject
@@ -247,7 +245,6 @@ uberspace
unixhttpd unixhttpd
unmarshal unmarshal
uvx uvx
UXP
Varis Varis
Velen Velen
vendored vendored

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@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with: with:
sarif_file: results.sarif sarif_file: results.sarif
category: zizmor category: zizmor

View File

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

View File

@@ -43,7 +43,7 @@ Anubis is brought to you by sponsors and donors like:
## Overview ## Overview
Anubis is a Web AI Firewall Utility that [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using one or more challenges in order to protect upstream resources from scraper bots. Anubis [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using a proof-of-work challenge in order to protect upstream resources from scraper bots.
This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them. This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them.

View File

@@ -68,7 +68,6 @@ var (
extractResources = flag.String("extract-resources", "", "if set, extract the static resources to the specified folder") 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") 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") versionFlag = flag.Bool("version", false, "print Anubis version")
xffStripPrivate = flag.Bool("xff-strip-private", true, "if set, strip private addresses from X-Forwarded-For")
) )
func keyFromHex(value string) (ed25519.PrivateKey, error) { func keyFromHex(value string) (ed25519.PrivateKey, error) {
@@ -337,7 +336,7 @@ func main() {
h = s h = s
h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h) h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h)
h = internal.XForwardedForToXRealIP(h) h = internal.XForwardedForToXRealIP(h)
h = internal.XForwardedForUpdate(*xffStripPrivate, h) h = internal.XForwardedForUpdate(h)
srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()} srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()}
listener, listenerUrl := setupListener(*bindNetwork, *bind) listener, listenerUrl := setupListener(*bindNetwork, *bind)
@@ -421,11 +420,11 @@ func extractEmbedFS(fsys embed.FS, root string, destDir string) error {
return os.MkdirAll(destPath, 0o700) return os.MkdirAll(destPath, 0o700)
} }
embeddedData, err := fs.ReadFile(fsys, path) data, err := fs.ReadFile(fsys, path)
if err != nil { if err != nil {
return err return err
} }
return os.WriteFile(destPath, embeddedData, 0o644) return os.WriteFile(destPath, data, 0o644)
}) })
} }

View File

@@ -55,9 +55,7 @@ bots:
- name: generic-browser - name: generic-browser
user_agent_regex: >- user_agent_regex: >-
Mozilla|Opera Mozilla|Opera
action: WEIGH action: CHALLENGE
weight:
adjust: 10
dnsbl: false dnsbl: false

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
- name: cloudflare-workers - name: cloudflare-workers
headers_regex: headers_regex:
CF-Worker: .* CF-Worker: .*
action: WEIGH action: DENY
weight:
adjust: 15

View File

@@ -1,2 +0,0 @@
- import: (data)/clients/small-internet-browsers/netsurf.yaml
- import: (data)/clients/small-internet-browsers/palemoon.yaml

View File

@@ -1,5 +0,0 @@
- name: "reduce-weight-netsurf"
user_agent_regex: "NetSurf"
action: WEIGH
weight:
adjust: -5

View File

@@ -1,5 +0,0 @@
- name: "reduce-weight-palemoon"
user_agent_regex: "PaleMoon"
action: WEIGH
weight:
adjust: -5

View File

@@ -1,6 +1,4 @@
# https://connect.mozilla.org/t5/firefox-labs/try-out-link-previews-in-firefox-labs-138-and-share-your/td-p/92012 # https://connect.mozilla.org/t5/firefox-labs/try-out-link-previews-in-firefox-labs-138-and-share-your/td-p/92012
- name: x-firefox-ai - name: x-firefox-ai
action: WEIGH action: CHALLENGE
expression: '"X-Firefox-Ai" in headers' expression: '"X-Firefox-Ai" in headers'
weight:
adjust: 5

View File

@@ -1,15 +1,15 @@
- name: ipv4-rfc-1918 - name: ipv4-rfc-1918
action: ALLOW action: ALLOW
remote_addresses: remote_addresses:
- 10.0.0.0/8 - 10.0.0.0/8
- 172.16.0.0/12 - 172.16.0.0/12
- 192.168.0.0/16 - 192.168.0.0/16
- 100.64.0.0/10 - 100.64.0.0/10
- name: ipv6-ula - name: ipv6-ula
action: ALLOW action: ALLOW
remote_addresses: remote_addresses:
- fc00::/7 - fc00::/7
- name: ipv6-link-local - name: ipv6-link-local
action: ALLOW action: ALLOW
remote_addresses: remote_addresses:
- fe80::/10 - fe80::/10

View File

@@ -1,27 +0,0 @@
variable "ALPINE_VERSION" { default = "3.22" }
variable "GITHUB_SHA" { default = "devel" }
group "default" {
targets = [
"anubis",
]
}
target "anubis" {
args = {
ALPINE_VERSION = "3.22"
}
context = "."
dockerfile = "./Dockerfile"
platforms = [
"linux/amd64",
"linux/arm64",
"linux/arm/v7",
"linux/ppc64le",
"linux/riscv64",
]
pull = true
tags = [
"ghcr.io/techarohq/anubis:${GITHUB_SHA}"
]
}

View File

@@ -11,19 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
- Remove the unused `/test-error` endpoint and update the testing endpoint `/make-challenge` to only be enabled in
development
- Add `--xff-strip-private` flag/envvar to toggle skipping X-Forwarded-For private addresses or not
- Requests can have their weight be adjusted, if a request weighs zero or less than it is allowed through
- Refactor challenge presentation logic to use a challenge registry - Refactor challenge presentation logic to use a challenge registry
- Allow challenge implementations to register HTTP routes
- Implement a no-JS challenge method: [`metarefresh`](./admin/configuration/challenges/metarefresh.mdx) ([#95](https://github.com/TecharoHQ/anubis/issues/95))
- Bump AI-robots.txt to version 1.34
- Make progress bar styling more compatible (UXP, etc)
## v1.19.1: Jenomis cen Lexentale - Echo 1 ## v1.19.1: Jenomis cen Lexentale - Echo 1
- Return `data/bots/ai-robots-txt.yaml` to avoid breaking configs [#599](https://github.com/TecharoHQ/anubis/issues/599) Return `data/bots/ai-robots-txt.yaml` to avoid breaking configs [#599](https://github.com/TecharoHQ/anubis/issues/599)
## v1.19.0: Jenomis cen Lexentale ## v1.19.0: Jenomis cen Lexentale
@@ -38,27 +30,27 @@ Mostly a bunch of small features, no big ticket things this time.
- Add `--target-insecure-skip-verify` flag/envvar to allow Anubis to hit a self-signed HTTPS backend - Add `--target-insecure-skip-verify` flag/envvar to allow Anubis to hit a self-signed HTTPS backend
- Minor adjustments to FreeBSD rc.d script to allow for more flexible configuration. - Minor adjustments to FreeBSD rc.d script to allow for more flexible configuration.
- Added Podman and Docker support for running Playwright tests - Added Podman and Docker support for running Playwright tests
- Add a default rule to throw challenges when a request with the `X-Firefox-Ai` header is set - Add a default rule to throw challenges when a request with the `X-Firefox-Ai` header is set.
- Updated the nonce value in the challenge JWT cookie to be a string instead of a number - Updated the nonce value in the challenge JWT cookie to be a string instead of a number
- Rename cookies in response to user feedback - Rename cookies in response to user feedback
- Ensure cookie renaming is consistent across configuration options - Ensure cookie renaming is consistent across configuration options
- Add Bookstack app in data - Add Bookstack app in data
- Truncate everything but the first five characters of Accept-Language headers when making challenges - Truncate everything but the first five characters of Accept-Language headers when making challenges
- Ensure client JavaScript is served with Content-Type text/javascript. - Ensure client JavaScript is served with Content-Type text/javascript.
- Add `--target-host` flag/envvar to allow changing the value of the Host header in requests forwarded to the target service - Add `--target-host` flag/envvar to allow changing the value of the Host header in requests forwarded to the target service.
- Bump AI-robots.txt to version 1.31 - Bump AI-robots.txt to version 1.31
- Add `RuntimeDirectory` to systemd unit settings so native packages can listen over unix sockets - Add `RuntimeDirectory` to systemd unit settings so native packages can listen over unix sockets
- Added SearXNG instance tracker whitelist policy - Added SearXNG instance tracker whitelist policy
- Added Qualys SSL Labs whitelist policy - Added Qualys SSL Labs whitelist policy
- Fixed cookie deletion logic ([#520](https://github.com/TecharoHQ/anubis/issues/520), [#522](https://github.com/TecharoHQ/anubis/pull/522)) - Fixed cookie deletion logic ([#520](https://github.com/TecharoHQ/anubis/issues/520), [#522](https://github.com/TecharoHQ/anubis/pull/522))
- Add `--target-sni` flag/envvar to allow changing the value of the TLS handshake hostname in requests forwarded to the target service - Add `--target-sni` flag/envvar to allow changing the value of the TLS handshake hostname in requests forwarded to the target service.
- Fixed CEL expression matching validator to now properly error out when it receives empty expressions - Fixed CEL expression matching validator to now properly error out when it receives empty expressions
- Added OpenRC init.d script - Added OpenRC init.d script.
- Added `--version` flag - Added `--version` flag.
- Added `anubis_proxied_requests_total` metric to count proxied requests - Added `anubis_proxied_requests_total` metric to count proxied requests.
- Add `Applebot` as "good" web crawler - Add `Applebot` as "good" web crawler
- Reorganize AI/LLM crawler blocking into three separate stances, maintaining existing status quo as default - Reorganize AI/LLM crawler blocking into three separate stances, maintaining existing status quo as default.
- Split out AI/LLM user agent blocking policies, adding documentation for each - Split out AI/LLM user agent blocking policies, adding documentation for each.
## v1.18.0: Varis zos Galvus ## v1.18.0: Varis zos Galvus

View File

@@ -1,8 +0,0 @@
{
"label": "Challenges",
"position": 10,
"link": {
"type": "generated-index",
"description": "The different challenge methods that Anubis supports."
}
}

View File

@@ -1,19 +0,0 @@
# Meta Refresh (No JavaScript)
The `metarefresh` challenge sends a browser a much simpler challenge that makes it refresh the page after a set period of time. This enables clients to pass challenges without executing JavaScript.
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: metarefresh # Specify a non-JS challenge method
```
This is not enabled by default while this method is tested and its false positive rate is ascertained. Many modern scrapers use headless Google Chrome, so this will have a much higher false positive rate.

View File

@@ -1,5 +0,0 @@
# Proof of Work (JavaScript)
When Anubis is configured to use the `fast` or `slow` challenge methods, clients will be sent a small [proof of work](https://en.wikipedia.org/wiki/Proof_of_work) challenge. In order to get a token used to access the upstream resource, clients must calculate a complicated math puzzle with JavaScript.
A `fast` challenge uses a heavily optimized multithreaded implementation and a `slow` challenge uses a simplistic single-threaded implementation. The `slow` method is kept around for legacy compatibility.

View File

@@ -10,20 +10,6 @@ Anubis can act in one of two modes:
1. Reverse proxy (the default): Anubis sits in the middle of all traffic and then will reverse proxy it to its destination. This is the moral equivalent of a middleware in your favorite web framework. 1. Reverse proxy (the default): Anubis sits in the middle of all traffic and then will reverse proxy it to its destination. This is the moral equivalent of a middleware in your favorite web framework.
2. Subrequest authentication mode: Anubis listens for requests and if they don't pass muster then they are forwarded to Anubis for challenge processing. This is the equivalent of Anubis being a sidecar service. 2. Subrequest authentication mode: Anubis listens for requests and if they don't pass muster then they are forwarded to Anubis for challenge processing. This is the equivalent of Anubis being a sidecar service.
:::note
Subrequest authentication requires changing the default policy because nginx interprets the default `DENY` status code `200` as successful authentication and allows the request.
```yaml
status_codes:
CHALLENGE: 200
DENY: 403
```
[See policy definitions](../policies.mdx).
:::
## Nginx ## Nginx
Anubis can perform [subrequest authentication](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) with the `auth_request` module in Nginx. In order to set this up, keep the following things in mind: Anubis can perform [subrequest authentication](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-subrequest-authentication/) with the `auth_request` module in Nginx. In order to set this up, keep the following things in mind:

View File

@@ -120,14 +120,6 @@ Make sure to add a separate configuration file for the listener on port 3001:
```text ```text
# /etc/httpd/conf.d/listener-3001.conf # /etc/httpd/conf.d/listener-3001.conf
Listen [::1]:3001
```
In case you are running an IPv4-only system, use the following configuration instead:
```text
# /etc/httpd/conf.d/listener-3001.conf
Listen 127.0.0.1:3001 Listen 127.0.0.1:3001
``` ```

View File

@@ -49,30 +49,29 @@ For more detailed information on installing Anubis with native packages, please
Anubis uses these environment variables for configuration: Anubis uses these environment variables for configuration:
| Environment Variable | Default value | Explanation | | 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. | | `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` | `: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. | | `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_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_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. | | `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_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. |
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. | | `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` | 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. | | `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` | `: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. | | `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. | | `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. |
| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. | | `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. |
| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. | | `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. |
| `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. | | `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. | | `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. | | `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. | | `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. |
| `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`. | | `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_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. | | `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> <details>
<summary>Advanced configuration settings</summary> <summary>Advanced configuration settings</summary>

View File

@@ -244,39 +244,3 @@ In case your service needs it for risk calculation reasons, Anubis exposes infor
| `X-Anubis-Status` | The status and how strict Anubis was in its checks | `PASS` | | `X-Anubis-Status` | The status and how strict Anubis was in its checks | `PASS` |
Policy rules are matched using [Go's standard library regular expressions package](https://pkg.go.dev/regexp). You can mess around with the syntax at [regex101.com](https://regex101.com), make sure to select the Golang option. Policy rules are matched using [Go's standard library regular expressions package](https://pkg.go.dev/regexp). You can mess around with the syntax at [regex101.com](https://regex101.com), make sure to select the Golang option.
## Request Weight
Anubis rules can also add or remove "weight" from requests, allowing administrators to configure custom levels of suspicion. For example, if your application uses session tokens named `i_love_gitea`:
```yaml
- name: gitea-session-token
action: WEIGH
expression:
all:
- '"Cookie" in headers'
- headers["Cookie"].contains("i_love_gitea=")
# Remove 5 weight points
weight:
adjust: -5
```
This would remove five weight points from the request, making Anubis present the [Meta Refresh challenge](./configuration/challenges/metarefresh.mdx).
### Weight Thresholds
Weight thresholds and challenge associations will be configurable with CEL expressions in the configuration file in an upcoming patch, for now here's how Anubis configures the weight thresholds:
| Weight Expression | Action |
| -----------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------- |
| `weight < 0` (weight is less than 0) | Allow the request through. |
| `weight < 10` (weight is less than 10) | Challenge the client with the [Meta Refresh challenge](./configuration/challenges/metarefresh.mdx) at the default difficulty level. |
| `weight >= 10` (weight is greater than or equal to 10) | Challenge the client with the [Proof of Work challenge](./configuration/challenges/proof-of-work.mdx) at the default difficulty level. |
### Advice
Weight is still very new and needs work. This is an experimental feature and should be treated as such. Here's some advice to help you better tune requests:
- The default weight for browser-like clients is 10. This triggers an aggressive challenge.
- Remove and add weight in multiples of five.
- Be careful with how you configure weight.

View File

@@ -91,7 +91,7 @@ work valid?"}
## Proof of passing challenges ## Proof of passing challenges
When a client passes a challenge, Anubis sets an HTTP cookie named `"techaro.lol-anubis-auth"` containing a signed [JWT](https://jwt.io/) (JSON Web Token). This JWT contains the following claims: When a client passes a challenge, Anubis sets an HTTP cookie named `"within.website-x-cmd-anubis-auth"` containing a signed [JWT](https://jwt.io/) (JSON Web Token). This JWT contains the following claims:
- `challenge`: The challenge string derived from user request metadata - `challenge`: The challenge string derived from user request metadata
- `nonce`: The nonce / iteration number used to generate the passing response - `nonce`: The nonce / iteration number used to generate the passing response

View File

@@ -0,0 +1,202 @@
# Techaro HTTP Request Fingerprinting Version 1
The naïve way to identify HTTP clients is to use the HTTP User-Agent string as a signal. In an ideal world, this would give you a perfect view of what clients are connecting to your server. We do not live in that ideal world. As such, we need an alternative method that can scale to the world we have.
## Prior Art
The biggest source of prior art is [FoxIO's JA4H fingerprinting method](https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4H.md). This is fine, but there's a problem with it in the real world: Go doesn't allow you to observe the order headers arrived in. As Anubis is written in Go and I don't feel like boiling the HTTP server ocean today, there needs to be an alternative.
## THR1
The fingerprint consists of four concatenated components:
```text
<thr1_head>_<thr1_lang>_<thr1_sec>_<thr1_ua>_<thr1_enc>
```
Example:
```text
get201004_enca-d6b272e5b_sec-a9649072c_2a347fcf7_zs
```
Each component is described below:
### `thr1_head`
Overall request summary of method, protocol, and header counts:
- First three letters of the HTTP method, lowercased (e.g. get, pos).
- HTTP protocol version formatted in two digits (`10` for HTTP/1.0, `11` for HTTP/1.1, `20` for HTTP/2, `30` for HTTP/3 etc.).
- If present, prefer the HTTP protocol version in `X-Http-Version`.
- Number of HTTP headers sent by the client, zero-padded to two digits (e.g. `10`).
- Number of `Sec-*` headers sent by the client, zero-padded to two digits (e.g. `04`).
Example:
```text
get201004
```
### `thr1_lang`
`Accept-Language` header details.
- If no `Accept-Language` header is set, then:
```
-000000000
```
- Otherwise:
- The first 4 alphanumeric characters of the header value (lowercased, right-padded with `0` to length 4), e.g. `enca`.
- The first 9 hex characters of the SHA-256 hash of the full `Accept-Language` header value.
Example:
```
enca-d6b272e5b
```
### `thr1_sec`
Details about the `Sec-*` headers sent by the client.
```
thr1_sec = "sec-" + HASH9
```
Where:
- Collect **all headers whose names start with `sec-` (case-insensitive)**, excluding `Sec-Fetch-User`.
- For each header:
1. Normalize the header name by lowercasing.
2. If the header is one of the `Sec-CH-UA` family:
- `Sec-CH-UA`
- `Sec-CH-UA-Mobile`
- `Sec-CH-UA-Platform`
- `Sec-CH-UA-Platform-Version`
- `Sec-CH-UA-Model`
- `Sec-CH-UA-Full-Version`
Apply **special normalization rules** (see below).
3. For all other `sec-` headers:
- Unquote values if quoted.
- Trim leading/trailing whitespace.
- Keep the value as-is (do not parse further).
- Sort all included headers by their normalized header name (ASCII order).
- Serialize each header as:
```text
<header_name>:<normalized_value>
```
- Join all serialized lines with `\n`.
- Compute SHA-256 hash of the resulting canonical string.
- Take the first 9 hex characters of the hash and prefix with `sec-`.
Example:
```text
sec-a9649072c
```
#### Special Normalization Rules for `Sec-CH-UA*` headers
| Header | Normalization |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Sec-CH-UA` | Parse into `{brand, version}` pairs. Omit any with brand `"Not=A?Brand"`. Sort by brand ASC. Serialize as: `ua:Brand1/Version1,Brand2/Version2,...` |
| `Sec-CH-UA-Mobile` | Convert `"?1"` → `true`, `"?0"` → `false`. Serialize as: `mobile:true` or `mobile:false` |
| `Sec-CH-UA-Platform` | Lowercase, unquoted, trimmed. Serialize as: `platform:<value>` |
| `Sec-CH-UA-Platform-Version` | Unquoted, trimmed. Serialize as: `platform_version:<value>` |
| `Sec-CH-UA-Model` | Unquoted, trimmed. Serialize as: `model:<value>` |
| `Sec-CH-UA-Full-Version` | Unquoted, trimmed. Serialize as: `full_version:<value>` |
Given these headers:
```text
Sec-CH-UA: "Google Chrome";v="123", "Not=A?Brand";v="8", "Chromium";v="123"
Sec-CH-UA-Mobile: ?1
Sec-CH-UA-Platform: "Windows"
Sec-CH-UA-Platform-Version: "10.0.0"
Sec-CH-UA-Model: "Pixel 7"
Sec-CH-UA-Full-Version: "123.0.6312.122"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
```
Normalized canonical string before hashing:
```text
sec-fetch-dest:document
sec-fetch-mode:navigate
mobile:true
platform:windows
platform_version:10.0.0
full_version:123.0.6312.122
model:Pixel 7
ua:Chromium/123,Google Chrome/123
```
Then sort by header name:
```text
full_version:123.0.6312.122
mobile:true
model:Pixel 7
platform:windows
platform_version:10.0.0
sec-fetch-dest:document
sec-fetch-mode:navigate
ua:Chromium/123,Google Chrome/123
```
### `thr1_ua`
SHA256 fingerprint of the `User-Agent` string, taking the first 9 hex digits.
Example output:
```text
2a347fcf7
```
### `thr1_enc`
Heres the updated spec and Go implementation for the `thr1_enc` (compression) component, now including:
- **Most preferred compression encoding** (`*`, `gzip`, `deflate`, `br`, `zstd`)
- **Number of encodings declared**, truncated to **two digits** (`01``99`, capped)
---
### ✅ `thr1_enc` Spec (Revised)
**Format:**
```
<preferred_encoding>-<count>
```
- `preferred_encoding` is the first matching value in this priority order:
1. `*`
2. `gzip`
3. `deflate`
4. `br`
5. `zstd`
- If none match, use `none`
- `count` is the number of encoding options, zero-padded to 2 digits (max 99)
**Examples:**
- `gzip, deflate` → `gzip-02`
- `gzip;q=0.9, br;q=0.8` → `gzip-02`
- `zstd` → `zstd-01`
- `bogus` → `none-01`
- _empty_ → `none-00`

View File

@@ -60,7 +60,7 @@ Anubis is brought to you by sponsors and donors like:
## Overview ## Overview
Anubis is a Web AI Firewall Utility that [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using one or more challenges in order to protect upstream resources from scraper bots. Anubis [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using a proof-of-work challenge in order to protect upstream resources from scraper bots.
This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them. This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them.

View File

@@ -40,7 +40,6 @@ This page contains a non-exhaustive list with all websites using Anubis.
- https://openwrt.org/ - https://openwrt.org/
- https://minihoot.site - https://minihoot.site
- https://catgirl.click/ - https://catgirl.click/
- https://wiki.dolphin-emu.org/
- <details> - <details>
<summary>FreeCAD</summary> <summary>FreeCAD</summary>
- https://forum.freecad.org/ - https://forum.freecad.org/

View File

@@ -1,72 +0,0 @@
## Anubis has the ability to let you import snippets of configuration into the main
## configuration file. This allows you to break up your config into smaller parts
## that get logically assembled into one big file.
##
## Of note, a bot rule can either have inline bot configuration or import a
## bot config snippet. You cannot do both in a single bot rule.
##
## Import paths can either be prefixed with (data) to import from the common/shared
## rules in the data folder in the Anubis source tree or will point to absolute/relative
## paths in your filesystem. If you don't have access to the Anubis source tree, check
## /usr/share/docs/anubis/data or in the tarball you extracted Anubis from.
bots:
# 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
# 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: metarefresh # Specify a non-JS challenge method
dnsbl: false
# By default, send HTTP 200 back to clients that either get issued a challenge
# or a denial. This seems weird, but this is load-bearing due to the fact that
# the most aggressive scraper bots seem to really, really, want an HTTP 200 and
# will stop sending requests once they get it.
status_codes:
CHALLENGE: 200
DENY: 200

View File

@@ -11,58 +11,48 @@ spec:
labels: labels:
app: anubis-docs app: anubis-docs
spec: spec:
volumes:
- name: anubis
configMap:
name: anubis-cfg
containers: containers:
- name: anubis-docs - name: anubis-docs
image: ghcr.io/techarohq/anubis/docs:main image: ghcr.io/techarohq/anubis/docs:main
imagePullPolicy: Always imagePullPolicy: Always
resources: resources:
limits: limits:
memory: "128Mi" memory: "128Mi"
cpu: "500m" cpu: "500m"
requests: ports:
cpu: 250m - containerPort: 80
memory: 128Mi - name: anubis
ports: image: ghcr.io/techarohq/anubis:main
- containerPort: 80 imagePullPolicy: Always
- name: anubis env:
image: ghcr.io/techarohq/anubis:main - name: "BIND"
imagePullPolicy: Always value: ":8081"
env: - name: "DIFFICULTY"
- name: "BIND" value: "4"
value: ":8081" - name: "METRICS_BIND"
- name: "DIFFICULTY" value: ":9090"
value: "4" - name: "POLICY_FNAME"
- name: "METRICS_BIND" value: ""
value: ":9090" - name: "SERVE_ROBOTS_TXT"
- name: "POLICY_FNAME" value: "false"
value: "/xe/cfg/anubis/botPolicies.yaml" - name: "TARGET"
- name: "SERVE_ROBOTS_TXT" value: "http://localhost:80"
value: "false" # - name: "SLOG_LEVEL"
- name: "TARGET" # value: "debug"
value: "http://localhost:80" resources:
# - name: "SLOG_LEVEL" limits:
# value: "debug" cpu: 500m
volumeMounts: memory: 128Mi
- name: anubis requests:
mountPath: /xe/cfg/anubis cpu: 250m
resources: memory: 128Mi
limits: securityContext:
cpu: 500m runAsUser: 1000
memory: 128Mi runAsGroup: 1000
requests: runAsNonRoot: true
cpu: 250m allowPrivilegeEscalation: false
memory: 128Mi capabilities:
securityContext: drop:
runAsUser: 1000 - ALL
runAsGroup: 1000 seccompProfile:
runAsNonRoot: true type: RuntimeDefault
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault

View File

@@ -2,10 +2,4 @@ resources:
- deployment.yaml - deployment.yaml
- ingress.yaml - ingress.yaml
- onionservice.yaml - onionservice.yaml
- service.yaml - service.yaml
configMapGenerator:
- name: anubis-cfg
behavior: create
files:
- ./cfg/anubis/botPolicies.yaml

16
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/TecharoHQ/anubis
go 1.24.2 go 1.24.2
require ( require (
github.com/a-h/templ v0.3.898 github.com/a-h/templ v0.3.887
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/cel-go v0.25.0 github.com/google/cel-go v0.25.0
@@ -11,7 +11,7 @@ require (
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.22.0
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/yl2chen/cidranger v1.0.2 github.com/yl2chen/cidranger v1.0.2
golang.org/x/net v0.41.0 golang.org/x/net v0.40.0
k8s.io/apimachinery v0.33.1 k8s.io/apimachinery v0.33.1
) )
@@ -40,7 +40,7 @@ require (
github.com/cli/go-gh v0.1.0 // indirect github.com/cli/go-gh v0.1.0 // indirect
github.com/cloudflare/circl v1.6.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/deckarep/golang-set/v2 v2.8.0 // indirect github.com/deckarep/golang-set/v2 v2.7.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c // indirect github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
@@ -88,16 +88,16 @@ require (
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
golang.org/x/crypto v0.39.0 // indirect golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.15.0 // indirect golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect
golang.org/x/term v0.32.0 // indirect golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect golang.org/x/tools v0.32.0 // indirect
golang.org/x/vuln v1.1.4 // indirect golang.org/x/vuln v1.1.4 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect

32
go.sum
View File

@@ -32,8 +32,8 @@ github.com/TecharoHQ/yeet v0.6.0 h1:RCBAjr7wIlllsgy0tpvWpLX7jsZgu2tiuBY3RrprcR0=
github.com/TecharoHQ/yeet v0.6.0/go.mod h1:bj2V4Fg8qKQXoiuPZa3HuawrE8g+LsOQv/9q2WyGSsA= github.com/TecharoHQ/yeet v0.6.0/go.mod h1:bj2V4Fg8qKQXoiuPZa3HuawrE8g+LsOQv/9q2WyGSsA=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
github.com/a-h/templ v0.3.898 h1:g9oxL/dmM6tvwRe2egJS8hBDQTncokbMoOFk1oJMX7s= github.com/a-h/templ v0.3.887 h1:QKk7kFzqWGfVwEm/phalqMmZncqnqTrmFEhXHozOXpk=
github.com/a-h/templ v0.3.898/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ= github.com/a-h/templ v0.3.887/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -73,8 +73,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= github.com/deckarep/golang-set/v2 v2.7.0 h1:gIloKvD7yH2oip4VLhsv3JyLLFnC0Y2mlusgcvJYW5k=
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/deckarep/golang-set/v2 v2.7.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c h1:mxWGS0YyquJ/ikZOjSrRjjFIbUqIP9ojyYQ+QZTU3Rg= github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c h1:mxWGS0YyquJ/ikZOjSrRjjFIbUqIP9ojyYQ+QZTU3Rg=
@@ -274,16 +274,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -291,13 +291,13 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -338,14 +338,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -81,12 +81,12 @@ func XForwardedForToXRealIP(next http.Handler) http.Handler {
// XForwardedForUpdate sets or updates the X-Forwarded-For header, adding // XForwardedForUpdate sets or updates the X-Forwarded-For header, adding
// the known remote address to an existing chain if present // the known remote address to an existing chain if present
func XForwardedForUpdate(stripPrivate bool, next http.Handler) http.Handler { func XForwardedForUpdate(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer next.ServeHTTP(w, r) defer next.ServeHTTP(w, r)
pref := XFFComputePreferences{ pref := XFFComputePreferences{
StripPrivate: stripPrivate, StripPrivate: true,
StripLoopback: true, StripLoopback: true,
StripCGNAT: true, StripCGNAT: true,
Flatten: true, Flatten: true,

View File

@@ -23,7 +23,7 @@ func TestXForwardedForUpdateIgnoreUnix(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
XForwardedForUpdate(true, h).ServeHTTP(w, r) XForwardedForUpdate(h).ServeHTTP(w, r)
if r.RemoteAddr != remoteAddr { if r.RemoteAddr != remoteAddr {
t.Errorf("wanted remoteAddr to be %s, got: %s", r.RemoteAddr, remoteAddr) t.Errorf("wanted remoteAddr to be %s, got: %s", r.RemoteAddr, remoteAddr)
@@ -43,7 +43,7 @@ func TestXForwardedForUpdateAddToChain(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
}) })
srv := httptest.NewServer(XForwardedForUpdate(true, h)) srv := httptest.NewServer(XForwardedForUpdate(h))
r, err := http.NewRequest(http.MethodGet, srv.URL, nil) r, err := http.NewRequest(http.MethodGet, srv.URL, nil)
if err != nil { if err != nil {
@@ -81,15 +81,6 @@ func TestComputeXFFHeader(t *testing.T) {
}, },
result: "1.1.1.1,127.0.0.1", result: "1.1.1.1,127.0.0.1",
}, },
{
name: "StripPrivate",
remoteAddr: "127.0.0.1:80",
origXFFHeader: "1.1.1.1,10.0.0.1",
pref: XFFComputePreferences{
StripPrivate: false,
},
result: "1.1.1.1,10.0.0.1,127.0.0.1",
},
{ {
name: "StripLoopback", name: "StripLoopback",
remoteAddr: "127.0.0.1:80", remoteAddr: "127.0.0.1:80",

View File

@@ -27,9 +27,9 @@ import (
"github.com/TecharoHQ/anubis/lib/challenge" "github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/policy" "github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config" "github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/TecharoHQ/anubis/lib/thr1"
// challenge implementations // challenge implementations
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork" _ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
) )
@@ -66,27 +66,22 @@ type Server struct {
policy *policy.ParsedConfig policy *policy.ParsedConfig
DNSBLCache *decaymap.Impl[string, dnsbl.DroneBLResponse] DNSBLCache *decaymap.Impl[string, dnsbl.DroneBLResponse]
OGTags *ogtags.OGTagCache OGTags *ogtags.OGTagCache
cookieName string
priv ed25519.PrivateKey priv ed25519.PrivateKey
pub ed25519.PublicKey pub ed25519.PublicKey
opts Options opts Options
cookieName string
} }
func (s *Server) challengeFor(r *http.Request, difficulty int) string { func (s *Server) challengeFor(r *http.Request, difficulty int) string {
fp := sha256.Sum256(s.pub[:]) fp := sha256.Sum256(s.pub[:])
acceptLanguage := r.Header.Get("Accept-Language")
if len(acceptLanguage) > 5 {
acceptLanguage = acceptLanguage[:5]
}
challengeData := fmt.Sprintf( challengeData := fmt.Sprintf(
"Accept-Language=%s,X-Real-IP=%s,User-Agent=%s,WeekTime=%s,Fingerprint=%x,Difficulty=%d", "THR1=%s,JA4=%s,Fingerprint=%x,User-Agent=%s,WeekTime=%s,Difficulty=%d",
acceptLanguage, thr1.Fingerprint(r),
r.Header.Get("X-Real-Ip"), r.Header.Get("X-Tls-Fingerprint-Ja4"),
fp,
r.UserAgent(), r.UserAgent(),
time.Now().UTC().Round(24*7*time.Hour).Format(time.RFC3339), time.Now().UTC().Round(24*7*time.Hour).Format(time.RFC3339),
fp,
difficulty, difficulty,
) )
return internal.SHA256sum(challengeData) return internal.SHA256sum(challengeData)
@@ -402,19 +397,17 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, redir, http.StatusFound) http.Redirect(w, r, redir, http.StatusFound)
} }
func cr(name string, rule config.Rule, weight int) policy.CheckResult { func (s *Server) TestError(w http.ResponseWriter, r *http.Request) {
return policy.CheckResult{ err := r.FormValue("err")
Name: name, s.respondWithError(w, r, err)
Rule: rule,
Weight: weight,
}
} }
var ( func cr(name string, rule config.Rule) policy.CheckResult {
weightOkayStatic = policy.NewStaticHashChecker("weight/okay") return policy.CheckResult{
weightMildSusStatic = policy.NewStaticHashChecker("weight/mild-suspicion") Name: name,
weightVerySusStatic = policy.NewStaticHashChecker("weight/extreme-suspicion") Rule: rule,
) }
}
// Check evaluates the list of rules, and returns the result // Check evaluates the list of rules, and returns the result
func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) { func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) {
@@ -428,8 +421,6 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error)
return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] %q is not an IP address", host) return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] %q is not an IP address", host)
} }
weight := 0
for _, b := range s.policy.Bots { for _, b := range s.policy.Bots {
match, err := b.Rules.Check(r) match, err := b.Rules.Check(r)
if err != nil { if err != nil {
@@ -437,47 +428,11 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error)
} }
if match { if match {
switch b.Action { return cr("bot/"+b.Name, b.Action), &b, nil
case config.RuleDeny, config.RuleAllow, config.RuleBenchmark, config.RuleChallenge:
return cr("bot/"+b.Name, b.Action, weight), &b, nil
case config.RuleWeigh:
slog.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
weight += b.Weight.Adjust
}
} }
} }
switch { return cr("default/allow", config.RuleAllow), &policy.Bot{
case weight <= 0:
return cr("weight/okay", config.RuleAllow, weight), &policy.Bot{
Challenge: &config.ChallengeRules{
Difficulty: s.policy.DefaultDifficulty,
ReportAs: s.policy.DefaultDifficulty,
Algorithm: config.DefaultAlgorithm,
},
Rules: weightOkayStatic,
}, nil
case weight > 0 && weight < 10:
return cr("weight/mild-suspicion", config.RuleChallenge, weight), &policy.Bot{
Challenge: &config.ChallengeRules{
Difficulty: s.policy.DefaultDifficulty,
ReportAs: s.policy.DefaultDifficulty,
Algorithm: "metarefresh",
},
Rules: weightMildSusStatic,
}, nil
case weight >= 10:
return cr("weight/extreme-suspicion", config.RuleChallenge, weight), &policy.Bot{
Challenge: &config.ChallengeRules{
Difficulty: s.policy.DefaultDifficulty,
ReportAs: s.policy.DefaultDifficulty,
Algorithm: "fast",
},
Rules: weightVerySusStatic,
}, nil
}
return cr("default/allow", config.RuleAllow, weight), &policy.Bot{
Challenge: &config.ChallengeRules{ Challenge: &config.ChallengeRules{
Difficulty: s.policy.DefaultDifficulty, Difficulty: s.policy.DefaultDifficulty,
ReportAs: s.policy.DefaultDifficulty, ReportAs: s.policy.DefaultDifficulty,

View File

@@ -41,12 +41,7 @@ func Methods() []string {
} }
type Impl interface { type Impl interface {
// Setup registers any additional routes with the Impl for assets or API routes. Fail(w http.ResponseWriter, r *http.Request) error
Setup(mux *http.ServeMux)
// Issue a new challenge to the user, called by the Anubis.
Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error)
// Validate a challenge, making sure that it passes muster.
Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string) error Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string) error
} }

View File

@@ -1,53 +0,0 @@
package metarefresh
import (
"crypto/subtle"
"fmt"
"log/slog"
"net/http"
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/web"
"github.com/a-h/templ"
)
//go:generate go tool github.com/a-h/templ/cmd/templ generate
func init() {
challenge.Register("metarefresh", &Impl{})
}
type Impl struct{}
func (i *Impl) Setup(mux *http.ServeMux) {}
func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) {
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
if err != nil {
return nil, fmt.Errorf("can't render page: %w", err)
}
q := u.Query()
q.Set("redir", r.URL.String())
q.Set("challenge", challenge)
u.RawQuery = q.Encode()
component, err := web.BaseWithChallengeAndOGTags("Making sure you're not a bot!", page(challenge, u.String(), rule.Challenge.Difficulty), challenge, rule.Challenge, ogTags)
if err != nil {
return nil, fmt.Errorf("can't render page: %w", err)
}
return component, nil
}
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, rule *policy.Bot, wantChallenge string) error {
gotChallenge := r.FormValue("challenge")
if subtle.ConstantTimeCompare([]byte(wantChallenge), []byte(gotChallenge)) != 1 {
return challenge.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", challenge.ErrFailed, wantChallenge, gotChallenge))
}
return nil
}

View File

@@ -1,17 +0,0 @@
package metarefresh
import (
"fmt"
"github.com/TecharoHQ/anubis"
)
templ page(challenge, redir string, difficulty int) {
<div class="centered-div">
<img id="image" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>
<p id="status">Loading...</p>
<p>Please wait a moment while we ensure the security of your connection.</p>
<meta http-equiv="refresh" content={ fmt.Sprintf("%d; url=%s", difficulty, redir) }/>
</div>
}

View File

@@ -1,85 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898
package metarefresh
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"github.com/TecharoHQ/anubis"
)
func page(challenge, redir string, difficulty int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 11, Col: 165}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"> <img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 12, Col: 174}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p id=\"status\">Loading...</p><p>Please wait a moment while we ensure the security of your connection.</p><meta http-equiv=\"refresh\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty, redir))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 15, Col: 83}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -24,8 +24,8 @@ type Impl struct {
Algorithm string Algorithm string
} }
func (i *Impl) Setup(mux *http.ServeMux) { func (i *Impl) Fail(w http.ResponseWriter, r *http.Request) error {
/* no implementation required */ return nil
} }
func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) { func (i *Impl) Issue(r *http.Request, lg *slog.Logger, rule *policy.Bot, challenge string, ogTags map[string]string) (templ.Component, error) {

View File

@@ -145,21 +145,12 @@ func New(opts Options) (*Server, error) {
}), "GET") }), "GET")
} }
registerWithPrefix(anubis.APIPrefix+"make-challenge", http.HandlerFunc(result.MakeChallenge), "POST")
registerWithPrefix(anubis.APIPrefix+"pass-challenge", http.HandlerFunc(result.PassChallenge), "GET") registerWithPrefix(anubis.APIPrefix+"pass-challenge", http.HandlerFunc(result.PassChallenge), "GET")
registerWithPrefix(anubis.APIPrefix+"check", http.HandlerFunc(result.maybeReverseProxyHttpStatusOnly), "") registerWithPrefix(anubis.APIPrefix+"check", http.HandlerFunc(result.maybeReverseProxyHttpStatusOnly), "")
registerWithPrefix(anubis.APIPrefix+"test-error", http.HandlerFunc(result.TestError), "GET")
registerWithPrefix("/", http.HandlerFunc(result.maybeReverseProxyOrPage), "") registerWithPrefix("/", http.HandlerFunc(result.maybeReverseProxyOrPage), "")
//goland:noinspection GoBoolExpressions
if anubis.Version == "devel" {
// make-challenge is only used in tests. Only enable while version is devel
registerWithPrefix(anubis.APIPrefix+"make-challenge", http.HandlerFunc(result.MakeChallenge), "POST")
}
for _, implKind := range challenge.Methods() {
impl, _ := challenge.Get(implKind)
impl.Setup(mux)
}
result.mux = mux result.mux = mux
return result, nil return result, nil

View File

@@ -12,7 +12,6 @@ type Bot struct {
Challenge *config.ChallengeRules Challenge *config.ChallengeRules
Name string Name string
Action config.Rule Action config.Rule
Weight *config.Weight
} }
func (b Bot) Hash() string { func (b Bot) Hash() string {

View File

@@ -47,20 +47,6 @@ func (cl CheckerList) Hash() string {
return internal.SHA256sum(sb.String()) return internal.SHA256sum(sb.String())
} }
type staticHashChecker struct {
hash string
}
func (staticHashChecker) Check(r *http.Request) (bool, error) {
return true, nil
}
func (s staticHashChecker) Hash() string { return s.hash }
func NewStaticHashChecker(hashable string) Checker {
return staticHashChecker{hash: internal.SHA256sum(hashable)}
}
type RemoteAddrChecker struct { type RemoteAddrChecker struct {
ranger cidranger.Ranger ranger cidranger.Ranger
hash string hash string
@@ -76,10 +62,7 @@ func NewRemoteAddrChecker(cidrs []string) (Checker, error) {
return nil, fmt.Errorf("%w: range %s not parsing: %w", ErrMisconfiguration, cidr, err) return nil, fmt.Errorf("%w: range %s not parsing: %w", ErrMisconfiguration, cidr, err)
} }
err = ranger.Insert(cidranger.NewBasicRangerEntry(*rng)) ranger.Insert(cidranger.NewBasicRangerEntry(*rng))
if err != nil {
return nil, fmt.Errorf("%w: error inserting ip range: %w", ErrMisconfiguration, err)
}
fmt.Fprintln(&sb, cidr) fmt.Fprintln(&sb, cidr)
} }

View File

@@ -7,15 +7,12 @@ import (
) )
type CheckResult struct { type CheckResult struct {
Name string Name string
Rule config.Rule Rule config.Rule
Weight int
} }
func (cr CheckResult) LogValue() slog.Value { func (cr CheckResult) LogValue() slog.Value {
return slog.GroupValue( return slog.GroupValue(
slog.String("name", cr.Name), slog.String("name", cr.Name),
slog.String("rule", string(cr.Rule)), slog.String("rule", string(cr.Rule)))
slog.Int("weight", cr.Weight),
)
} }

View File

@@ -39,22 +39,20 @@ const (
RuleAllow Rule = "ALLOW" RuleAllow Rule = "ALLOW"
RuleDeny Rule = "DENY" RuleDeny Rule = "DENY"
RuleChallenge Rule = "CHALLENGE" RuleChallenge Rule = "CHALLENGE"
RuleWeigh Rule = "WEIGH"
RuleBenchmark Rule = "DEBUG_BENCHMARK" RuleBenchmark Rule = "DEBUG_BENCHMARK"
) )
const DefaultAlgorithm = "fast" const DefaultAlgorithm = "fast"
type BotConfig struct { type BotConfig struct {
UserAgentRegex *string `json:"user_agent_regex,omitempty"` UserAgentRegex *string `json:"user_agent_regex"`
PathRegex *string `json:"path_regex,omitempty"` PathRegex *string `json:"path_regex"`
HeadersRegex map[string]string `json:"headers_regex,omitempty"` HeadersRegex map[string]string `json:"headers_regex"`
Expression *ExpressionOrList `json:"expression,omitempty"` Expression *ExpressionOrList `json:"expression"`
Challenge *ChallengeRules `json:"challenge,omitempty"` Challenge *ChallengeRules `json:"challenge,omitempty"`
Weight *Weight `json:"weight,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Action Rule `json:"action"` Action Rule `json:"action"`
RemoteAddr []string `json:"remote_addresses,omitempty"` RemoteAddr []string `json:"remote_addresses"`
} }
func (b BotConfig) Zero() bool { func (b BotConfig) Zero() bool {
@@ -75,7 +73,7 @@ func (b BotConfig) Zero() bool {
return true return true
} }
func (b *BotConfig) Valid() error { func (b BotConfig) Valid() error {
var errs []error var errs []error
if b.Name == "" { if b.Name == "" {
@@ -146,7 +144,7 @@ func (b *BotConfig) Valid() error {
} }
switch b.Action { switch b.Action {
case RuleAllow, RuleBenchmark, RuleChallenge, RuleDeny, RuleWeigh: case RuleAllow, RuleBenchmark, RuleChallenge, RuleDeny:
// okay // okay
default: default:
errs = append(errs, fmt.Errorf("%w: %q", ErrUnknownAction, b.Action)) errs = append(errs, fmt.Errorf("%w: %q", ErrUnknownAction, b.Action))
@@ -158,10 +156,6 @@ func (b *BotConfig) Valid() error {
} }
} }
if b.Action == RuleWeigh && b.Weight == nil {
b.Weight = &Weight{Adjust: 5}
}
if len(errs) != 0 { if len(errs) != 0 {
return fmt.Errorf("config: bot entry for %q is not valid:\n%w", b.Name, errors.Join(errs...)) return fmt.Errorf("config: bot entry for %q is not valid:\n%w", b.Name, errors.Join(errs...))
} }

View File

@@ -168,25 +168,6 @@ func TestBotValid(t *testing.T) {
}, },
err: nil, err: nil,
}, },
{
name: "weight rule without weight",
bot: BotConfig{
Name: "weight-adjust-if-mozilla",
Action: RuleWeigh,
UserAgentRegex: p("Mozilla"),
},
},
{
name: "weight rule with weight adjust",
bot: BotConfig{
Name: "weight-adjust-if-mozilla",
Action: RuleWeigh,
UserAgentRegex: p("Mozilla"),
Weight: &Weight{
Adjust: 5,
},
},
},
} }
for _, cs := range tests { for _, cs := range tests {

View File

@@ -14,8 +14,8 @@ var (
type ExpressionOrList struct { type ExpressionOrList struct {
Expression string `json:"-"` Expression string `json:"-"`
All []string `json:"all,omitempty"` All []string `json:"all"`
Any []string `json:"any,omitempty"` Any []string `json:"any"`
} }
func (eol ExpressionOrList) Equal(rhs *ExpressionOrList) bool { func (eol ExpressionOrList) Equal(rhs *ExpressionOrList) bool {

View File

@@ -1,6 +0,0 @@
bots:
- name: simple-weight-adjust
action: WEIGH
user_agent_regex: Mozilla
weight:
adjust: 5

View File

@@ -1,4 +0,0 @@
bots:
- name: weight
action: WEIGH
user_agent_regex: Mozilla

View File

@@ -1,5 +0,0 @@
package config
type Weight struct {
Adjust int `json:"adjust"`
}

View File

@@ -18,14 +18,14 @@ func TestHTTPHeaders(t *testing.T) {
t.Run("contains-existing-header", func(t *testing.T) { t.Run("contains-existing-header", func(t *testing.T) {
resp := headers.Contains(types.String("User-Agent")) resp := headers.Contains(types.String("User-Agent"))
if !resp.(types.Bool) { if !bool(resp.(types.Bool)) {
t.Fatal("headers does not contain User-Agent") t.Fatal("headers does not contain User-Agent")
} }
}) })
t.Run("not-contains-missing-header", func(t *testing.T) { t.Run("not-contains-missing-header", func(t *testing.T) {
resp := headers.Contains(types.String("Xxx-Random-Header")) resp := headers.Contains(types.String("Xxx-Random-Header"))
if resp.(types.Bool) { if bool(resp.(types.Bool)) {
t.Fatal("headers does not contain User-Agent") t.Fatal("headers does not contain User-Agent")
} }
}) })

View File

@@ -16,14 +16,14 @@ func TestURLValues(t *testing.T) {
t.Run("contains-existing-key", func(t *testing.T) { t.Run("contains-existing-key", func(t *testing.T) {
resp := headers.Contains(types.String("format")) resp := headers.Contains(types.String("format"))
if !resp.(types.Bool) { if !bool(resp.(types.Bool)) {
t.Fatal("headers does not contain User-Agent") t.Fatal("headers does not contain User-Agent")
} }
}) })
t.Run("not-contains-missing-key", func(t *testing.T) { t.Run("not-contains-missing-key", func(t *testing.T) {
resp := headers.Contains(types.String("not-there")) resp := headers.Contains(types.String("not-there"))
if resp.(types.Bool) { if bool(resp.(types.Bool)) {
t.Fatal("headers does not contain User-Agent") t.Fatal("headers does not contain User-Agent")
} }
}) })

View File

@@ -117,10 +117,6 @@ func ParseConfig(fin io.Reader, fname string, defaultDifficulty int) (*ParsedCon
} }
} }
if b.Weight != nil {
parsedBot.Weight = b.Weight
}
parsedBot.Rules = cl parsedBot.Rules = cl
result.Bots = append(result.Bots, parsedBot) result.Bots = append(result.Bots, parsedBot)

246
lib/thr1/thr1.go Normal file
View File

@@ -0,0 +1,246 @@
package thr1
import (
"crypto/sha256"
"encoding/hex"
"log/slog"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
)
func Fingerprint(r *http.Request) string {
result := strings.Join([]string{
thr1Head(r),
thr1Lang(r),
thr1Sec(r),
thr1UA(r),
thr1Encoding(r),
}, "_")
slog.Info("THR1 got", "method", r.Method, "path", r.URL.Path, "thr1", result)
return result
}
func thr1Head(r *http.Request) string {
method := strings.ToLower(r.Method)
if len(method) > 3 {
method = method[:3]
}
version := "00"
if override := r.Header.Get("X-Http-Version"); override != "" {
switch strings.TrimSpace(strings.ToUpper(override)) {
case "HTTP/1.0":
version = "10"
case "HTTP/1.1":
version = "11"
case "HTTP/2.0":
version = "20"
case "HTTP/3.0":
version = "30"
}
} else {
switch {
case r.ProtoMajor == 1 && r.ProtoMinor == 0:
version = "10"
case r.ProtoMajor == 1 && r.ProtoMinor == 1:
version = "11"
case r.ProtoMajor == 2:
version = "20"
case r.ProtoMajor == 3:
version = "30"
}
}
hasSec := false
for k := range r.Header {
if strings.HasPrefix(strings.ToLower(k), "sec-") {
hasSec = true
break
}
}
return method + version + strconv.FormatBool(hasSec)[:2]
}
func thr1Encoding(r *http.Request) string {
raw := r.Header.Get("Accept-Encoding")
if raw == "" {
return "none-00"
}
encodings := strings.Split(raw, ",")
count := len(encodings)
if count > 99 {
count = 99
}
seen := make(map[string]struct{})
var available []string
for _, e := range encodings {
enc := strings.ToLower(strings.TrimSpace(strings.Split(e, ";")[0]))
if enc != "" {
if _, exists := seen[enc]; !exists {
available = append(available, enc)
seen[enc] = struct{}{}
}
}
}
priorities := map[string]int{
"zstd": 1,
"br": 2,
"deflate": 3,
"gzip": 4,
"*": 5,
}
best := "none"
bestRank := 999 // arbitrarily high
for _, enc := range available {
if rank, ok := priorities[enc]; ok {
if rank < bestRank {
best = enc
bestRank = rank
}
}
}
if best == "*" {
best = "wild"
}
return best + "-" + pad2(count)
}
func pad2(n int) string {
if n < 10 {
return "0" + strconv.Itoa(n)
}
if n > 99 {
return "99"
}
return strconv.Itoa(n)
}
func thr1Lang(r *http.Request) string {
raw := r.Header.Get("Accept-Language")
if raw == "" {
return "-000000000"
}
trimmed := first4AlphaNum(strings.ToLower(raw)) + "-"
sum := sha256.Sum256([]byte(raw))
return trimmed + hex.EncodeToString(sum[:])[:9]
}
func first4AlphaNum(s string) string {
out := make([]rune, 0, 4)
for _, ch := range s {
if len(out) == 4 {
break
}
if ('a' <= ch && ch <= 'z') || ('0' <= ch && ch <= '9') {
out = append(out, ch)
}
}
for len(out) < 4 {
out = append(out, '0')
}
return string(out)
}
func thr1Sec(r *http.Request) string {
var lines []string
for k, vs := range r.Header {
lkey := strings.ToLower(k)
if !strings.HasPrefix(lkey, "sec-") || lkey == "sec-fetch-user" {
continue
}
switch lkey {
case "sec-ch-ua":
lines = append(lines, parseSecChUA(vs))
case "sec-ch-ua-mobile":
lines = append(lines, parseSecCHSimple("mobile", vs))
case "sec-ch-ua-platform":
lines = append(lines, parseSecCHSimple("platform", vs))
case "sec-ch-ua-platform-version":
lines = append(lines, parseSecCHSimple("platform_version", vs))
case "sec-ch-ua-model":
lines = append(lines, parseSecCHSimple("model", vs))
case "sec-ch-ua-full-version":
lines = append(lines, parseSecCHSimple("full_version", vs))
default:
for _, v := range vs {
v = strings.Trim(v, `" `)
lines = append(lines, lkey+":"+v)
}
}
}
sort.Strings(lines)
canonical := strings.Join(lines, "\n")
sum := sha256.Sum256([]byte(canonical))
return "sec-" + hex.EncodeToString(sum[:])[:9]
}
var brandVersionRe = regexp.MustCompile(`\s*"([^"]+)";v="([^"]+)"`)
func parseSecChUA(vs []string) string {
type pair struct{ Brand, Version string }
var pairs []pair
for _, v := range vs {
for _, match := range brandVersionRe.FindAllStringSubmatch(v, -1) {
if len(match) != 3 {
continue
}
brand := match[1]
version := match[2]
if brand == "Not=A?Brand" {
continue
}
pairs = append(pairs, pair{brand, version})
}
}
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].Brand < pairs[j].Brand
})
var sb strings.Builder
sb.WriteString("ua:")
for i, p := range pairs {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p.Brand + "/" + p.Version)
}
return sb.String()
}
func parseSecCHSimple(key string, vs []string) string {
for _, v := range vs {
v = strings.Trim(v, `" `)
if key == "mobile" {
switch v {
case "?1":
return "mobile:true"
case "?0":
return "mobile:false"
default:
continue
}
}
return key + ":" + v
}
return key + ":"
}
func thr1UA(r *http.Request) string {
ua := r.Header.Get("User-Agent")
sum := sha256.Sum256([]byte(ua))
return hex.EncodeToString(sum[:])[:9]
}

View File

@@ -5,41 +5,33 @@ go 1.24.2
replace github.com/TecharoHQ/anubis => .. replace github.com/TecharoHQ/anubis => ..
require ( require (
github.com/TecharoHQ/anubis v1.18.0
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
) )
require ( require (
cel.dev/expr v0.24.0 // indirect github.com/TecharoHQ/anubis v1.16.0 // indirect
github.com/a-h/templ v0.3.865 // indirect github.com/a-h/templ v0.3.857 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/cel-go v0.25.0 // indirect
github.com/jsha/minica v1.1.0 // indirect github.com/jsha/minica v1.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.64.0 // indirect github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect github.com/yl2chen/cidranger v1.0.2 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/sys v0.33.0 // indirect google.golang.org/protobuf v1.36.5 // indirect
golang.org/x/text v0.25.0 // indirect k8s.io/apimachinery v0.32.3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
google.golang.org/protobuf v1.36.6 // indirect
k8s.io/apimachinery v0.33.1 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect
) )

View File

@@ -1,16 +1,10 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M=
github.com/a-h/templ v0.3.865 h1:nYn5EWm9EiXaDgWcMQaKiKvrydqgxDUtT1+4zU2C43A=
github.com/a-h/templ v0.3.865/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0=
@@ -23,76 +17,39 @@ github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpm
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jsha/minica v1.1.0 h1:O2ZbzAN75w4RTB+5+HfjIEvY5nxRqDlwj3ZlLVG5JD8= github.com/jsha/minica v1.1.0 h1:O2ZbzAN75w4RTB+5+HfjIEvY5nxRqDlwj3ZlLVG5JD8=
github.com/jsha/minica v1.1.0/go.mod h1:dxC3wNmD+gU1ewXo/R8jB2ihB6wNpyXrG8aUk5Iuf/k= github.com/jsha/minica v1.1.0/go.mod h1:dxC3wNmD+gU1ewXo/R8jB2ihB6wNpyXrG8aUk5Iuf/k=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM= github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM=
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0= github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -37,7 +37,6 @@ templ base(title string, body templ.Component, challenge any, ogTags map[string]
#progress { #progress {
display: none; display: none;
width: 90%;
width: min(20rem, 90%); width: min(20rem, 90%);
height: 2rem; height: 2rem;
border-radius: 1rem; border-radius: 1rem;

34
web/index_templ.go generated
View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898 // templ: version: v0.3.865
package web package web
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.
@@ -96,7 +96,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<style>\n body,\n html {\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n margin-left: auto;\n margin-right: auto;\n }\n\n .centered-div {\n text-align: center;\n }\n\n #status {\n font-variant-numeric: tabular-nums;\n }\n\n #progress {\n display: none;\n width: 90%;\n width: min(20rem, 90%);\n height: 2rem;\n border-radius: 1rem;\n overflow: hidden;\n margin: 1rem 0 2rem;\n\t\t\toutline-offset: 2px;\n\t\t\toutline: #b16286 solid 4px;\n\t\t}\n\n .bar-inner {\n background-color: #b16286;\n height: 100%;\n width: 0;\n transition: width 0.25s ease-in;\n }\n </style>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<style>\n body,\n html {\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n margin-left: auto;\n margin-right: auto;\n }\n\n .centered-div {\n text-align: center;\n }\n\n #status {\n font-variant-numeric: tabular-nums;\n }\n\n #progress {\n display: none;\n width: min(20rem, 90%);\n height: 2rem;\n border-radius: 1rem;\n overflow: hidden;\n margin: 1rem 0 2rem;\n\t\t\toutline-offset: 2px;\n\t\t\toutline: #b16286 solid 4px;\n\t\t}\n\n .bar-inner {\n background-color: #b16286;\n height: 100%;\n width: 0;\n transition: width 0.25s ease-in;\n }\n </style>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -121,7 +121,7 @@ func base(title string, body templ.Component, challenge any, ogTags map[string]s
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title) templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 66, Col: 49} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 65, Col: 49}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -171,7 +171,7 @@ func index() templ.Component {
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 86, Col: 165} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 85, Col: 165}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -184,7 +184,7 @@ func index() templ.Component {
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 87, Col: 174} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 86, Col: 174}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -197,7 +197,7 @@ func index() templ.Component {
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 89, Col: 136} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 88, Col: 136}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -210,7 +210,7 @@ func index() templ.Component {
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.Version) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 125, Col: 67} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 124, Col: 67}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -252,7 +252,7 @@ func errorPage(message string, mail string) templ.Component {
var templ_7745c5c3_Var13 string var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version) templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/reject.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 140, Col: 181} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 139, Col: 181}
} }
_, 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 {
@@ -265,7 +265,7 @@ func errorPage(message string, mail string) templ.Component {
var templ_7745c5c3_Var14 string var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(message) templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(message)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 141, Col: 14} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 140, Col: 14}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -280,12 +280,8 @@ func errorPage(message string, mail string) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var15 templ.SafeURL var templ_7745c5c3_Var15 templ.SafeURL = "mailto:" + templ.SafeURL(mail)
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinURLErrs("mailto:" + templ.SafeURL(mail)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var15)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 146, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@@ -296,7 +292,7 @@ func errorPage(message string, mail string) templ.Component {
var templ_7745c5c3_Var16 string var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(mail) templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(mail)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 147, Col: 11} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 146, Col: 11}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -349,7 +345,7 @@ func StaticHappy() templ.Component {
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" +
anubis.Version) anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 162, Col: 18} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 161, Col: 18}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -391,7 +387,7 @@ func bench() templ.Component {
var templ_7745c5c3_Var20 string var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 191, Col: 166} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 190, Col: 166}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@@ -404,7 +400,7 @@ func bench() templ.Component {
var templ_7745c5c3_Var21 string var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version) templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/bench.mjs?cacheBuster=" + anubis.Version)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 193, Col: 138} return templ.Error{Err: templ_7745c5c3_Err, FileName: `index.templ`, Line: 192, Col: 138}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@@ -2,11 +2,9 @@ User-agent: AI2Bot
User-agent: Ai2Bot-Dolma User-agent: Ai2Bot-Dolma
User-agent: aiHitBot User-agent: aiHitBot
User-agent: Amazonbot User-agent: Amazonbot
User-agent: Andibot
User-agent: anthropic-ai User-agent: anthropic-ai
User-agent: Applebot User-agent: Applebot
User-agent: Applebot-Extended User-agent: Applebot-Extended
User-agent: bedrockbot
User-agent: Brightbot 1.0 User-agent: Brightbot 1.0
User-agent: Bytespider User-agent: Bytespider
User-agent: CCBot User-agent: CCBot
@@ -35,6 +33,7 @@ User-agent: iaskspider/2.0
User-agent: ICC-Crawler User-agent: ICC-Crawler
User-agent: ImagesiftBot User-agent: ImagesiftBot
User-agent: img2dataset User-agent: img2dataset
User-agent: imgproxy
User-agent: ISSCyberRiskCrawler User-agent: ISSCyberRiskCrawler
User-agent: Kangaroo Bot User-agent: Kangaroo Bot
User-agent: meta-externalagent User-agent: meta-externalagent
@@ -48,16 +47,10 @@ User-agent: omgili
User-agent: omgilibot User-agent: omgilibot
User-agent: Operator User-agent: Operator
User-agent: PanguBot User-agent: PanguBot
User-agent: Panscient
User-agent: panscient.com
User-agent: Perplexity-User User-agent: Perplexity-User
User-agent: PerplexityBot User-agent: PerplexityBot
User-agent: PetalBot User-agent: PetalBot
User-agent: PhindBot
User-agent: QualifiedBot User-agent: QualifiedBot
User-agent: QuillBot
User-agent: quillbot.com
User-agent: SBIntuitionsBot
User-agent: Scrapy User-agent: Scrapy
User-agent: SemrushBot-OCOB User-agent: SemrushBot-OCOB
User-agent: SemrushBot-SWA User-agent: SemrushBot-SWA
@@ -67,8 +60,6 @@ User-agent: Timpibot
User-agent: VelenPublicWebCrawler User-agent: VelenPublicWebCrawler
User-agent: Webzio-Extended User-agent: Webzio-Extended
User-agent: wpbot User-agent: wpbot
User-agent: YandexAdditional
User-agent: YandexAdditionalBot
User-agent: YouBot User-agent: YouBot
Disallow: / Disallow: /

View File

@@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT. // Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.898 // templ: version: v0.3.865
package xess package xess
//lint:file-ignore SA4006 This context is only used if a nested component is present. //lint:file-ignore SA4006 This context is only used if a nested component is present.