A review of v0.10.4-rc.4, and a look at the live network, found: - Alertmanager listening on every interface: from any node its API took silences, with no login. Bound to loopback on the monitoring host, and the monitoring README now installs it that way; Prometheus beside it is its only client, and one Alertmanager needs no cluster port. - Login and signup unbounded in memory. Each attempt runs argon2 with 64 MiB, and nothing limited how many ran at once; the per-address throttle kept one entry per address ever tried. Hashing now takes one of a few slots and waits for one otherwise. The throttle expires entries whose window has passed when it fills, and if it is still full it refuses new addresses: evicting live ones instead would let a flood of made-up addresses reset the throttle on the address actually being guessed. - Metric labels taken from the request. A refused request counted the model name it asked for, so any key holder could mint a series per request. Requests are counted under the catalog's name, or "unknown". - Two in code written this week. A SQLite backup made its snapshot with the default mode in the listable backup directory, a full copy of the store readable while the backup ran; it is now made in a private directory, and the unit sets UMask=0077. And archive links were checked by name only, so "a -> ." then "a/a/b -> ../.." passed and a file written through them landed above the install directory, as root. Archives now unpack through os.Root, and a link may only point at a file beside it — which every llama.cpp b10991 archive, CPU and CUDA, was checked to follow. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
34 lines
976 B
Go
34 lines
976 B
Go
package account
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSprayingAddressesCannotGrowTheThrottleOrEscapeIt(t *testing.T) {
|
|
now := time.Unix(0, 0)
|
|
s := &Service{cfg: DefaultConfig(), attempts: map[string]*attemptWindow{}, now: func() time.Time { return now }}
|
|
|
|
// The target is being guessed at.
|
|
for i := 0; i < s.cfg.MaxAttempts; i++ {
|
|
s.allowAttempt("victim@example.com")
|
|
}
|
|
// Then the table is flooded with made-up addresses.
|
|
for i := 0; i < maxTrackedAddresses+100; i++ {
|
|
s.allowAttempt(fmt.Sprintf("x%d@example.com", i))
|
|
}
|
|
if n := len(s.attempts); n > maxTrackedAddresses {
|
|
t.Errorf("the table grew to %d", n)
|
|
}
|
|
if s.allowAttempt("victim@example.com") {
|
|
t.Error("flooding the table reset the target's throttle")
|
|
}
|
|
|
|
// Once the window passes, everything expires and sign-ins work again.
|
|
now = now.Add(s.cfg.AttemptWindow + time.Second)
|
|
if !s.allowAttempt("new@example.com") {
|
|
t.Error("a new address was refused after the flood expired")
|
|
}
|
|
}
|