+6
-3
cmd/anubis/main.go
+6
-3
cmd/anubis/main.go
···
117
117
118
118
err = os.Chmod(address, os.FileMode(mode))
119
119
if err != nil {
120
-
listener.Close()
120
+
err := listener.Close()
121
+
if err != nil {
122
+
log.Printf("failed to close listener: %v", err)
123
+
}
121
124
log.Fatal(fmt.Errorf("could not change socket mode: %w", err))
122
125
}
123
126
}
···
227
230
log.Fatalf("failed to parse and validate ED25519_PRIVATE_KEY_HEX: %v", err)
228
231
}
229
232
} else if *ed25519PrivateKeyHexFile != "" {
230
-
hex, err := os.ReadFile(*ed25519PrivateKeyHexFile)
233
+
hexData, err := os.ReadFile(*ed25519PrivateKeyHexFile)
231
234
if err != nil {
232
235
log.Fatalf("failed to read ED25519_PRIVATE_KEY_HEX_FILE %s: %v", *ed25519PrivateKeyHexFile, err)
233
236
}
234
237
235
-
priv, err = keyFromHex(string(bytes.TrimSpace(hex)))
238
+
priv, err = keyFromHex(string(bytes.TrimSpace(hexData)))
236
239
if err != nil {
237
240
log.Fatalf("failed to parse and validate content of ED25519_PRIVATE_KEY_HEX_FILE: %v", err)
238
241
}
+1
-1
cmd/containerbuild/main.go
+1
-1
cmd/containerbuild/main.go
+1
docs/docs/CHANGELOG.md
+1
docs/docs/CHANGELOG.md
···
26
26
- Added headers support to bot policy rules
27
27
- Moved configuration file from JSON to YAML by default
28
28
- Added documentation on how to use Anubis with Traefik in Docker
29
+
- Improved error handling in some edge cases
29
30
- Disable `generic-bot-catchall` rule because of its high false positive rate in real-world scenarios
30
31
31
32
## v1.16.0
+1
-1
internal/headers.go
+1
-1
internal/headers.go
···
73
73
})
74
74
}
75
75
76
-
// Do not allow browsing directory listings in paths that end with /
76
+
// NoBrowsing prevents directory browsing by returning a 404 for any request that ends with a "/".
77
77
func NoBrowsing(next http.Handler) http.Handler {
78
78
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
79
79
if strings.HasSuffix(r.URL.Path, "/") {
+4
-4
internal/test/playwright_test.go
+4
-4
internal/test/playwright_test.go
···
378
378
}
379
379
380
380
func pwTimeout(tc testCase, deadline time.Time) *float64 {
381
-
max := *playwrightMaxTime
381
+
maxTime := *playwrightMaxTime
382
382
if tc.isHard {
383
-
max = *playwrightMaxHardTime
383
+
maxTime = *playwrightMaxHardTime
384
384
}
385
385
386
386
d := time.Until(deadline)
387
-
if d <= 0 || d > max {
388
-
return playwright.Float(float64(max.Milliseconds()))
387
+
if d <= 0 || d > maxTime {
388
+
return playwright.Float(float64(maxTime.Milliseconds()))
389
389
}
390
390
return playwright.Float(float64(d.Milliseconds()))
391
391
}
+27
-12
lib/anubis.go
+27
-12
lib/anubis.go
···
96
96
}
97
97
}
98
98
99
-
defer fin.Close()
99
+
defer func(fin io.ReadCloser) {
100
+
err := fin.Close()
101
+
if err != nil {
102
+
slog.Error("failed to close policy file", "file", fname, "err", err)
103
+
}
104
+
}(fin)
100
105
101
106
anubisPolicy, err := policy.ParseConfig(fin, fname, defaultDifficulty)
102
107
···
201
206
r.Header.Add("X-Anubis-Rule", cr.Name)
202
207
r.Header.Add("X-Anubis-Action", string(cr.Rule))
203
208
lg = lg.With("check_result", cr)
204
-
policy.PolicyApplications.WithLabelValues(cr.Name, string(cr.Rule)).Add(1)
209
+
policy.Applications.WithLabelValues(cr.Name, string(cr.Rule)).Add(1)
205
210
206
211
ip := r.Header.Get("X-Real-Ip")
207
212
···
258
263
if err != nil {
259
264
lg.Debug("cookie not found", "path", r.URL.Path)
260
265
s.ClearCookie(w)
261
-
s.RenderIndex(w, r, cr, rule)
266
+
s.RenderIndex(w, r, rule)
262
267
return
263
268
}
264
269
265
270
if err := ckie.Valid(); err != nil {
266
271
lg.Debug("cookie is invalid", "err", err)
267
272
s.ClearCookie(w)
268
-
s.RenderIndex(w, r, cr, rule)
273
+
s.RenderIndex(w, r, rule)
269
274
return
270
275
}
271
276
272
277
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
273
278
lg.Debug("cookie expired", "path", r.URL.Path)
274
279
s.ClearCookie(w)
275
-
s.RenderIndex(w, r, cr, rule)
280
+
s.RenderIndex(w, r, rule)
276
281
return
277
282
}
278
283
···
283
288
if err != nil || !token.Valid {
284
289
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
285
290
s.ClearCookie(w)
286
-
s.RenderIndex(w, r, cr, rule)
291
+
s.RenderIndex(w, r, rule)
287
292
return
288
293
}
289
294
···
298
303
if !ok {
299
304
lg.Debug("invalid token claims type", "path", r.URL.Path)
300
305
s.ClearCookie(w)
301
-
s.RenderIndex(w, r, cr, rule)
306
+
s.RenderIndex(w, r, rule)
302
307
return
303
308
}
304
309
challenge := s.challengeFor(r, rule.Challenge.Difficulty)
···
306
311
if claims["challenge"] != challenge {
307
312
lg.Debug("invalid challenge", "path", r.URL.Path)
308
313
s.ClearCookie(w)
309
-
s.RenderIndex(w, r, cr, rule)
314
+
s.RenderIndex(w, r, rule)
310
315
return
311
316
}
312
317
···
323
328
lg.Debug("invalid response", "path", r.URL.Path)
324
329
failedValidations.Inc()
325
330
s.ClearCookie(w)
326
-
s.RenderIndex(w, r, cr, rule)
331
+
s.RenderIndex(w, r, rule)
327
332
return
328
333
}
329
334
···
332
337
s.next.ServeHTTP(w, r)
333
338
}
334
339
335
-
func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.CheckResult, rule *policy.Bot) {
340
+
func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *policy.Bot) {
336
341
lg := slog.With(
337
342
"user_agent", r.UserAgent(),
338
343
"accept_language", r.Header.Get("Accept-Language"),
···
374
379
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
375
380
lg := slog.With("user_agent", r.UserAgent(), "accept_language", r.Header.Get("Accept-Language"), "priority", r.Header.Get("Priority"), "x-forwarded-for", r.Header.Get("X-Forwarded-For"), "x-real-ip", r.Header.Get("X-Real-Ip"))
376
381
382
+
encoder := json.NewEncoder(w)
377
383
cr, rule, err := s.check(r)
378
384
if err != nil {
379
385
lg.Error("check failed", "err", err)
380
386
w.WriteHeader(http.StatusInternalServerError)
381
-
json.NewEncoder(w).Encode(struct {
387
+
err := encoder.Encode(struct {
382
388
Error string `json:"error"`
383
389
}{
384
390
Error: "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"makeChallenge\"",
385
391
})
392
+
if err != nil {
393
+
lg.Error("failed to encode error response", "err", err)
394
+
w.WriteHeader(http.StatusInternalServerError)
395
+
}
386
396
return
387
397
}
388
398
lg = lg.With("check_result", cr)
389
399
challenge := s.challengeFor(r, rule.Challenge.Difficulty)
390
400
391
-
json.NewEncoder(w).Encode(struct {
401
+
err = encoder.Encode(struct {
392
402
Challenge string `json:"challenge"`
393
403
Rules *config.ChallengeRules `json:"rules"`
394
404
}{
395
405
Challenge: challenge,
396
406
Rules: rule.Challenge,
397
407
})
408
+
if err != nil {
409
+
lg.Error("failed to encode challenge", "err", err)
410
+
w.WriteHeader(http.StatusInternalServerError)
411
+
return
412
+
}
398
413
lg.Debug("made challenge", "challenge", challenge, "rules", rule.Challenge, "cr", cr)
399
414
challengesIssued.Inc()
400
415
}
+1
-1
lib/policy/policy.go
+1
-1
lib/policy/policy.go