Registro de la auditoria de autorizacion y de las reglas que hay que respetar al tocar la API. Informe con formato para leer/compartir: artefacto publicado en claude.ai (privado del propietario del repo).
Ultima auditoria: commit a8a94e8 — 60 rutas de API revisadas, 5 hallazgos,
5 corregidos.
Cualquier cambio en apps/web/src/app/api/** debe seguir cumpliendo esto:
- El contenido de una leccion es el producto.
videoUrl,contentyresourcessolo se serializan si quien pregunta es miembro activo o gestor. Nunca se filtra "en el cliente": si el dato sale del servidor, ya se perdio. Los titulos si son publicos (indice = escaparate). - Contenido interno de una comunidad (feed, miembros, ranking, chat) pasa
por
canReadCommunity()delib/community.ts: publico solo si la comunidad es publica Y gratuita; si cobra o es privada exige miembro activo, gestor o super admin. currentUser()es la unica puerta de sesion y ya rechaza cuentas condeletedAt. No reimplementar la lectura del JWT por fuera.- Escribir siempre exige membresia activa (publicar, comentar, dar me gusta, chatear), no solo tener sesion.
- Acciones sensibles (contrasena, 2FA, medios de pago, borrado de cuenta,
cuentas de cobro) pasan por
verifyStepUp(): PIN por correo o codigo 2FA. - Subir archivos exige correo verificado y esta limitado por usuario.
- Nada de HTML sin escapar. El unico
dangerouslySetInnerHTMLpermitido es el derenderMarkdown(), que escapa el HTML antes de generar etiquetas y solo acepta URLshttp(s). - Listas blancas en los
zodde los PATCH de usuario: jamas aceptarplatformRole,producerStatusni nada que permita ascenderse solo.
No basta con leer el codigo: hay que probar contra el servidor.
cd apps/web && npx next dev -p 3100Se firman sesiones reales con el JWT_SECRET de apps/web/.env (jose +
SignJWT) y se llama a la API con la cookie klanly_session.
Probar SIEMPRE las dos direcciones:
- que nadie vea de mas (fuga),
- que el owner y el super admin no vean de menos (regresion que rompe el producto a quien si pago).
Para el caso "con sesion pero NO miembro" hay que crear un usuario temporal en la base de datos —todos los usuarios reales son miembros de algo— y borrarlo al terminar.
Matriz obtenida en la ultima auditoria, sobre una comunidad de pago:
| Quien pregunta | feed | miembros | ranking | chat | contenido |
|---|---|---|---|---|---|
| Sin sesion | 401 | 401 | 401 | 401 | 0/7 |
| Con sesion, no miembro | 403 | 403 | 403 | 403 | 0/7 |
| Productor (owner) | 200 | 200 | 200 | 200 | 7/7 |
| Super admin | 200 | 200 | 200 | 200 | 7/7 |
| ID | Severidad | Que pasaba | Correccion |
|---|---|---|---|
| H-01 | Critico | GET /api/courses/[id] devolvia video, texto y material de todas las lecciones sin sesion: el muro de pago solo existia en la interfaz |
Contenido solo para miembros activos y gestores, con doble candado membresia + nivel |
| H-02 | Alto | GET de feed, miembros y ranking sin ninguna comprobacion |
Helper canReadCommunity() |
| H-03 | Alto | currentUser() no miraba deletedAt: el JWT de una cuenta borrada servia 7 dias |
Se rechaza en cada peticion |
| H-04 | Medio | /api/upload solo pedia sesion: cuentas desechables podian llenar el almacenamiento |
Correo verificado + 20/min por usuario |
| H-05 | Medio | Sin Content-Security-Policy |
Anadida en next.config.mjs, verificada sin violaciones |
- Rate limit en memoria.
lib/ratelimit.tsvive en la instancia serverless, que Vercel crea y destruye: frena a una persona insistiendo, no a un ataque distribuido. Migrar a Vercel KV o Upstash cuando haya trafico real. - Cambiar la contrasena no cierra las demas sesiones. Falta un
tokenVersionenusersque invalide los JWT anteriores. Recomendable antes de abrir la plataforma a mucha gente. - La CSP permite
unsafe-inline/unsafe-evalenscript-srcporque lo exige el runtime de Next 14. Endurecerlo pide nonces por peticion desde un middleware. - El APK se firma con la clave de depuracion. Google Play exige una clave propia guardada fuera del repo, y no se puede cambiar despues de publicar.
- Los titulos de las lecciones son publicos — decision de producto, no
descuido. Revertirlo es una linea en
courses/[id]/route.ts. - Correo por Gmail SMTP sin dominio propio: sin SPF/DKIM los avisos caen en spam. Ver el apartado de entregabilidad en el historial del proyecto.