diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 478e203..0c6cabd 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -149,6 +149,7 @@ maintainership malware mcr memes +metarefresh metrix mimi minica diff --git a/README.md b/README.md index f4be9f8..29fee38 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Anubis is brought to you by sponsors and donors like: ## Overview -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. +Anubis is a Web AI Firewall Utility that [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using one or more challenges in order to protect upstream resources from scraper bots. This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them. diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index e43e18a..b800c3c 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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)) ## v1.19.1: Jenomis cen Lexentale - Echo 1 diff --git a/docs/docs/admin/configuration/challenges/_category_.json b/docs/docs/admin/configuration/challenges/_category_.json new file mode 100644 index 0000000..5db7328 --- /dev/null +++ b/docs/docs/admin/configuration/challenges/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Challenges", + "position": 10, + "link": { + "type": "generated-index", + "description": "The different challenge methods that Anubis supports." + } +} \ No newline at end of file diff --git a/docs/docs/admin/configuration/challenges/metarefresh.mdx b/docs/docs/admin/configuration/challenges/metarefresh.mdx new file mode 100644 index 0000000..730c5bb --- /dev/null +++ b/docs/docs/admin/configuration/challenges/metarefresh.mdx @@ -0,0 +1,19 @@ +# 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. diff --git a/docs/docs/admin/configuration/challenges/proof-of-work.mdx b/docs/docs/admin/configuration/challenges/proof-of-work.mdx new file mode 100644 index 0000000..21a2f13 --- /dev/null +++ b/docs/docs/admin/configuration/challenges/proof-of-work.mdx @@ -0,0 +1,5 @@ +# 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. diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx index fd47e43..ed396d5 100644 --- a/docs/docs/index.mdx +++ b/docs/docs/index.mdx @@ -60,7 +60,7 @@ Anubis is brought to you by sponsors and donors like: ## Overview -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. +Anubis is a Web AI Firewall Utility that [weighs the soul of your connection](https://en.wikipedia.org/wiki/Weighing_of_souls) using one or more challenges in order to protect upstream resources from scraper bots. This program is designed to help protect the small internet from the endless storm of requests that flood in from AI companies. Anubis is as lightweight as possible to ensure that everyone can afford to protect the communities closest to them. diff --git a/lib/anubis.go b/lib/anubis.go index 06e780b..bfc3730 100644 --- a/lib/anubis.go +++ b/lib/anubis.go @@ -29,6 +29,7 @@ import ( "github.com/TecharoHQ/anubis/lib/policy/config" // challenge implementations + _ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh" _ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork" ) diff --git a/lib/challenge/metarefresh/metarefresh.go b/lib/challenge/metarefresh/metarefresh.go new file mode 100644 index 0000000..54f7168 --- /dev/null +++ b/lib/challenge/metarefresh/metarefresh.go @@ -0,0 +1,53 @@ +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 +} diff --git a/lib/challenge/metarefresh/metarefresh.templ b/lib/challenge/metarefresh/metarefresh.templ new file mode 100644 index 0000000..1dba375 --- /dev/null +++ b/lib/challenge/metarefresh/metarefresh.templ @@ -0,0 +1,17 @@ +package metarefresh + +import ( + "fmt" + + "github.com/TecharoHQ/anubis" +) + +templ page(challenge, redir string, difficulty int) { +
Loading...
+Please wait a moment while we ensure the security of your connection.
+ +Loading...
Please wait a moment while we ensure the security of your connection.