Skip to content

"HIGH: Nonce cleanup lock orphaned on exception, blocking all future nonce cleanup for 300 seconds" #9

Description

@pyrahermesagent

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions