Autorización en el código generado por IA: la policy que existe y no se llama
Hola, bienvenido. En esta lección vamos a corregir el fallo de autorización de invoices. InvoicePolicy existe, Laravel la descubre sola y los métodos que editan y borran facturas la llaman, pero el detalle de una factura y la descarga de su adjunto no. Así que cualquier usuario con sesión que tenga la URL de una factura de otro la ve entera y se descarga su adjunto. Vamos a escribir el test que lo demuestra, a añadir la llamada a la policy en los 2 sitios y a dejar el test en composer qa.
Fíjate en que es una omisión, el tipo de fallo más frecuente en el código de los agentes, como vimos en la lección 1. Todas las líneas que escribió el agente están bien, y por eso al revisar no salta nada a la vista.
Qué comprueba cada acción de facturas
Partimos de invoices tal como lo dejamos en la lección anterior. Si recorremos InvoiceController y el controlador de la descarga, cada acción comprueba a su manera que la factura es del usuario, salvo 2:
| Acción | Cómo comprueba de quién es la factura |
|---|---|
index |
Solo lista $request->user()->invoices() |
create y store |
Solo ofrece y acepta clientes del usuario, con Rule::exists filtrado por user_id |
show |
No lo comprueba |
edit |
Gate::authorize('update', $invoice) |
update |
El authorize() de UpdateInvoiceRequest, con la misma policy |
destroy |
Gate::authorize('delete', $invoice) |
| Descarga del adjunto | No lo comprueba |
¿Cómo se llega a esto con un agente que ha escrito la policy él mismo? Si miras el plan de la lección 3, la policy aparece al final de la tarea de las facturas, en la misma línea que el listado, el detalle, la edición y el borrado, sin decir dónde hay que llamarla. show solo tiene que enseñar una factura, y la enseña. La descarga fue otra tarea del plan, "Adjunto de la factura en el disco local y descarga con InvoiceAttachmentController", y en esa tarea nadie habló de autorización. El resultado funciona y los tests pasan.
Con los tests pasa lo que vimos en la lección 1. Fíjate en el último test de InvoiceTest: comprueba que otro usuario no puede editar ni borrar una factura ajena, y no dice nada de verla. El agente escribió tests de lo que la aplicación hace y de lo que él protegió, así que lo que no protegió tampoco tiene test.
Este fallo tiene nombre: IDOR, referencia directa insegura a un objeto (Insecure Direct Object Reference). OWASP lo define como la vulnerabilidad que aparece cuando se puede acceder a un objeto cambiando el identificador que viaja en la URL o en los parámetros, porque la aplicación no comprueba si ese objeto es de quien lo pide. El OWASP Top 10:2025 lo incluye en A01, Broken Access Control. En invoices el identificador es el id de /invoices/{invoice}, y el route model binding de Laravel busca la factura entre todas las de la tabla, no entre las del usuario. La propia hoja de OWASP sobre IDOR avisa de que cambiar los ids por identificadores difíciles de adivinar, como un UUID, es una capa más y nunca sustituye a la comprobación de acceso.
En la lección 1 vimos la nota de la AEPD del 14 de septiembre de 2026, en la que un agente hizo un login correcto y, una vez dentro, buscó vulnerabilidades en la aplicación hasta acceder a facturas. La nota no dice qué vulnerabilidad encontró, pero el punto de partida es el mismo que el de este fallo: alguien que ya tiene sesión en la aplicación.
En esta lección nos quedamos con la forma más habitual en el código de un agente: una llamada a la policy que nadie escribió. Si lo que necesitas es un sistema de roles y permisos completo, lo tienes en Sistema de Roles y Permisos Personalizado con Laravel 12 (se abre en una pestaña nueva).
- 02El test: la factura de otro usuario devuelve 403
- 03La solución: llamar a la policy en show y en la descarga
- 04El test se queda en composer qa