/laravel-security
Laravel security best practices — authentication, authorization, Eloquent safety, CSRF, XSS prevention, API security, and secure deployment configurations.
$ npx -y skills add affaan-m/ECC --skill laravel-security --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/laravel-security
Context preview
The summary Claude sees to decide when to auto-load this skill.
Laravel security best practices — authentication, authorization, Eloquent safety, CSRF, XSS prevention, API security, and secure deployment configurations.
SKILL.md
laravel-security.SKILL.mdname: laravel-security
description: Laravel security best practices — authentication, authorization, Eloquent safety, CSRF, XSS prevention, API security, and secure deployment configurations.
metadata:
origin: ECC
Laravel Security Best Practices
Comprehensive security guidelines for Laravel applications to protect against common vulnerabilities.
When to Activate
- Setting up Laravel authentication and authorization (Sanctum, Passport, Jetstream, Breeze)
- Implementing user roles, permissions, and policies
- Configuring production security settings and environment variables
- Reviewing Laravel applications for security vulnerabilities
- Deploying Laravel applications to production
- Writing secure Eloquent queries and migrations
Production Configuration
Essential Production Settings
// config/app.php
'env' => env('APP_ENV', 'production'),
'debug' => (bool) env('APP_DEBUG', false), // CRITICAL: Never true in production
'key' => env('APP_KEY'), // Must be set: php artisan key:generate
// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax',
// Verify APP_KEY is set at boot
// bootstrap/app.php or a service provider
if (empty(config('app.key'))) {
throw new RuntimeException('APP_KEY is not set. Run: php artisan key:generate');
}Environment File Security
# NEVER commit .env to version control
# .gitignore already includes .env by default
# Use .env.example with placeholders instead
DB_PASSWORD=
APP_KEY=
SANCTUM_TOKEN_PREFIX=
# Validate required variables at boot
// In AppServiceProvider::boot()
$requiredKeys = ['app.key', 'database.connections.mysql.database', 'database.connections.mysql.username'];
foreach ($requiredKeys as $key) {
if (empty(config($key))) {
throw new RuntimeException("Missing required config key: {$key}");
}
}HTTPS Enforcement
// AppServiceProvider::boot() or middleware
if (app()->environment('production')) {
URL::forceScheme('https');
request()->server->set('HTTPS', 'on');
}
// config/app.php for trusted proxies (load balancers)
// Use specific IP ranges — * trusts all, allowing X-Forwarded-* spoofing
// AWS: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],
// Force HTTPS in production via middleware
// app/Http/Middleware/ForceHttps.php
public function handle($request, Closure $next)
{
if (!$request->secure() && app()->environment('production')) {
return redirect()->secure($request->getRequestUri());
}
return $next($request);
}Authentication
Sanctum (API Token Authentication)
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
)));
'expiration' => 60 * 24, // Token expiration in minutes (null = never)
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
// Issuing tokens with abilities
$token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;
// Validate abilities on routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/orders', function () {
// User must have 'read' ability
abort_unless(Auth::user()->tokenCan('read'), 403);
// ...
})->middleware('abilities:read');
Route::post('/orders', function () {
// User must have 'write' ability
abort_unless(Auth::user()->tokenCan('write'), 403);
// ...
})->middleware('abilities:write');
});Password Security
// config/hashing.php
// Default is bcrypt. Argon2id is stronger.
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12), // Increase for stronger hashing
],
'argon' => [
'memory' => 65536,
'threads' => 4,
'time' => 4,
],
// Password validation in RegisterRequest
public function rules(): array
{
return [
'password' => [
'required',
'confirmed',
Password::min(12)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised(), // Checks haveibeenpwned
],
];
}
// Rate limit login attempts
// App\Http\Controllers\Auth\AuthenticatedSessionController
protected function authenticated(Request $request, $user)
{
if ($user->wasRecentlyLockedOut()) {
// Notify user of suspicious login
$user->notify(new SuspiciousLoginNotification($request->ip()));
}
}Session Management
// config/session.php
'driver' => env('SESSION_DRIVER', 'database'), // database/redis > file
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
'encrypt' => env('SESSION_ENCRYPT', false),
// Regenerate session on login
// App\Http\Controllers\Auth\AuthenticatedSessionController
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate(); // CRITICAL: prevents session fixation
return redirect()->intended(RouteServiceProvider::HOME);
}
// Invalidate session on logout
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}Authorization
Gates
// App\Providers\AuthServiceProvider
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('update-post', function (User $user, Post $post): bool {
return $user->id === $post->user_id;
});
Gate::define('publish-post', function (User $user): bool {
return $user->role === 'editor' || $user->role === 'admin';
});
// Using before() for super-admin override
Gate::before(function (UseRead more
name: laravel-security description: Laravel security best practices — authentication, authorization, Eloquent safety, CSRF, XSS prevention, API security, and secure deployment configurations. metadata: origin: ECC
Laravel Security Best Practices
Comprehensive security guidelines for Laravel applications to protect against common vulnerabilities.
When to Activate
- Setting up Laravel authentication and authorization (Sanctum, Passport, Jetstream, Breeze)
- Implementing user roles, permissions, and policies
- Configuring production security settings and environment variables
- Reviewing Laravel applications for security vulnerabilities
- Deploying Laravel applications to production
- Writing secure Eloquent queries and migrations
Production Configuration
Essential Production Settings
// config/app.php
'env' => env('APP_ENV', 'production'),
'debug' => (bool) env('APP_DEBUG', false), // CRITICAL: Never true in production
'key' => env('APP_KEY'), // Must be set: php artisan key:generate
// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax',
// Verify APP_KEY is set at boot
// bootstrap/app.php or a service provider
if (empty(config('app.key'))) {
throw new RuntimeException('APP_KEY is not set. Run: php artisan key:generate');
}Environment File Security
# NEVER commit .env to version control
# .gitignore already includes .env by default
# Use .env.example with placeholders instead
DB_PASSWORD=
APP_KEY=
SANCTUM_TOKEN_PREFIX=
# Validate required variables at boot
// In AppServiceProvider::boot()
$requiredKeys = ['app.key', 'database.connections.mysql.database', 'database.connections.mysql.username'];
foreach ($requiredKeys as $key) {
if (empty(config($key))) {
throw new RuntimeException("Missing required config key: {$key}");
}
}HTTPS Enforcement
// AppServiceProvider::boot() or middleware
if (app()->environment('production')) {
URL::forceScheme('https');
request()->server->set('HTTPS', 'on');
}
// config/app.php for trusted proxies (load balancers)
// Use specific IP ranges — * trusts all, allowing X-Forwarded-* spoofing
// AWS: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],
// Force HTTPS in production via middleware
// app/Http/Middleware/ForceHttps.php
public function handle($request, Closure $next)
{
if (!$request->secure() && app()->environment('production')) {
return redirect()->secure($request->getRequestUri());
}
return $next($request);
}Authentication
Sanctum (API Token Authentication)
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
)));
'expiration' => 60 * 24, // Token expiration in minutes (null = never)
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
// Issuing tokens with abilities
$token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;
// Validate abilities on routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/orders', function () {
// User must have 'read' ability
abort_unless(Auth::user()->tokenCan('read'), 403);
// ...
})->middleware('abilities:read');
Route::post('/orders', function () {
// User must have 'write' ability
abort_unless(Auth::user()->tokenCan('write'), 403);
// ...
})->middleware('abilities:write');
});Password Security
// config/hashing.php
// Default is bcrypt. Argon2id is stronger.
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12), // Increase for stronger hashing
],
'argon' => [
'memory' => 65536,
'threads' => 4,
'time' => 4,
],
// Password validation in RegisterRequest
public function rules(): array
{
return [
'password' => [
'required',
'confirmed',
Password::min(12)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised(), // Checks haveibeenpwned
],
];
}
// Rate limit login attempts
// App\Http\Controllers\Auth\AuthenticatedSessionController
protected function authenticated(Request $request, $user)
{
if ($user->wasRecentlyLockedOut()) {
// Notify user of suspicious login
$user->notify(new SuspiciousLoginNotification($request->ip()));
}
}Session Management
// config/session.php
'driver' => env('SESSION_DRIVER', 'database'), // database/redis > file
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
'encrypt' => env('SESSION_ENCRYPT', false),
// Regenerate session on login
// App\Http\Controllers\Auth\AuthenticatedSessionController
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate(); // CRITICAL: prevents session fixation
return redirect()->intended(RouteServiceProvider::HOME);
}
// Invalidate session on logout
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}Authorization
Gates
// App\Providers\AuthServiceProvider
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('update-post', function (User $user, Post $post): bool {
return $user->id === $post->user_id;
});
Gate::define('publish-post', function (User $user): bool {
return $user->role === 'editor' || $user->role === 'admin';
});
// Using before() for super-admin override
Gate::before(function (UseYour agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Other skills on ecc.
- /everything-claude-code
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Open skill - /accessibility
Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA
Open skill - /agent-architecture-audit
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for
Open skill - /agent-eval
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics
Open skill - /agent-harness-construction
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates.
Open skill - /agent-introspection-debugging
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Open skill

