fix(s3tables,s3vectors): bind DeleteNamespace and the vector bucket cleanup to the authorized records (#12331)

* s3tables: bind DeleteNamespace to the authorized namespace

The delete claimed whatever finished namespace held the name at claim
time, so one deleted and created again by another owner after the
authorization read was removed under the first one's authorization.
The claim now requires the creation time that was authorized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* s3vectors: bind DeleteVectorBucket's namespace cleanup to the authorized bucket

After the bound DeleteTableBucket reported the bucket not empty, the
index namespace cleanup ran unbound, so a bucket deleted and created
again in between lost its empty index namespace under the first
bucket's authorization. DeleteNamespace now honors the bucket binding,
checking the bucket after reading the namespace, and the cleanup
carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 authored and GitHub committed 2026-11-02 09:55:09 -08:00
1 parent 984eac6a79
commit 7298b4ed37
5 files changed
+84 -7

No files matched your search

@@ -583,3 +583,28 @@ func TestUpdateNamespaceSparesARecreatedNamespace(t *testing.T) {
assert.Equal(t, http.StatusConflict, code, "%+v", resp)
assert.Equal(t, recreated, fs.Get(bucketPath, "ns").Extended, "the new namespace is untouched")
}
// DeleteNamespace removes only the namespace it was authorized against: one
// deleted and created again before the claim reads as the first one gone.
func TestDeleteNamespaceSparesARecreatedNamespace(t *testing.T) {
fs, m := startEmptyBucket(t)
requireOK(t, m, fs, "CreateNamespace", createNamespaceNsRequest(t))
bucketPath := GetTableBucketPath(renameTestBucket)
recreated := recreatedNamespace(t, "ns")
fs.OnUpdate = func(dir, name string) {
if dir != bucketPath || name != "ns" {
return
}
fs.OnUpdate = nil
fs.Put(bucketPath, "ns", recreated)
}
code, resp := runHandler(t, m, fs.Client, "DeleteNamespace", &DeleteNamespaceRequest{
TableBucketARN: mustBucketARN(t),
Namespace: []string{"ns"},
})
assert.Equal(t, http.StatusNotFound, code, "%+v", resp)
require.NotNil(t, fs.Get(bucketPath, "ns"), "the new namespace survives")
assert.Equal(t, recreated, fs.Get(bucketPath, "ns").Extended, "the new namespace is untouched")
}