mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-06-09 22:08:15 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d88de5878 | |||
| 3c2e2f5940 | |||
| 5c16bf5592 | |||
| 44d5fa3ce0 | |||
| ef3ea08b79 | |||
| a08b0f4262 | |||
| 3dc962b301 | |||
| 926f3d1d0e |
@@ -44,3 +44,6 @@ xou
|
||||
AWOO
|
||||
firewalls
|
||||
bindhosts
|
||||
handrolled
|
||||
xai
|
||||
gitlab
|
||||
|
||||
@@ -10,4 +10,3 @@ builds:
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -extldflags "-static"
|
||||
- -X github.com/TecharoHQ/anubis.Version={{.Env.VERSION}}
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
// Package anubis contains the version number of Anubis.
|
||||
package anubis
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// XXX(Xe): many things in this repo assume that the development version
|
||||
// of anubis is `devel` and ReadBuildInfo returns `(devel)`. Shim the gap.
|
||||
if bi.Main.Version != "(devel)" {
|
||||
Version = bi.Main.Version
|
||||
}
|
||||
}
|
||||
|
||||
// Version is the current version of Anubis.
|
||||
//
|
||||
// This variable is set at build time using the -X linker flag. If not set,
|
||||
// it defaults to "devel".
|
||||
// This is set from the Go module runtime version.
|
||||
var Version = "devel"
|
||||
|
||||
// CookieName is the name of the cookie that Anubis uses in order to validate
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/TecharoHQ/anubis/lib/thoth"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/facebookgo/flagenv"
|
||||
"github.com/google/uuid"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
@@ -193,6 +194,9 @@ func main() {
|
||||
flagenv.Parse()
|
||||
flag.Parse()
|
||||
|
||||
// Must be set before any concurrent UUID call.
|
||||
uuid.EnableRandPool()
|
||||
|
||||
if *versionFlag {
|
||||
fmt.Println("Anubis", anubis.Version)
|
||||
return
|
||||
|
||||
@@ -41,6 +41,9 @@ bots:
|
||||
# Challenge Firefox AI previews
|
||||
- import: (data)/clients/x-firefox-ai.yaml
|
||||
|
||||
# x.ai has a scraper that is killing gitlab instances
|
||||
- import: (data)/crawlers/xai.yaml
|
||||
|
||||
# Allow common "keeping the internet working" routes (well-known, favicon, robots.txt)
|
||||
- import: (data)/common/keep-internet-working.yaml
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
- name: xai-crawler-and-asn
|
||||
action: DENY
|
||||
user_agent_regex: code-review-sourcing.*\+xai-research
|
||||
remote_addresses:
|
||||
- 69.12.56.0/12
|
||||
- name: xai-crawler-user-agent
|
||||
action: DENY
|
||||
user_agent_regex: code-review-sourcing.*\+xai-research
|
||||
@@ -25,6 +25,9 @@
|
||||
# Challenge Firefox AI previews
|
||||
- import: (data)/clients/x-firefox-ai.yaml
|
||||
|
||||
# x.ai has a scraper that is killing gitlab instances
|
||||
- import: (data)/crawlers/xai.yaml
|
||||
|
||||
# Allow common "keeping the internet working" routes (well-known, favicon, robots.txt)
|
||||
- import: (data)/common/keep-internet-working.yaml
|
||||
|
||||
|
||||
@@ -23,12 +23,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Improve error messages and fix broken REDIRECT_DOMAINS link in docs ([#1193](https://github.com/TecharoHQ/anubis/issues/1193))
|
||||
- Add Bulgarian locale ([#1394](https://github.com/TecharoHQ/anubis/pull/1394))
|
||||
- Fixed case-sensitivity mismatch in geoipchecker.go
|
||||
- Use [Go's native version stamping](https://michael.stapelberg.ch/posts/2026-04-05-stamp-it-all-programs-must-report-their-version/) instead of a handrolled variant.
|
||||
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
||||
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls).
|
||||
- Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server.
|
||||
- Fix a bug in the dataset poisoning maze that could allow denial of service [#1580](https://github.com/TecharoHQ/anubis/issues/1580).
|
||||
- Add config option to add ASN to logs/metrics.
|
||||
- Log weight when issuing challenge.
|
||||
- Block x.ai's crawler for code review training.
|
||||
- Gate pprof endpoints behind `metrics.debug` in the policy file.
|
||||
- Limit naive honeypot r9k delay to one second.
|
||||
- Fix an obscure case where adding query values to a subrequest match could cause an invalid rule match when using path based matching for protected resources.
|
||||
@@ -39,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fix a race in the bbolt store where the asynchronous cleanup scheduled by an expired read could delete a value that had just been refreshed; the delete now only fires when the key still carries the same expired generation it observed.
|
||||
- Marginally increase the performances of requests processing
|
||||
- Marginally improve the performances of PoW validation
|
||||
- Marginally improve the performances of challenges generation/display
|
||||
- Significantly improve the performances of the gzip middleware
|
||||
- Significantly improve the performances of the PoW validation
|
||||
|
||||
## v1.25.0: Necron
|
||||
|
||||
|
||||
+24
-5
@@ -2,11 +2,28 @@ package internal
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func GzipMiddleware(level int, next http.Handler) http.Handler {
|
||||
// Validate the level once at setup; gzip.NewWriterLevel only fails for
|
||||
// invalid levels and we'd rather panic now than mid-request.
|
||||
if _, err := gzip.NewWriterLevel(io.Discard, level); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Per-middleware pool of *gzip.Writer. Each entry carries ~40 KiB of
|
||||
// deflate buffers; reusing them avoids that allocation on every request.
|
||||
pool := sync.Pool{
|
||||
New: func() any {
|
||||
gz, _ := gzip.NewWriterLevel(io.Discard, level)
|
||||
return gz
|
||||
},
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
next.ServeHTTP(w, r)
|
||||
@@ -14,11 +31,13 @@ func GzipMiddleware(level int, next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz, err := gzip.NewWriterLevel(w, level)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer gz.Close()
|
||||
gz := pool.Get().(*gzip.Writer)
|
||||
gz.Reset(w)
|
||||
defer func() {
|
||||
gz.Close()
|
||||
gz.Reset(io.Discard)
|
||||
pool.Put(gz)
|
||||
}()
|
||||
|
||||
grw := gzipResponseWriter{ResponseWriter: w, sink: gz}
|
||||
next.ServeHTTP(grw, r)
|
||||
|
||||
+7
-5
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -162,6 +163,7 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idStr := id.String()
|
||||
|
||||
var randomData = make([]byte, 64)
|
||||
if _, err := rand.Read(randomData); err != nil {
|
||||
@@ -169,9 +171,9 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
||||
}
|
||||
|
||||
chall := challenge.Challenge{
|
||||
ID: id.String(),
|
||||
ID: idStr,
|
||||
Method: rule.Challenge.Algorithm,
|
||||
RandomData: fmt.Sprintf("%x", randomData),
|
||||
RandomData: hex.EncodeToString(randomData),
|
||||
IssuedAt: time.Now(),
|
||||
Difficulty: rule.Challenge.Difficulty,
|
||||
PolicyRuleHash: rule.Hash(),
|
||||
@@ -182,11 +184,11 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
||||
}
|
||||
|
||||
j := store.JSON[challenge.Challenge]{Underlying: s.store}
|
||||
if err := j.Set(ctx, "challenge:"+id.String(), chall, 30*time.Minute); err != nil {
|
||||
if err := j.Set(ctx, "challenge:"+idStr, chall, 30*time.Minute); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lg.Info("new challenge issued", "challenge", id.String(), "weight", cr.Weight)
|
||||
lg.Info("new challenge issued", "challenge", idStr, "weight", cr.Weight)
|
||||
|
||||
return &chall, err
|
||||
}
|
||||
@@ -240,7 +242,7 @@ func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request)
|
||||
func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
|
||||
lg, r := s.getRequestLogger(r)
|
||||
|
||||
if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil {
|
||||
if val, _ := s.store.Get(r.Context(), "ogtags:allow:"+r.Host+r.URL.String()); val != nil {
|
||||
lg.Debug("serving opengraph tag asset")
|
||||
s.ServeHTTPNext(w, r)
|
||||
return
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package proofofwork
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
chall "github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/a-h/templ"
|
||||
@@ -66,11 +67,20 @@ func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInpu
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w response", chall.ErrMissingField))
|
||||
}
|
||||
|
||||
calcString := challenge + nonceStr
|
||||
calculated := internal.SHA256sum(calcString)
|
||||
// Stream the challenge and nonce into a single sha256 hasher to avoid
|
||||
// the intermediate "challenge + nonceStr" concatenation. Hex-encode
|
||||
// the digest into a stack buffer so the comparison runs without
|
||||
// allocating a heap string.
|
||||
h := sha256.New()
|
||||
h.Write([]byte(challenge))
|
||||
h.Write([]byte(nonceStr))
|
||||
var sumBuf [sha256.Size]byte
|
||||
sum := h.Sum(sumBuf[:0])
|
||||
var hexBuf [sha256.Size * 2]byte
|
||||
hex.Encode(hexBuf[:], sum)
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", chall.ErrFailed, calculated, response))
|
||||
if subtle.ConstantTimeCompare([]byte(response), hexBuf[:]) != 1 {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", chall.ErrFailed, string(hexBuf[:]), response))
|
||||
}
|
||||
|
||||
// compare the leading zeroes
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ $`npm run assets`;
|
||||
},
|
||||
|
||||
build: ({ bin, etc, systemd, doc }) => {
|
||||
$`go build -o ${bin}/anubis -ldflags '-s -w -extldflags "-static" -X "github.com/TecharoHQ/anubis.Version=${git.tag()}"' ./cmd/anubis`;
|
||||
$`go build -o ${bin}/anubis-robots2policy -ldflags '-s -w -extldflags "-static" -X "github.com/TecharoHQ/anubis.Version=${git.tag()}"' ./cmd/robots2policy`;
|
||||
$`go build -o ${bin}/anubis -ldflags '-s -w -extldflags "-static" ./cmd/anubis`;
|
||||
$`go build -o ${bin}/anubis-robots2policy -ldflags '-s -w -extldflags "-static"' ./cmd/robots2policy`;
|
||||
|
||||
file.install("./run/anubis@.service", `${systemd}/anubis@.service`);
|
||||
file.install("./run/default.env", `${etc}/default.env`);
|
||||
|
||||
Reference in New Issue
Block a user