laravel-security
Skill10 starsChanged 3 months agoMITView on GitHub
Use when building, configuring, or hardening security-sensitive Laravel features — authentication, authorization, Eloquent safety, CSRF/XSS, API security, file uploads, secrets, and production configuration. Provides condensed, copy-ready secure defaults for Laravel 11 / PHP 8.3.
- Reads credentials
We split this skill into the rules it gives an agent
9 rules. 0 are common, 4 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 11 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
If the project uses Laravel, also apply @rules/laravel/laravel.mdc, @rules/laravel/architecture.mdc, @rules/laravel/filament.mdc, …
- Distinctive: line 16
Never hardcode secrets; never reveal them in output
- Very specific: line 63
Keep .env out of version control (.gitignore ships with .env).
- Distinctive: line 242
When user HTML must survive, purify with an allowlist before storing/output:
- Very specific: line 264
Always validate through a FormRequest.
- Very specific: line 286
Keep validation messages generic — never leak which auth factor failed, whether a record exists, or framework internals …
- Very specific: line 346
Read every secret from env()/config().
- Distinctive: line 416
No secret is hardcoded; required config/secrets are validated at boot
- Distinctive: line 417
Every applicable row of the checklist is satisfied or consciously waived
“Common” is measured: kept by at least 6 in 10 of the 11 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Laravel Security Best Practices
Constraints
- Apply
@rules/security/backend.mdand@rules/security/frontend.md - Apply
@rules/php/core-standards.mdc—finalclasses,declare(strict_types=1), typed signatures - If the project uses Laravel, also apply
@rules/laravel/laravel.mdc,@rules/laravel/architecture.mdc,@rules/laravel/filament.mdc,@rules/laravel/livewire.mdc - Stack: Laravel 11 / PHP 8.3, Filament, Livewire, Alpine.js, Blade, Tailwind, Pest, Vite, MySQL, Redis
- Never hardcode secrets; never reveal them in output
- Hard limits: this file stays <= 500 lines and <= 5000 tokens
Purpose
Secure-by-default building blocks for security-sensitive Laravel work. Use the matching section, copy the minimal snippet, and verify against the checklist. For an audit of existing code use @skills/security-review/SKILL.md.
Use when
- Setting up authentication / authorization (Sanctum, gates, policies, middleware)
- Configuring production settings and environment variables
- Writing secure Eloquent queries and models
- Hardening CSRF / XSS / input validation / file uploads / API endpoints
- Managing secrets, queue payloads, and security event logging
Production Configuration
// config/app.php
'debug' => (bool) env('APP_DEBUG', false), // CRITICAL: never true in production
'key' => env('APP_KEY'), // php artisan key:generate
// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax',Validate required config at boot and fail fast:
// AppServiceProvider::boot()
foreach (['app.key', 'database.connections.mysql.database'] as $key) {
if (empty(config($key))) {
throw new RuntimeException("Missing required config key: {$key}");
}
}HTTPS and trusted proxies:
if (app()->environment('production')) {
URL::forceScheme('https');
}
// Use specific CIDR ranges, never '*' (X-Forwarded-* spoofing)
'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],Keep .env out of version control (.gitignore ships with .env); ship .env.example with empty placeholders.
Authentication
Sanctum (API tokens)
// config/sanctum.php
'expiration' => 60 * 24, // minutes; null = never
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
$token = $user->createToken('api', ['posts:read', 'posts:write'])->plainTextToken;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/posts', [PostController::class, 'index'])->middleware('abilities:posts:read');
Route::post('/posts', [PostController::class, 'store'])->middleware('abilities:posts:write');
});Passwords
// config/hashing.php — bcrypt rounds >= 12, or Argon2id
'bcrypt' => ['rounds' => env('BCRYPT_ROUNDS', 12)],
// FormRequest rules
'password' => [
'required', 'confirmed',
Password::min(12)->letters()->mixedCase()->numbers()->symbols()->uncompromised(),
],Sessions
// config/session.php — 'driver' => 'database' or 'redis' (avoid 'file' in prod)
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate(); // CRITICAL: prevents session fixation
return redirect()->intended('/dashboard');
}
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}Authorization
Gates
// AppServiceProvider::boot()
Gate::define('update-post', fn (User $user, Post $post): bool => $user->id === $post->user_id);
Gate::before(function (User $user, string $ability): ?bool {
return $user->role === 'super-admin' ? true : null; // null = fall through
});
// Controller
Gate::authorize('update-post', $post);Policies
final class PostPolicy
{
public function view(?User $user, Post $post): bool
{
return $post->is_published || ($user && $user->id === $post->user_id);
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
// Controller: $this->authorize('update', $post);
// Blade: @can('update', $post) ... @endcanLaravel 11 auto-discovers policies by naming convention; register explicitly only when names differ.
Middleware
Route::put('/posts/{post}', [PostController::class, 'update'])->middleware('can:update,post');
Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
});For Filament, enforce access via policies and canAccessPanel(); for Livewire, re-check authorization inside actions — a mounted component is not an authorization boundary (@rules/laravel/filament.mdc, @rules/laravel/livewire.mdc).
Eloquent Security
Mass assignment
final class User extends Authenticatable
{
protected $fillable = ['name', 'email', 'phone', 'avatar'];
// NEVER list 'role', 'is_admin'; NEVER use $guarded = []
}
User::create($request->validated()); // GOOD: validated fields only
// User::create($request->all()); // VULNERABLESQL injection
User::where('email', $userInput)->first(); // parameterized
User::whereRaw('email = ?', [$userInput])->first(); // parameterized
DB::select('SELECT * FROM users WHERE email = ?', [$input]); // parameterized
// VULNERABLE — never interpolate user input:
// User::whereRaw("email = '{$userInput}'")->first();
// User::orderByRaw($userInput); DB::statement("... '{$userInput}'");Casting and hidden attributes
final class User extends Authenticatable
{
protected $casts = [
'is_admin' => 'boolean',
'settings' => 'array',
'metadata' => 'encrypted:array', // Laravel 11 encrypted cast
'password' => 'hashed', // auto-hash on set
];
protected $hidden = ['password', 'remember_token', 'two_factor_secret'];
}CSRF Protection
CSRF is on by default for the web group. State-changing forms need @csrf:
<form method="POST" action="/posts">@csrf ...</form>Exclude only specific signature-verified webhooks — never blanket api/* (stateful Sanctum needs CSRF):
// bootstrap/app.php — $middleware->validateCsrfTokens(except: ['stripe/*'])For JS, send the token header (Axios ships preconfigured in Laravel):
<meta name="csrf-token" content="{{ csrf_token() }}">XSS Prevention
{{ $userInput }} {{-- SAFE: auto-escaped --}}
{!! $userInput !!} {{-- DANGEROUS: raw, never with user input --}}
{!! $trustedHtml !!} {{-- only for content you fully control --}}
<script>
const user = @js($user); {{-- escaped for JS context --}}
const cfg = @json($config);
</script>When user HTML must survive, purify with an allowlist before storing/output:
// composer require ezyang/htmlpurifier
$config = \HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,a[href],ul,ol,li,br');
$config->set('URI.AllowedSchemes', ['http', 'https', 'mailto']);
$clean = (new \HTMLPurifier($config))->purify($dirty);In Alpine, prefer x-text over x-html; only use x-html on sanitized content. Add security headers via middleware:
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Content-Security-Policy',
"default-src 'self'; frame-ancestors 'none'");Input Validation
Always validate through a FormRequest; never persist $request->all():
final class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('create', Post::class) ?? false;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string', 'max:10000'],
'tags' => ['array'],
'tags.*' => ['integer', 'exists:tags,id'],
];
}
}Keep validation messages generic — never leak which auth factor failed, whether a record exists, or framework internals (@rules/security/backend.md).
API Security
Rate limiting
// AppServiceProvider::boot()
RateLimiter::for('api', fn (Request $r) =>
Limit::perMinute(60)->by($r->user()?->id ?: $r->ip()));
RateLimiter::for('auth', fn (Request $r) =>
Limit::perMinute(5)->by($r->ip()));
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:auth');CORS
// config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', '')), // explicit allowlist
'supports_credentials' => true, // required for Sanctum SPA auth
// NEVER ['*'] when credentials are supportedFile Upload Security
public function rules(): array
{
return [
'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:10240',
'extensions:pdf,doc,docx'],
'avatar' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048',
'dimensions:max_width=2000,max_height=2000'],
];
}Store sensitive files off the public disk and serve through an authorized, time-limited URL:
$path = $request->file('document')->store('documents', 'local'); // not 'public'
public function download(Request $request, string $path): RedirectResponse
{
$this->authorize('download', $path);
return redirect(Storage::temporaryUrl($path, now()->addMinutes(15)));
}Secrets and Dependencies
composer audit # run in CI; fail the build on advisories
# keep composer.lock committed; run composer update deliberately, never in CIRead every secret from env()/config(); validate presence at boot. For production use a secret manager rather than a deployed .env.
Queue Security
// Encrypt sensitive payloads on the wire
final class ProcessPaymentJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private readonly string $paymentIntentId,
private readonly string $cardFingerprint,
) {}
public function handle(): void { /* ... */ }
public function retryUntil(): \Carbon\CarbonInterface
{
return now()->addMinutes(5);
}
}Logging Security Events
// config/logging.php — dedicated 'security' channel
final class SecurityLogger
{
public static function log(string $event, array $context = []): void
{
Log::channel('security')->warning($event, array_merge([
'user_id' => Auth::id(),
'ip' => request()->ip(),
'url' => request()->fullUrl(),
], $context));
}
}
SecurityLogger::log('failed_login_attempt', ['email' => $email]);
SecurityLogger::log('role_change', ['target_user' => $targetId, 'new_role' => 'admin']);Quick Security Checklist
| Check | Description |
APP_DEBUG=false | Never run with debug enabled in production |
APP_KEY set | Always run php artisan key:generate |
| HTTPS enforced | Force HTTPS in production via middleware or proxy |
$fillable whitelisted | Never use $guarded = [] |
| CSRF active | @csrf on all state-changing forms |
| Sanctum scopes | Token abilities enforced per route |
| Rate limiting | Throttle API and auth endpoints |
| Input validation | FormRequest with specific rules, never $request->all() |
| File upload restrictions | Validate MIME, extension, size, dimensions |
composer audit in CI | Check dependencies for known vulnerabilities |
| Password hashing | Laravel bcrypt/Argon2, 'password' => 'hashed' cast |
| Session regeneration | Call $request->session()->regenerate() on login |
| Security headers | CSP, X-Frame-Options, X-Content-Type-Options |
| Security event logging | Audit auth failures, role changes, suspicious activity |
.env not committed | Verify .gitignore includes .env |
Laravel Security Audit
When auditing an existing Laravel application (instead of building new features), use @skills/laravel-security/references/audit-workflow.md. It covers the 7 audit areas — Authorization/IDOR/BOLA, Authentication, Validation, XSS, File upload, Secrets/configuration, Dependencies — with severity mapping (Critical/High/Medium/Low/Info → CR scale Critical/Moderate/Minor), Grep patterns, and a required regression-test sketch per confirmed finding. The building blocks in this file (Production Configuration, Authentication, Authorization, Eloquent Security, CSRF, XSS Prevention, Input Validation, File Upload Security, Secrets and Dependencies) are the reference fixes the audit workflow links back to.
Done when
- The relevant secure default is applied and matches the project's existing style
- No secret is hardcoded; required config/secrets are validated at boot
- Every applicable row of the checklist is satisfied or consciously waived
- Tests (Pest) cover the new auth/authorization/validation behavior
Related Skills
@skills/security-review/SKILL.md— review workflow for existing code@skills/security-threat-analysis/SKILL.md— remediate a referenced advisory/CVE@skills/test-driven-development/SKILL.md— drive the implementation test-first
Other versions17 projects use a version of this.
Word for word · 0
None found.
Edited · 5
- gongyijie85/dsh-ecc★ 814% same
- NISTALTALSON/essential-claude-skills★ 114% same
- romaintoso78/claude-skills★ 114% same
- SamuelNDCE/claude-super-skill-library★ 114% same
- renepf/claude-skills★ 014% same
Wrote their own version · 12
- loulanyue/awesome-claude-notes★ 272same name
- Fmarzochi/EGC★ 61same name
- PUPT-Quantum-Leap/capstone-nlcom-volunteer-management-system★ 4same name
- ashrafiucse/security-audit-skills★ 2same name
- ronmkr/PromptBook★ 2same name
- wakqasahmed/skills★ 2same name
- junimnjw/everything-claude-code★ 1same name
- TironFox/everything-claude-code★ 1same name
- davidanthonyn/claude-skills★ 0same name
- MohamedEssam0-0/claude-skills★ 0same name
2 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository62 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- analyze-problemSkill
- api-reviewSkill
- article-writingSkill
- assignment-compliance-checkSkill
- automation-audit-opsSkill
- autonomous-loopsSkill
- benchmark-optimization-loopSkill
- benchmarkSkill
- blueprintSkill
- class-refactoringSkill
- cleanup-local-branchesSkill
- codebase-simplification-auditSkill
- code-review-bugsnagSkill
- code-review-githubSkill
- code-review-jiraSkill
- code-reviewSkill
- composer-updateSkill
- create-issues-from-textSkill
- create-issueSkill
- create-missing-tests-in-prSkill
- create-testSkill
- design-systemSkill
- docker-patternsSkill
- e2e-testingSkill
- frontend-a11ySkill
- frontend-design-directionSkill
- frontend-patternsSkill
- frontend-slidesSkill
- git-workflowSkill
- laravel-authorization-reviewSkill
- laravel-telescopeSkill
- latency-critical-systemsSkill
- merge-github-prSkill
- mysql-patternsSkill
- mysql-problem-solverSkill
- penetration-testerSkill
- postgres-patternsSkill
- prepare-issue-contextSkill
- process-code-reviewSkill
- product-capabilitySkill
- production-auditSkill
- pr-staged-merge-planSkill
- pr-summarySkill
- record-project-memorySkill
- redis-patternsSkill
- refactor-entry-point-to-actionSkill
- refresh-claude-mdSkill
- resolve-issueSkill
- rewrite-tests-pestSkill
- security-bounty-hunterSkill
- security-reviewSkill
- security-threat-analysisSkill
- seoSkill
- skill-creatorSkill
- slack-messagingSkill
- smartest-project-additionSkill
- test-driven-developmentSkill
- tester-cookbookSkill
- test-like-humanSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

