docs(api): record the quota-claim TOCTOU as a KNOWN-LIMITATION (audit #4)

QuotaAvailable and ClaimServer run as two separate statements, so the
count read is not serialized against a concurrent claim's UPDATE: two
claims by one user for two different ownerless servers can both pass the
gate and both succeed, leaving the user one server over quota. It is low
severity — quota over-provisioning under a deliberate burst, not an
authorization, ownership, or isolation break, since each server is still
claimed atomically via UPDATE ... WHERE owner_id IS NULL.

Closing it requires Postgres transaction semantics (advisory-xact-lock on
the user, or SERIALIZABLE with retry) folding the gate into a single repo
method — verifiable only against a real Postgres, not the hermetic
fakeRepo suite. Documented at QuotaAvailable with back-references from the
two claim gates (handleClaim and the internal UUID claim) rather than
patched blind.
This commit is contained in:
flyemoji committed 2026-07-01 22:37:36 +09:00
1 parent d6e3189629
commit 2c56d17989
3 files changed
+22 -2

No files matched your search

+3 -1
View File
@@ -120,7 +120,9 @@ func (a *API) handleClaim(w http.ResponseWriter, r *http.Request) {
return
}
// ② quota gate, evaluated before the ownership write
// ② quota gate, evaluated before the ownership write. This gate and ③ are two
// separate statements, not one transaction — see the quota TOCTOU KNOWN-LIMITATION
// on QuotaAvailable (audit #4, ENV-blocked: needs real Postgres to close/verify).
ok, err := a.Repo.QuotaAvailable(r.Context(), p.UserID)
if err != nil {
writeError(w, r, err)