CreaRack-SL

Decisión: Bump coordinado Cluster C auth/crypto — fido2 v2 + cryptography 47 + allauth 65.16

Contexto

El plan post-audit Fase B agrupó las dependencias de autenticación en Cluster C por su acoplamiento rígido: fido2 1.1.x imponía un cap cryptography<45 que bloqueaba bumps de seguridad de cryptography desde hacía meses. A su vez, django-allauth[mfa] 65.15.x solo soportaba fido2<1.2. Las tres debían subirse en un único commit coordinado o no podían subirse.

Este commit cierra el Cluster C como parte del plan post-audit Fase B (sesión 31). Tras él, solo queda el Cluster F (Redis/huey 3.0), diferido a junio 2026 por madurez de huey 3.0 (release 14-04-2026).

Decisión

Bump simultáneo de las tres librerías acopladas:

DependenciaAntesDespuésNotas
cryptography>=43.0.0,<45>=47.0.0,<49Cap <45 impuesto por fido2 1.1.x — liberado al subir fido2 a v2
fido2>=1.1.0,<1.2.0>=2.2.0,<3Salto mayor v1→v2 (breaking API changes)
django-allauth[mfa]65.15.165.16.1Drop-in; acepta fido2<3,>=1.1.2

Impacto en código: core/auth_api.py::complete_biometric_setup

fido2 v2 introduce una API simplificada para el registro de passkeys. El único call site directo en el código (el resto del flujo WebAuthn lo gestiona allauth.mfa.webauthn) debía adaptarse.

Antes (fido2 v1)

from fido2.webauthn import (
    AttestationObject,
    CollectedClientData,
    PublicKeyCredentialRpEntity,
)

def b64decode(s):
    padding = 4 - len(s) % 4
    if padding != 4:
        s += "=" * padding
    return base64.urlsafe_b64decode(s)

client_data = CollectedClientData(b64decode(credential["response"]["clientDataJSON"]))
attestation_object = AttestationObject(b64decode(credential["response"]["attestationObject"]))
server.register_complete(state, client_data, attestation_object)

Después (fido2 v2)

from fido2.webauthn import PublicKeyCredentialRpEntity

# v2: register_complete acepta el dict del cliente directamente (formato webauthn-json estándar).
# La librería deserializa internamente vía RegistrationResponse.from_dict().
server.register_complete(state, credential)

Eliminado: imports CollectedClientData, AttestationObject; helper local b64decode; deserialización manual de los dos blobs binarios (clientDataJSON, attestationObject).

Sin cambios: Fido2Server(rp, verify_origin=lambda) — firma estable en v2. PublicKeyCredentialRpEntity(id=..., name=...) — ya usaba kwargs (kwargs-only en v2 desde 2.0.0).

Alternativas consideradas

  • No subir: inviable a largo plazo — el cap cryptography<45 bloquea CVEs de seguridad futuros.
  • Subir solo cryptography: imposible mientras fido2 1.1.x imponga cryptography<45.
  • Subir fido2 sin adaptar auth_api.py: build roto — CollectedClientData y AttestationObject eliminados de la API pública en v2.
  • Esperar a fido2 2.3+: innecesario; v2.2.0 es estable y allauth 65.16 la soporta explícitamente.

Riesgos y validación

El flujo de registro de passkeys desde login page es el único vector de riesgo real. El resto del ciclo passkey (login passwordless, TOTP coexistencia) lo gestiona allauth internamente y no tocó código custom.

Tests STAGE obligatorios antes de PROD (sin tests unitarios automatizables — WebAuthn requiere hardware autenticador real):

  1. Registro de passkey desde login page (complete_biometric_setup)
  2. Login passwordless con passkey registrada
  3. Coexistencia TOTP + passkey (misma cuenta)
  4. Logout vía POST

Lección aprendida

Los caps cruzados entre dependencias merecen revisión periódica. El cap cryptography<45 llevaba meses bloqueando bumps de seguridad, pero la causa raíz era fido2 viejo. A veces la “imposibilidad” de bumpear A es porque B está desactualizado. Registrar caps con comentario de causa en requirements.txt (como ya hace este repo) es clave para detectarlo.

Estado del plan post-audit Fase B

  • Clusters cerrados: 10/10 + drift Regla 8 ✅
  • Cluster C (auth/crypto): cerrado en esta sesión ✅
  • Cluster F (Redis/huey 3.0): diferido a junio 2026 ⏳

Véase también

  • [[entity—core—function—complete-biometric-setup]]
  • [[concept—security—webauthn-passkeys]]
  • [[decision—20260426—cluster-j2-python-nmap]]
  • [[runbook—infra—stage-passkeys-validation]]
  • [[concept—core—dependency-audit-fase-b]]