format: run prettier tree-wide

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso
2026-02-15 11:53:39 +00:00
parent 7075f74764
commit 5e2bff8397
157 changed files with 1741 additions and 1621 deletions
+1 -3
View File
@@ -2,9 +2,7 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
"name": "Dev",
"dockerComposeFile": [
"./docker-compose.yaml"
],
"dockerComposeFile": ["./docker-compose.yaml"],
"service": "workspace",
"workspaceFolder": "/workspace/anubis",
"postStartCommand": "bash ./.devcontainer/poststart.sh",
-1
View File
@@ -58,4 +58,3 @@ body:
attributes:
label: Additional context
description: Add any other context about the problem here.
+1 -1
View File
@@ -1,6 +1,6 @@
name: Feature request
description: Suggest an idea for this project
title: '[Feature request] '
title: "[Feature request] "
body:
- type: textarea
+12 -12
View File
@@ -1,17 +1,17 @@
# check-spelling/check-spelling configuration
File | Purpose | Format | Info
-|-|-|-
[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)
[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes)
[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)
| File | Purpose | Format | Info |
| -------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [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) |
| [excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes) |
| [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.
+8 -11
View File
@@ -2,30 +2,27 @@
<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).
* well-formed pattern.
- well-formed pattern.
If you can write a [pattern](
https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
) that would match it,
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.
+2 -2
View File
@@ -24,10 +24,10 @@ jobs:
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24.11.0'
node-version: "24.11.0"
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- name: install node deps
run: |
+2 -2
View File
@@ -28,10 +28,10 @@ jobs:
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24.11.0'
node-version: "24.11.0"
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
+2 -2
View File
@@ -38,10 +38,10 @@ jobs:
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24.11.0'
node-version: "24.11.0"
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- name: Check go.mod and go.sum in main directory
run: |
+2 -2
View File
@@ -26,10 +26,10 @@ jobs:
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24.11.0'
node-version: "24.11.0"
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- name: Cache playwright binaries
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
+2 -2
View File
@@ -27,10 +27,10 @@ jobs:
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24.11.0'
node-version: "24.11.0"
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- name: install node deps
run: |
@@ -28,10 +28,10 @@ jobs:
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24.11.0'
node-version: "24.11.0"
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- name: install node deps
run: |
+6 -6
View File
@@ -59,16 +59,16 @@ name: Check Spelling
on:
push:
branches:
- '**'
- "**"
tags-ignore:
- '**'
- "**"
pull_request:
branches:
- '**'
- "**"
types:
- 'opened'
- 'reopened'
- 'synchronize'
- "opened"
- "reopened"
- "synchronize"
jobs:
spelling:
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
with:
go-version: '1.25.4'
go-version: "1.25.4"
- name: Run CI
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
+2 -2
View File
@@ -3,10 +3,10 @@ name: zizmor
on:
push:
paths:
- '.github/workflows/*.ya?ml'
- ".github/workflows/*.ya?ml"
pull_request:
paths:
- '.github/workflows/*.ya?ml'
- ".github/workflows/*.ya?ml"
jobs:
zizmor:
+4 -4
View File
@@ -1,11 +1,11 @@
defaultBaseImage: cgr.dev/chainguard/static
defaultPlatforms:
- linux/arm64
- linux/amd64
- linux/arm/v7
- linux/arm64
- linux/amd64
- linux/arm/v7
builds:
- id: anubis
- id: anubis
main: ./cmd/anubis
ldflags:
- -s -w
+1 -4
View File
@@ -4,7 +4,4 @@
user_agent_regex: MistralAI-User/.+; \+https\://docs\.mistral\.ai/robots
action: ALLOW
# https://mistral.ai/mistralai-user-ips.json
remote_addresses: [
"20.240.160.161/32",
"20.240.160.1/32",
]
remote_addresses: ["20.240.160.161/32", "20.240.160.1/32"]
+2 -1
View File
@@ -5,7 +5,8 @@
action: ALLOW
# https://openai.com/chatgpt-user.json
# curl 'https://openai.com/chatgpt-user.json' | jq '.prefixes.[].ipv4Prefix' | sed 's/$/,/'
remote_addresses: [
remote_addresses:
[
"13.65.138.112/28",
"23.98.179.16/28",
"13.65.138.96/28",
+2 -6
View File
@@ -4,9 +4,5 @@
user_agent_regex: Perplexity-User/.+; \+https\://perplexity\.ai/perplexity-user
action: ALLOW
# https://www.perplexity.com/perplexity-user.json
remote_addresses: [
"44.208.221.197/32",
"34.193.163.52/32",
"18.97.21.0/30",
"18.97.43.80/29",
]
remote_addresses:
["44.208.221.197/32", "34.193.163.52/32", "18.97.21.0/30", "18.97.43.80/29"]
+2 -1
View File
@@ -4,7 +4,8 @@
user_agent_regex: Applebot
action: ALLOW
# https://search.developer.apple.com/applebot.json
remote_addresses: [
remote_addresses:
[
"17.241.208.160/27",
"17.241.193.160/27",
"17.241.200.160/27",
+3 -2
View File
@@ -2,7 +2,8 @@
user_agent_regex: \+http\://www\.bing\.com/bingbot\.htm
action: ALLOW
# https://www.bing.com/toolbox/bingbot.json
remote_addresses: [
remote_addresses:
[
"157.55.39.0/24",
"207.46.13.0/24",
"40.77.167.0/24",
@@ -30,5 +31,5 @@
"20.74.197.0/28",
"20.15.133.160/27",
"40.77.177.0/24",
"40.77.178.0/23"
"40.77.178.0/23",
]
+3 -2
View File
@@ -2,7 +2,8 @@
user_agent_regex: DuckDuckBot/1\.1; \(\+http\://duckduckgo\.com/duckduckbot\.html\)
action: ALLOW
# https://duckduckgo.com/duckduckgo-help-pages/results/duckduckbot
remote_addresses: [
remote_addresses:
[
"57.152.72.128/32",
"51.8.253.152/32",
"40.80.242.63/32",
@@ -271,5 +272,5 @@
"4.213.46.14/32",
"172.169.17.165/32",
"51.8.71.117/32",
"20.3.1.178/32"
"20.3.1.178/32",
]
+3 -2
View File
@@ -2,7 +2,8 @@
user_agent_regex: \+http\://www\.google\.com/bot\.html
action: ALLOW
# https://developers.google.com/static/search/apis/ipranges/googlebot.json
remote_addresses: [
remote_addresses:
[
"2001:4860:4801:10::/64",
"2001:4860:4801:11::/64",
"2001:4860:4801:12::/64",
@@ -259,5 +260,5 @@
"66.249.79.224/27",
"66.249.79.32/27",
"66.249.79.64/27",
"66.249.79.96/27"
"66.249.79.96/27",
]
+1 -5
View File
@@ -1,8 +1,4 @@
- name: internet-archive
action: ALLOW
# https://ipinfo.io/AS7941
remote_addresses: [
"207.241.224.0/20",
"208.70.24.0/21",
"2620:0:9c0::/48"
]
remote_addresses: ["207.241.224.0/20", "208.70.24.0/21", "2620:0:9c0::/48"]
+3 -2
View File
@@ -2,9 +2,10 @@
user_agent_regex: \+https\://kagi\.com/bot
action: ALLOW
# https://kagi.com/bot
remote_addresses: [
remote_addresses:
[
"216.18.205.234/32",
"35.212.27.76/32",
"104.254.65.50/32",
"209.151.156.194/32"
"209.151.156.194/32",
]
+3 -2
View File
@@ -2,10 +2,11 @@
user_agent_regex: search\.marginalia\.nu
action: ALLOW
# Received directly over email
remote_addresses: [
remote_addresses:
[
"193.183.0.162/31",
"193.183.0.164/30",
"193.183.0.168/30",
"193.183.0.172/31",
"193.183.0.174/32"
"193.183.0.174/32",
]
+1 -1
View File
@@ -2,4 +2,4 @@
user_agent_regex: \+https\://www\.mojeek\.com/bot\.html
action: ALLOW
# https://www.mojeek.com/bot.html
remote_addresses: [ "5.102.173.71/32" ]
remote_addresses: ["5.102.173.71/32"]
+2 -1
View File
@@ -4,7 +4,8 @@
user_agent_regex: GPTBot/1\.1; \+https\://openai\.com/gptbot
action: ALLOW
# https://openai.com/gptbot.json
remote_addresses: [
remote_addresses:
[
"52.230.152.0/24",
"20.171.206.0/24",
"20.171.207.0/24",
+3 -2
View File
@@ -4,10 +4,11 @@
user_agent_regex: OAI-SearchBot/1\.0; \+https\://openai\.com/searchbot
action: ALLOW
# https://openai.com/searchbot.json
remote_addresses: [
remote_addresses:
[
"20.42.10.176/28",
"172.203.190.128/28",
"104.210.140.128/28",
"51.8.102.0/24",
"135.234.64.0/24"
"135.234.64.0/24",
]
+2 -1
View File
@@ -4,7 +4,8 @@
user_agent_regex: PerplexityBot/.+; \+https\://perplexity\.ai/perplexitybot
action: ALLOW
# https://www.perplexity.com/perplexitybot.json
remote_addresses: [
remote_addresses:
[
"107.20.236.150/32",
"3.224.62.45/32",
"18.210.92.235/32",
+1 -1
View File
@@ -2,4 +2,4 @@
user_agent_regex: \+https\://help\.qwant\.com/bot/
action: ALLOW
# https://help.qwant.com/wp-content/uploads/sites/2/2025/01/qwantbot.json
remote_addresses: [ "91.242.162.0/24" ]
remote_addresses: ["91.242.162.0/24"]
+2 -1
View File
@@ -2,7 +2,8 @@
user_agent_regex: UptimeRobot
action: ALLOW
# https://api.uptimerobot.com/meta/ips
remote_addresses: [
remote_addresses:
[
"3.12.251.153/32",
"3.20.63.178/32",
"3.77.67.4/32",
@@ -1,14 +1,16 @@
import React, { useState, useEffect, useMemo } from 'react';
import styles from './styles.module.css';
import React, { useState, useEffect, useMemo } from "react";
import styles from "./styles.module.css";
// A helper function to perform SHA-256 hashing.
// It takes a string, encodes it, hashes it, and returns a hex string.
async function sha256(message) {
try {
const msgBuffer = new TextEncoder().encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
return hashHex;
} catch (error) {
console.error("Hashing failed:", error);
@@ -21,21 +23,42 @@ const generateRandomHex = (bytes = 16) => {
const buffer = new Uint8Array(bytes);
crypto.getRandomValues(buffer);
return Array.from(buffer)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
};
// Icon components for better visual feedback
const CheckIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconGreen} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.iconGreen}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
);
const XCircleIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconRed} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.iconRed}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
);
@@ -46,7 +69,7 @@ export default function App() {
// State for the nonce, which is the variable we can change
const [nonce, setNonce] = useState(0);
// State to store the resulting hash
const [hash, setHash] = useState('');
const [hash, setHash] = useState("");
// A flag to indicate if the current hash is the "winning" one
const [isMining, setIsMining] = useState(false);
const [isFound, setIsFound] = useState(false);
@@ -55,7 +78,10 @@ export default function App() {
const difficulty = "00";
// Memoize the combined data to avoid recalculating on every render
const combinedData = useMemo(() => `${challenge}${nonce}`, [challenge, nonce]);
const combinedData = useMemo(
() => `${challenge}${nonce}`,
[challenge, nonce],
);
// This effect hook recalculates the hash whenever the combinedData changes.
useEffect(() => {
@@ -68,7 +94,9 @@ export default function App() {
}
};
calculateHash();
return () => { isMounted = false; };
return () => {
isMounted = false;
};
}, [combinedData, difficulty]);
// This effect handles the automatic mining process
@@ -93,7 +121,7 @@ export default function App() {
// Update the UI periodically to avoid freezing the browser
if (miningNonce % 100 === 0) {
setNonce(miningNonce);
await new Promise(resolve => setTimeout(resolve, 0)); // Yield to the browser
await new Promise((resolve) => setTimeout(resolve, 0)); // Yield to the browser
}
}
};
@@ -102,28 +130,27 @@ export default function App() {
return () => {
continueMining = false;
}
};
}, [isMining, challenge, nonce, difficulty]);
const handleMineClick = () => {
setIsMining(true);
}
};
const handleStopClick = () => {
setIsMining(false);
}
};
const handleResetClick = () => {
setIsMining(false);
setNonce(0);
}
};
const handleNewChallengeClick = () => {
setIsMining(false);
setChallenge(generateRandomHex(16));
setNonce(0);
}
};
// Helper to render the hash with colored leading characters
const renderHash = () => {
@@ -153,12 +180,46 @@ export default function App() {
<div className={styles.block}>
<h2 className={styles.blockTitle}>2. Nonce</h2>
<div className={styles.nonceControls}>
<button onClick={() => setNonce(n => n - 1)} disabled={isMining} className={styles.nonceButton}>
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconSmall} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" /></svg>
<button
onClick={() => setNonce((n) => n - 1)}
disabled={isMining}
className={styles.nonceButton}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.iconSmall}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20 12H4"
/>
</svg>
</button>
<span className={styles.nonceValue}>{nonce}</span>
<button onClick={() => setNonce(n => n + 1)} disabled={isMining} className={styles.nonceButton}>
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconSmall} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
<button
onClick={() => setNonce((n) => n + 1)}
disabled={isMining}
className={styles.nonceButton}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.iconSmall}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
</button>
</div>
</div>
@@ -172,13 +233,26 @@ export default function App() {
{/* Arrow pointing down */}
<div className={styles.arrowContainer}>
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconGray} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
<svg
xmlns="http://www.w3.org/2000/svg"
className={styles.iconGray}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 14l-7 7m0 0l-7-7m7 7V3"
/>
</svg>
</div>
{/* Hash Output Block */}
<div className={`${styles.hashContainer} ${isFound ? styles.hashContainerSuccess : styles.hashContainerError}`}>
<div
className={`${styles.hashContainer} ${isFound ? styles.hashContainerSuccess : styles.hashContainerError}`}
>
<div className={styles.hashContent}>
<div className={styles.hashText}>
<h2 className={styles.blockTitle}>4. Resulting Hash (SHA-256)</h2>
@@ -193,18 +267,30 @@ export default function App() {
{/* Mining Controls */}
<div className={styles.buttonContainer}>
{!isMining ? (
<button onClick={handleMineClick} className={`${styles.button} ${styles.buttonCyan}`}>
<button
onClick={handleMineClick}
className={`${styles.button} ${styles.buttonCyan}`}
>
Auto-Mine
</button>
) : (
<button onClick={handleStopClick} className={`${styles.button} ${styles.buttonYellow}`}>
<button
onClick={handleStopClick}
className={`${styles.button} ${styles.buttonYellow}`}
>
Stop Mining
</button>
)}
<button onClick={handleNewChallengeClick} className={`${styles.button} ${styles.buttonIndigo}`}>
<button
onClick={handleNewChallengeClick}
className={`${styles.button} ${styles.buttonIndigo}`}
>
New Challenge
</button>
<button onClick={handleResetClick} className={`${styles.button} ${styles.buttonGray}`}>
<button
onClick={handleResetClick}
className={`${styles.button} ${styles.buttonGray}`}
>
Reset Nonce
</button>
</div>
@@ -48,7 +48,9 @@
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);
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;
@@ -158,7 +160,9 @@
.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);
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;
}
@@ -244,7 +244,7 @@ function regexSafe(input: string): string;
`regexSafe` takes a string and escapes it for safe use inside of a regular expression. This is useful when you are creating regular expressions from headers or variables such as `remoteAddress`.
| Input | Output |
| :------------------------ | :------------------------------ |
| :------------------------- | :-------------- |
| `regexSafe("1.2.3.4")` | `1\\.2\\.3\\.4` |
| `regexSafe("techaro.lol")` | `techaro\\.lol` |
| `regexSafe("star*")` | `star\\*` |
@@ -302,7 +302,7 @@ function arpaReverseIP(ip: string): string;
`arpaReverseIP` takes an IP address and returns its value in [ARPA notation](https://www.ietf.org/rfc/rfc2317.html). This can be useful when matching PTR record patterns.
| Input | Output |
| :----------------------------- | :------------------------------------------------------------------- |
| :----------------------------- | :---------------------------------------------------------------- |
| `arpaReverseIP("1.2.3.4")` | `4.3.2.1` |
| `arpaReverseIP("2001:db8::1")` | `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2` |
@@ -94,10 +94,8 @@ containers:
- ALL
seccompProfile:
type: RuntimeDefault
```
Then add a Service entry for Anubis:
```yaml
@@ -1,8 +1,2 @@
# /etc/nginx/conf-anubis.inc
# Forward to anubis
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://anubis;
}
# /etc/nginx/conf-anubis.inc # Forward to anubis location / { proxy_set_header
Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://anubis; }
+1 -1
View File
@@ -75,7 +75,7 @@ services:
# Telling Anubis, where to listen for Traefik
- BIND=:8080
# Telling Anubis to do redirect — ensure there is a space after '='
- 'TARGET= '
- "TARGET= "
# Specifies which domains Anubis is allowed to redirect to.
- REDIRECT_DOMAINS=example.com
# Should be the full external URL for Anubis (including scheme)
+1 -1
View File
@@ -67,7 +67,7 @@ Currently the following settings are configurable via the policy file:
Anubis uses these environment variables for configuration:
| Environment Variable | Default value | Explanation |
|:-------------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| :----------------------------- | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ASSET_LOOKUP_HEADER` | unset | <EO /> If set, use the contents of this header in requests when looking up custom assets in `OVERLAY_FOLDER`. See [Header-based overlay dispatch](./botstopper.mdx#header-based-overlay-dispatch) for more details. |
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
+61 -61
View File
@@ -1,62 +1,62 @@
import { themes as prismThemes } from 'prism-react-renderer';
import type { Config } from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
import { themes as prismThemes } from "prism-react-renderer";
import type { Config } from "@docusaurus/types";
import type * as Preset from "@docusaurus/preset-classic";
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
const config: Config = {
title: 'Anubis',
tagline: 'Weigh the soul of incoming HTTP requests to protect your website!',
favicon: 'img/favicon.ico',
title: "Anubis",
tagline: "Weigh the soul of incoming HTTP requests to protect your website!",
favicon: "img/favicon.ico",
// Set the production url of your site here
url: 'https://anubis.techaro.lol',
url: "https://anubis.techaro.lol",
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
baseUrl: "/",
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'TecharoHQ', // Usually your GitHub org/user name.
projectName: 'anubis', // Usually your repo name.
organizationName: "TecharoHQ", // Usually your GitHub org/user name.
projectName: "anubis", // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
defaultLocale: "en",
locales: ["en"],
},
markdown: {
mermaid: true,
},
themes: ['@docusaurus/theme-mermaid'],
themes: ["@docusaurus/theme-mermaid"],
presets: [
[
'classic',
"classic",
{
blog: {
showReadingTime: true,
feedOptions: {
type: ['rss', 'atom', "json"],
type: ["rss", "atom", "json"],
xslt: true,
},
editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/',
onInlineTags: 'warn',
onInlineAuthors: 'warn',
onUntruncatedBlogPosts: 'throw',
editUrl: "https://github.com/TecharoHQ/anubis/tree/main/docs/",
onInlineTags: "warn",
onInlineAuthors: "warn",
onUntruncatedBlogPosts: "throw",
},
docs: {
sidebarPath: './sidebars.ts',
editUrl: 'https://github.com/TecharoHQ/anubis/tree/main/docs/',
sidebarPath: "./sidebars.ts",
editUrl: "https://github.com/TecharoHQ/anubis/tree/main/docs/",
},
theme: {
customCss: './src/css/custom.css',
customCss: "./src/css/custom.css",
},
} satisfies Preset.Options,
],
@@ -67,47 +67,47 @@ const config: Config = {
respectPrefersColorScheme: true,
},
// Replace with your project's social card
image: 'img/social-card.jpg',
image: "img/social-card.jpg",
navbar: {
title: 'Anubis',
title: "Anubis",
logo: {
alt: 'A happy jackal woman with brown hair and red eyes',
src: 'img/favicon.webp',
alt: "A happy jackal woman with brown hair and red eyes",
src: "img/favicon.webp",
},
items: [
{ to: '/blog', label: 'Blog', position: 'left' },
{ to: "/blog", label: "Blog", position: "left" },
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Docs',
type: "docSidebar",
sidebarId: "tutorialSidebar",
position: "left",
label: "Docs",
},
{
to: '/docs/admin/botstopper',
to: "/docs/admin/botstopper",
label: "Unbranded Version",
position: "left"
position: "left",
},
{
href: 'https://github.com/TecharoHQ/anubis',
label: 'GitHub',
position: 'right',
href: "https://github.com/TecharoHQ/anubis",
label: "GitHub",
position: "right",
},
{
href: 'https://github.com/sponsors/Xe',
href: "https://github.com/sponsors/Xe",
label: "Sponsor the Project",
position: 'right'
position: "right",
},
],
},
footer: {
style: 'dark',
style: "dark",
links: [
{
title: 'Docs',
title: "Docs",
items: [
{
label: 'Intro',
to: '/docs/',
label: "Intro",
to: "/docs/",
},
{
label: "Installation",
@@ -116,32 +116,32 @@ const config: Config = {
],
},
{
title: 'Community',
title: "Community",
items: [
{
label: 'GitHub Discussions',
href: 'https://github.com/TecharoHQ/anubis/discussions',
label: "GitHub Discussions",
href: "https://github.com/TecharoHQ/anubis/discussions",
},
{
label: 'Bluesky',
href: 'https://bsky.app/profile/techaro.lol',
label: "Bluesky",
href: "https://bsky.app/profile/techaro.lol",
},
],
},
{
title: 'More',
title: "More",
items: [
{
label: 'Blog',
to: '/blog',
label: "Blog",
to: "/blog",
},
{
label: 'GitHub',
href: 'https://github.com/TecharoHQ/anubis',
label: "GitHub",
href: "https://github.com/TecharoHQ/anubis",
},
{
label: 'Status',
href: 'https://techarohq.github.io/status/'
label: "Status",
href: "https://techarohq.github.io/status/",
},
],
},
@@ -153,13 +153,13 @@ const config: Config = {
darkTheme: prismThemes.dracula,
magicComments: [
{
className: 'code-block-diff-add-line',
line: 'diff-add'
className: "code-block-diff-add-line",
line: "diff-add",
},
{
className: 'code-block-diff-remove-line',
line: 'diff-remove'
}
className: "code-block-diff-remove-line",
line: "diff-remove",
},
],
},
} satisfies Preset.ThemeConfig,
+2 -2
View File
@@ -1,4 +1,4 @@
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
@@ -14,7 +14,7 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
*/
const sidebars: SidebarsConfig = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
// But you can create a sidebar manually
/*
+1 -1
View File
@@ -1,4 +1,4 @@
import styles from './styles.module.css';
import styles from "./styles.module.css";
export default function EnterpriseOnly({ link }) {
return (
@@ -8,7 +8,9 @@
font-weight: 700;
padding: 0.5rem 1rem; /* py-2 px-4 */
border-radius: 9999px; /* rounded-full */
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg approximation */
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg approximation */
display: inline-flex; /* flex */
align-items: center; /* items-center */
}
+3 -1
View File
@@ -5,7 +5,9 @@
*/
const h = (name, data = {}, children = []) => {
const result =
typeof name == "function" ? name(data) : Object.assign(document.createElement(name), data);
typeof name == "function"
? name(data)
: Object.assign(document.createElement(name), data);
if (!Array.isArray(children)) {
children = [children];
}
+2 -2
View File
@@ -1,7 +1,7 @@
bots:
- name: path-bad
- name: path-bad
path_regex: "a(b"
action: DENY
- name: user-agent-bad
- name: user-agent-bad
user_agent_regex: "a(b"
action: DENY
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- import: (data)/bots/ai-catchall.yaml
- import: (data)/bots/ai-catchall.yaml
name: generic-browser
user_agent_regex: >
Mozilla|Opera
+1 -1
View File
@@ -1,2 +1,2 @@
bots:
- import: (data)/does-not-exist-fake-file.yaml
- import: (data)/does-not-exist-fake-file.yaml
+1 -3
View File
@@ -1,5 +1,3 @@
{
"bots": [
{}
]
"bots": [{}]
}
+1 -3
View File
@@ -8,9 +8,7 @@
"userAgent.startsWith(\"git/\") || userAgent.contains(\"libgit\")",
"\"Git-Protocol\" in headers && headers[\"Git-Protocol\"] == \"version=2\"\n"
],
"any": [
"userAgent.startsWith(\"evilbot/\")"
]
"any": ["userAgent.startsWith(\"evilbot/\")"]
}
}
]
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: multiple-expression-types
- name: multiple-expression-types
action: ALLOW
expression:
all:
+3 -3
View File
@@ -1,15 +1,15 @@
bots:
- name: user-agent-ends-newline
- name: user-agent-ends-newline
# Subtle bug: this ends with a newline
user_agent_regex: >
Mozilla
action: CHALLENGE
- name: path-ends-newline
- name: path-ends-newline
# Subtle bug: this ends with a newline
path_regex: >
^/evil/.*$
action: CHALLENGE
- name: headers-ends-newline
- name: headers-ends-newline
# Subtle bug: this ends with a newline
headers_regex:
CF-Worker: >
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: everything
- name: everything
user_agent_regex: .*
action: DENY
+1 -4
View File
@@ -2,10 +2,7 @@
"bots": [
{
"name": "everyones-invited",
"remote_addresses": [
"0.0.0.0/0",
"::/0"
],
"remote_addresses": ["0.0.0.0/0", "::/0"],
"action": "ALLOW"
}
]
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: everyones-invited
- name: everyones-invited
remote_addresses:
- "0.0.0.0/0"
- "::/0"
+1 -1
View File
@@ -1,4 +1,4 @@
bots:
- name: generic-browser
- name: generic-browser
user_agent_regex: Mozilla
action: CHALLENGE
+1 -1
View File
@@ -1,4 +1,4 @@
bots:
- name: everything
- name: everything
user_agent_regex: .*
action: DENY
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: allow-git-clients
- name: allow-git-clients
action: ALLOW
expression:
all:
+1 -1
View File
@@ -1,2 +1,2 @@
bots:
- import: ./testdata/hack-test.yaml
- import: ./testdata/hack-test.yaml
+1 -1
View File
@@ -1,2 +1,2 @@
bots:
- import: (data)/common/keep-internet-working.yaml
- import: (data)/common/keep-internet-working.yaml
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: everything
- name: everything
user_agent_regex: .*
action: DENY
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: everything
- name: everything
user_agent_regex: .*
action: DENY
+1 -3
View File
@@ -2,8 +2,6 @@
{
"name": "ipv6-ula",
"action": "ALLOW",
"remote_addresses": [
"fc00::/7"
]
"remote_addresses": ["fc00::/7"]
}
]
+1 -3
View File
@@ -2,8 +2,6 @@
{
"name": "ipv6-ula",
"action": "ALLOW",
"remote_addresses": [
"fc00::/7"
]
"remote_addresses": ["fc00::/7"]
}
]
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: cloudflare-workers
- name: cloudflare-workers
expression: '"Cf-Worker" in headers'
action: DENY
+1 -1
View File
@@ -1,5 +1,5 @@
bots:
- name: cloudflare-workers
- name: cloudflare-workers
headers_regex:
CF-Worker: .*
action: DENY
+1 -3
View File
@@ -2,8 +2,6 @@
{
"name": "ipv6-ula",
"action": "ALLOW",
"remote_addresses": [
"fc00::/7"
]
"remote_addresses": ["fc00::/7"]
}
]
+2 -2
View File
@@ -1,9 +1,9 @@
bots:
- name: old-rule
- name: old-rule
path_regex: ^/old$
action: CHALLENGE
- name: new-rule
- name: new-rule
path_regex: ^/new$
action: CHALLENGE
+4 -2
View File
@@ -12,8 +12,10 @@
"build": "npm run assets && go build -o ./var/anubis ./cmd/anubis",
"dev": "npm run assets && go run ./cmd/anubis --use-remote-address --target http://localhost:3000",
"container": "npm run assets && go run ./cmd/containerbuild",
"package": "yeet",
"lint": "make lint"
"package": "go tool yeet",
"lint": "make lint",
"prepare": "go mod download",
"format": "prettier -w . 2>&1 >/dev/null"
},
"author": "",
"license": "ISC",
+2 -2
View File
@@ -1,9 +1,9 @@
bots:
- name: deny
- name: deny
user_agent_regex: DENY
action: DENY
- name: challenge
- name: challenge
user_agent_regex: CHALLENGE
action: CHALLENGE
+2 -2
View File
@@ -3,13 +3,13 @@ import { createInterface } from "readline";
async function getPage(path) {
return fetch(`http://localhost:8923${path}`)
.then(resp => {
.then((resp) => {
if (resp.status !== 200) {
throw new Error(`wanted status 200, got status: ${resp.status}`);
}
return resp;
})
.then(resp => resp.text());
.then((resp) => resp.text());
}
(async () => {
+4 -4
View File
@@ -3,22 +3,22 @@ async function getChallengePage() {
headers: {
"Accept-Language": "en",
"User-Agent": "CHALLENGE",
}
},
})
.then(resp => {
.then((resp) => {
if (resp.status !== 200) {
throw new Error(`wanted status 200, got status: ${resp.status}`);
}
return resp;
})
.then(resp => resp.text());
.then((resp) => resp.text());
}
(async () => {
const page = await getChallengePage();
if (!page.includes(`<html lang="de">`)) {
console.log(page)
console.log(page);
throw new Error("force language smoke test failed");
}
+9 -7
View File
@@ -1,12 +1,14 @@
async function fetchLanguages() {
return fetch("http://localhost:8923/.within.website/x/cmd/anubis/static/locales/manifest.json")
.then(resp => {
return fetch(
"http://localhost:8923/.within.website/x/cmd/anubis/static/locales/manifest.json",
)
.then((resp) => {
if (resp.status !== 200) {
throw new Error(`wanted status 200, got status: ${resp.status}`);
}
return resp;
})
.then(resp => resp.json());
.then((resp) => resp.json());
}
async function getChallengePage(lang) {
@@ -14,15 +16,15 @@ async function getChallengePage(lang) {
headers: {
"Accept-Language": lang,
"User-Agent": "CHALLENGE",
}
},
})
.then(resp => {
.then((resp) => {
if (resp.status !== 200) {
throw new Error(`wanted status 200, got status: ${resp.status}`);
}
return resp;
})
.then(resp => resp.text());
.then((resp) => resp.text());
}
(async () => {
@@ -42,7 +44,7 @@ async function getChallengePage(lang) {
console.log(`getting for ${lang}`);
const page = await getChallengePage(lang);
resultSheet[lang] = page.includes(`<html lang="${lang}">`)
resultSheet[lang] = page.includes(`<html lang="${lang}">`);
}
for (const [lang, result] of Object.entries(resultSheet)) {
+13 -7
View File
@@ -3,16 +3,16 @@ import { statSync } from "fs";
async function getPage(path) {
return fetch(`http://localhost:8923${path}`, {
headers: {
'User-Agent': 'CHALLENGE'
}
"User-Agent": "CHALLENGE",
},
})
.then(resp => {
.then((resp) => {
if (resp.status !== 200) {
throw new Error(`wanted status 200, got status: ${resp.status}`);
}
return resp;
})
.then(resp => resp.text());
.then((resp) => resp.text());
}
async function getFileSize(filePath) {
@@ -63,7 +63,9 @@ async function getFileSize(filePath) {
// Verify that log file size increased
if (finalSize <= initialSize) {
console.error("ERROR: Log file size did not increase after making requests!");
console.error(
"ERROR: Log file size did not increase after making requests!",
);
failed = true;
}
@@ -79,10 +81,14 @@ async function getFileSize(filePath) {
console.log(`Successful requests: ${successCount}/${requests.length}`);
if (failed) {
console.error("Test failed: Some requests failed or log file size did not increase");
console.error(
"Test failed: Some requests failed or log file size did not increase",
);
process.exit(1);
} else {
console.log("Test passed: All requests succeeded and log file size increased");
console.log(
"Test passed: All requests succeeded and log file size increased",
);
process.exit(0);
}
})();
+2 -8
View File
@@ -1,8 +1,2 @@
# /etc/nginx/conf-anubis.inc
# Forward to anubis
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://anubis;
}
# /etc/nginx/conf-anubis.inc # Forward to anubis location / { proxy_set_header
Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://anubis; }
+4 -4
View File
@@ -3,22 +3,22 @@ async function getRobotsTxt() {
headers: {
"Accept-Language": "en",
"User-Agent": "Mozilla/5.0",
}
},
})
.then(resp => {
.then((resp) => {
if (resp.status !== 200) {
throw new Error(`wanted status 200, got status: ${resp.status}`);
}
return resp;
})
.then(resp => resp.text());
.then((resp) => resp.text());
}
(async () => {
const page = await getRobotsTxt();
if (page.includes(`<html>`)) {
console.log(page)
console.log(page);
throw new Error("serve robots.txt smoke test failed");
}
+7 -4
View File
@@ -1,9 +1,9 @@
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<title>Anubis works!</title>
<link rel="stylesheet" href="/.within.website/x/xess/xess.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/.within.website/x/xess/xess.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body id="top">
<main>
@@ -11,7 +11,10 @@
<p>If you see this, everything has gone according to keikaku.</p>
<img height=128 src="/.within.website/x/cmd/anubis/static/img/happy.webp"/>
<img
height="128"
src="/.within.website/x/cmd/anubis/static/img/happy.webp"
/>
</main>
</body>
</html>
+11 -8
View File
@@ -1,19 +1,20 @@
async function testWithUserAgent(userAgent) {
const statusCode =
await fetch("https://relayd.local.cetacean.club:3004/reqmeta", {
const statusCode = await fetch(
"https://relayd.local.cetacean.club:3004/reqmeta",
{
headers: {
"User-Agent": userAgent,
}
})
.then(resp => resp.status);
},
},
).then((resp) => resp.status);
return statusCode;
}
const codes = {
allow: await testWithUserAgent("ALLOW"),
challenge: await testWithUserAgent("CHALLENGE"),
deny: await testWithUserAgent("DENY")
}
deny: await testWithUserAgent("DENY"),
};
const expected = {
allow: 200,
@@ -26,5 +27,7 @@ console.log("CHALLENGE:", codes.challenge);
console.log("DENY: ", codes.deny);
if (JSON.stringify(codes) !== JSON.stringify(expected)) {
throw new Error(`wanted ${JSON.stringify(expected)}, got: ${JSON.stringify(codes)}`);
throw new Error(
`wanted ${JSON.stringify(expected)}, got: ${JSON.stringify(codes)}`,
);
}
+7 -2
View File
@@ -6,7 +6,9 @@ interface ProcessOptions {
}
const getHardwareConcurrency = () =>
navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
navigator.hardwareConcurrency !== undefined
? navigator.hardwareConcurrency
: 1;
export default function process(
options: ProcessOptions,
@@ -25,7 +27,10 @@ export default function process(
workerMethod = "webcrypto";
}
if (navigator.userAgent.includes("Firefox") || navigator.userAgent.includes("Goanna")) {
if (
navigator.userAgent.includes("Firefox") ||
navigator.userAgent.includes("Goanna")
) {
console.log("Firefox detected, using pure-JS fallback");
workerMethod = "purejs";
}
+1 -1
View File
@@ -3,4 +3,4 @@ import fast from "./fast";
export default {
fast: fast,
slow: fast, // XXX(Xe): slow is deprecated, but keep this around in case anything goes bad
}
};
+27 -8
View File
@@ -2,13 +2,27 @@ import algorithms from "./algorithms";
const defaultDifficulty = 4;
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
const difficultyInput: HTMLInputElement = document.getElementById("difficulty-input") as HTMLInputElement;
const algorithmSelect: HTMLSelectElement = document.getElementById("algorithm-select") as HTMLSelectElement;
const compareSelect: HTMLSelectElement = document.getElementById("compare-select") as HTMLSelectElement;
const header: HTMLTableRowElement = document.getElementById("table-header") as HTMLTableRowElement;
const headerCompare: HTMLTableSectionElement = document.getElementById("table-header-compare") as HTMLTableSectionElement;
const results: HTMLTableRowElement = document.getElementById("results") as HTMLTableRowElement;
const status: HTMLParagraphElement = document.getElementById(
"status",
) as HTMLParagraphElement;
const difficultyInput: HTMLInputElement = document.getElementById(
"difficulty-input",
) as HTMLInputElement;
const algorithmSelect: HTMLSelectElement = document.getElementById(
"algorithm-select",
) as HTMLSelectElement;
const compareSelect: HTMLSelectElement = document.getElementById(
"compare-select",
) as HTMLSelectElement;
const header: HTMLTableRowElement = document.getElementById(
"table-header",
) as HTMLTableRowElement;
const headerCompare: HTMLTableSectionElement = document.getElementById(
"table-header-compare",
) as HTMLTableSectionElement;
const results: HTMLTableRowElement = document.getElementById(
"results",
) as HTMLTableRowElement;
const setupControls = () => {
if (defaultDifficulty == null) {
@@ -41,7 +55,12 @@ const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
.join("");
const t0 = performance.now();
const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal);
const { hash, nonce } = await process(
{ basePrefix: "/", version: "devel" },
challenge,
Number(difficulty),
signal,
);
const t1 = performance.now();
console.log({ hash, nonce });
+49 -31
View File
@@ -29,22 +29,25 @@ const getAvailableLanguages = async () => {
}
try {
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`);
const response = await fetch(
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`,
);
if (response.ok) {
const manifest = await response.json();
return manifest.supportedLanguages || ['en'];
return manifest.supportedLanguages || ["en"];
}
} catch (error) {
console.warn('Failed to load language manifest, falling back to default languages');
console.warn(
"Failed to load language manifest, falling back to default languages",
);
}
// Fallback to default languages if manifest loading fails
return ['en'];
return ["en"];
};
// Use the browser language from the HTML lang attribute which is set by the server settings or request headers
const getBrowserLanguage = async () =>
document.documentElement.lang;
const getBrowserLanguage = async () => document.documentElement.lang;
// Load translations from JSON files
const loadTranslations = async (lang) => {
@@ -54,12 +57,16 @@ const loadTranslations = async (lang) => {
}
try {
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`);
const response = await fetch(
`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`,
);
return await response.json();
} catch (error) {
console.warn(`Failed to load translations for ${lang}, falling back to English`);
if (lang !== 'en') {
return await loadTranslations('en');
console.warn(
`Failed to load translations for ${lang}, falling back to English`,
);
if (lang !== "en") {
return await loadTranslations("en");
}
throw error;
}
@@ -72,10 +79,10 @@ const getRedirectUrl = () => {
}
if (publicUrl && window.location.href.startsWith(publicUrl)) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('redir');
return urlParams.get("redir");
}
return window.location.href;
}
};
let translations = {};
let currentLang;
@@ -95,20 +102,28 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const dependencies = [
{
name: "Web Workers",
msg: t('web_workers_error'),
msg: t("web_workers_error"),
value: window.Worker,
},
{
name: "Cookies",
msg: t('cookies_error'),
msg: t("cookies_error"),
value: navigator.cookieEnabled,
},
];
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
const image: HTMLImageElement = document.getElementById("image") as HTMLImageElement;
const title: HTMLHeadingElement = document.getElementById("title") as HTMLHeadingElement;
const progress: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
const status: HTMLParagraphElement = document.getElementById(
"status",
) as HTMLParagraphElement;
const image: HTMLImageElement = document.getElementById(
"image",
) as HTMLImageElement;
const title: HTMLHeadingElement = document.getElementById(
"title",
) as HTMLHeadingElement;
const progress: HTMLDivElement = document.getElementById(
"progress",
) as HTMLDivElement;
const anubisVersion = j("anubis_version");
const basePrefix = j("anubis_base_prefix");
@@ -130,12 +145,12 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
progress.style.display = "none";
};
status.innerHTML = t('calculating');
status.innerHTML = t("calculating");
for (const { value, name, msg } of dependencies) {
if (!value) {
ohNoes({
titleMsg: `${t('missing_feature')} ${name}`,
titleMsg: `${t("missing_feature")} ${name}`,
statusMsg: msg,
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
@@ -148,20 +163,20 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const process = algorithms[rules.algorithm];
if (!process) {
ohNoes({
titleMsg: t('challenge_error'),
statusMsg: t('challenge_error_msg'),
titleMsg: t("challenge_error"),
statusMsg: t("challenge_error_msg"),
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
return;
}
status.innerHTML = `${t('calculating_difficulty')} ${rules.difficulty}, `;
status.innerHTML = `${t("calculating_difficulty")} ${rules.difficulty}, `;
progress.style.display = "inline-block";
// the whole text, including "Speed:", as a single node, because some browsers
// (Firefox mobile) present screen readers with each node as a separate piece
// of text.
const rateText = document.createTextNode(`${t('speed')} 0kH/s`);
const rateText = document.createTextNode(`${t("speed")} 0kH/s`);
status.appendChild(rateText);
let lastSpeedUpdate = 0;
@@ -180,7 +195,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
// only update the speed every second so it's less visually distracting
if (delta - lastSpeedUpdate > 1000) {
lastSpeedUpdate = delta;
rateText.data = `${t('speed')} ${(iters / delta).toFixed(3)}kH/s`;
rateText.data = `${t("speed")} ${(iters / delta).toFixed(3)}kH/s`;
}
// the probability of still being on the page is (1 - likelihood) ^ iters.
// by definition, half of the time the progress bar only gets to half, so
@@ -192,13 +207,14 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
const distance = (1 - Math.pow(probability, 2)) * 100;
progress["aria-valuenow"] = distance;
if (progress.firstElementChild !== null) {
(progress.firstElementChild as HTMLElement).style.width = `${distance}%`;
(progress.firstElementChild as HTMLElement).style.width =
`${distance}%`;
}
if (probability < 0.1 && !showingApology) {
status.append(
document.createElement("br"),
document.createTextNode(t('verification_longer')),
document.createTextNode(t("verification_longer")),
);
showingApology = true;
}
@@ -208,7 +224,9 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
console.log({ hash, nonce });
if (userReadDetails) {
const container: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
const container: HTMLDivElement = document.getElementById(
"progress",
) as HTMLDivElement;
// Style progress bar as a continue button
container.style.display = "flex";
@@ -224,7 +242,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
container.style.outlineOffset = "2px";
container.style.width = "min(20rem, 90%)";
container.style.margin = "1rem auto 2rem";
container.innerHTML = t('finished_reading');
container.innerHTML = t("finished_reading");
function onDetailsExpand() {
const redir = getRedirectUrl();
@@ -255,8 +273,8 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
}
} catch (err) {
ohNoes({
titleMsg: t('calculation_error'),
statusMsg: `${t('calculation_error_msg')} ${err.message}`,
titleMsg: t("calculation_error"),
statusMsg: `${t("calculation_error_msg")} ${err.message}`,
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
}
+4 -4
View File
@@ -1,4 +1,4 @@
import { Sha256 } from '@aws-crypto/sha256-js';
import { Sha256 } from "@aws-crypto/sha256-js";
const calculateSHA256 = (text) => {
const hash = new Sha256();
@@ -12,7 +12,7 @@ function toHexString(arr: Uint8Array): string {
.join("");
}
addEventListener('message', async ({ data: eventData }) => {
addEventListener("message", async ({ data: eventData }) => {
const { data, difficulty, threads } = eventData;
let nonce = eventData.nonce;
const isMainThread = nonce === 0;
@@ -21,7 +21,7 @@ addEventListener('message', async ({ data: eventData }) => {
const requiredZeroBytes = Math.floor(difficulty / 2);
const isDifficultyOdd = difficulty % 2 !== 0;
for (; ;) {
for (;;) {
const hashBuffer = await calculateSHA256(data + nonce);
const hashArray = new Uint8Array(hashBuffer);
@@ -34,7 +34,7 @@ addEventListener('message', async ({ data: eventData }) => {
}
if (isValid && isDifficultyOdd) {
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
if (hashArray[requiredZeroBytes] >> 4 !== 0) {
isValid = false;
}
}
+6 -3
View File
@@ -6,7 +6,10 @@ const calculateSHA256 = async (input: string) => {
};
const toHexString = (byteArray: Uint8Array) => {
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
return byteArray.reduce(
(str, byte) => str + byte.toString(16).padStart(2, "0"),
"",
);
};
addEventListener("message", async ({ data: eventData }) => {
@@ -18,7 +21,7 @@ addEventListener("message", async ({ data: eventData }) => {
const requiredZeroBytes = Math.floor(difficulty / 2);
const isDifficultyOdd = difficulty % 2 !== 0;
for (; ;) {
for (;;) {
const hashBuffer = await calculateSHA256(data + nonce);
const hashArray = new Uint8Array(hashBuffer);
@@ -31,7 +34,7 @@ addEventListener("message", async ({ data: eventData }) => {
}
if (isValid && isDifficultyOdd) {
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
if (hashArray[requiredZeroBytes] >> 4 !== 0) {
isValid = false;
}
}
+9 -11
View File
@@ -1,14 +1,11 @@
$`npm run assets`;
[
"amd64",
"arm64",
"ppc64le",
"riscv64",
].forEach(goarch => {
[deb, rpm, tarball].forEach(method => method.build({
["amd64", "arm64", "ppc64le", "riscv64"].forEach((goarch) => {
[deb, rpm, tarball].forEach((method) =>
method.build({
name: "anubis",
description: "Anubis weighs the souls of incoming HTTP requests and uses a sha256 proof-of-work challenge in order to protect upstream resources from scraper bots.",
description:
"Anubis weighs the souls of incoming HTTP requests and uses a sha256 proof-of-work challenge in order to protect upstream resources from scraper bots.",
homepage: "https://anubis.techaro.lol",
license: "MIT",
goarch,
@@ -26,7 +23,7 @@ $`npm run assets`;
file.install("./run/anubis@.service", `${systemd}/anubis@.service`);
file.install("./run/default.env", `${etc}/default.env`);
$`mkdir -p ${doc}/docs`
$`mkdir -p ${doc}/docs`;
$`cp -a docs/docs ${doc}`;
$`find ${doc} -name _category_.json -delete`;
$`mkdir -p ${doc}/data`;
@@ -37,7 +34,8 @@ $`npm run assets`;
$`cp -a data/crawlers ${doc}/data/crawlers`;
$`cp -a data/meta ${doc}/data/meta`;
},
}));
}),
);
});
// NOTE(Xe): Fixes #217. This is a "half baked" tarball that includes the harder
@@ -77,7 +75,7 @@ tarball.build({
// vendor Go dependencies
$`cd ${out} && go mod vendor`;
// build NPM-bound dependencies
$`cd ${out} && npm ci && npm run assets && rm -rf node_modules`
$`cd ${out} && npm ci && npm run assets && rm -rf node_modules`;
// write VERSION file
$`echo ${git.tag()} > ${out}/VERSION`;
},