+ );
+}
diff --git a/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css b/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css
new file mode 100644
index 00000000..4bf391c0
--- /dev/null
+++ b/docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/styles.module.css
@@ -0,0 +1,317 @@
+/* Main container styles */
+.container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-family: ui-sans-serif, system-ui, sans-serif;
+ margin-top: 2rem;
+ margin-bottom: 2rem;
+}
+
+.innerContainer {
+ width: 100%;
+ max-width: 56rem;
+ margin: 0 auto;
+}
+
+/* Header styles */
+.header {
+ text-align: center;
+ margin-bottom: 2.5rem;
+}
+
+.title {
+ font-size: 2.25rem;
+ font-weight: 700;
+ color: rgb(34 211 238);
+}
+
+.subtitle {
+ font-size: 1.125rem;
+ color: rgb(156 163 175);
+ margin-top: 0.5rem;
+}
+
+/* Grid layout styles */
+.grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+ align-items: center;
+ text-align: center;
+}
+
+/* Block styles */
+.block {
+ background-color: rgb(31 41 55);
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.blockTitle {
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: rgb(34 211 238);
+ margin-bottom: 0.5rem;
+}
+
+.challengeText {
+ font-size: 0.875rem;
+ color: rgb(209 213 219);
+ word-break: break-all;
+ font-family: ui-monospace, SFMono-Regular, monospace;
+}
+
+.combinedDataText {
+ font-size: 0.875rem;
+ color: rgb(156 163 175);
+ word-break: break-all;
+ font-family: ui-monospace, SFMono-Regular, monospace;
+}
+
+/* Nonce control styles */
+.nonceControls {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+}
+
+.nonceButton {
+ background-color: rgb(55 65 81);
+ border-radius: 9999px;
+ padding: 0.5rem;
+ transition: background-color 200ms;
+}
+
+.nonceButton:hover:not(:disabled) {
+ background-color: rgb(34 211 238);
+}
+
+.nonceButton:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.nonceValue {
+ font-size: 1.5rem;
+ font-family: ui-monospace, SFMono-Regular, monospace;
+ width: 6rem;
+ text-align: center;
+}
+
+/* Icon styles */
+.icon {
+ height: 2rem;
+ width: 2rem;
+}
+
+.iconGreen {
+ height: 2rem;
+ width: 2rem;
+ color: rgb(74 222 128);
+}
+
+.iconRed {
+ height: 2rem;
+ width: 2rem;
+ color: rgb(248 113 113);
+}
+
+.iconSmall {
+ height: 1.5rem;
+ width: 1.5rem;
+}
+
+.iconGray {
+ height: 2.5rem;
+ width: 2.5rem;
+ color: rgb(75 85 99);
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+/* Arrow animation */
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+.arrowContainer {
+ display: flex;
+ justify-content: center;
+ margin: 1.5rem 0;
+}
+
+/* Hash output styles */
+.hashContainer {
+ padding: 1.5rem;
+ border-radius: 0.5rem;
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ transition: all 300ms;
+ border: 2px solid;
+}
+
+.hashContainerSuccess {
+ background-color: rgb(20 83 45 / 0.5);
+ border-color: rgb(74 222 128);
+}
+
+.hashContainerError {
+ background-color: rgb(127 29 29 / 0.5);
+ border-color: rgb(248 113 113);
+}
+
+.hashContent {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.hashText {
+ text-align: center;
+}
+
+.hashTextLg {
+ text-align: left;
+}
+
+.hashValue {
+ font-size: 0.875rem;
+ word-break: break-all;
+}
+
+.hashValueLg {
+ font-size: 1rem;
+ word-break: break-all;
+}
+
+.hashIcon {
+ margin-top: 1rem;
+}
+
+.hashIconLg {
+ margin-top: 0;
+}
+
+/* Hash highlighting */
+.hashPrefix {
+ font-family: ui-monospace, SFMono-Regular, monospace;
+}
+
+.hashPrefixGreen {
+ color: rgb(74 222 128);
+}
+
+.hashPrefixRed {
+ color: rgb(248 113 113);
+}
+
+.hashSuffix {
+ font-family: ui-monospace, SFMono-Regular, monospace;
+ color: rgb(156 163 175);
+}
+
+/* Button styles */
+.buttonContainer {
+ margin-top: 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+}
+
+.button {
+ font-weight: 700;
+ padding: 0.75rem 1.5rem;
+ border-radius: 0.5rem;
+ transition: transform 150ms;
+}
+
+.button:hover {
+ transform: scale(1.05);
+}
+
+.buttonCyan {
+ background-color: rgb(8 145 178);
+ color: white;
+}
+
+.buttonCyan:hover {
+ background-color: rgb(6 182 212);
+}
+
+.buttonYellow {
+ background-color: rgb(202 138 4);
+ color: white;
+}
+
+.buttonYellow:hover {
+ background-color: rgb(245 158 11);
+}
+
+.buttonIndigo {
+ background-color: rgb(79 70 229);
+ color: white;
+}
+
+.buttonIndigo:hover {
+ background-color: rgb(99 102 241);
+}
+
+.buttonGray {
+ background-color: rgb(55 65 81);
+ color: white;
+}
+
+.buttonGray:hover {
+ background-color: rgb(75 85 99);
+}
+
+/* Responsive styles */
+@media (min-width: 768px) {
+ .title {
+ font-size: 3rem;
+ }
+
+ .grid {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+ }
+
+ .hashContent {
+ flex-direction: row;
+ }
+
+ .hashText {
+ text-align: left;
+ }
+
+ .hashValue {
+ font-size: 1rem;
+ }
+
+ .hashIcon {
+ margin-top: 0;
+ }
+}
+
+@media (max-width: 767px) {
+ .grid {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+}
diff --git a/docs/blog/2025-08-28-cpu-core-odd/index.mdx b/docs/blog/2025-08-28-cpu-core-odd/index.mdx
new file mode 100644
index 00000000..d12abb64
--- /dev/null
+++ b/docs/blog/2025-08-28-cpu-core-odd/index.mdx
@@ -0,0 +1,129 @@
+---
+slug: 2025/cpu-core-odd
+title: Sometimes CPU cores are odd
+description: "TL;DR: all the assumptions you have about processor design are wrong and if you are unlucky you will never run into problems that users do through sheer chance."
+authors: [xe]
+tags:
+ - bugfix
+ - implementation
+image: parc-dsilence.webp
+---
+
+import ProofOfWorkDiagram from "./ProofOfWorkDiagram";
+
+
+
+One of the biggest lessons that I've learned in my career is that all software has bugs, and the more complicated your software gets the more complicated your bugs get. A lot of the time those bugs will be fairly obvious and easy to spot, validate, and replicate. Sometimes, the process of fixing it will uncover your core assumptions about how things work in ways that will leave you feeling like you just got trolled.
+
+Today I'm going to talk about a single line fix that prevents people on a large number of devices from having weird irreproducible issues with Anubis rejecting people when it frankly shouldn't. Stick around, it's gonna be a wild ride.
+
+{/* truncate */}
+
+## How this happened
+
+Anubis is a web application firewall that tries to make sure that the client is a browser. It uses a few [challenge methods](/docs/admin/configuration/challenges/) to do this determination, but the main method is the [proof of work](/docs/admin/configuration/challenges/proof-of-work/) challenge which makes clients grind away at cryptographic checksums in order to rate limit clients from connecting too eagerly.
+
+:::note
+
+In retrospect implementing the proof of work challenge may have been a mistake and it's likely to be supplanted by things like [Proof of React](https://github.com/TecharoHQ/anubis/pull/1038) or other methods that have yet to be developed. Your patience and polite behaviour in the bug tracker is appreciated.
+
+:::
+
+In order to make sure the proof of work challenge screen _goes away as fast as possible_, the [worker code](https://github.com/TecharoHQ/anubis/tree/main/web/js/worker) is optimized within an inch of its digital life. One of the main ways that this code is optimized is with how it's run. Over the last 10-20 years, the main way that CPUs have gotten fast is via increasing multicore performance. Anubis tries to make sure that it can use as many cores as possible in order to take advantage of your device's CPU as much as it can.
+
+This strategy sometimes has some issues though, for one Firefox seems to get _much slower_ if you have Anubis try to absolutely saturate all of the cores on the system. It also has a fairly high overhead between JavaScript JIT code and [WebCrypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). I did some testing and found out that Firefox's point of diminishing returns was about half of the CPU cores.
+
+## Another "invalid response" bug
+
+One of the complaints I've been getting from users and administrators using Anubis is that they've been running into issues where users get randomly rejected with an error message only saying "invalid response". This happens when the challenge validating process fails. This issue has been blocking the release of the next version of Anubis.
+
+In order to demonstrate this better, I've made a little interactive diagram for the proof of work process:
+
+
+
+I've fixed a lot of the easy bugs in Anubis by this point. A lot of what's left is the hard bugs, but also specifically the kinds of hard bugs that involve weird hardware configurations. In order to try and catch these issues before software hits prod, I test Anubis against a bunch of hardware I have locally. Any issues I find and fix before software ships are issues that you don't hit in production.
+
+Let's consider [the line of code](https://github.com/TecharoHQ/anubis/blob/main/web/js/algorithms/fast.mjs) that was causing this issue:
+
+```js
+threads = Math.max(navigator.hardwareConcurrency / 2, 1),
+```
+
+This is intended to make your browser spawn a proof of work worker for _half_ of your available CPU cores. If you only have one CPU core, you should only have one worker. Each thread is given this number of threads and uses that to increment the nonce so that each thread doesn't try to find a solution that another worker has already performed.
+
+One of the subtle problems here is that all of the parts of this assume that the thread ID and nonce are integers without a decimal portion. Famously, [all JavaScript numbers are IEEE 754 floating point numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). Surely there wouldn't be a case where the thread count could be a _decimal_ number, right?
+
+Here's all the devices I use to test Anubis _and their core counts_:
+
+| Device Name | Core Count |
+| :--------------------------- | :--------- |
+| MacBook Pro M3 Max | 16 |
+| MacBook Pro M4 Max | 16 |
+| AMD Ryzen 9 7950x3D | 32 |
+| Google Pixel 9a (GrapheneOS) | 8 |
+| iPhone 15 Pro Max | 6 |
+| iPad Pro (M1) | 8 |
+| iPad mini | 6 |
+| Steam Deck | 8 |
+| Core i5 10600 (homelab) | 12 |
+| ROG Ally | 16 |
+
+Notice something? All of those devices have an _even_ number of cores. Some devices such as the [Pixel 8 Pro](https://www.gsmarena.com/google_pixel_8_pro-12545.php) have an _odd_ number of cores. So what happens with that line of code as the JavaScript engine evaluates it?
+
+Let's replace the [`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency) with the Pixel 8 Pro's 9 cores:
+
+```js
+threads = Math.max(9 / 2, 1),
+```
+
+Then divide it by two:
+
+```js
+threads = Math.max(4.5, 1),
+```
+
+Oops, that's not ideal. However `4.5` is bigger than `1`, so [`Math.max`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) returns that:
+
+```js
+threads = 4.5,
+```
+
+This means that each time the proof of work equation is calculated, there is a 50% chance that a valid solution would include a nonce with a decimal portion in it. If the client finds a solution with such a nonce, then it would think the client was successful and submit the solution to the server, but the server only expects whole numbers back so it rejects that as an invalid response.
+
+I keep telling more junior people that when you have the weirdest, most inconsistent bugs in software that it's going to boil down to the dumbest possible thing you can possibly imagine. People don't believe me, then they encounter bugs like this. Then they suddenly believe me.
+
+Here is the fix:
+
+```js
+threads = Math.trunc(Math.max(navigator.hardwareConcurrency / 2, 1)),
+```
+
+This uses [`Math.trunc`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc) to truncate away the decimal portion so that the Pixel 8 Pro has `4` workers instead of `4.5` workers.
+
+## Today I learned this was possible
+
+This was a total "today I learned" moment. I didn't actually think that hardware vendors shipped processors with an odd number of cores, however if you look at the core geometry of the Pixel 8 Pro, it has _three_ tiers of processor cores:
+
+| Core type | Core model | Number |
+| :----------------- | :------------------- | :----- |
+| High performance | 3 Ghz Cortex X3 | 1 |
+| Medium performance | 2.45 Ghz Cortex A715 | 4 |
+| High efficiency | 2.15 Cortex A510 | 4 |
+| Total | | 9 |
+
+I guess every assumption that developers have about CPU design is probably wrong.
+
+This probably isn't helped by the fact that for most of my career, the core count in phones has been largely irrelevant and most of the desktop / laptop CPUs I've had (where core count does matter) uses [simultaneous multithreading](https://en.wikipedia.org/wiki/Simultaneous_multithreading) to "multiply" the core count by two.
+
+The client side fix is a bit of an "emergency stop" button to try and mitigate the badness as early as possible. In general I'm quite aware of the terrible UX involved with this flow failing and I'm still noodling through ways to make that UX better and easier for users / administrators to debug.
+
+I'm looking into the following:
+
+1. This could have been prevented on the server side by doing less strict input validation in compliance with [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle). I feel nervous about making such a security-sensitive endpoint _more liberal_ with the inputs it can accept, but it may be fine? I need to consult with a security expert.
+2. Showing an encrypted error message on the "invalid response" page so that the user and administrator can work together to fix or report the issue. I remember Google doing this at least once, but I can't recall where I've seen it in the past. Either way, this is probably the most robust method even though it would require developing some additional tooling. I think it would be worth it.
+
+I'm likely going to go with the second option. I will need to figure out a good flow for this. It's likely going to involve [age](https://github.com/FiloSottile/age). I'll say more about this when I have more to say.
+
+In the meantime though, looks like I need to expense a used Pixel 8 Pro to add to the testing jungle for Anubis. If anyone has a deal out there, please let me know!
+
+Thank you to the people that have been polite and helpful when trying to root cause and fix this issue.
diff --git a/docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp b/docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp
new file mode 100644
index 00000000..4d471752
Binary files /dev/null and b/docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp differ
diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index 4d2a6afb..bf3051dc 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -7,13 +7,16 @@ sidebar_position: 999
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).https://bsky.app/profile/xeiaso.net/post/3lxkqbd25hk22
## [Unreleased]
- Add a "proof of React" challenge to prove that the client is able to run a simple JSX app.
+- Added possibility to disable HTTP keep-alive to support backends not properly
+ handling it
+- Added a missing link to the Caddy installation environment in the installation documentation.
- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
- The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
- [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)).
@@ -31,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The hard dependency on WebCrypto has been removed, allowing a proof of work challenge to work over plain (unencrypted) HTTP.
- Firefox for Android support has been fixed by embedding the challenge ID into the pass-challenge route. This also fixes some inconsistent issues with other mobile browsers.
- The Anubis version number is put in the footer of every page.
+- Prevent the proof of work nonce from being a decimal value by using Math.trunc to coerce it back to an integer if it happens ([#1043](https://github.com/TecharoHQ/anubis/issues/1043)).
- The legacy JSON based policy file example has been removed and all documentation for how to write a policy file in JSON has been deleted. JSON based policy files will still work, but YAML is the superior option for Anubis configuration.
- A standard library HTTP server log message about HTTP pipelining not working has been filtered out of Anubis' logs. There is no action that can be taken about it.
- The default `favicon` pattern in `data/common/keep-internet-working.yaml` has been updated to permit requests for png/gif/jpg/svg files as well as ico.
@@ -39,12 +43,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add optional restrictions for JWT based on the value of a header ([#697](https://github.com/TecharoHQ/anubis/pull/697))
- The word "hack" has been removed from the translation strings for Anubis due to incidents involving people misunderstanding that word and sending particularly horrible things to the project lead over email.
- Bump AI-robots.txt to version 1.39
+- Add a default block rule for Huawei Cloud.
+- Add a default block rule for Alibaba Cloud.
+- Add X-Request-URI support so that Subrequest Authentication has path support.
+- Add better logging when using Subrequest Authentication.
+- Two of Slackware's community git repository servers are now poxied by Anubis.
+- Added support to use Traefik forwardAuth middleware.
### Security-relevant changes
#### Fix potential double-spend for challenges
-Anubis operates by issuing a challenge and having the client present a solution for that challenge. Challenges are identified by a unique UUID, which is tored in the database.
+Anubis operates by issuing a challenge and having the client present a solution for that challenge. Challenges are identified by a unique UUID, which is stored in the database.
The problem is that a challenge could potentially be used twice by a dedicated attacker making a targeted attack against Anubis. Challenge records did not have a "spent" or "used" field. In total, a dedicated attacker could solve a challenge once and reuse that solution across multiple sessions in order to mint additional tokens.
@@ -282,7 +292,6 @@ And some cleanups/refactors were added:
- Bump AI-robots.txt to version 1.37
- Make progress bar styling more compatible (UXP, etc)
- Add `--strip-base-prefix` flag/envvar to strip the base prefix from request paths when forwarding to target servers
-- Added support to use Traefik forwardAuth middleware
- Fix an off-by-one in the default threshold config
- Add functionality for HS512 JWT algorithm
- Add support for dynamic cookie domains with the `--cookie-dynamic-domain`/`COOKIE_DYNAMIC_DOMAIN` flag/envvar
diff --git a/docs/docs/admin/caveats-xff.mdx b/docs/docs/admin/caveats-xff.mdx
new file mode 100644
index 00000000..655534f3
--- /dev/null
+++ b/docs/docs/admin/caveats-xff.mdx
@@ -0,0 +1,25 @@
+# Client IP Headers
+
+Currently Anubis will always flatten the `X-Forwarded-For` when it contains multiple IP addresses. From right to left, the first IP address that is not in one of the following categories will be set as `X-Forwarded-For` in the request passed to the upstream.
+
+- Private (`XFF_STRIP_PRIVATE`, enabled by default)
+- CGNAT (always stripped)
+- Link-local Unicast (always stripped)
+
+```
+Incoming: X-Forwarded-For: 1.2.3.4, 5.6.7.8, 10.0.0.1
+Upstream: X-Forwarded-For: 5.6.7.8
+```
+
+This behavior will cause problems if the proxy in front of Anubis is from a public IP, such as Cloudflare, because Anubis will use the Cloudflare IP instead of your client's real IP. You will likely see all requests from your browser being blocked and/or an infinite challenge loop.
+
+```
+Incoming: X-Forwarded-For: REAL_CLIENT_IP, CF_IP
+Upstream: X-Forwarded-For: CF_IP
+```
+
+As a workaround, you should configure your web server to parse an alternative source (such as `CF-Connecting-IP`), or pre-process the incoming `X-Forwarded-For` with your web server to ensure it only contains the real client IP address, then pass it to Anubis as `X-Forwarded-For`.
+
+The `X-Real-IP` header will be automatically inferred from `X-Forwarded-For` if not set, setting it explicitly is not necessary as long as `X-Forwarded-For` contains only the real client IP. However setting it explicitly can eliminate spoofed values if your web server doesn't set this.
+
+See [Cloudflare](environments/cloudflare.mdx) for an example configuration.
diff --git a/docs/docs/admin/environments/cloudflare.mdx b/docs/docs/admin/environments/cloudflare.mdx
new file mode 100644
index 00000000..c84b67e1
--- /dev/null
+++ b/docs/docs/admin/environments/cloudflare.mdx
@@ -0,0 +1,26 @@
+# Cloudflare
+
+If you are using Cloudflare, you should configure your server to use `CF-Connecting-IP` as the source of the real client IP, and pass that address to Anubis as `X-Forwarded-For`. Read [Client IP Headers](../caveats-xff.mdx) for details.
+
+Example configuration with Caddy:
+
+```Caddyfile
+{
+ servers {
+ # Cloudflare IP ranges from https://www.cloudflare.com/en-gb/ips/
+ trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
+ # Use CF-Connecting-IP to determine the client IP instead of XFF
+ # https://caddyserver.com/docs/caddyfile/options#client-ip-headers
+ client_ip_headers CF-Connecting-IP
+ }
+}
+
+example.com {
+ reverse_proxy http://anubis:3000 {
+ # Pass the client IP read from CF-Connecting-IP
+ header_up X-Forwarded-For {client_ip}
+ header_up X-Real-IP {client_ip}
+ header_up X-Http-Version {http.request.proto}
+ }
+}
+```
diff --git a/docs/docs/admin/environments/nginx.mdx b/docs/docs/admin/environments/nginx.mdx
index ec961cd4..fcbba26f 100644
--- a/docs/docs/admin/environments/nginx.mdx
+++ b/docs/docs/admin/environments/nginx.mdx
@@ -129,6 +129,8 @@ server {
listen unix:/run/nginx/nginx.sock;
server_name mimi.techaro.lol;
+
+ port_in_redirect off;
root "/srv/http/mimi.techaro.lol";
index index.html;
diff --git a/docs/docs/admin/frameworks/wordpress.mdx b/docs/docs/admin/frameworks/wordpress.mdx
index 25cc34bd..9d7cac8f 100644
--- a/docs/docs/admin/frameworks/wordpress.mdx
+++ b/docs/docs/admin/frameworks/wordpress.mdx
@@ -1,6 +1,6 @@
-# Wordpress
+# WordPress
-Wordpress is the most popular blog engine on the planet.
+WordPress is the most popular blog engine on the planet.
## Using a multi-site setup with Anubis
@@ -27,7 +27,7 @@ flowchart LR
US --> |whatever you're doing| B
```
-Wordpress may not realize that the underlying connection is being done over HTTPS. This could lead to a redirect loop in the `/wp-admin/` routes. In order to fix this, add the following to your `wp-config.php` file:
+WordPress may not realize that the underlying connection is being done over HTTPS. This could lead to a redirect loop in the `/wp-admin/` routes. In order to fix this, add the following to your `wp-config.php` file:
```php
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
@@ -36,4 +36,4 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
}
```
-This will make Wordpress think that your connection is over HTTPS instead of plain HTTP.
+This will make WordPress think that your connection is over HTTPS instead of plain HTTP.
diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx
index bdc94268..04da9f46 100644
--- a/docs/docs/admin/installation.mdx
+++ b/docs/docs/admin/installation.mdx
@@ -178,6 +178,7 @@ Alternatively here is a key generated by your browser:
To get Anubis filtering your traffic, you need to make sure it's added to your HTTP load balancer or platform configuration. See the [environments category](/docs/category/environments) for detailed information on individual environments.
- [Apache](./environments/apache.mdx)
+- [Caddy](./environments/caddy.mdx)
- [Docker compose](./environments/docker-compose.mdx)
- [Kubernetes](./environments/kubernetes.mdx)
- [Nginx](./environments/nginx.mdx)
diff --git a/docs/docs/admin/policies.mdx b/docs/docs/admin/policies.mdx
index c40868d4..1646b0d4 100644
--- a/docs/docs/admin/policies.mdx
+++ b/docs/docs/admin/policies.mdx
@@ -54,7 +54,7 @@ bots:
action: CHALLENGE
```
-This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.json) is a bit more cohesive, but this should be more than enough for most users.
+This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml) is a bit more cohesive, but this should be more than enough for most users.
If no rules match the request, it is allowed through. For more details on this default behavior and its implications, see [Default allow behavior](./default-allow-behavior.mdx).
diff --git a/docs/docs/user/known-instances.md b/docs/docs/user/known-instances.md
index c151a549..21dda39f 100644
--- a/docs/docs/user/known-instances.md
+++ b/docs/docs/user/known-instances.md
@@ -118,3 +118,8 @@ This page contains a non-exhaustive list with all websites using Anubis.
- https://hacklab.to/
- https://knowledge.hacklab.to/
+-
+ Slackware
+ - https://git.slackware.nl/
+ - https://git.liveslak.org/
+
diff --git a/internal/log.go b/internal/log.go
index 2cc32d08..fb2f2509 100644
--- a/internal/log.go
+++ b/internal/log.go
@@ -27,8 +27,13 @@ func InitSlog(level string) {
}
func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger {
+ host := r.Host
+ if host == "" {
+ host = r.Header.Get("X-Forwarded-Host")
+ }
+
return base.With(
- "host", r.Host,
+ "host", host,
"method", r.Method,
"path", r.URL.Path,
"user_agent", r.UserAgent(),
diff --git a/internal/log_test.go b/internal/log_test.go
index 50bd8493..83c92189 100644
--- a/internal/log_test.go
+++ b/internal/log_test.go
@@ -3,6 +3,8 @@ package internal
import (
"bytes"
"log"
+ "log/slog"
+ "net/http"
"strings"
"testing"
)
@@ -44,3 +46,37 @@ func TestErrorLogFilter(t *testing.T) {
}
buf.Reset()
}
+
+func TestGetRequestLogger(t *testing.T) {
+ // Test case 1: Normal request with Host header
+ req1, _ := http.NewRequest("GET", "http://example.com/test", nil)
+ req1.Host = "example.com"
+
+ logger := slog.Default()
+ reqLogger := GetRequestLogger(logger, req1)
+
+ // We can't easily test the actual log output without setting up a test handler,
+ // but we can verify the function doesn't panic and returns a logger
+ if reqLogger == nil {
+ t.Error("GetRequestLogger returned nil")
+ }
+
+ // Test case 2: Subrequest auth mode with X-Forwarded-Host
+ req2, _ := http.NewRequest("GET", "http://test.com/auth", nil)
+ req2.Host = ""
+ req2.Header.Set("X-Forwarded-Host", "original-site.com")
+
+ reqLogger2 := GetRequestLogger(logger, req2)
+ if reqLogger2 == nil {
+ t.Error("GetRequestLogger returned nil for X-Forwarded-Host case")
+ }
+
+ // Test case 3: No host information available
+ req3, _ := http.NewRequest("GET", "http://test.com/nohost", nil)
+ req3.Host = ""
+
+ reqLogger3 := GetRequestLogger(logger, req3)
+ if reqLogger3 == nil {
+ t.Error("GetRequestLogger returned nil for no host case")
+ }
+}
diff --git a/lib/anubis.go b/lib/anubis.go
index 2cc3eccb..1e2ccca1 100644
--- a/lib/anubis.go
+++ b/lib/anubis.go
@@ -436,6 +436,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
return
}
if (len(urlParsed.Host) > 0 && len(s.opts.RedirectDomains) != 0 && !slices.Contains(s.opts.RedirectDomains, urlParsed.Host)) || urlParsed.Host != r.URL.Host {
+ lg.Debug("domain not allowed", "domain", urlParsed.Host)
s.respondWithError(w, r, localizer.T("redirect_domain_not_allowed"))
return
}
diff --git a/lib/config.go b/lib/config.go
index 101d0abc..58c4a4a5 100644
--- a/lib/config.go
+++ b/lib/config.go
@@ -106,6 +106,7 @@ func New(opts Options) (*Server, error) {
}
anubis.BasePrefix = opts.BasePrefix
+ anubis.PublicUrl = opts.PublicUrl
result := &Server{
next: opts.Next,
diff --git a/lib/http.go b/lib/http.go
index 54180328..b1a449d2 100644
--- a/lib/http.go
+++ b/lib/http.go
@@ -212,12 +212,16 @@ func (s *Server) constructRedirectURL(r *http.Request) (string, error) {
host := r.Header.Get("X-Forwarded-Host")
uri := r.Header.Get("X-Forwarded-Uri")
+ localizer := localization.GetLocalizer(r)
+
if proto == "" || host == "" || uri == "" {
- return "", errors.New("missing required X-Forwarded-* headers")
+ return "", errors.New(localizer.T("missing_required_forwarded_headers"))
}
// Check if host is allowed in RedirectDomains
if len(s.opts.RedirectDomains) > 0 && !slices.Contains(s.opts.RedirectDomains, host) {
- return "", errors.New("redirect domain not allowed")
+ lg := internal.GetRequestLogger(s.logger, r)
+ lg.Debug("domain not allowed", "domain", host)
+ return "", errors.New(localizer.T("redirect_domain_not_allowed"))
}
redir := proto + "://" + host + uri
@@ -290,6 +294,8 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
hostMismatch := r.URL.Host != "" && urlParsed.Host != r.URL.Host
if hostNotAllowed || hostMismatch {
+ lg := internal.GetRequestLogger(s.logger, r)
+ lg.Debug("domain not allowed", "domain", urlParsed.Host)
s.respondWithStatus(w, r, localizer.T("redirect_domain_not_allowed"), http.StatusBadRequest)
return
}
diff --git a/lib/localization/locales/cs.json b/lib/localization/locales/cs.json
index 8d21d3e7..db4eded3 100644
--- a/lib/localization/locales/cs.json
+++ b/lib/localization/locales/cs.json
@@ -61,5 +61,6 @@
"js_iterations": "iterací",
"js_finished_reading": "Čtení dokončeno, pokračovat →",
"js_calculation_error": "Chyba výpočtu!",
- "js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:"
+ "js_calculation_error_msg": "Nepodařilo se vypočítat výzvu:",
+ "missing_required_forwarded_headers": "Chybějící požadované hlavičky X-Forwarded-*"
}
\ No newline at end of file
diff --git a/lib/localization/locales/de.json b/lib/localization/locales/de.json
index 670a5f84..cf47ae35 100644
--- a/lib/localization/locales/de.json
+++ b/lib/localization/locales/de.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Fertig gelesen, weiter zur Seite →",
"js_calculation_error": "Berechnung fehlgeschlagen!",
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:",
+ "missing_required_forwarded_headers": "Fehlende erforderliche X-Forwarded-* Header",
"simplified_explanation": "Dies ist eine Maßnahme gegen Bots und bösartige Anfragen, ähnlich einem CAPTCHA. Anstatt jedoch selbst arbeiten zu müssen, erhält Ihr Browser eine Berechnungsaufgabe, die er lösen muss, um sicherzustellen, dass es sich um einen gültigen Client handelt. Dieses Konzept wird als Proof of Work bezeichnet. Die Aufgabe wird in wenigen Sekunden berechnet und Sie erhalten Zugriff auf die Website. Vielen Dank für Ihr Verständnis und Ihre Geduld."
}
\ No newline at end of file
diff --git a/lib/localization/locales/en.json b/lib/localization/locales/en.json
index f3c8ef52..0280b975 100644
--- a/lib/localization/locales/en.json
+++ b/lib/localization/locales/en.json
@@ -36,6 +36,7 @@
"invalid_redirect": "Invalid redirect",
"redirect_not_parseable": "Redirect URL not parseable",
"redirect_domain_not_allowed": "Redirect domain not allowed",
+ "missing_required_forwarded_headers": "Missing required X-Forwarded-* headers",
"failed_to_sign_jwt": "failed to sign JWT",
"invalid_invocation": "Invalid invocation of MakeChallenge",
"client_error_browser": "Client Error: Please ensure your browser is up to date and try again later.",
diff --git a/lib/localization/locales/es.json b/lib/localization/locales/es.json
index 6fd4bd40..8beb3bf8 100644
--- a/lib/localization/locales/es.json
+++ b/lib/localization/locales/es.json
@@ -61,5 +61,6 @@
"js_finished_reading": "He terminado de leer, continuar →",
"js_calculation_error": "¡Error de cálculo!",
"js_calculation_error_msg": "Falló al calcular el desafío:",
+ "missing_required_forwarded_headers": "Faltan los encabezados X-Forwarded-* requeridos",
"simplified_explanation": "Esta es una medida contra bots y solicitudes maliciosas similar a un CAPTCHA. Sin embargo, en lugar de tener que hacer el trabajo usted mismo, a su navegador se le asigna una tarea de cálculo que debe resolver para garantizar que es un cliente válido. Este concepto se llama Prueba de trabajo. La tarea se calcula en unos segundos y se le concede acceso al sitio web. Gracias por su comprensión y paciencia."
}
\ No newline at end of file
diff --git a/lib/localization/locales/et.json b/lib/localization/locales/et.json
index cf283270..09125b9c 100644
--- a/lib/localization/locales/et.json
+++ b/lib/localization/locales/et.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Lugesin ära, edasi →",
"js_calculation_error": "Arvutamise viga!",
"js_calculation_error_msg": "Ei suutnud kontrolli arvutada:",
+ "missing_required_forwarded_headers": "Puuduvad nõutud X-Forwarded-* päised",
"simplified_explanation": "See on meede robotite ja pahatahtlike päringute vastu, mis sarnaneb CAPTCHA-le. Kuid selle asemel, et peaksite ise tööd tegema, antakse teie brauserile arvutusülesanne, mille see peab lahendama, et tagada selle kehtivus kliendina. Seda kontseptsiooni nimetatakse Töötõendiks. Ülesanne arvutatakse mõne sekundiga ja teile antakse juurdepääs veebisaidile. Täname teid mõistva suhtumise ja kannatlikkuse eest."
}
\ No newline at end of file
diff --git a/lib/localization/locales/fi.json b/lib/localization/locales/fi.json
index 25d3c094..bd06d3a7 100644
--- a/lib/localization/locales/fi.json
+++ b/lib/localization/locales/fi.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Luettu, jatka →",
"js_calculation_error": "Laskentavirhe!",
"js_calculation_error_msg": "Haasteen laskenta ei onnistunut:",
+ "missing_required_forwarded_headers": "Puuttuvat vaaditut X-Forwarded-* otsikot",
"simplified_explanation": "Tämä on toimenpide botteja ja haitallisia pyyntöjä vastaan, joka on samanlainen kuin CAPTCHA. Sen sijaan, että joutuisit tekemään työtä itse, selaimesi saa laskentatehtävän, joka sen on ratkaistava varmistaakseen, että se on kelvollinen asiakas. Tätä käsitettä kutsutaan nimellä Työtodistus. Tehtävä lasketaan muutamassa sekunnissa ja saat pääsyn verkkosivustolle. Kiitos ymmärryksestäsi ja kärsivällisyydestäsi."
}
\ No newline at end of file
diff --git a/lib/localization/locales/fil.json b/lib/localization/locales/fil.json
index ad24dc73..ab89d034 100644
--- a/lib/localization/locales/fil.json
+++ b/lib/localization/locales/fil.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Tapos na akong magbasa, magpatuloy →",
"js_calculation_error": "Error sa pagkalkula!",
"js_calculation_error_msg": "Nabigong ikalkula ang hamon:",
+ "missing_required_forwarded_headers": "Nawawala ang kinakailangang X-Forwarded-* na mga header",
"simplified_explanation": "Ito ay isang panukala laban sa mga bot at malisyosong mga kahilingan na katulad ng isang CAPTCHA. Gayunpaman, sa halip na ikaw mismo ang gumawa ng trabaho, binibigyan ang iyong browser ng isang gawain sa pagkalkula na kailangan nitong lutasin upang matiyak na ito ay isang wastong kliyente. Ang konseptong ito ay tinatawag na Proof of Work. Ang gawain ay kinakalkula sa loob ng ilang segundo at binibigyan ka ng access sa website. Salamat sa iyong pag-unawa at pasensya."
}
diff --git a/lib/localization/locales/fr.json b/lib/localization/locales/fr.json
index b8ac335e..b696e04c 100644
--- a/lib/localization/locales/fr.json
+++ b/lib/localization/locales/fr.json
@@ -61,5 +61,6 @@
"js_finished_reading": "J'ai fini de lire, continuer →",
"js_calculation_error": "Erreur de calcul !",
"js_calculation_error_msg": "Échec du calcul du défi :",
+ "missing_required_forwarded_headers": "En-têtes X-Forwarded-* requis manquants",
"simplified_explanation": "Il s'agit d'une mesure contre les robots et les requêtes malveillantes similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour s'assurer qu'il est un client valide. Ce concept s'appelle Preuve de travail. La tâche est calculée en quelques secondes et vous avez accès au site Web. Merci de votre compréhension et de votre patience."
}
\ No newline at end of file
diff --git a/lib/localization/locales/is.json b/lib/localization/locales/is.json
index 70b1bef0..b4f624b9 100644
--- a/lib/localization/locales/is.json
+++ b/lib/localization/locales/is.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Ég hef lokið lestrinum, höldum áfram →",
"js_calculation_error": "Reiknivilla!",
"js_calculation_error_msg": "Mistókst að reikna áskorun:",
+ "missing_required_forwarded_headers": "Vantar nauðsynleg X-Forwarded-* hausar",
"simplified_explanation": "Þetta er ráðstöfun gegn vélmennum og illgjarnum beiðnum svipað og CAPTCHA. Hins vegar, í stað þess að þurfa að vinna sjálfur, fær vafrinn þinn útreikningsverkefni sem hann þarf að leysa til að tryggja að hann sé gildur biðlari. Þetta hugtak er kallað Sönnun-á-vinnu. Verkefnið er reiknað á nokkrum sekúndum og þú færð aðgang að vefsíðunni. Takk fyrir skilninginn og þolinmæðina."
}
diff --git a/lib/localization/locales/it.json b/lib/localization/locales/it.json
index 97f32b55..e2ecc11b 100644
--- a/lib/localization/locales/it.json
+++ b/lib/localization/locales/it.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Ho finito di leggere, continua →",
"js_calculation_error": "Errore nel calcolo!",
"js_calculation_error_msg": "Impossibile superare il test:",
+ "missing_required_forwarded_headers": "Mancano gli header X-Forwarded-* richiesti",
"simplified_explanation": "Questa è una misura contro bot e richieste dannose simile a un CAPTCHA. Tuttavia, invece di dover lavorare tu stesso, al tuo browser viene assegnato un compito di calcolo che deve risolvere per garantire che sia un client valido. Questo concetto è chiamato Proof of Work. Il compito viene calcolato in pochi secondi e ti viene concesso l'accesso al sito web. Grazie per la tua comprensione e pazienza."
}
\ No newline at end of file
diff --git a/lib/localization/locales/ja.json b/lib/localization/locales/ja.json
index 751d9c20..3042173d 100644
--- a/lib/localization/locales/ja.json
+++ b/lib/localization/locales/ja.json
@@ -61,5 +61,6 @@
"js_finished_reading": "読み終わりました。続行 →",
"js_calculation_error": "計算エラー!",
"js_calculation_error_msg": "チャレンジの計算に失敗しました:",
+ "missing_required_forwarded_headers": "必要な X-Forwarded-* ヘッダーがありません",
"simplified_explanation": "これは、CAPTCHAと同様の、ボットや悪意のあるリクエストに対する対策です。ただし、自分で作業する代わりに、ブラウザに計算タスクが与えられ、それを解決して有効なクライアントであることを確認する必要があります。この概念はProof of Workと呼ばれます。タスクは数秒で計算され、ウェブサイトへのアクセスが許可されます。ご理解とご協力をお願いいたします。"
}
\ No newline at end of file
diff --git a/lib/localization/locales/nb.json b/lib/localization/locales/nb.json
index 10900468..0b8b6fcc 100644
--- a/lib/localization/locales/nb.json
+++ b/lib/localization/locales/nb.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Jeg har sluttet å lese, fortsett →",
"js_calculation_error": "Beregningsfeil!",
"js_calculation_error_msg": "Mislyktes i å beregne utfordring:",
+ "missing_required_forwarded_headers": "Mangler nødvendige X-Forwarded-* header",
"simplified_explanation": "Dette er et tiltak mot roboter og ondsinnede forespørsler som ligner på en CAPTCHA. Men i stedet for å måtte gjøre arbeidet selv, får nettleseren din en beregningsoppgave som den må løse for å sikre at den er en gyldig klient. Dette konseptet kalles Proof of Work. Oppgaven beregnes på noen få sekunder, og du får tilgang til nettstedet. Takk for din forståelse og tålmodighet."
}
\ No newline at end of file
diff --git a/lib/localization/locales/nl.json b/lib/localization/locales/nl.json
index 936d4b21..990a0122 100644
--- a/lib/localization/locales/nl.json
+++ b/lib/localization/locales/nl.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Ik ben klaar met lezen, ga verder →",
"js_calculation_error": "Rekenfout!",
"js_calculation_error_msg": "Uitdaging niet berekend:",
+ "missing_required_forwarded_headers": "Ontbrekende vereiste X-Forwarded-* headers",
"simplified_explanation": "Dit is een maatregel tegen bots en kwaadwillende verzoeken, vergelijkbaar met een CAPTCHA. In plaats van dat u zelf werk moet verrichten, krijgt uw browser een rekentaak die hij moet oplossen om ervoor te zorgen dat het een geldige client is. Dit concept wordt Proof of Work genoemd. De taak wordt in een paar seconden berekend en u krijgt toegang tot de website. Bedankt voor uw begrip en geduld."
}
diff --git a/lib/localization/locales/nn.json b/lib/localization/locales/nn.json
index a44040f8..d49396a8 100644
--- a/lib/localization/locales/nn.json
+++ b/lib/localization/locales/nn.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Eg har slutta å lesa, hald fram →",
"js_calculation_error": "Rekningsfeil!",
"js_calculation_error_msg": "Mislukkast i å rekne utfordring:",
+ "missing_required_forwarded_headers": "Manglende nødvendige X-Forwarded-* headers",
"simplified_explanation": "Dette er eit tiltak mot robotar og vondsinna førespurnader som liknar på ein CAPTCHA. Men i staden for å måtte gjere arbeidet sjølv, får nettlesaren din ei utrekningsoppgåve som han må løyse for å sikre at han er ein gyldig klient. Dette konseptet blir kalla Arbeidsbevis. Oppgåva blir rekna ut på nokre få sekund, og du får tilgang til nettstaden. Takk for di forståing og tålmod."
}
\ No newline at end of file
diff --git a/lib/localization/locales/pt-BR.json b/lib/localization/locales/pt-BR.json
index b1a5a37d..61769689 100644
--- a/lib/localization/locales/pt-BR.json
+++ b/lib/localization/locales/pt-BR.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Terminei de ler, continue →",
"js_calculation_error": "Erro de cálculo!",
"js_calculation_error_msg": "Falha ao calcular a validação:",
+ "missing_required_forwarded_headers": "Faltam os cabeçalhos X-Forwarded-* obrigatórios",
"simplified_explanation": "Esta é uma medida contra bots e solicitações maliciosas, semelhante a um CAPTCHA. No entanto, em vez de você mesmo ter que fazer o trabalho, seu navegador recebe uma tarefa de cálculo que ele deve resolver para garantir que seja um cliente válido. Esse conceito é chamado de Prova de Trabalho. A tarefa é calculada em poucos segundos e você tem acesso ao site. Obrigado pela sua compreensão e paciência."
}
diff --git a/lib/localization/locales/ru.json b/lib/localization/locales/ru.json
index aee253f0..632efd3d 100644
--- a/lib/localization/locales/ru.json
+++ b/lib/localization/locales/ru.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Я дочитал, продолжить →",
"js_calculation_error": "Ошибка расчёта!",
"js_calculation_error_msg": "Не удалось рассчитать задачу:",
+ "missing_required_forwarded_headers": "Отсутствуют требуемые заголовки X-Forwarded-*",
"simplified_explanation": "Это мера против ботов и вредоносных запросов, аналогичная CAPTCHA. Однако вместо того, чтобы вам приходилось работать самостоятельно, вашему браузеру дается задача вычисления, которую он должен решить, чтобы убедиться, что он является действительным клиентом. Эта концепция называется Доказательство выполнения работы. Задача рассчитывается за несколько секунд, и вам предоставляется доступ к веб-сайту. Спасибо за понимание и терпение."
}
\ No newline at end of file
diff --git a/lib/localization/locales/sv.json b/lib/localization/locales/sv.json
index 7de48cae..dbb18cca 100644
--- a/lib/localization/locales/sv.json
+++ b/lib/localization/locales/sv.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Jag har läst klart, fortsätt →",
"js_calculation_error": "Beräkningsfel!",
"js_calculation_error_msg": "Misslyckades att kalkylera utmaning:",
+ "missing_required_forwarded_headers": "Saknar nödvändiga X-Forwarded-* headers",
"simplified_explanation": "Detta är en åtgärd mot botar och skadliga förfrågningar som liknar en CAPTCHA. Men i stället för att du själv måste göra jobbet får din webbläsare en beräkningsuppgift som den måste lösa för att säkerställa att den är en giltig klient. Detta koncept kallas Arbetsbevis. Uppgiften beräknas på några sekunder och du beviljas tillgång till webbplatsen. Tack för din förståelse och ditt tålamod."
}
\ No newline at end of file
diff --git a/lib/localization/locales/tr.json b/lib/localization/locales/tr.json
index 0174b15f..79773b5b 100644
--- a/lib/localization/locales/tr.json
+++ b/lib/localization/locales/tr.json
@@ -61,5 +61,6 @@
"js_finished_reading": "Okumayı bitirdim, devam et →",
"js_calculation_error": "Hesaplama hatası!",
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:",
+ "missing_required_forwarded_headers": "Gerekli X-Forwarded-* başlıkları eksik",
"simplified_explanation": "Bu, botlara ve kötü niyetli isteklere karşı CAPTCHA'ya benzer bir önlemdir. Ancak, kendiniz çalışmak yerine, tarayıcınıza geçerli bir istemci olduğundan emin olmak için çözmesi gereken bir hesaplama görevi verilir. Bu kavrama İş Kanıtı denir. Görev birkaç saniye içinde hesaplanır ve web sitesine erişim hakkı kazanırsınız. Anlayışınız ve sabrınız için teşekkür ederiz."
}
diff --git a/lib/localization/locales/zh-CN.json b/lib/localization/locales/zh-CN.json
index 63364563..19529607 100644
--- a/lib/localization/locales/zh-CN.json
+++ b/lib/localization/locales/zh-CN.json
@@ -61,5 +61,6 @@
"js_finished_reading": "我读完了,继续 →",
"js_calculation_error": "计算错误!",
"js_calculation_error_msg": "计算挑战失败:",
+ "missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 头",
"simplified_explanation": "这是一种类似于验证码的措施,用于防止机器人和恶意请求。但是,您无需自己动手,您的浏览器会收到一个计算任务,必须解决该任务以确保它是有效的客户端。这个概念称为工作量证明。该任务在几秒钟内计算完毕,您将被授予访问网站的权限。感谢您的理解和耐心。"
}
diff --git a/lib/localization/locales/zh-TW.json b/lib/localization/locales/zh-TW.json
index 1e6584e0..4e7da25c 100644
--- a/lib/localization/locales/zh-TW.json
+++ b/lib/localization/locales/zh-TW.json
@@ -61,5 +61,6 @@
"js_finished_reading": "我讀完了,繼續 →",
"js_calculation_error": "計算錯誤!",
"js_calculation_error_msg": "計算挑戰失敗:",
+ "missing_required_forwarded_headers": "缺少必要的 X-Forwarded-* 標頭",
"simplified_explanation": "這是一種類似於驗證碼的措施,用於防止機器人和惡意請求。但是,您無需自己動手,您的瀏覽器會收到一個計算任務,必須解決該任務以確保它是有效的客戶端。這個概念稱為工作量證明。該任務在幾秒鐘內計算完畢,您將被授予訪問網站的權限。感謝您的理解和耐心。"
}
\ No newline at end of file
diff --git a/lib/policy/checker.go b/lib/policy/checker.go
index 5753e144..4f5ad556 100644
--- a/lib/policy/checker.go
+++ b/lib/policy/checker.go
@@ -102,6 +102,13 @@ func NewPathChecker(rexStr string) (checker.Impl, error) {
}
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
+ originalUrl := r.Header.Get("X-Original-URI")
+ if originalUrl != "" {
+ if pc.regexp.MatchString(originalUrl) {
+ return true, nil
+ }
+ }
+
if pc.regexp.MatchString(r.URL.Path) {
return true, nil
}
diff --git a/lib/policy/checker_test.go b/lib/policy/checker_test.go
index 6109babe..6cc3e0d7 100644
--- a/lib/policy/checker_test.go
+++ b/lib/policy/checker_test.go
@@ -198,3 +198,96 @@ func TestHeaderExistsChecker(t *testing.T) {
})
}
}
+
+func TestPathChecker_XOriginalURI(t *testing.T) {
+ tests := []struct {
+ name string
+ regex string
+ xOriginalURI string
+ urlPath string
+ headerKey string
+ expectedMatch bool
+ expectError bool
+ }{
+ {
+ name: "X-Original-URI matches regex (with trailing space - current typo)",
+ regex: "^/api/.*",
+ xOriginalURI: "/api/users",
+ urlPath: "/different/path",
+ headerKey: "X-Original-URI",
+ expectedMatch: true,
+ expectError: false,
+ },
+ {
+ name: "X-Original-URI doesn't match, falls back to URL.Path",
+ regex: "^/admin/.*",
+ xOriginalURI: "/api/users",
+ urlPath: "/admin/dashboard",
+ headerKey: "X-Original-URI",
+ expectedMatch: true,
+ expectError: false,
+ },
+ {
+ name: "Neither X-Original-URI nor URL.Path match",
+ regex: "^/admin/.*",
+ xOriginalURI: "/api/users",
+ urlPath: "/public/info",
+ headerKey: "X-Original-URI ",
+ expectedMatch: false,
+ expectError: false,
+ },
+ {
+ name: "Empty X-Original-URI, URL.Path matches",
+ regex: "^/static/.*",
+ xOriginalURI: "",
+ urlPath: "/static/css/style.css",
+ headerKey: "X-Original-URI",
+ expectedMatch: true,
+ expectError: false,
+ },
+ {
+ name: "Complex regex matching X-Original-URI",
+ regex: `^/api/v[0-9]+/(users|posts)/[0-9]+$`,
+ xOriginalURI: "/api/v1/users/123",
+ urlPath: "/different",
+ headerKey: "X-Original-URI",
+ expectedMatch: true,
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Create the PathChecker
+ pc, err := NewPathChecker(tt.regex)
+ if err != nil {
+ if !tt.expectError {
+ t.Fatalf("NewPathChecker() unexpected error: %v", err)
+ }
+ return
+ }
+
+ if tt.expectError {
+ t.Fatal("NewPathChecker() expected error but got none")
+ }
+
+ req, err := http.NewRequest("GET", "http://example.com"+tt.urlPath, nil)
+ if err != nil {
+ t.Fatalf("Failed to create request: %v", err)
+ }
+
+ if tt.xOriginalURI != "" {
+ req.Header.Set(tt.headerKey, tt.xOriginalURI)
+ }
+
+ match, err := pc.Check(req)
+ if err != nil {
+ t.Fatalf("Check() unexpected error: %v", err)
+ }
+
+ if match != tt.expectedMatch {
+ t.Errorf("Check() = %v, want %v", match, tt.expectedMatch)
+ }
+ })
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 9e47ed0a..e6d79b87 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,21 +1,21 @@
{
"name": "@techaro/anubis",
- "version": "1.21.3",
+ "version": "1.22.0-pre1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@techaro/anubis",
- "version": "1.21.3",
+ "version": "1.22.0-pre1",
"license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
"preact": "^10.27.1"
},
"devDependencies": {
- "cssnano": "^7.1.0",
- "cssnano-preset-advanced": "^7.0.8",
- "esbuild": "^0.25.8",
+ "cssnano": "^7.1.1",
+ "cssnano-preset-advanced": "^7.0.9",
+ "esbuild": "^0.25.9",
"playwright": "^1.52.0",
"postcss-cli": "^11.0.1",
"postcss-import": "^16.1.1",
@@ -62,9 +62,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
- "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
+ "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
"cpu": [
"ppc64"
],
@@ -79,9 +79,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
- "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
+ "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
"cpu": [
"arm"
],
@@ -96,9 +96,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
- "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
+ "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
"cpu": [
"arm64"
],
@@ -113,9 +113,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
- "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
+ "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
"cpu": [
"x64"
],
@@ -130,9 +130,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
- "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
+ "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
"cpu": [
"arm64"
],
@@ -147,9 +147,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
- "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
+ "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
"cpu": [
"x64"
],
@@ -164,9 +164,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
- "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
"cpu": [
"arm64"
],
@@ -181,9 +181,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
- "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
+ "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
"cpu": [
"x64"
],
@@ -198,9 +198,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
- "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
+ "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
"cpu": [
"arm"
],
@@ -215,9 +215,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
- "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
+ "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
"cpu": [
"arm64"
],
@@ -232,9 +232,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
- "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
+ "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
"cpu": [
"ia32"
],
@@ -249,9 +249,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
- "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
+ "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
"cpu": [
"loong64"
],
@@ -266,9 +266,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
- "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
+ "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
"cpu": [
"mips64el"
],
@@ -283,9 +283,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
- "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
+ "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
"cpu": [
"ppc64"
],
@@ -300,9 +300,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
- "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
+ "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
"cpu": [
"riscv64"
],
@@ -317,9 +317,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
- "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
+ "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
"cpu": [
"s390x"
],
@@ -334,9 +334,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
- "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
+ "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
"cpu": [
"x64"
],
@@ -351,9 +351,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
- "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
"cpu": [
"arm64"
],
@@ -368,9 +368,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
- "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
"cpu": [
"x64"
],
@@ -385,9 +385,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
- "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
+ "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
"cpu": [
"arm64"
],
@@ -402,9 +402,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
- "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
+ "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
"cpu": [
"x64"
],
@@ -419,9 +419,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
- "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
+ "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
"cpu": [
"arm64"
],
@@ -436,9 +436,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
- "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
+ "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
"cpu": [
"x64"
],
@@ -453,9 +453,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
- "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
+ "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
"cpu": [
"arm64"
],
@@ -470,9 +470,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
- "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
+ "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
"cpu": [
"ia32"
],
@@ -487,9 +487,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
- "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
+ "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
"cpu": [
"x64"
],
@@ -904,13 +904,13 @@
}
},
"node_modules/cssnano": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz",
- "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
+ "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cssnano-preset-default": "^7.0.8",
+ "cssnano-preset-default": "^7.0.9",
"lilconfig": "^3.1.3"
},
"engines": {
@@ -925,15 +925,15 @@
}
},
"node_modules/cssnano-preset-advanced": {
- "version": "7.0.8",
- "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-7.0.8.tgz",
- "integrity": "sha512-KYw7gH8xmIzTwHefuM/m3lkMz4jn5EbjxZO2RHVsOGvrXCxSjbc0/f/gELWW9ZIgbQdJMCkijEo76gBYGY4S3Q==",
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-7.0.9.tgz",
+ "integrity": "sha512-nczUlcRbMuupYDIjlYTQ6nqWh2AjV1omdf5OkUEKpOqPuRb8CZp/KgpZjD2eelX/sfFh6V/aybfgu7lVJH7Z/w==",
"dev": true,
"license": "MIT",
"dependencies": {
"autoprefixer": "^10.4.21",
"browserslist": "^4.25.1",
- "cssnano-preset-default": "^7.0.8",
+ "cssnano-preset-default": "^7.0.9",
"postcss-discard-unused": "^7.0.4",
"postcss-merge-idents": "^7.0.1",
"postcss-reduce-idents": "^7.0.1",
@@ -947,9 +947,9 @@
}
},
"node_modules/cssnano-preset-default": {
- "version": "7.0.8",
- "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz",
- "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==",
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz",
+ "integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -958,7 +958,7 @@
"cssnano-utils": "^5.0.1",
"postcss-calc": "^10.1.1",
"postcss-colormin": "^7.0.4",
- "postcss-convert-values": "^7.0.6",
+ "postcss-convert-values": "^7.0.7",
"postcss-discard-comments": "^7.0.4",
"postcss-discard-duplicates": "^7.0.2",
"postcss-discard-empty": "^7.0.1",
@@ -1144,9 +1144,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.25.8",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
- "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
+ "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1157,32 +1157,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.8",
- "@esbuild/android-arm": "0.25.8",
- "@esbuild/android-arm64": "0.25.8",
- "@esbuild/android-x64": "0.25.8",
- "@esbuild/darwin-arm64": "0.25.8",
- "@esbuild/darwin-x64": "0.25.8",
- "@esbuild/freebsd-arm64": "0.25.8",
- "@esbuild/freebsd-x64": "0.25.8",
- "@esbuild/linux-arm": "0.25.8",
- "@esbuild/linux-arm64": "0.25.8",
- "@esbuild/linux-ia32": "0.25.8",
- "@esbuild/linux-loong64": "0.25.8",
- "@esbuild/linux-mips64el": "0.25.8",
- "@esbuild/linux-ppc64": "0.25.8",
- "@esbuild/linux-riscv64": "0.25.8",
- "@esbuild/linux-s390x": "0.25.8",
- "@esbuild/linux-x64": "0.25.8",
- "@esbuild/netbsd-arm64": "0.25.8",
- "@esbuild/netbsd-x64": "0.25.8",
- "@esbuild/openbsd-arm64": "0.25.8",
- "@esbuild/openbsd-x64": "0.25.8",
- "@esbuild/openharmony-arm64": "0.25.8",
- "@esbuild/sunos-x64": "0.25.8",
- "@esbuild/win32-arm64": "0.25.8",
- "@esbuild/win32-ia32": "0.25.8",
- "@esbuild/win32-x64": "0.25.8"
+ "@esbuild/aix-ppc64": "0.25.9",
+ "@esbuild/android-arm": "0.25.9",
+ "@esbuild/android-arm64": "0.25.9",
+ "@esbuild/android-x64": "0.25.9",
+ "@esbuild/darwin-arm64": "0.25.9",
+ "@esbuild/darwin-x64": "0.25.9",
+ "@esbuild/freebsd-arm64": "0.25.9",
+ "@esbuild/freebsd-x64": "0.25.9",
+ "@esbuild/linux-arm": "0.25.9",
+ "@esbuild/linux-arm64": "0.25.9",
+ "@esbuild/linux-ia32": "0.25.9",
+ "@esbuild/linux-loong64": "0.25.9",
+ "@esbuild/linux-mips64el": "0.25.9",
+ "@esbuild/linux-ppc64": "0.25.9",
+ "@esbuild/linux-riscv64": "0.25.9",
+ "@esbuild/linux-s390x": "0.25.9",
+ "@esbuild/linux-x64": "0.25.9",
+ "@esbuild/netbsd-arm64": "0.25.9",
+ "@esbuild/netbsd-x64": "0.25.9",
+ "@esbuild/openbsd-arm64": "0.25.9",
+ "@esbuild/openbsd-x64": "0.25.9",
+ "@esbuild/openharmony-arm64": "0.25.9",
+ "@esbuild/sunos-x64": "0.25.9",
+ "@esbuild/win32-arm64": "0.25.9",
+ "@esbuild/win32-ia32": "0.25.9",
+ "@esbuild/win32-x64": "0.25.9"
}
},
"node_modules/escalade": {
@@ -1719,9 +1719,9 @@
}
},
"node_modules/postcss-convert-values": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz",
- "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==",
+ "version": "7.0.7",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz",
+ "integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 1e0576c0..6b6aff56 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@techaro/anubis",
- "version": "1.21.3",
+ "version": "1.22.0-pre1",
"description": "",
"main": "index.js",
"scripts": {
@@ -18,9 +18,9 @@
"author": "",
"license": "ISC",
"devDependencies": {
- "cssnano": "^7.1.0",
- "cssnano-preset-advanced": "^7.0.8",
- "esbuild": "^0.25.8",
+ "cssnano": "^7.1.1",
+ "cssnano-preset-advanced": "^7.0.9",
+ "esbuild": "^0.25.9",
"playwright": "^1.52.0",
"postcss-cli": "^11.0.1",
"postcss-import": "^16.1.1",
@@ -31,4 +31,4 @@
"@aws-crypto/sha256-js": "^5.2.0",
"preact": "^10.27.1"
}
-}
+}
\ No newline at end of file
diff --git a/web/index.templ b/web/index.templ
index 65d704ca..54b368cc 100644
--- a/web/index.templ
+++ b/web/index.templ
@@ -60,6 +60,7 @@ templ base(title string, body templ.Component, impressum *config.Impressum, chal
@templ.JSONScript("anubis_version", anubis.Version)
@templ.JSONScript("anubis_challenge", challenge)
@templ.JSONScript("anubis_base_prefix", anubis.BasePrefix)
+ @templ.JSONScript("anubis_public_url", anubis.PublicUrl)
diff --git a/web/index_templ.go b/web/index_templ.go
index ee72ec17..13f17e42 100644
--- a/web/index_templ.go
+++ b/web/index_templ.go
@@ -128,6 +128,10 @@ func base(title string, body templ.Component, impressum *config.Impressum, chall
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+ templ_7745c5c3_Err = templ.JSONScript("anubis_public_url", anubis.PublicUrl).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "