Resumen
En Lovable, el propietario de un proyecto puede compartirlo mediante un enlace de invitación. La interfaz solo deja poner ese enlace en "View access" (solo lectura) si la cuenta tiene el plan Pro; con el plan gratuito hay que quedarse en "Edit access".
Esa restricción no se aplicaba en el servidor. El endpoint que genera el enlace, POST /projects/<ID>/magic-codes en api.lovable.dev, aceptaba el valor read en el parámetro access_level sin comprobar la suscripción. Interceptando la petición y cambiando ese valor, una cuenta gratuita obtenía un enlace de invitación con rol de lector que funcionaba de verdad: la persona invitada entraba al proyecto con permiso viewer.
El programa (lovable-vdp) lo clasificó con severidad media y lo dio por resuelto. El reporte se envió el 8 de marzo de 2026 y se divulgó el 16 de marzo de 2026.
Pasos de reproducción
Hacen falta dos cuentas: la del propietario del proyecto (en plan gratuito) y la de quien recibirá la invitación, además de un proxy de interceptación para modificar las peticiones.
- Con la cuenta del propietario, crea un proyecto: elige cualquier plantilla, pulsa el botón para usarla, ponle nombre y pulsa Remix.
- Abre el proyecto creado desde la sección
Projects. - Pulsa
share, arriba a la derecha. En el enlace de invitación se indica que hace falta el plan Pro para cambiar el acceso aView access. - En el enlace de invitación, pulsa
Disabled. - Con la interceptación activada, vuelve a poner el acceso en
Edit access. - Primero sale una petición
OPTIONSal endpoint de proyectos: déjala pasar y espera a la peticiónPOST. - En esa petición
POST, cambia el parámetroaccess_leveldewriteareade intercepta también la respuesta. Ejemplo de la petición ya modificada, tal como aparece en el reporte:
POST /projects/Project-ID/magic-codes HTTP/2
Host: api.lovable.dev
Cookie: [Your-Cookie]
User-Agent: []
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://lovable.dev/
Authorization: Bearer [Your-Authorization-Header]
Content-Type: application/json
Content-Length: 23
Origin: https://lovable.dev
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Priority: u=0
Te: trailers
{"access_level":"read"}
El único cambio respecto a la petición original es el cuerpo:
-{"access_level":"write"}
+{"access_level":"read"}
- La respuesta refleja el permiso
readsin ningún error. - Vuelve al proyecto y recarga la página. Al pulsar
share, el enlace aparece ahora conView access, aunque la cuenta sigue en el plan gratuito. - Copia el enlace de invitación, ábrelo con la segunda cuenta y acepta la invitación al proyecto.
- En
share→people you invitedse ve que la segunda cuenta ha entrado con el permisoviewer.
Impacto
- Un usuario del plan gratuito puede usar una función que Lovable reserva a los suscriptores Pro (invitar con rol de solo lectura) sin pagar por ella.
- Para la plataforma supone un uso no autorizado de funcionalidad de pago y un posible perjuicio económico al saltarse la suscripción.
- Desde el punto de vista de la seguridad, afecta sobre todo a la integridad de las reglas de negocio: el sistema no validaba qué permisos podía asignar cada usuario según su plan.
Qué aprender de este caso
- Si una opción aparece bloqueada en la interfaz "por plan", prueba a mandarla igualmente: aquí bastaba con cambiar
writeporreaden el cuerpo de la petición que crea el enlace de invitación. - Los endpoints que generan invitaciones, enlaces mágicos o enlaces compartidos son buen sitio donde buscar: suelen recibir el rol o nivel de acceso como parámetro, y es fácil que el servidor no compruebe si quien lo pide tiene derecho a concederlo.
- Al programar, la comprobación de suscripción tiene que hacerse en el servidor, en el mismo endpoint que recibe
access_level, comparando el valor pedido con los roles que permite el plan del usuario, y no solo deshabilitando la opción en el selector. - Conviene repetir esa comprobación al aceptar la invitación: así un enlace generado de forma indebida, o antes de un cambio de plan, no concede permisos de pago.