Compare commits

..

6 Commits

Author SHA1 Message Date
dependabot[bot] 80ecd5cfdc build(deps-dev): bump the npm group with 5 updates
Bumps the npm group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) | `20.5.3` | `21.0.0` |
| [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) | `20.5.3` | `21.0.0` |
| [baseline-browser-mapping](https://github.com/web-platform-dx/baseline-browser-mapping) | `2.10.27` | `2.10.29` |
| [cssnano](https://github.com/cssnano/cssnano) | `7.1.8` | `8.0.1` |
| [cssnano-preset-advanced](https://github.com/cssnano/cssnano) | `7.0.16` | `8.0.1` |


Updates `@commitlint/cli` from 20.5.3 to 21.0.0
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.0.0/@commitlint/cli)

Updates `@commitlint/config-conventional` from 20.5.3 to 21.0.0
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.0.0/@commitlint/config-conventional)

Updates `baseline-browser-mapping` from 2.10.27 to 2.10.29
- [Release notes](https://github.com/web-platform-dx/baseline-browser-mapping/releases)
- [Commits](https://github.com/web-platform-dx/baseline-browser-mapping/compare/v2.10.27...v2.10.29)

Updates `cssnano` from 7.1.8 to 8.0.1
- [Release notes](https://github.com/cssnano/cssnano/releases)
- [Commits](https://github.com/cssnano/cssnano/compare/cssnano@7.1.8...cssnano@8.0.1)

Updates `cssnano-preset-advanced` from 7.0.16 to 8.0.1
- [Release notes](https://github.com/cssnano/cssnano/releases)
- [Commits](https://github.com/cssnano/cssnano/compare/cssnano-preset-advanced@7.0.16...cssnano-preset-advanced@8.0.1)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 21.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 21.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: baseline-browser-mapping
  dependency-version: 2.10.29
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: cssnano
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: cssnano-preset-advanced
  dependency-version: 8.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 00:52:07 +00:00
Xe Iaso b57508afcd fix(honeypot/naive): apply robot9001 style delays (#1632)
Currently the honeypotting feature has no limits or delays anywhere and
uses that to feed an internal greylist of IP networks. This can cause
issues such as in #1613 where Claude's crawler seemed to pick up on it
and egress data at over one megabit per second until the administrator
noticed and blocked the address range.

This takes a different approach by inspiration of how the classic #xkcd
IRC bot Robot9000 works. The first time a given IPv4 /24 or IPv6 /48
visits a honepot page, Anubis sleeps for 1 millisecond. The second it
sleeps for two milliseconds. The third is four milliseconds and so on.
The goal of this is to make the scraping inherently self-limiting such
that the scrapers go off in their own corner where they won't really
hurt anyone.

Let's see if this works out according to keikaku.

Ref: https://github.com/TecharoHQ/anubis/issues/1613

Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-05-15 17:56:37 -04:00
Xe Iaso 276b537776 fix(policy): correctly wire subrequest mode through CEL/path checkers (#1630)
* fix(policy): correctly wire subrequest mode through CEL/path checkers

Previously Anubis only checked for the X-Original-Url when using
subrequest mode. This header is used by the example nginx config to pass
the request path through from the original client request to Anubis in
order to do path-based filtering.

According to facts and circumstances, Traefik hardcodes its own
headers[1]:

```text
httpdebug-1  | GET /.within.website/x/cmd/anubis/api/check
httpdebug-1  | X-Forwarded-Method: GET
httpdebug-1  | X-Forwarded-Proto: http
httpdebug-1  | X-Forwarded-Server: b9a5d299c929
httpdebug-1  | X-Forwarded-Port: 8080
httpdebug-1  | X-Forwarded-Uri: /
httpdebug-1  | X-Real-Ip: 172.18.0.1
httpdebug-1  | Accept-Encoding: gzip
httpdebug-1  | User-Agent: curl/8.20.0
httpdebug-1  | Accept: */*
httpdebug-1  | X-Forwarded-For: 172.18.0.1
httpdebug-1  | X-Forwarded-Host: localhost:8080
```

As a result, this means that path-based filtering did not work.

This commit fixes this issue by amending how path based checking logic
works:

* For CEL based checks, this pipes through the `subrequestMode` flag from
  main and alters the behaviour if either `X-Original-Url` or
  `X-Forwarded-Url` are found. These values are currently hardcoded for
  convenience but probably need to be made configurable in the policy
  file at a future date.
* For path-based checks, this uses the existing `subrequestMode` flag
  from main and adds `X-Forwarded-Url` to the list of headers it checks.

A smoke test was added to make sure that traefik in this mode continues
to work. Thank you https://github.com/flifloo for filing a detailed
issue with the relevant configuration fragments. Those configuration
fragments formed the core of this smoke test.

[1]: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/

Closes: https://github.com/TecharoHQ/anubis/issues/1628
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-Authored-By: flifloo <flifloo@gmail.com>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: flifloo <flifloo@gmail.com>
2026-05-14 21:37:02 -04:00
dependabot[bot] 9f479f578a build(deps): bump github.com/go-jose/go-jose/v3 from 3.0.4 to 3.0.5 (#1629)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 14:07:24 -04:00
dependabot[bot] c184028d42 build(deps-dev): bump the npm group across 1 directory with 6 updates (#1621)
Bumps the npm group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) | `20.5.0` | `20.5.3` |
| [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) | `20.5.0` | `20.5.3` |
| [baseline-browser-mapping](https://github.com/web-platform-dx/baseline-browser-mapping) | `2.10.15` | `2.10.27` |
| [cssnano](https://github.com/cssnano/cssnano) | `7.1.4` | `7.1.8` |
| [cssnano-preset-advanced](https://github.com/cssnano/cssnano) | `7.0.12` | `7.0.16` |
| [prettier](https://github.com/prettier/prettier) | `3.8.1` | `3.8.3` |



Updates `@commitlint/cli` from 20.5.0 to 20.5.3
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.5.3/@commitlint/cli)

Updates `@commitlint/config-conventional` from 20.5.0 to 20.5.3
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.5.3/@commitlint/config-conventional)

Updates `baseline-browser-mapping` from 2.10.15 to 2.10.27
- [Release notes](https://github.com/web-platform-dx/baseline-browser-mapping/releases)
- [Commits](https://github.com/web-platform-dx/baseline-browser-mapping/compare/v2.10.15...v2.10.27)

Updates `cssnano` from 7.1.4 to 7.1.8
- [Release notes](https://github.com/cssnano/cssnano/releases)
- [Commits](https://github.com/cssnano/cssnano/compare/cssnano@7.1.4...cssnano@7.1.8)

Updates `cssnano-preset-advanced` from 7.0.12 to 7.0.16
- [Release notes](https://github.com/cssnano/cssnano/releases)
- [Commits](https://github.com/cssnano/cssnano/compare/cssnano-preset-advanced@7.0.12...cssnano-preset-advanced@7.0.16)

Updates `prettier` from 3.8.1 to 3.8.3
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.1...3.8.3)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 20.5.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 20.5.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: baseline-browser-mapping
  dependency-version: 2.10.27
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: cssnano
  dependency-version: 7.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: cssnano-preset-advanced
  dependency-version: 7.0.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: prettier
  dependency-version: 3.8.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jason Cameron <git@jasoncameron.dev>
2026-05-12 16:32:01 -04:00
Xe Iaso 0491f1fac2 fix: patch GHSA-6wcg-mqvh-fcvg (#1616)
* fix: patch GHSA-6wcg-mqvh-fcvg

PR https://github.com/TecharoHQ/anubis/pull/1015 added the ability for
reverse proxies using Anubis in subrequest auth mode to look at the path
of a request as there are many rules in the wild that rely on checking
the path. This is how access to things like robots.txt or anything in the
.well-known directory is unaffected by Anubis.

However this logic was also enabled for non-subrequest deployments of Anubis,
meaning that a specially crafted request could include a /.well-known/
path in it and then get around Anubis with little effort.

This fix gates the logic behind a new plumbed variable named subrequestMode
that only fires when Anubis is running in subrequest auth mode. This
properly contains that workaround so that the logic does not fire in
most deployments.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-05-08 19:17:25 -04:00
20 changed files with 986 additions and 550 deletions
+2
View File
@@ -39,3 +39,5 @@ wenet
qwertiko
setuplistener
mba
xfu
xou
+1
View File
@@ -27,6 +27,7 @@ jobs:
- palemoon/amd64
#- palemoon/i386
- robots_txt
- traefik
runs-on: ubuntu-latest
steps:
- name: Checkout code
+2
View File
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- This changes the project to: -->
- Patch [GHSA-6wcg-mqvh-fcvg](https://github.com/TecharoHQ/anubis/security/advisories/GHSA-6wcg-mqvh-fcvg) by containing subrequest logic to Anubis instances in subrequest mode.
- Implement robot9001 style delays on the honeypot feature so that the first hit takes 1 millisecond, the second takes 2, etc.
- Move metrics server configuration to [the policy file](./admin/policies.mdx#metrics-server).
- Expose [pprof endpoints](https://pkg.go.dev/net/http/pprof) on the metrics listener to enable profiling Anubis in production.
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
@@ -28,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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
- Fix `path_regex` and CEL `path` rules not matching when using Traefik `forwardAuth` middleware. Anubis now checks `X-Forwarded-Uri` (Traefik) in addition to `X-Original-URI` (nginx) when resolving the request path in subrequest mode ([#1628](https://github.com/TecharoHQ/anubis/issues/1628)).
## v1.25.0: Necron
+1 -1
View File
@@ -106,7 +106,7 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.16.2 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v3 v3.0.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
+2 -2
View File
@@ -189,8 +189,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v3 v3.0.5 h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=
github.com/go-jose/go-jose/v3 v3.0.5/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+4
View File
@@ -5,6 +5,7 @@ import (
_ "embed"
"fmt"
"log/slog"
"math"
"math/rand/v2"
"net/http"
"net/netip"
@@ -168,6 +169,9 @@ func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
millisecondAmount := math.Pow(float64(networkCount), 2)
time.Sleep(time.Duration(millisecondAmount) * time.Millisecond)
spins := i.makeSpins()
affirmations := i.makeAffirmations()
title := i.makeTitle()
+17 -6
View File
@@ -13,11 +13,12 @@ import (
)
type CELChecker struct {
program cel.Program
src string
program cel.Program
src string
subRequestMode bool
}
func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns) (*CELChecker, error) {
func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns, subRequestMode bool) (*CELChecker, error) {
env, err := expressions.BotEnvironment(dnsObj)
if err != nil {
return nil, err
@@ -29,8 +30,9 @@ func NewCELChecker(cfg *config.ExpressionOrList, dnsObj *dns.Dns) (*CELChecker,
}
return &CELChecker{
src: cfg.String(),
program: program,
src: cfg.String(),
program: program,
subRequestMode: subRequestMode,
}, nil
}
@@ -39,7 +41,7 @@ func (cc *CELChecker) Hash() string {
}
func (cc *CELChecker) Check(r *http.Request) (bool, error) {
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r})
result, _, err := cc.program.ContextEval(r.Context(), &CELRequest{r, cc.subRequestMode})
if err != nil {
return false, err
@@ -54,6 +56,7 @@ func (cc *CELChecker) Check(r *http.Request) (bool, error) {
type CELRequest struct {
*http.Request
subRequestMode bool
}
func (cr *CELRequest) Parent() cel.Activation { return nil }
@@ -71,6 +74,14 @@ func (cr *CELRequest) ResolveName(name string) (any, bool) {
case "userAgent":
return cr.UserAgent(), true
case "path":
if cr.subRequestMode {
if xou := cr.Header.Get("X-Original-URI"); xou != "" {
return xou, true
}
if xfu := cr.Header.Get("X-Forwarded-Uri"); xfu != "" {
return xfu, true
}
}
return cr.URL.Path, true
case "query":
return expressions.URLValues{Values: cr.URL.Query()}, true
+75 -1
View File
@@ -23,7 +23,7 @@ func TestCELChecker_MapIterationWrappers(t *testing.T) {
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
}
checker, err := NewCELChecker(cfg, newTestDNS(t))
checker, err := NewCELChecker(cfg, newTestDNS(t), false)
if err != nil {
t.Fatalf("creating CEL checker failed: %v", err)
}
@@ -42,3 +42,77 @@ func TestCELChecker_MapIterationWrappers(t *testing.T) {
t.Fatal("expected expression to evaluate true")
}
}
func TestCELChecker_PathWithForwardedUri(t *testing.T) {
tests := []struct {
name string
expression string
xForwardedUri string
urlPath string
subRequestMode bool
want bool
}{
{
name: "path matches X-Forwarded-Uri in subrequest mode",
expression: `path.startsWith("/admin")`,
xForwardedUri: "/admin/secret",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "path with query string",
expression: `path.startsWith("/api/secret")`,
xForwardedUri: "/api/secret?token=abc",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "path falls back to url path when no header",
expression: `path == "/public/page"`,
urlPath: "/public/page",
subRequestMode: true,
want: true,
},
{
name: "non-subrequest mode ignores X-Forwarded-Uri",
expression: `path.startsWith("/admin")`,
xForwardedUri: "/admin/secret",
urlPath: "/public/page",
subRequestMode: false,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.ExpressionOrList{
Expression: tt.expression,
}
checker, err := NewCELChecker(cfg, newTestDNS(t), tt.subRequestMode)
if err != nil {
t.Fatalf("NewCELChecker() error: %v", err)
}
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
if err != nil {
t.Fatalf("http.NewRequest: %v", err)
}
if tt.xForwardedUri != "" {
req.Header.Set("X-Forwarded-Uri", tt.xForwardedUri)
}
got, err := checker.Check(req)
if err != nil {
t.Fatalf("Check() error: %v", err)
}
if got != tt.want {
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Forwarded-Uri=%q)",
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xForwardedUri)
}
})
}
}
+3
View File
@@ -110,6 +110,9 @@ func NewPathChecker(rexStr string, subrequestMode bool) (checker.Impl, error) {
func (pc *PathChecker) Check(r *http.Request) (bool, error) {
if pc.subRequestMode {
originalUrl := r.Header.Get("X-Original-URI")
if originalUrl == "" {
originalUrl = r.Header.Get("X-Forwarded-Uri")
}
if originalUrl != "" {
if pc.regexp.MatchString(originalUrl) {
return true, nil
+116
View File
@@ -410,3 +410,119 @@ func TestPathChecker_GHSA_6wcg_mqvh_fcvg(t *testing.T) {
})
}
}
func TestPathChecker_XForwardedUri(t *testing.T) {
tests := []struct {
name string
regex string
xForwardedUri string
xOriginalURI string
urlPath string
subRequestMode bool
want bool
}{
{
name: "X-Forwarded-Uri matches regex in subrequest mode",
regex: "^/admin/.*",
xForwardedUri: "/admin/users",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "X-Forwarded-Uri with query string",
regex: "^/admin/.*",
xForwardedUri: "/admin/users?page=1",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "X-Original-URI takes priority over X-Forwarded-Uri",
regex: "^/admin/.*",
xForwardedUri: "/public/page",
xOriginalURI: "/admin/users",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "falls back to X-Forwarded-Uri when no X-Original-URI",
regex: "^/admin/.*",
xForwardedUri: "/admin/dashboard",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: true,
},
{
name: "neither header matches, url path matches",
regex: "^/public/.*",
xForwardedUri: "/admin/users",
urlPath: "/public/page",
subRequestMode: true,
want: true,
},
{
name: "nothing matches",
regex: "^/admin/.*",
xForwardedUri: "/public/page",
urlPath: "/.within.website/x/cmd/anubis/api/check",
subRequestMode: true,
want: false,
},
{
name: "non-subrequest mode ignores X-Forwarded-Uri",
regex: "^/admin/.*",
xForwardedUri: "/admin/users",
urlPath: "/public/page",
subRequestMode: false,
want: false,
},
{
name: "non-subrequest mode uses url path",
regex: "^/admin/.*",
xForwardedUri: "/public/page",
urlPath: "/admin/secret",
subRequestMode: false,
want: true,
},
{
name: "empty X-Forwarded-Uri falls back to url path",
regex: "^/check$",
urlPath: "/check",
subRequestMode: true,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pc, err := NewPathChecker(tt.regex, tt.subRequestMode)
if err != nil {
t.Fatalf("NewPathChecker(%q, %v) returned error: %v", tt.regex, tt.subRequestMode, err)
}
req, err := http.NewRequest(http.MethodGet, "http://example.com"+tt.urlPath, nil)
if err != nil {
t.Fatalf("http.NewRequest: %v", err)
}
if tt.xForwardedUri != "" {
req.Header.Set("X-Forwarded-Uri", tt.xForwardedUri)
}
if tt.xOriginalURI != "" {
req.Header.Set("X-Original-URI", tt.xOriginalURI)
}
got, err := pc.Check(req)
if err != nil {
t.Fatalf("Check() unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("Check() = %v, want %v (subRequestMode=%v, urlPath=%q, X-Forwarded-Uri=%q, X-Original-URI=%q)",
got, tt.want, tt.subRequestMode, tt.urlPath, tt.xForwardedUri, tt.xOriginalURI)
}
})
}
}
+1 -1
View File
@@ -170,7 +170,7 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
}
if b.Expression != nil {
c, err := NewCELChecker(b.Expression, result.Dns)
c, err := NewCELChecker(b.Expression, result.Dns, subrequestMode)
if err != nil {
validationErrs = append(validationErrs, fmt.Errorf("while processing rule %s expressions: %w", b.Name, err))
} else {
+618 -533
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -20,11 +20,11 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0",
"baseline-browser-mapping": "^2.10.15",
"cssnano": "^7.1.4",
"cssnano-preset-advanced": "^7.0.12",
"@commitlint/cli": "^21.0.0",
"@commitlint/config-conventional": "^21.0.0",
"baseline-browser-mapping": "^2.10.29",
"cssnano": "^8.0.1",
"cssnano-preset-advanced": "^8.0.1",
"esbuild": "^0.28.0",
"husky": "^9.1.7",
"playwright": "^1.52.0",
@@ -32,7 +32,7 @@
"postcss-import": "^16.1.1",
"postcss-import-url": "^7.2.0",
"postcss-url": "^10.1.3",
"prettier": "^3.8.1"
"prettier": "^3.8.3"
},
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",
+16
View File
@@ -0,0 +1,16 @@
bots:
- name: block-admin-via-regex
path_regex: ^/admin(/.*)?$
action: DENY
- name: block-secret-via-cel
expression:
all:
- 'path.startsWith("/api/secret")'
action: DENY
- import: (data)/meta/default-config.yaml
status_codes:
CHALLENGE: 200
DENY: 403
+27
View File
@@ -0,0 +1,27 @@
services:
traefik:
image: traefik:v3.3
restart: always
ports:
- 8080:80
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./http.yaml:/config/http.yaml:ro
anubis:
image: ko.local/anubis
restart: always
environment:
BIND: ":8080"
TARGET: " "
POLICY_FNAME: /etc/techaro/anubis.yaml
PUBLIC_URL: http://localhost:8080/.within.website/x/cmd/anubis
COOKIE_DOMAIN: localhost
USE_REMOTE_ADDRESS: "true"
volumes:
- ./anubis.yaml:/etc/techaro/anubis.yaml
backend:
image: ghcr.io/xe/x/httpdebug
pull_policy: always
restart: always
+30
View File
@@ -0,0 +1,30 @@
http:
middlewares:
anubis:
forwardAuth:
address: http://anubis:8080/.within.website/x/cmd/anubis/api/check
trustForwardHeader: true
routers:
anubis-assets:
rule: Host(`localhost`) && PathPrefix(`/.within.website/x/cmd/anubis`)
entryPoints:
- web
service: anubis
backend:
rule: Host(`localhost`)
entryPoints:
- web
service: backend
middlewares:
- anubis
services:
anubis:
loadBalancer:
servers:
- url: http://anubis:8080
backend:
loadBalancer:
servers:
- url: http://backend:3000
+33
View File
@@ -0,0 +1,33 @@
// Smoke test for https://github.com/TecharoHQ/anubis/issues/1628
//
// Traefik's forwardAuth middleware calls Anubis at the literal path
// /.within.website/x/cmd/anubis/api/check and conveys the original URL in the
// X-Forwarded-Uri header. Path-targeting policy rules must match that header
// (not r.URL.Path), otherwise every request looks like a request to /check.
const BASE = "http://localhost:8080";
const UA = "Mozilla/5.0 (compatible; AnubisTraefikSmoke/1.0)";
const cases = [
{ path: "/", expected: 307, why: "control: no DENY rule, default challenge redirect" },
{ path: "/free", expected: 307, why: "control: no DENY rule, default challenge redirect" },
{ path: "/admin", expected: 403, why: "path_regex must match X-Forwarded-Uri, not 307 or 200" },
{ path: "/admin/users", expected: 403, why: "path_regex must match X-Forwarded-Uri, not 307 or 200" },
{ path: "/api/secret", expected: 403, why: "CEL path must match X-Forwarded-Uri, not 307 or 200" },
];
let failed = false;
for (const c of cases) {
const resp = await fetch(`${BASE}${c.path}`, {
headers: { "User-Agent": UA },
redirect: "manual",
});
const ok = resp.status === c.expected;
console.log(
`${ok ? "PASS" : "FAIL"}: GET ${c.path}${resp.status} (want ${c.expected}: ${c.why})`,
);
if (!ok) failed = true;
}
process.exit(failed ? 1 : 0);
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -eo pipefail
export VERSION=${GITHUB_SHA:-devel}-test
export KO_DOCKER_REPO=ko.local
set -u
source ../lib/lib.sh
build_anubis_ko
function cleanup() {
docker compose down -t 1 || :
}
trap cleanup EXIT SIGINT
docker compose up -d
backoff-retry --try-count 20 node ./test.mjs
+8
View File
@@ -0,0 +1,8 @@
entryPoints:
web:
address: ":80"
providers:
file:
directory: /config
watch: false
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore