package blob import ( "strings" "testing" "time" ) func testStore(t *testing.T) *Store { t.Helper() s, err := New(Config{ Region: "sgp1", Endpoint: "sgp1.example.com", Bucket: "b", AccessKey: "AK", SecretKey: "SK", Prefix: "behavision", }) if err != nil { t.Fatal(err) } return s } func TestNewNamesTheMissingSetting(t *testing.T) { _, err := New(Config{Region: "sgp1"}) if err == nil { t.Fatal("an empty config was accepted") } // A deploy fails at 9am in a shop, not at a keyboard, so the error has to // say which variable is missing. if !strings.Contains(err.Error(), "is missing") { t.Fatalf("unhelpful error: %v", err) } } // The server builds keys from the credential the request authenticated with, // so a site cannot write into another site's prefix. This is the property that // lets uploads be presigned instead of handing shop PCs a bucket key. func TestKeyIsNamespacedAndCannotEscapeItsPrefix(t *testing.T) { s := testStore(t) at := time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC) got := s.Key("acme", "store1", "aaaa-bbbb", at) want := "behavision/acme/store1/2026/08/31/aaaa-bbbb.jpg" if got != want { t.Fatalf("got %q, want %q", got, want) } // Slugs are constrained by a CHECK and visit ids are uuids, so this should // never fire - which is why it is cheap to keep. One "../" reaching a key // builder is a cross-tenant overwrite. evil := s.Key("../../rival", "../store9", "../../../etc/passwd", at) if strings.Contains(evil, "..") || strings.Contains(evil, "/etc/") { t.Fatalf("a key escaped its prefix: %q", evil) } if !strings.HasPrefix(evil, "behavision/") { t.Fatalf("key left the deployment prefix: %q", evil) } } // The agent sends back the key it was given and a buggy or compromised one // could send any string. Without this the server would presign reads for // arbitrary objects in a bucket it shares with another application. func TestOwnsKeyRejectsAnythingOutsideThePrefix(t *testing.T) { s := testStore(t) for _, key := range []string{ "", "Profile/93/user_profile-28.jpg", "behavision/../Profile/x.jpg", "behavision//x.jpg", "other/behavision/x.jpg", } { if s.OwnsKey(key) { t.Errorf("OwnsKey(%q) = true", key) } } if !s.OwnsKey("behavision/acme/store1/2026/08/31/x.jpg") { t.Error("a legitimate key was rejected") } } func TestPresignRefusesForeignKeys(t *testing.T) { s := testStore(t) if _, err := s.PresignGet("Profile/93/user_profile-28.jpg", time.Minute); err == nil { t.Fatal("presigned a read for another application's object") } if _, _, err := s.PresignPut("Profile/93/x.jpg", time.Minute); err == nil { t.Fatal("presigned a write outside our prefix") } } // A presigned URL is a bearer token for one object. A day-long one forwarded // in an email outlives every reason it was issued for. func TestPresignClampsAbsurdLifetimes(t *testing.T) { s := testStore(t) for _, ttl := range []time.Duration{0, -time.Hour, 72 * time.Hour} { u, err := s.PresignGet("behavision/a/b/2026/08/31/x.jpg", ttl) if err != nil { t.Fatal(err) } if !strings.Contains(u, "X-Amz-Expires=900") { t.Errorf("ttl %s was not clamped to 15 minutes: %s", ttl, u) } } } func TestPresignedPutSignsThePrivateACL(t *testing.T) { s := testStore(t) u, hdr, err := s.PresignPut("behavision/a/b/2026/08/31/x.jpg", time.Minute) if err != nil { t.Fatal(err) } // Signed, so the agent must send it and cannot choose to publish instead. if !strings.Contains(u, "x-amz-acl") { t.Fatalf("the ACL is not part of the signature: %s", u) } if hdr.Get("x-amz-acl") != "private" { t.Fatalf("caller is not told to send a private ACL: %v", hdr) } if strings.Contains(u, s.cfg.SecretKey) { t.Fatal("the secret key is in the URL") } }