A01: Control de Acceso Roto (Blog)
¿Qué es el control de acceso roto?
El Control de Acceso Roto ocurre cuando una aplicación permite que un usuario realice acciones o acceda a datos que no debería poder. En un blog suele verse cuando se considera suficiente que el usuario esté autenticado, pero no se aplican de forma consistente las reglas de autor (solo autores pueden escribir posts) y la propiedad del recurso.
Fallo típico en Laravel (blog)
- Verificas
auth()pero olvidas la regla de negocio (solo autores pueden crear posts). - Permites que usuarios editen posts/comentarios que no les pertenecen.
- Expones IDs y permites que usuarios accedan a registros que no les pertenecen.
- Olvidas checks de policies en Actions, controllers, componentes Livewire o endpoints API.
Impacto
- Escalamiento horizontal de privilegios (leer/actualizar/eliminar registros de otros usuarios).
- Fuga de contenido privado/borradores.
- Enumeración de IDs (adivinar IDs para descubrir recursos ocultos).
Remediación en Laravel 12 (reglas del blog)
Laravel 12 documenta oficialmente Policies y ofrece denyAsNotFound() como un patrón de primera clase.
- Lectura pública: permitir
viewAny/viewpara posts y comentarios (incluye guests). - Escritura de posts: restringir
create/updatea usuarios autorizados (rol/permisos). - Escritura de comentarios: permitir
createpara cualquier usuario autenticado. - Edición de comentarios: restringir a owner (o admin).
- Usa Gates para permisos transversales (roles globales, admin-only, feature flags).
- Prefiere responder
404en lugar de403cuando exista riesgo de enumeración, usandodenyAsNotFound(). - Aplica scope por usuario/propiedad antes de obtener registros.
- En Actions, la autorización debe ser el primer invariante.
Arreglo concreto
Solo usuarios autorizados pueden crear posts:
use App\Example\Models\Post;use App\Models\User;use Illuminate\Support\Facades\Gate; public function handle(User $user, array $data): Post{ Gate::authorize('create', Post::class); return Post::query()->create([ 'user_id' => $user->id, 'title' => $data['title'], 'body' => $data['body'] ?? null, 'status' => 'draft', ]);}
Lectura publica: no protejas rutas de lectura con auth (todos pueden leer posts/comentarios):
use Illuminate\Support\Facades\Route; Route::get('/posts', [PostController::class, 'index']);Route::get('/posts/{post}', [PostController::class, 'show']);
Enfoque de patrón de diseño
Si tu base de código usa Actions, aplica un wrapper tipo Proxy/Decorator para reforzar permisos de forma repetible.
Evita dispersar checks como if ($user->id !== ...) en controllers: eso degrada la arquitectura.
use Illuminate\Support\Facades\Gate; final class Authorize{ public static function for(string $ability, mixed $resource, callable $next): mixed { Gate::authorize($ability, $resource); return $next(); }} // Ejemplo (Action)final class UpdatePostAction{ public function handle(User $user, Post $post, array $data): void { $post->fill($data)->save(); }} // En un controller / endpoint$action = app(UpdatePostAction::class); return Authorize::for('update', $post, function () use ($action, $user, $post, $data) { $action->handle($user, $post, $data); return redirect()->back();});