Root cause
Resource abuse & availability introduced
A Claude-assisted change adds TOTP and backup-code checks for destructive routes with no limit on failed attempts.
AI-assisted change: Claude
How AI contributed: Direct introductionPublished Mar 31, 2026 in
jahlives/openssl_encryptAll 20 findings in this repository
openssl_encrypt's TOTP verification gained rate limiting through an in-memory defaultdict(list) held as a class variable in openssl_encrypt_server/modules/pepper/totp.py. Attempt timestamps and lockout deadlines lived inside a single process, so each extra worker and every restart started with an empty history. An attacker could spread guesses across workers or wait for a restart to keep submitting codes past the intended limit. The fix moves limiter storage behind a backend interface and adds a database implementation with its own table and expired-entry cleanup.
The Pepper module's TOTP and backup-code checks in the openssl_encrypt server had no attempt limit when a Claude-assisted change first added them, so a client holding a valid certificate could keep guessing codes on routes such as account deletion, the panic routes, TOTP disable and backup-code regeneration. Later hardening added a five-attempt limit, but it counts attempts in each process's memory, so the count resets on restart and is not shared between workers. A shared database backend exists but is not the default, and the extracted server project still ships the in-memory default, so the flaw remains unpatched. Published releases 1.4.0b3 through 1.4.1 carry it.
Root cause
A Claude-assisted change adds TOTP and backup-code checks for destructive routes with no limit on failed attempts.
AI-assisted change: Claude
Fix status
No minimum fix commit has been established for this finding.
Fix status unresolved
Case facts
@@ -0,0 +194,29 @@+ async def verify_code(self, client: PPClient, code: str) -> bool:+ """+ Verify TOTP code or backup code.++ Args:+ client: PPClient instance+ code: TOTP code (6 digits) or backup code (longer)++ Returns:+ True if code valid, False otherwise+ """+ if not client.totp_secret_encrypted or not client.totp_verified:+ return False++ # Try TOTP code first (6 digits)+ if len(code) == 6 and code.isdigit():+ secret = self._decrypt_secret(client.totp_secret_encrypted)+ totp = pyotp.TOTP(secret)+ if totp.verify(code, valid_window=1):+ logger.debug(f"TOTP code verified for {client.cert_fingerprint[:16]}")+ return True++ # Try backup code+ if await self._verify_backup_code(client.id, code):+ logger.info(f"Backup code used for {client.cert_fingerprint[:16]}")+ return True++ return False+@@ -0,0 +60,38 @@+async def verify_totp_if_enabled(+ cert_fingerprint: str,+ totp_code: Optional[str],+ db: AsyncSession+):+ """+ Verify TOTP code if 2FA enabled for client.++ Args:+ cert_fingerprint: Client certificate fingerprint+ totp_code: TOTP code from X-TOTP-Code header+ db: Database session++ Raises:+ HTTPException: If TOTP enabled but code invalid/missing+ """+ service = PepperService(db, settings.get_pepper_config())+ client = await service.get_client(cert_fingerprint)++ if client and client.totp_verified:+ # TOTP is enabled - require code+ if not totp_code:+ raise HTTPException(+ status_code=status.HTTP_403_FORBIDDEN,+ detail="TOTP code required. Provide X-TOTP-Code header."+ )++ # Verify code+ totp_service = TOTPService(+ db,+ issuer="openssl_encrypt",+ fernet_key=settings.pepper_totp_secret_key+ )+ if not await totp_service.verify_code(client, totp_code):+ raise HTTPException(+ status_code=status.HTTP_403_FORBIDDEN,+ detail="Invalid TOTP code"+ )