A06: Diseno Inseguro (Blog)
¿Que es el diseno inseguro?
El Diseno Inseguro ocurre cuando las reglas de seguridad no existen a nivel de caso de uso. El codigo puede estar "correcto" y aun asi el flujo ser explotable porque no se disenan abusos, restricciones e invariantes.
Fallo tipico en Laravel
- Construir features primero, seguridad despues.
- Sin casos de abuso.
- Sin restricciones de workflow para operaciones criticas (publicar posts).
- Sin rate limits/moderacion para comentarios.
- Sin aprobacion o doble control para acciones peligrosas.
Impacto
- Abuso de logica de negocio (el atacante usa tu flujo valido en tu contra).
- Exposicion de borradores/contenido privado si no modelas visibilidad.
- Cambios criticos sin control (pagos, roles, transferencias de propiedad).
- Sin rastro forense cuando falta auditoria.
Remediacion en Laravel 12
- Disena reglas de seguridad antes de codificar endpoints.
- Modela abusos en el propio caso de uso.
- Usa Actions como limites de caso de uso.
- Para Actions criticas, exige invariantes:
- actor autorizado
- transicion de estado permitida
- evento de auditoria emitido
- Introduce rate limits, idempotencia, flujos de aprobacion y auditoria inmutable cuando aplique.
Arreglo concreto
Trata autorizacion + invariantes como las primeras lineas de tu Action (publicar posts):
use Illuminate\Support\Facades\Gate; final class PublishPostAction{ public function handle(User $actor, Post $post): void { // invariante: actor autorizado a escribir posts Gate::authorize('update', $post); // invariante: transicion de estado permitida abort_unless($post->status === 'draft', 422); // cambio de estado $post->update(['status' => 'published']); // invariante: evento de auditoria emitido // event(new PostPublished($post->id, $actor->id)); }}
Enfoque de patron de diseno
Usa un Action como limite y refuerza invariantes con una cadena pequena tipo pipeline / decorador. Asi mantienes checks criticos consistentes entre casos de uso (auth, ownership/visibilidad, transicion de estado, auditoria).
interface Invariant{ public function check(array $context): void;} final class EnsureDraftStatus implements Invariant{ public function check(array $context): void { /** @var Post $post */ $post = $context['post']; abort_unless($post->status === 'draft', 422); }} final class InvariantRunner{ /** @param array<int, Invariant> $invariants */ public function __construct(private array $invariants) {} public function run(array $context): void { foreach ($this->invariants as $invariant) { $invariant->check($context); } }} // Uso dentro de un Action$runner = new InvariantRunner([ new EnsureDraftStatus(), // new EnsureAuditEnabled(),]); $runner->run([ 'actor' => $actor, 'post' => $post,]); // continuar con el caso de uso