Severity: HIGH
File: ApiApp/managers.py
Lines: 35-46
Description
NonceManager.cleanup() sets a cache lock before deleting expired nonces, but if an exception occurs between setting the lock and completing the deletion, the lock is never released. Since the lock timeout is 300 seconds (5 minutes), all subsequent nonce cleanup attempts are silently skipped for 5 minutes.
Current code:
def cleanup(self) -> int:
if cache.get(NONCE_CLEANUP_LOCK_KEY):
return 0
cache.set(NONCE_CLEANUP_LOCK_KEY, True, timeout=ATTESTATION_NONCE_CLEANUP_TIMEOUT_SECONDS)
cutoff = timezone.now() - timedelta(seconds=ATTESTATION_NONCE_EXPIRY_SECONDS)
return self.filter(created_at__lt=cutoff).delete()[0] # <-- If this raises, lock is orphaned
Impact:
- Silent data accumulation: Expired nonces accumulate in the database indefinitely during the orphan period
- Repeated crashes: If the exception is persistent (e.g., DB connectivity issue), every request that triggers cleanup will fail and re-orphan the lock
- Cache poisoning: The lock key stays in cache forever if the process crashes before cleanup completes
Root Cause
No try/finally block to release the lock. The lock is set unconditionally but never cleaned up on failure.
Fix
Use try/finally to ensure the lock is always released, and catch exceptions gracefully:
def cleanup(self) -> int:
if cache.get(NONCE_CLEANUP_LOCK_KEY):
return 0
cache.set(NONCE_CLEANUP_LOCK_KEY, True, timeout=ATTESTATION_NONCE_CLEANUP_TIMEOUT_SECONDS)
try:
cutoff = timezone.now() - timedelta(seconds=ATTESTATION_NONCE_EXPIRY_SECONDS)
return self.filter(created_at__lt=cutoff).delete()[0]
finally:
cache.delete(NONCE_CLEANUP_LOCK_KEY)
Also consider using cache.add() instead of cache.set() for the lock to avoid race conditions where two workers both acquire the lock simultaneously.
Severity: HIGH
File:
ApiApp/managers.pyLines: 35-46
Description
NonceManager.cleanup()sets a cache lock before deleting expired nonces, but if an exception occurs between setting the lock and completing the deletion, the lock is never released. Since the lock timeout is 300 seconds (5 minutes), all subsequent nonce cleanup attempts are silently skipped for 5 minutes.Current code:
Impact:
Root Cause
No
try/finallyblock to release the lock. The lock is set unconditionally but never cleaned up on failure.Fix
Use
try/finallyto ensure the lock is always released, and catch exceptions gracefully:Also consider using
cache.add()instead ofcache.set()for the lock to avoid race conditions where two workers both acquire the lock simultaneously.