← Volver a mis trabajos

Muestras de mi trabajo / Auditoría de seguridad

Study case de
ciberseguridad:
Pawsi.

Detecté y corregí dos vulnerabilidades en el sistema de mensajes de mi aplicación. Preparé un laboratorio, probé los cambios y comprobé su funcionamiento en producción.

Pawsi
Mi responsabilidad
Desarrollo, auditoría y correcciones
Alcance del caso
Dos vulnerabilidades del sistema de mensajes
Producto
Web y Android con Capacitor
Revisión de seguridad
Octubre de 2026
01 / El producto

Descripción y trayectoria
de Pawsi

Pawsi nació para ayudar a reunir mascotas con sus familias: publicar animales perdidos, compartir avistamientos y encontrar adopciones. La lancé como webapp en septiembre de 2025 y ganó la Hackathon del Milagro en Salta con su MVP web.

Lanzamiento de la webapp

Publicación y primeras personas utilizando Pawsi.

Llegada a Android

Publicación en Google Play con Capacitor y una base web compartida.

Auditoría y mejoras de seguridad

Laboratorio local, restauración selectiva y correcciones del sistema de mensajes.

También hubo que salir a contarlo.

Participé en más de 30 entrevistas para presentar el proyecto. Explicar su funcionamiento a personas sin conocimientos técnicos fue parte del trabajo de construirlo. La aplicación sigue funcionando, con menor actividad que durante su pico inicial.

Archivo de prensa y piezas de difusión del proyecto. Las imágenes se pueden ampliar.

02 / Arquitectura

Entender cómo se
conecta cada parte.

La interfaz usa React y TypeScript. Capacitor permite distribuirla en Android. Supabase aporta autenticación, PostgreSQL y funciones de backend; Cloudflare R2 y el CDN sirven las imágenes, y Firebase entrega las notificaciones push.

Web / AndroidReact · Capacitor
SupabaseAuth · API · PostgreSQL
ImágenesCloudflare R2 + CDN
Eventos firmadosEdge Functions → Firebase
Vista simplificada. El cliente consulta datos e imágenes; el backend controla el envío de notificaciones.
Decisión de caché

Los medios tienen un TTL de un año en Cloudflare y en el navegador. Al reemplazar o retirar una imagen hay que considerar las copias existentes: purgar el CDN no borra la caché del navegador.

La base de datos y los archivos necesitan recuperarse por separado. Recuperar un registro con la URL de una imagen no recupera el archivo al que apunta.

03 / Laboratorio

Un lugar para probar
antes de cambiar.

Preparé Supabase local con Docker y WSL y conecté el frontend a sus propias credenciales. Las integraciones externas se deshabilitaron o sustituyeron durante las pruebas.

El primer arranque mostró un problema concreto: las migraciones intentaban alterar una tabla que todavía no existía. Reconstruí el entorno mediante una restauración selectiva del respaldo PostgreSQL y comparé los conteos de tablas y registros.

Qué se comprobó

Recuperación parcial de estructura y datos. Las pruebas posteriores usaron cuentas sintéticas y se comprobó que no dejaran registros de prueba.

Qué quedó fuera

Archivos de almacenamiento, sesiones y configuración completa de proveedores. El ensayo no demuestra una recuperación integral ni establece un RTO o RPO medido.

Tener un respaldo y saber qué permite recuperar son dos trabajos distintos.

04 / Vulnerabilidad 1

Invocaciones no autorizadas
de notificaciones

El flujo anterior permitía solicitar notificaciones desde el cliente sin validar suficientemente la autorización del envío. Trasladé esa decisión a un evento generado por la base de datos al guardar un mensaje válido.

  1. El cliente autenticado guarda un mensaje bajo los controles de la base de datos.
  2. Un trigger crea el aviso interno y un evento privado con su destinatario.
  3. La base firma una solicitud con HMAC y una marca temporal.
  4. La Edge Function valida la firma y reclama el evento de forma atómica.
  5. Si corresponde el envío, llama a Firebase y registra el resultado.

El endpoint anterior conserva la compatibilidad con la app publicada, pero ya no envía notificaciones. No fue necesario exigir una nueva versión de Android.

Evitar duplicados y ráfagas

Agregué vencimiento de eventos, límites de frecuencia y un único intento por evento. Activar el envío no procesa mensajes históricos. Los eventos que exceden los límites se omiten y no quedan acumulados para después.

Ver las 23 comprobaciones del laboratorio

Resultados registrados el 20 de octubre de 2026. Auth, PostgreSQL, pg_net y Edge Runtime locales; proveedor push simulado.

  1. Instalar/reaplicar no genera avisos de mensajes anterioresAPROBADO
  2. INSERT crea aviso in-app aun con push desactivadoAPROBADO
  3. Activar push no procesa historial ni eventos desactivadosAPROBADO
  4. INSERT con cliente antiguo dispara webhook firmado y un pushAPROBADO
  5. 20 llamadas antiguas con identidades inventadas no producen avisosAPROBADO
  6. Marcar leído y editar mensaje no disparan nuevos avisosAPROBADO
  7. Repetir INSERT con mismo ID no duplica avisosAPROBADO
  8. Repetir webhook ya enviado no duplica pushAPROBADO
  9. Sin firma, con JWT de usuario o con clave pública: 401APROBADO
  10. Firma adulterada: 401APROBADO
  11. Firma de un evento no sirve para otro: 401APROBADO
  12. Firma de más de dos minutos: 401APROBADO
  13. Usuario no puede reclamar ni finalizar eventos por RPCAPROBADO
  14. Ráfaga de 15 mensajes al mismo destinatario no agrega pushAPROBADO
  15. Tope global alcanzado omite el push, sin cola de reintentosAPROBADO
  16. Proveedor falla: mensaje y aviso quedan guardadosAPROBADO
  17. Proveedor falla: repetición no reenvíaAPROBADO
  18. 10 reclamos concurrentes de evento nuevo hacen un solo envío con snapshot originalAPROBADO
  19. Evento viejo con firma reciente se descarta sin envíoAPROBADO
  20. Fallo de configuración no impide guardar mensaje ni avisoAPROBADO
  21. Error de INSERT del aviso no revierte mensaje; queda registradoAPROBADO
  22. ROLLBACK de mensaje tampoco deja aviso ni eventoAPROBADO
  23. Reaplicar parche no recorre mensajes existentesAPROBADO

La comprobación en producción

Durante el despliegue apareció un 503: el nombre del secreto no coincidía con el esperado por la función. Corregida la configuración, llegó la notificación a Android. Una llamada sin firma recibió 401 Unauthorized.

La contrapartida

Un único intento reduce la duplicación, pero un fallo después de reclamar el evento puede perder el push. El aviso dentro de la aplicación y el push son mecanismos distintos.

05 / Vulnerabilidad 2

Permisos excesivos
para modificar mensajes

La política RLS permitía al destinatario actualizar el mensaje para marcarlo como leído. Sin embargo, el permiso SQL de actualización alcanzaba todas las columnas: también podía cambiar el asunto o el contenido.

Reproduje el cambio de asunto en el laboratorio, retiré el permiso general y concedí únicamente la actualización de read_at. RLS sigue decidiendo qué mensajes puede modificar cada persona.

Alcance de actualización del destinatario
CampoAntesDespués
Asunto y contenidoPermitidoDenegado
Identificadores y otras fechasPermitidoDenegado
Estado de lecturaPermitidoPermitido
Ver las 15 comprobaciones de permisos

Resultados del 23 de octubre de 2026 con cuentas sintéticas. La primera comprobación reproduce el permiso excesivo antes de la corrección.

  1. Antes: destinatario podía cambiar asuntoAPROBADO
  2. Destinatario marca como leídoAPROBADO
  3. UPDATE denegado: idAPROBADO
  4. UPDATE denegado: sender_idAPROBADO
  5. UPDATE denegado: recipient_idAPROBADO
  6. UPDATE denegado: post_idAPROBADO
  7. UPDATE denegado: post_typeAPROBADO
  8. UPDATE denegado: subjectAPROBADO
  9. UPDATE denegado: contentAPROBADO
  10. UPDATE denegado: created_atAPROBADO
  11. UPDATE denegado: updated_atAPROBADO
  12. RLS rechaza marcar leído: remitenteAPROBADO
  13. RLS rechaza marcar leído: terceroAPROBADO
  14. Anónimo sin UPDATEAPROBADO
  15. Marcar leído no crea eventos adicionalesAPROBADO

En producción comprobé los permisos efectivos y que el estado de lectura persistiera entre dos cuentas. El frontend ya actualizaba solo ese campo, por lo que no necesitó cambios.

RLS limita las filas.
Los privilegios limitan las columnas.

06 / Resultados

De una intención
a un control verificable.

Las reglas del producto necesitaban quedar reflejadas en el backend: quién puede modificar un mensaje, qué campos puede tocar y qué evento habilita una notificación.

Documenté el problema, la decisión, las pruebas y sus límites. El caso presenta la detección y corrección de dos vulnerabilidades del sistema de mensajes, además de una recuperación parcial en laboratorio. Las comprobaciones no constituyen una certificación de seguridad de toda la aplicación.

El caso, para llevar.

Historia, arquitectura, decisiones y resultados en un documento de tres páginas.

Conversemos

Datos de contacto

Disponible para trabajo remoto o presencial, con disponibilidad para viajar. Podemos conversar en español o inglés.

Tel. +543875044912diegopuzio@gmail.com