mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-05 08:18:17 +00:00
feat(http): set "Cache-Control: no-store" on error responses (#1474)
* refactor(http): split long line in respondWithStatus Signed-off-by: Max Chernoff <git@maxchernoff.ca> * feat(http): set `Cache-Control: no-store` on error responses Since #132, Anubis has set `Cache-Control: no-store` on challenge responses. However, this does not apply to deny responses, meaning that if Anubis is configured to block certain user agents and is behind a caching reverse proxy, this error page will be cached and served to all subsequent requests, even those with an allowed user agent. This commit configures the error page responder to also set the `Cache-Control` header, meaning that deny and challenge responses will now both have the same behaviour. Signed-off-by: Max Chernoff <git@maxchernoff.ca> * chore(spelling): add new words to allowlist Signed-off-by: Max Chernoff <git@maxchernoff.ca> * chore(actions): bump Go version to fix govulncheck errors Signed-off-by: Max Chernoff <git@maxchernoff.ca> --------- Signed-off-by: Max Chernoff <git@maxchernoff.ca> Signed-off-by: Xe Iaso <xe.iaso@techaro.lol> Co-authored-by: Xe Iaso <xe.iaso@techaro.lol>
This commit is contained in:
3
.github/actions/spelling/allow.txt
vendored
3
.github/actions/spelling/allow.txt
vendored
@@ -26,3 +26,6 @@ blocklists
|
|||||||
rififi
|
rififi
|
||||||
prolocation
|
prolocation
|
||||||
Prolocation
|
Prolocation
|
||||||
|
Necron
|
||||||
|
Stargate
|
||||||
|
FFXIV
|
||||||
|
|||||||
2
.github/workflows/asset-verification.yml
vendored
2
.github/workflows/asset-verification.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "1.25.7"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Instruct reverse proxies to not cache error pages.
|
||||||
- Fixed mixed tab/space indentation in Caddy documentation code block
|
- Fixed mixed tab/space indentation in Caddy documentation code block
|
||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
|||||||
@@ -333,7 +333,14 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag
|
|||||||
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
|
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
|
|
||||||
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
|
component := web.Base(
|
||||||
|
localizer.T("oh_noes"),
|
||||||
|
web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer),
|
||||||
|
s.policy.Impressum,
|
||||||
|
localizer,
|
||||||
|
)
|
||||||
|
handler := internal.NoStoreCache(templ.Handler(component, templ.WithStatus(status)))
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis"
|
"github.com/TecharoHQ/anubis"
|
||||||
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy"
|
"github.com/TecharoHQ/anubis/lib/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -191,3 +192,34 @@ func TestRenderIndexUnauthorized(t *testing.T) {
|
|||||||
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoCacheOnError(t *testing.T) {
|
||||||
|
pol := loadPolicies(t, "testdata/useragent.yaml", 0)
|
||||||
|
srv := spawnAnubis(t, Options{Policy: pol})
|
||||||
|
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
for userAgent, expectedCacheControl := range map[string]string{
|
||||||
|
"DENY": "no-store",
|
||||||
|
"CHALLENGE": "no-store",
|
||||||
|
"ALLOW": "",
|
||||||
|
} {
|
||||||
|
t.Run(userAgent, func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", userAgent)
|
||||||
|
|
||||||
|
resp, err := ts.Client().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Header.Get("Cache-Control") != expectedCacheControl {
|
||||||
|
t.Errorf("wanted Cache-Control header %q, got %q", expectedCacheControl, resp.Header.Get("Cache-Control"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
12
lib/testdata/useragent.yaml
vendored
Normal file
12
lib/testdata/useragent.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
bots:
|
||||||
|
- name: deny
|
||||||
|
user_agent_regex: DENY
|
||||||
|
action: DENY
|
||||||
|
|
||||||
|
- name: challenge
|
||||||
|
user_agent_regex: CHALLENGE
|
||||||
|
action: CHALLENGE
|
||||||
|
|
||||||
|
- name: allow
|
||||||
|
user_agent_regex: ALLOW
|
||||||
|
action: ALLOW
|
||||||
Reference in New Issue
Block a user