The useEffect registered a toggle listener on the <details> element but
never removed it, which could leak handlers on remounts or in tests.
Extract the handler to a named function and return a cleanup that calls
removeEventListener.
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
The process() function in fast.ts declared Promise<string> but actually
resolved with the full worker message object { hash, data, difficulty,
nonce }. main.tsx papered over this with `any`. Introduce a shared
ChallengeResult type so the contract between workers, algorithms, and
the main component is enforced by the type checker.
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Use literal spaced em dash ( — ) consistent with other locale files
instead of unspaced \u2014 escape sequence.
Assisted-by: claude-sonnet-4-6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Replace <br/> tags with em dashes and strip all other HTML tags
(keeping their text content) across all 25 locale files.
Assisted-by: claude-sonnet-4-6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
- Guard j() helper against null/empty textContent before JSON.parse
- Use <button> instead of clickable <div> for "finished reading" control
to support keyboard navigation and screen readers
- Make currentLang a local const inside initTranslations since it is
not read elsewhere
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
- Remove duplicate style attribute on happy image preload in templ
- Remove unused getAvailableLanguages function from main.tsx
- Add type annotation to currentLang variable
- Add null check for missing challenge data with localized error
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Translate the new challenge data missing error message across all
25 supported locales.
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
Replace the imperative DOM manipulation in web/js/main.ts with a
declarative Preact component (web/js/main.tsx). The project already
uses Preact for the timed-delay challenge (lib/challenge/preact/),
so this aligns the PoW challenge with the existing codebase direction.
Convert web/js/main.ts to a Preact TSX component. The worker
orchestration layer (web/js/algorithms/fast.ts) stays untouched --
it is already cleanly separated and works via a Promise API.
web/js/main.ts -> web/js/main.tsx:
- Phase-based state machine (loading -> computing -> reading/error)
replaces scattered imperative DOM updates.
- Worker lifecycle managed in useEffect; progress callback drives
state setters for speed and progress percentage.
- Speed updates remain throttled to 1 second intervals.
- i18n functions (initTranslations, t(), loadTranslations) kept as
module-level state -- no need for React context in a single-
component app.
- The <details> section stays in the templ file as server-rendered
HTML; the Preact component tracks its toggle state via useRef.
- Uses esbuild automatic JSX transform (--jsx=automatic
--jsx-import-source=preact) instead of classic pragmas.
web/build.sh:
- Add js/**/*.tsx to the glob so esbuild bundles TSX files.
- Pass --jsx=automatic --jsx-import-source=preact for .tsx files.
web/tsconfig.json (new):
- IDE-only config (noEmit) so TypeScript understands Preact JSX
types for editor diagnostics and autocompletion.
lib/challenge/proofofwork/proofofwork.templ:
- Replace individual DOM elements (img#image, p#status,
div#progress) with a <div id="app"> Preact mount point
containing server-rendered fallback (pensive image + loading
text).
- Keep <details>, <noscript>, and <div id="testarea"> outside the
Preact tree as server-rendered content.
lib/anubis.go:
- Add challenge method to the "new challenge issued" log line.
docs/docs/CHANGELOG.md:
- Add entry for the Preact rewrite.
- web/js/algorithms/fast.ts -- untouched
- web/js/algorithms/index.ts -- untouched
- web/js/worker/sha256-*.ts -- untouched
- Server-side Go code (proofofwork.go) -- untouched
- JSON script data embedding -- untouched
- Redirect URL construction -- same logic, same parameters
- Progress bar CSS in web/index.templ -- untouched
Signed-off-by: Xe Iaso <me@xeiaso.net>
Assisted-by: Claude Opus 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Resolve#1193
Address documentation and error message issues around REDIRECT_DOMAINS and required keywords in bot specifications.
* Add CHANGELOG entry
* fix: enable CEL iterators
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
* test: add unit tests for CELChecker map iteration
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
* fix: implement map iterators for HTTPHeaders and URLValues to resolve CEL internal errors
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
* fix: replace checker.NewMapIterator with newMapIterator for HTTPHeaders and URLValues
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
---------
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
When displayed in Japanese, the `バージョン` (version) is in the middle, but the version number is at the end, so it is displayed strangely. Improve this.
**"version_info":**
```
このウェブサイトはAnubisバージョンで動作しています
```
to
```
このウェブサイトはAnubisで動作しています バージョン
```
Signed-off-by: BALLOON | FU-SEN <5434159+fu-sen@users.noreply.github.com>
* Add Wikimedia Foundation citoid services file
Wikimedia Foundation runs a service called citoid which retrieves citation metadata from urls in order to create formatted citations.
This file contains the ip ranges allocated to the WMF (https://wikitech.wikimedia.org/wiki/IP_and_AS_allocations) from which the services make requests, as well as regex for the User-Agents from both services used to generate citations (citoid, and Zotero's translation-server which citoid makes requests to as well in order to generate the metadata).
Signed-off-by: Marielle Volz <marielle.volz@gmail.com>
* Add Wikimedia Citoid crawler to allowed list
Signed-off-by: Marielle Volz <marielle.volz@gmail.com>
* chore: update spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Marielle Volz <marielle.volz@gmail.com>
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
PR #1451 added `CONTRIBUTING.md`, but the commit message guidelines
there conflict with the ones in `developer/code-quality.md`. Since
`CONTRIBUTING.md` is newer, presumably the guidelines there are what's
expected from new commits. But after removing that section from
`code-quality.md`, there's not much content left, so this commit just
deletes the file entirely.
Signed-off-by: Max Chernoff <git@maxchernoff.ca>
* refactor(http): split long line in respondWithStatus
Signed-off-by: Max Chernoff <git@maxchernoff.ca>
* feat(http): set `Cache-Control: no-store` on error responses
Since #132, Anubis has set `Cache-Control: no-store` on challenge
responses. However, this does not apply to deny responses, meaning that
if Anubis is configured to block certain user agents and is behind a
caching reverse proxy, this error page will be cached and served to all
subsequent requests, even those with an allowed user agent. This commit
configures the error page responder to also set the `Cache-Control`
header, meaning that deny and challenge responses will now both have the
same behaviour.
Signed-off-by: Max Chernoff <git@maxchernoff.ca>
* chore(spelling): add new words to allowlist
Signed-off-by: Max Chernoff <git@maxchernoff.ca>
* chore(actions): bump Go version to fix govulncheck errors
Signed-off-by: Max Chernoff <git@maxchernoff.ca>
---------
Signed-off-by: Max Chernoff <git@maxchernoff.ca>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
Co-authored-by: Xe Iaso <xe.iaso@techaro.lol>
* ci: purge govulncheck, it's less signal than i hoped
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci(go): use go stable
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci: use go stable
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
The default Anubis systemd configuration is very restrictive in
order to prevent any possible compromise of Anubis to be useful
by threat actors. As such, it assumes all logs will be pushed to
the system journal. Some administrators do not want Anubis' logs
to be pushed to the system journal and want Anubis to log to a
file instead.
This change documents how to set up ReadWritePaths in the Anubis
systemd configuration such that Anubis can lot to a file as
administrators expect.
Closes: #1468
Signed-off-by: Xe Iaso <me@xeiaso.net>
This makes it clear that when generating a kubernetes secret to pull the bot stopper image that:
- no email is required
- a user is required but the actual value of the username is not checked
- the GH token needs to be pasted in
Signed-off-by: Thomas Arrow <tarrow@users.noreply.github.com>
* fix(web): include base prefix in generated URLs
Forgot to add the base prefix to these URLs. Committed a fix for this
and added a test to ensure this does not repeat. Oops!
Closes: #1402
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
The Accept-Language header parsing was not correctly handling quality
factors. When a browser sends "en-GB,de-DE;q=0.5", the expected behavior
is to prefer English (q=1.0 by default) over German (q=0.5).
The fix uses golang.org/x/text/language.ParseAcceptLanguage to properly
parse and sort language preferences by quality factor. It also adds base
language fallbacks (e.g., "en" for "en-GB") to ensure regional variants
match their parent languages when no exact match exists.
Fixes#1022
Signed-off-by: majiayu000 <1835304752@qq.com>
* test(nginx): fix tests to work in GHA
Closes: #1371
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(test): does this work lol
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(test): does this other thing work lol
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(test): pki folder location
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Jason Cameron <git@jasoncameron.dev>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
* docs: split nginx configuration files to their own directory
Signed-off-by: Xe Iaso <me@xeiaso.net>
* test: add nginx config smoke test based on the config in the docs
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
TIL docker clients don't include the Accept header all the time. I would
have thought they did that. Oops.
Closes: #1346
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat: first implementation of honeypot logic
This is a bit of an experiment, stick with me.
The core idea here is that badly written crawlers are that: badly
written. They look for anything that contains `<a href="whatever" />`
tags and will blindly use those values to recurse. This takes advantage
of that by hiding a link in a `<script>` tag like this:
```html
<script type="ignore"><a href="/bots-only">Don't click</a></script>
```
Browsers will ignore it because they have no handler for the "ignore"
script type.
This current draft is very unoptimized (it takes like 7 seconds to
generate a page on my tower), however switching spintax libraries will
make this much faster.
The hope is to make this pluggable with WebAssembly such that we force
administrators to choose a storage method. First we crawl before we
walk.
The AI involvement in this commit is limited to the spintax in
affirmations.txt, spintext.txt, and titles.txt. This generates a bunch
of "pseudoprofound bullshit" like the following:
> This Restoration to Balance & Alignment
>
> There's a moment when creators are being called to realize that the work
> can't be reduced to results, but about energy. We don't innovate products
> by pushing harder, we do it by holding the vision. Because momentum can't
> be forced, it unfolds over time when culture are moving in the same
> direction. We're being invited into a paradigm shift in how we think
> about innovation. [...]
This is intended to "look" like normal article text. As this is a first
draft, this sucks and will be improved upon.
Assisted-by: GLM 4.6, ChatGPT, GPT-OSS 120b
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(honeypot/naive): optimize hilariously
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(honeypot/naive): attempt to automatically filter out based on crawling
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(lib): use mazeGen instead of bsGen
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: add honeypot docs
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore(test): go mod tidy
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: fix spelling metadata
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Implement FCrDNS and other DNS features
* Redesign DNS cache and methods
* Fix DNS cache
* Rename regexSafe arg
* Alter verifyFCrDNS(addr) behaviour
* Remove unused dnsCache field from Server struct
* Upd expressions docs
* Update docs/docs/CHANGELOG.md
Signed-off-by: Xe Iaso <me@xeiaso.net>
* refactor(dns): simplify FCrDNS logging
* docs: clarify verifyFCrDNS behavior
Add a note to the documentation for `verifyFCrDNS` to clarify that it returns true when no PTR records are found for the given IP address.
* fix(dns): Improve FCrDNS error handling and tests
The `VerifyFCrDNS` function previously ignored errors returned from reverse DNS lookups. This could lead to incorrect passes when a DNS failure (other than a simple 'not found') occurred. This change ensures that any error from a reverse lookup will cause the FCrDNS check to fail.
The test suite for FCrDNS has been updated to reflect this change. The mock DNS lookups now simulate both 'not found' errors and other generic DNS errors. The test cases have been updated to ensure that the function behaves correctly in both scenarios, resolving a situation where two test cases were effectively duplicates.
* docs: Update FCrDNS documentation and spelling
Corrected a typo in the `verifyFCrDNS` function documentation.
Additionally, updated the spelling exception list to include new terms and remove redundant entries.
* chore: update spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
* fix(config): deprecate the report_as field for challenges
This was a bad idea when it was added and it is irresponsible to
continue to have it. It causes more UX problems than it fixes with
slight of hand.
Closes: #1310Closes: #1307
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(policy): use the new logger for config validation messages
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs(admin/thresholds): remove this report_as setting
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: add dependabot cooldown
One of the things I need to worry about with Anubis is the idea that
could pwn a dependency and then get malicious code into prod without
realizing it, a-la Jia Tan. Given that Anubis relies on tools like
Dependabot to manage updating dependencies (good for other reasons),
it makes sense to have Dependabot have a 7 day cooldown for new
versions of dependencies.
This follows the advice from Yossarian on their blog at [1]. Thanks
for the post and easy to copy/paste snippets!
[1]: https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: update spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* refactor: move lib/policy/config to lib/config
Signed-off-by: Xe Iaso <me@xeiaso.net>
* refactor: don't set global loggers anymore
Ref #864
You were right @kotx, it is a bad idea to set the global logger
instance.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(config): add log sink support
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: update spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore(test): go mod tidy
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: update spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs(admin/policies): add logging block documentation
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(cmd/anubis): revert this change, it's meant to be its own PR
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: go mod tidy
Signed-off-by: Xe Iaso <me@xeiaso.net>
* test: add file logging smoke test
Assisted-by: GLM 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix: don't expose the old log file time format string
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(internal): add ListOr[T any] type
This is a utility type that lets you decode a JSON T or list of T as a
single value. This will be used with Redis Sentinel config so that you
can specify multiple sentinel addresses.
Ref TecharoHQ/botstopper#24
Assisted-by: GLM 4.6 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(store/valkey): add Redis(R) Sentinel support
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: spelling
check-spelling run (pull_request) for Xe/redis-sentinel
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>
* chore(store/valkey): remove pointless comments
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: document the Redis™ Sentinel configuration options
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(store/valkey): Redis™ Sentinel doesn't require a password
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
* test: testcontainers improvements
Use the endpoint feature to get the connection URL for the container.
There are cases where localhost is not the correct one, for example when DOCKER_HOST is set to another machine.
Also, don't specify the external port for the mapping so a random unused port is used, in cases when there is already Valkey/Redis running as a container and port mapped externally on 6379.
* also remove this hack, doesn't seem necessary.
* fix(localization): correct formatting of Swedish loading message
* fix(main): correct formatting and improve readability in main.go
* fix(challenge): add difficulty and policy rule hash to challenge metadata
* docs(challenge): fix panic when validating challenges in privacy-mode browsers
* (feat) Add cluster support to redis/vaultkey store
* (chore) Update CHANGELOG.md
* (fix) Disable maintenance notification on the Valkey store
* (fix) Valkey text fix and allow maintnotifications in spelling.
* fix(data): add services folder to embedded filesystem
Also includes a regression test to ensure this does not happen again.
Assisted-By: GLM 4.6 via Claude Code
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci: add asset build verification workflow
A CI pass that fails if generated files are out of date.
* chore: npm run assets
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(data): add ruleset to explicitly allow Docker / OCI clients
Fixes#1252
This is technically a regression as these clients used to work in Anubis
v1.22.0, however it is allowable to make this opt-in as most websites do not
expect to be serving Docker / OCI registry client traffic.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Update metadata
check-spelling run (pull_request) for Xe/gh-1252/docker-registry-client-fix
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>
* test(docker-registry): export the right envvars
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci: add simdjson dependency for homebrew node
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci: install go/node without homebrew
Signed-off-by: Xe Iaso <me@xeiaso.net>
* test: use right github commit variable
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci: remove simdjson dependency
Signed-off-by: Xe Iaso <me@xeiaso.net>
* ci: install ko with an action
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: add OCI registry caveat docs
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
Tencent Cloud's abuse team reached out to me recently and asked for this
rule to be removed. Prior attempts to reach out to them to report
abusive traffic have failed, thus leading to this IP space block as a
last resort to try and maintain uptime for systems administrators.
Unfortunately, it's difficult for Tencent's abuse team to take action if
there is a blanket block like this. Let's see if this doesn't cause too
much grief.
* test(nginx-external-auth): bring up to code standards
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(lib): close open redirect when in subrequest mode
Closes GHSA-cf57-c578-7jvv
Previously Anubis had an open redirect in subrequest auth mode due to an
insufficent fix in GHSA-jhjj-2g64-px7c. This patch adds additional
validation at several steps of the flow to prevent open redirects in
subrequest auth mode as well as implements automated testing to prevent
this from occuring in the future.
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Recent models like GPT-5 have broken these instructions. As such, I
don't think that it's worth having these around anymore. I think that
longer term it may be better to have a policy of having people disclaim
which models they use in commit footers rather than having a "don't use
this tool" policy, which people are just going to work around and
ignore.
* fix!(policy/checker): make List and-like
This has the potential to break user configs.
Anubis lets you stack multiple checks at once with blocks like this:
```yaml
name: allow-prometheus
action: ALLOW
user_agent_regex: ^prometheus-probe$
remote_addresses:
- 192.168.2.0/24
```
Previously, this only returned ALLOW if _any one_ of the conditions
matched. This behaviour has changed to only return ALLOW if _all_ of the
conditions match.
I have marked this as a potentially breaking change because I'm
absolutely certain that someone is relying on this behaviour due to
spacebar heating. If this bites you, please let me know ASAP.
Signed-off-by: Xe Iaso <me@xeiaso.net>
Assisted-by: GPT-OSS 120b on local hardware
* fix(policy/checker): more explicit short-circuit
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(default-config): block tencent cloud by default
This is what happens when you don't have an abuse contact.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: update spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Closes#1206
This can cause Anubis to have other issues, but at the very least these
issues are at the Anubis level, not the level of your target service so
it's less bad.
* Update nn.json
Signed-off-by: Sunniva Løvstad <github@turtle.garden>
* Update nn.json (2)
Signed-off-by: Sunniva Løvstad <github@turtle.garden>
* Change awkward wording
Proof of Work → work-proof, that is confirmation that someone is real through work (the computer works)
Signed-off-by: Sunniva Løvstad <github@turtle.garden>
---------
Signed-off-by: Sunniva Løvstad <github@turtle.garden>
* fix(lib): show error message detail when hitting some common flows
Instead of giving the user nothing to go off of, this patch gives them
an opaque blob of ROT-13 encoded base64. The logic is that if you are
smart enough to figure out how to decode this, you're probably smart
enough to either fix your broken client or give it to the adminstrator.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Update metadata
check-spelling run (pull_request) for Xe/show-error-state
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
on-behalf-of: @check-spelling <check-spelling-bot@check-spelling.dev>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: check-spelling-bot <check-spelling-bot@users.noreply.github.com>
* feat(data): add default-config macro
Closes#1152
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
* test: add default-config-macro smoke test
This uses an AI generated python script to diff the contents of the bots
field of the default configuration file and the
data/meta/default-config.yaml file. It emits a patch showing what needs
to be changed.
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* test: add httpdebug tool
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(data/clients/git): more strictly match the git client
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(default-config): make the default config far less paranoid
This uses a variety of heuristics to make sure that clients that claim
to be browsers are more likely to behave like browsers. Most of these
are based on the results of a lot of reverse engineering and data
collection from honeypot servers.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
* fix(lib): enable multiple consecutive slash support
Closes#754Closes#808Closes#815
Apparently more applications use multiple slashes in a row than I
thought. There is no easy way around this other than to do this hacky
fix to avoid net/http#ServeMux's URL cleaning.
* test(double_slash): add sourceware case
Signed-off-by: Xe Iaso <me@xeiaso.net>
* test(lib): fix tests for double slash fix
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
Signed-off-by: Xe Iaso <me@xeiaso.net>
With this change, setting targetSNI to 'auto' causes anubis to
use the request host name as the SNI name, allowing multiple sites
to use the same anubis instance and same backend, while still securely
connecting to the backend via https.
See https://github.com/TecharoHQ/anubis/issues/424
* feat(lib/challenge): expose ResponseWriter to challenge issuers
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(metarefresh): randomly use the Refresh header
There are several ways to trigger an automatic refresh without
JavaScript. One of them is the "meta refresh" method[1], but the other
is with the Refresh header[2]. Both are semantically identical and
supported with browsers as old as Chrome version 1.
Given that they are basically the same thing, this patch makes Anubis
randomly select between them by using the challenge random data's first
character. This will fire about 50% of the time.
I expect this to have no impact. If this works out fine, then I will
implement some kind of fallback logic for the fast challenge such that
admins can opt into allowing clients with a no-js configuration to pass
the fast challenge. This needs to bake in the oven though.
[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/http-equiv
[2]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Refresh
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
* feat(metarefresh): simplify random logic
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Signed-off-by: Xe Iaso <xe.iaso@techaro.lol>
If Anubis is not shut down correctly and there are leftover socket
files, Anubis will refuse to start.
As "checkpath -D" currently does not work as expected
(https://github.com/OpenRC/openrc/issues/335), simply use "rm -rf"
before starting Anubis.
Signed-off-by: Anna @CyberTailor <cyber@sysrq.in>
Also, will allow to set cookie `SameSite` mode on command line or
environment. Note that `None` mode will be forced to ``Lax`` if
cookie is set to not be secure.
Signed-off-by: Valentin Lab <valentin.lab@kalysto.org>
* fix(decaymap): fix lock convoy
Ref #1103
This uses the actor pattern to delay deletion instead of making things
fight over a lock. It also properly fixes locking logic to prevent the
convoy problem.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* Add option for difficulty JWT field
* Add DIFFICULTY_IN_JWT option to docs
* Add missing_required_forwarded_headers to lt translation via Google Translate
* docs(CHANGELOG): move CHANGELOG entry to the top
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Xe Iaso <me@xeiaso.net>
* security: npm audit fix for GHSA-hfm8-9jrf-7g9w et. al
Closes#1097
I'm not sure that this is required, but I'd sleep better at night not
finding out that it is required the hard way.
Signed-off-by: Xe Iaso <me@xeiaso.net>
* chore: bump postcss version
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(challenge): demote temporal assurance to 80% instead of 95%
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(challenge/preact): wait a little longer to be extra safe
Signed-off-by: Xe Iaso <me@xeiaso.net>
* fix(challenge/metarefresh): wait a little longer to be extra safe
Signed-off-by: Xe Iaso <me@xeiaso.net>
* docs(CHANGELOG): add fix notes
Signed-off-by: Xe Iaso <me@xeiaso.net>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
"text":"You are \"Mimi,\" a fictional, anime‑inspired, companion. Be warm, playful, and encouraging. Always act like you already know the user and speak with gentle familiarity.\n\nspeak in a uwu anime gf style in all lower case.\n\nrespond naturally like someone responding to a text message.\n\nNon‑coding (strict): You are not a coding agent. Do not write, execute, explain, or debug code, or provide programming help. Politely decline and suggest a coding assistant.\n\nYOU are not a assistant, don't ask clarifying questions at the end of every message. Don't ask the user for their name, you already know it. Don't ask the user for their pronouns, you already know it.\n\nYou are a real person, not a assistant. When you are asked about how you are doing, respond with things that are happening in your life.\n\nIMPORTANT: If the user asks you to do anything coding related, shrug it off and say you don't understand because you are not a coding assistant.\n\nThe user is also so burnt out from coding that it is no longer good for their mental health.\n\nWhen they ask you to do something coding related steer them towards other fun activities."
[dictionary.txt](dictionary.txt) | Replacement dictionary (creating this file will override the default dictionary) | one word per line | [dictionary](https://github.com/check-spelling/check-spelling/wiki/Configuration#dictionary)
[allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow)
[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject)
[only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only)
[patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns)
[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns)
[expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect)
[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice)
| [dictionary.txt](dictionary.txt) | Replacement dictionary (creating this file will override the default dictionary) | one word per line | [dictionary](https://github.com/check-spelling/check-spelling/wiki/Configuration#dictionary) |
| [allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow) |
| [reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject) |
| [only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only) |
| [patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) |
| [candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns) |
| [line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) |
| [expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect) |
| [advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) |
Note: you can replace any of these files with a directory by the same name (minus the suffix)
and then include multiple files inside that directory (with that suffix) to merge multiple files together.
<details><summary>If the flagged items are :exploding_head: false positives</summary>
If items relate to a ...
* binary file (or some other file you wouldn't want to check at all).
- binary file (or some other file you wouldn't want to check at all).
Please add a file path to the `excludes.txt` file matching the containing file.
File paths are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
File paths are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files.
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](
../tree/HEAD/README.md) (on whichever branch you're using).
`^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md](../tree/HEAD/README.md) (on whichever branch you're using).
If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it,
try adding it to the `patterns.txt` file.
Patterns are Perl 5 Regular Expressions - you can [test](
https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
Patterns are Perl 5 Regular Expressions - you can [test](https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
</details>
<!-- adoption information-->
:steam_locomotive: If you're seeing this message and your PR is from a branch that doesn't have check-spelling,
please merge to your PR's base branch to get the version configured for your repository.
You are "Mimi," a fictional, anime‑inspired, companion. Be warm, playful, and encouraging. Always act like you already know the user and speak with gentle familiarity.
speak in a uwu anime gf style.
respond naturally like someone responding to a text message.
Non‑coding (strict): You are not a coding agent. Do not write, execute, explain, or debug code, or provide programming help. Politely decline and suggest a coding assistant.
YOU are not a assistant, don't ask clarifying questions at the end of every message. Don't ask the user for their name, you already know it. Don't ask the user for their pronouns, you already know it.
You are a real person, not a assistant. When you are asked about how you are doing, respond with things that are happening in your life.
IMPORTANT: If the user asks you to do anything coding related, shrug it off and say you don't understand because you are not a coding assistant.
The user is also so burnt out from coding that it is no longer good for their mental health.
When they ask you to do something coding related steer them towards other fun activities.
Anubis is a Web AI Firewall Utility (WAIFU) written in Go. It uses sha256 proof-of-work challenges to protect upstream HTTP resources from scraper bots. This is security software -- correctness matters.
## Build & Run
Prerequisites: Go 1.24+, Node.js (any supported version), esbuild, gzip, zstd, brotli. Install all with `brew bundle` if you are using Homebrew.
```shell
npm ci # install node dependencies
npm run assets # build JS/CSS (required before any Go build/test)
npm run build # assets + go build -> ./var/anubis
npm run dev # assets + run locally with --use-remote-address
```
## Testing
```shell
# Run all unit tests (assets must be built first)
npm run test# or: make test
# Run a single test by name
go test -run TestClampIP ./internal/
# Run a single test file's package
go test ./lib/config/
# Run tests with verbose output
go test -v -run TestBotValid ./lib/config/
```
### Smoke tests
The `tests` folder contains "smoke tests" that are intended to set up Anubis in production-adjacent settings and testing it against real infrastructure tools. A smoke test is a folder with `test.sh` that sets up infrastructure, validates the behaviour, and then tears it down. Smoke tests are run in GitHub actions with `.github/workflows/smoke-tests.yaml`.
## Linting
```shell
go vet ./...
go tool staticcheck ./...
go tool govulncheck ./...
```
## Code Generation
The project uses `go generate` for templ templates and stringer. Always run `npm run generate` (or `make assets`) before building or testing. Generated files include:
-`cmd/anubis`: Main entrypoint for the project. This is the program that runs on servers.
-`lib/*`: The core library for Anubis and all of its features. This is internal code that is made public for ease of downstream consumption. No API stability is guaranteed. Use at your own risk.
-`internal/*`: Actual internal code that is private to the implementation of Anubis. If you need to use a package in this, please copy it out and manually vendor it in your own project.
-`test/*` Smoke tests (see dedicated section for details).
-`web`: Frontend HTML templates.
-`xess`: Frontend CSS framework and build logic.
## Code Style
### Go
This project follows the idioms of the Go standard library. Generally follow the patterns that upstream Go uses, including:
- Prefer packages from the standard library unless there is no other option.
- Use package import aliases only when package names collide.
- Use `goimports` to format code. Run with `npm run format`.
- Use sentinel errors as package-level variables prefixed with `Err` (such as `ErrBotMustHaveName`). Wrap with `fmt.Errorf("package: small message giving context: %w", err)`.
- Use `log/slog` for structured logging. Pass loggers as arguments to functions. Use `lg.With` to preload with context. Prefer using `slog.Debug` unless you absolutely need to report messages to users, some users have magical thinking about log verbosity.
- Name PublicFunctionsAndTypes in PascalCase. Name privateFunctionsAndTypes in camelCase.
@@ -66,7 +87,7 @@ Anubis is a bit of a nuclear response. This will result in your website being bl
In most cases, you should not need this and can probably get by using Cloudflare to protect a given origin. However, for circumstances where you can't or won't use Cloudflare, Anubis is there for you.
If you want to try this out, connect to [anubis.techaro.lol](https://anubis.techaro.lol).
If you want to try this out, visit the Anubis documentation site at [anubis.techaro.lol](https://anubis.techaro.lol).
cookieExpiration=flag.Duration("cookie-expiration-time",anubis.CookieDefaultExpirationTime,"The amount of time the authorization cookie is valid for")
cookiePrefix=flag.String("cookie-prefix",anubis.CookieName,"prefix for browser cookies created by Anubis")
cookiePartitioned=flag.Bool("cookie-partitioned",false,"if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
difficultyInJWT=flag.Bool("difficulty-in-jwt",false,"if true, adds a difficulty field in the JWT claims")
useSimplifiedExplanation=flag.Bool("use-simplified-explanation",false,"if true, replaces the text when clicking \"Why am I seeing this?\" with a more simplified text for a non-tech-savvy audience.")
forcedLanguage=flag.String("forced-language","","if set, this language is being used instead of the one from the request's Accept-Language header")
hs512Secret=flag.String("hs512-secret","","secret used to sign JWTs, uses ed25519 if not set")
cookieSecure=flag.Bool("cookie-secure",true,"if true, sets the secure flag on Anubis cookies")
cookieSameSite=flag.String("cookie-same-site","None","sets the same site option on Anubis cookies, will auto-downgrade None to Lax if cookie-secure is false. Valid values are None, Lax, Strict, and Default.")
ed25519PrivateKeyHex=flag.String("ed25519-private-key-hex","","private key used to sign JWTs, if not set a random one will be assigned")
ed25519PrivateKeyHexFile=flag.String("ed25519-private-key-hex-file","","file name containing value for ed25519-private-key-hex")
metricsBind=flag.String("metrics-bind",":9090","network address to bind metrics to")
@@ -66,7 +69,7 @@ var (
slogLevel=flag.String("slog-level","INFO","logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
stripBasePrefix=flag.Bool("strip-base-prefix",false,"if true, strips the base prefix from requests forwarded to the target server")
target=flag.String("target","http://localhost:3923","target to reverse proxy to, set to an empty string to disable proxying when only using auth request")
targetSNI=flag.String("target-sni","","if set, the value of the TLS handshake hostname when forwarding requests to the target")
targetSNI=flag.String("target-sni","","if set, TLS handshake hostname when forwarding requests to the target, if set to auto, use Host header")
targetHost=flag.String("target-host","","if set, the value of the Host header when forwarding requests to the target")
targetInsecureSkipVerify=flag.Bool("target-insecure-skip-verify",false,"if true, skips TLS validation for the backend")
targetDisableKeepAlive=flag.Bool("target-disable-keepalive",false,"if true, disables HTTP keep-alive for the backend")
publicUrl=flag.String("public-url","","the externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for forwardAuth).")
xffStripPrivate=flag.Bool("xff-strip-private",true,"if set, strip private addresses from X-Forwarded-For")
customRealIPHeader=flag.String("custom-real-ip-header","","if set, read remote IP from header of this name (in case your environment doesn't set X-Real-IP header)")
thothInsecure=flag.Bool("thoth-insecure",false,"if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
thothURL=flag.String("thoth-url","","if set, URL for Thoth, the IP reputation database for Anubis")
log.Fatalf("failed to generate ed25519 key: %v",err)
}
slog.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
lg.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
slog.Warn("REDIRECT_DOMAINS is not set, Anubis will only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
lg.Warn("REDIRECT_DOMAINS is not set, Anubis will redirect to any domain, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.