agentleFS
cindovahqcindovahq/security-skills/skills/laravel-security/SKILL.md

laravel-security

Skill0 starsChanged 43 days agoApache-2.0View on GitHub

Security review and secure-coding guidance for Laravel (10.x–13.x) applications. Use when auditing, reviewing, pentest-prepping or hardening a Laravel/PHP codebase, or when writing or changing Laravel authentication, authorization (policies, gates), sessions, CSRF, Sanctum/Passport APIs, Eloquent queries, form requests and validation, Blade views, file uploads, Livewire/Inertia/Filament code, or configuration (.env, config/*.php, bootstrap/app.php). Triggers on projects containing artisan and laravel/framework in composer.json. Covers authentication, authorization/IDOR, sessions, CSRF, validation and mass assignment, SQL/command/deserialization injection, XSS, SSRF, uploads, secrets and debug exposure, API security, dependencies, and fix verification.

  • Reads credentials

19 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use skill-creator.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • SentrySkill

    skill-creator

    Create new agent skills following the Agent Skills specification. Use when asked to "create a skill", "add a new skill", "write a skill", "make a skill", "build a skill", or scaffold a new skill with SKILL.md. Guides through requirements, writing, registration, and verification.

    Also in 4 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

20 rules. 0 are common, 5 are distinctive, and 15 are very specific to this project.

  • 0

    Common

    Most of the 13 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 15

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    All paths in this skill (references/...) are relative to the directory containing this SKILL.md.

    line 18
  2. Very specific:

    If the appsec-review skill is installed, it owns the overall methodology and report format.

    line 25
  3. Very specific:

    Read the installed version from composer.lock ("name": "laravel/framework" → "version"), not the constraint in composer.json.

    line 32
  4. Very specific:

    Check support status. Laravel 10 (security fixes ended Feb 2025) and 11 (ended Mar 2026) no longer receive security patches.

    line 37
  5. Distinctive:

    Every finding needs evidence: file, line, the code or config, and the path from attacker-controlled input to the sensitive operation.

    line 67
  6. Distinctive:

    Do not report these without further evidence:

    line 109
  7. Very specific:

    FormRequest::authorize() returning true: when authorization is enforced by a policy, can middleware or Gate::authorize() elsewhere on …

    line 114
  8. Very specific:

    $fillable containing password: when the value is hashed via the hashed cast or a mutator and only the user's own record is updated.

    line 119
  9. Distinctive:

    When writing Laravel code, default to:

    line 132
  10. Very specific:

    Authorize every action server-side: Use policies plus Gate::authorize(), can: middleware or ->can() on routes.

    line 135
  11. Very specific:

    Models declare $fillable: Never put role, is_admin, team_id, user_id, email_verified_at, balances or status fields in $fillable if …

    line 136
  12. Distinctive:

    Use Eloquent and the query builder with bindings: Never interpolate variables into raw SQL.

    line 137
  13. Very specific:

    Escape output with {{ }}: Use @json/Js::from for data in <script>.

    line 138
  14. Very specific:

    Secrets: read through config(), never env() outside config/*.php.

    line 140
  15. Very specific:

    Sessions & auth: use the starter kit / Fortify flows.

    line 141
  16. Very specific:

    Never: use unserialize, decrypt() (it unserializes by default.

    line 143
  17. Very specific:

    Add a feature test for the security boundary you just wrote (e.g.

    line 145
  18. Very specific:

    Use these when appsec-review is not installed.

    line 149
  19. Distinctive:

    Hardening: not demonstrably exploitable, but a stronger control is recommended.

    line 155
  20. Very specific:

    Rules: never invent files, routes, packages or config values.

    line 173

“Common” is measured: kept by at least 6 in 10 of the 13 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Laravel Security

Find, explain, fix and verify security issues in Laravel applications, and write new Laravel code that does not introduce them.

All paths in this skill (references/...) are relative to the directory containing this SKILL.md.

Modes

  • Review mode: the user asks for an audit, security review, pentest prep, or "is this secure?". Follow the workflow below and produce findings.
  • Build mode: you are writing or modifying Laravel code. Apply the build-mode guardrails to every change. Load only the reference for the area you are touching.

If the appsec-review skill is installed, it owns the overall methodology and report format. This skill supplies the Laravel-specific knowledge. If it is not installed, use the evidence and reporting rules below.

Review workflow

1. Confirm the stack and version

  1. Confirm Laravel: artisan at the project root and laravel/framework in composer.json.
  2. Read the installed version from composer.lock ("name": "laravel/framework" → "version"), not the constraint in composer.json.
  3. Determine the application structure era. Upgraded apps often keep the old structure, so check the files rather than assuming from the version:
    • Laravel ≤10 structure: app/Http/Kernel.php, app/Http/Middleware/*.php (VerifyCsrfToken, TrustProxies, TrustHosts, EncryptCookies), app/Exceptions/Handler.php.
    • Laravel 11+ structure: middleware, exceptions and routing configured in bootstrap/app.php via ->withMiddleware(...) and ->withExceptions(...). There is no routes/api.php until php artisan install:api is run.
  4. Record security-relevant packages from composer.lock and package.json: Sanctum, Passport, Fortify, Breeze, Jetstream, Socialite, Livewire, Inertia, Filament, Nova, Telescope, Horizon, Pulse, Debugbar, Ignition, spatie/laravel-permission, Cashier, Lighthouse, any JWT package.
  5. Check support status. Laravel 10 (security fixes ended Feb 2025) and 11 (ended Mar 2026) no longer receive security patches. 12 is supported until Feb 2027 and 13 until Mar 2028. An unsupported framework version is a finding; see references/dependencies.md.

2. Map the attack surface

  • Routes: routes/web.php, routes/api.php, routes/channels.php, routes/console.php, and any route files registered in bootstrap/app.php or a RouteServiceProvider. If you can run commands, php artisan route:list -v shows each route's middleware.
  • Entry points beyond controllers: Livewire components (app/Livewire, app/Http/Livewire), Filament resources and panels, Nova resources, broadcast channels, queued jobs and listeners, console commands and scheduled tasks, webhook controllers, GraphQL schema.
  • Unauthenticated routes, admin routes, file download/upload routes, and anything taking an ID, URL, path, filename, sort column or HTML.

3. Review each area

Load the reference for each area as you reach it. Do not load all of them up front.

AreaReferenceStart by looking for
Authenticationreferences/authentication.mdCustom login code, password hashing, reset flows, throttling, Auth::loginUsingId, TrustHosts
Authorization / IDORreferences/authorization.mdRoute model binding without policies, find($request->id), Livewire actions, Filament/Nova access, Gate::before
Sessionsreferences/sessions.mdconfig/session.php, logout code, custom session-based auth, serialization, remember-me
CSRFreferences/csrf.mdCSRF except lists, state-changing GET routes, allowSameSite, Sanctum stateful domains
Validation & mass assignmentreferences/validation-mass-assignment.md$request->all(), $guarded = [], Model::unguard(), forceFill, array rules without keys
Injection & deserializationreferences/injection.mdDB::raw, *Raw( with interpolation, dynamic column names, Process/exec, unserialize, decrypt(, Blade::render
XSS & outputreferences/xss.md{!! !!}, href="{{ ... }}", Str::markdown, raw response() HTML, SVG uploads
SSRF & redirectsreferences/ssrf-redirects.mdHttp::get($userUrl), file_get_contents, PDF renderers, redirect()->to($request->...)
File uploads & storagereferences/file-uploads.mdstoreAs(..., getClientOriginalName()), public disk, response()->download($path), mimes vs extension
Secrets & configurationreferences/secrets-config.md.env in VCS, APP_DEBUG, APP_KEY, env() outside config/, debug tools, CORS, trustProxies
API securityreferences/api-security.mdSanctum/Passport config, returning models directly, paginate($request->per_page), webhooks, broadcast channels, rate limits
Dependenciesreferences/dependencies.mdcomposer audit, composer.lock, dev packages in require, EOL versions
Verificationreferences/verification.mdHow to prove each finding and each fix

4. Classify and report

Every finding needs evidence: file, line, the code or config, and the path from attacker-controlled input to the sensitive operation. Classify findings using the rules below.

5. Remediate and verify (when asked to fix)

Explain the issue before changing code. Make the smallest change that uses Laravel's own mechanism. Then verify using references/verification.md: the attack no longer works, legitimate use still works, and the same pattern isn't repeated elsewhere (search for siblings).

High-signal patterns

These are investigation signals, not findings. Trace each one before reporting.

# Injection / RCE
DB::raw(  ->whereRaw(  ->selectRaw(  ->orderByRaw(  ->havingRaw(  DB::statement(  DB::unprepared(  DB::select("...$
->orderBy($request  ->where($request->input('field')  DB::table($request
exec(  shell_exec(  system(  passthru(  proc_open(  popen(  `backticks`  Process::run("...$
unserialize(  decrypt($request  Crypt::decrypt(  Blade::render(  Blade::compileString(  eval(
'serialization' => 'php'   'serializable_classes' => true

# Authorization
Route::get('{model}') + controller without authorize/policy     ::find($request->     ::findOrFail($id)
public function authorize() { return true; }   Gate::before(   withoutGlobalScopes(   canAccessPanel   viewNova
Broadcast::channel(... => true)

# Mass assignment
$guarded = []   Model::unguard(   ->forceFill($request   ::create($request->all())   ->update($request->all())

# XSS
{!!   Str::markdown(   ->toHtml()   new HtmlString(   href="{{   response($request   v-html   dangerouslySetInnerHTML

# Files
getClientOriginalName()   getClientOriginalExtension()   storeAs(   ->download(   Storage::disk('public')   'image:allow_svg'

# Config / secrets
APP_DEBUG=true   APP_ENV=local   env(  (outside config/)   'supports_credentials' => true   trustProxies(at: '*')   $proxies = '*'
Telescope  Debugbar  Horizon  Pulse  log-viewer  phpinfo(

# SSRF / redirect
Http::get($   file_get_contents($   fopen($url   redirect()->to($request   redirect($request   away($request   isRemoteEnabled

Common false positives

Do not report these without further evidence:

  • No session()->regenerate() after Auth::attempt() / Auth::login(). Laravel's SessionGuard regenerates the session ID itself during login (updateSession() → $this->session->regenerate(true)), so this alone is not session fixation. It is a finding only for custom auth that writes user identity into the session manually.
  • DB::raw / whereRaw with ? placeholders or named bindings and only constant SQL text.
  • {!! !!} on output from a trusted sanitizer (HTMLPurifier, symfony/html-sanitizer) or framework helpers like $errors/csrf_field(). Confirm the sanitizer config before clearing it.
  • FormRequest::authorize() returning true when authorization is enforced by a policy, can middleware or Gate::authorize() elsewhere on the same route.
  • orderBy($column) with an allow-listed column (in_array, Rule::in, an enum, or spatie/laravel-query-builder allowedSorts).
  • Paths passed to Flysystem (Storage::...) containing user input. Flysystem rejects .. traversal outside the disk root, so treat it as a disk-scoped access-control question, not arbitrary file read. Native PHP paths (response()->download(storage_path(...)), file_get_contents) have no such protection.
  • APP_DEBUG=true in .env.example or a local-only .env that is not deployed.
  • CSRF exclusions for webhook routes that verify a provider signature.
  • $fillable containing password when the value is hashed via the hashed cast or a mutator and only the user's own record is updated.
  • Sanctum expiration => null is a hardening point, not a vulnerability, unless tokens are long-lived in a high-risk context or there is no revocation path.

Severity calibration

Common under-ratings to avoid:

  • User-controlled orderBy/where column names reaching a table with secret columns (password, remember_token, two_factor_secret, API tokens): the sort order is an oracle that leaks secret values character by character. Rate it High (Medium if no secret columns exist), not Low "input validation".
  • Livewire action IDOR (unlocked ID property plus an action without authorization) has the same impact as a controller IDOR. Rate it by the data and action exposed (usually High).
  • Mass assignment of a privilege field + an admin area that trusts it is a chain. Rate the chain (often Critical), and fix both parts.

Build-mode guardrails

When writing Laravel code, default to:

  1. Validate with Form Requests and only use $request->validated() / $request->safe()->only([...]). Never $request->all() into create, update, fill or a query.
  2. Authorize every action server-side. Use policies plus Gate::authorize(), can: middleware or ->can() on routes. Scope queries through the owner ($request->user()->posts()->findOrFail($id)). @can in Blade only hides UI.
  3. Models declare $fillable. Never put role, is_admin, team_id, user_id, email_verified_at, balances or status fields in $fillable if they're set from user input. Set those explicitly.
  4. Use Eloquent and the query builder with bindings. Never interpolate variables into raw SQL. Allow-list dynamic column names and sort directions.
  5. Escape output with {{ }}. Use @json/Js::from for data in <script>. Validate URLs before putting them in href/src (http/https only). Sanitize any HTML you must render raw.
  6. Uploads: validate with File::types([...])->max(...) or mimes:, store with store() (random name), keep private files off the public disk, and serve them through an authorized controller or Storage::temporaryUrl().
  7. Secrets: read through config(), never env() outside config/*.php. Never commit .env or keys, and never put real defaults in env('KEY', 'real-secret').
  8. Sessions & auth: use the starter kit / Fortify flows. On logout call Auth::logout(), $request->session()->invalidate() and $request->session()->regenerateToken(). Throttle login, OTP and reset endpoints with RateLimiter/throttle:.
  9. Outbound requests to user-supplied URLs go through an allow-list. Redirects to user-supplied targets use redirect()->intended() or a relative-path check.
  10. Never use unserialize, decrypt() (it unserializes by default; use decryptString), eval or Blade::render on user input. Never shell out with interpolated strings; pass Process::run([...]) an argument array.
  11. APIs return API Resources with explicit fields, not raw models. Clamp per_page. Enforce token abilities or scopes.
  12. Add a feature test for the security boundary you just wrote (e.g. another user gets 403, is_admin is ignored, a .php upload gets 422).

Evidence and reporting rules

Use these when appsec-review is not installed.

Classification

  • Confirmed: the code/config path from attacker input to impact is fully traced, or it was safely demonstrated.
  • Likely: strong evidence, but one runtime condition (deployment config, a middleware applied elsewhere, environment values) could not be verified. State which condition.
  • Hardening: not demonstrably exploitable, but a stronger control is recommended.
  • Informational: context with no direct security impact.

Severity follows impact × exploitability × required privileges × exposure. Do not raise severity because a scary keyword appears. Unauthenticated RCE, auth bypass and cross-tenant data access are Critical/High. Issues needing an admin account or an unusual configuration go down.

Finding format

### [SEVERITY] Title — Confirmed|Likely|Hardening
- **Location:** `path/to/File.php:42` (`Class::method`)
- **Evidence:** the exact code/config, and how attacker input reaches it
- **Impact:** what an attacker gains, and who the attacker must be
- **Preconditions:** auth level, configuration, deployment assumptions
- **Fix:** smallest Laravel-native change (code snippet)
- **Verify:** test or request that proves the fix
- **Refs:** CWE / OWASP / Laravel docs link

Rules: never invent files, routes, packages or config values. Redact secrets (show APP_KEY=base64:****). Say explicitly when runtime verification was not performed. Only test applications the user is authorized to assess, and use non-destructive checks.

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

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.