The OWASP Top 10 is the industry's consensus list of the most critical web application risks, and the 2025 edition is the current one. The categories are deliberately framework-agnostic, which is why they can feel abstract. This article makes them concrete: for each of the ten, here is what the risk actually looks like in a Laravel codebase — especially one written with heavy AI assistance — and what closes it.
If you last looked at the list in its 2021 edition, the headlines: Broken Access Control stays at #1, Security Misconfiguration jumps to #2, Software Supply Chain Failures is a new, broader #3 (absorbing "Vulnerable and Outdated Components"), SSRF is folded into access control, and a new A10 — Mishandling of Exceptional Conditions — enters the list.
Still #1, and deservedly: in Laravel terms this is the missing-policy problem. Routes get auth middleware (authentication) but no check that the logged-in user may act on the specific record (authorization):
Close it with policies on every model that has an owner, Gate::authorize() (or can: middleware) on every route that touches one, and scoped route bindings for nested resources. The 2025 edition also files SSRF here: if your app fetches user-supplied URLs (Http::get($request->url) for link previews, imports, webhooks), validate the scheme, resolve and reject private IP ranges, and prefer an allow-list of hosts. Verification is empirical — two accounts, swap IDs everywhere, as described in our pre-launch checklist.
Up from #5, and the category where Laravel apps fail most visibly: APP_DEBUG=true rendering stack traces with request data; a web root pointed at the project instead of public/, serving .env to anyone; Telescope or Horizon reachable without a gate; CORS set to * on a cookie-authenticated API; session cookies without the Secure flag. None of these are code bugs — they are settings — which is why they survive code review. We keep a dedicated, copy-paste hardening list in Turning off the footguns; the 60-second version:
New at #3, expanding the old "vulnerable components" category to the whole pipeline: dependencies, build, and CI. For a Laravel team the practical surface is Composer and npm:
Commit your lock files, run both audits in CI so a vulnerable release fails the build, schedule updates (Dependabot/Renovate), and be suspicious of abandoned packages — an unmaintained package with network access is a liability even without a CVE. AI assistants add a twist worth auditing: they sometimes hallucinate package names or pick long-dead libraries; verify that every package in composer.json is one you would choose on purpose.
In Laravel this is rarely "broken AES" and almost always misuse around the edges: passwords hashed with md5()/sha1() in AI-generated auth code instead of Hash::make(); home-rolled "encryption" instead of Crypt::encryptString(); an APP_KEY committed to the repo or shared between staging and production; tokens compared with == instead of hash_equals(); card numbers stored locally instead of tokenized at Stripe. The framework primitives are sound — use them and protect the key:
Injection — SQL, XSS, command — slides to #5 overall but remains the top finding in AI-written Laravel, because assistants reach for raw strings the moment a query or a shell call gets complex. The three Laravel forms:
DB::raw() / whereRaw() / orderByRaw(). Use bindings for values, allow-lists for column names — full treatment in Why AI loves raw queries.{!! $userContent !!} in Blade. Use {{ }}, or sanitize genuinely-rich content on output.exec("convert {$path}…"). Use the Process facade with an array of arguments so nothing passes through a shell.This category is about flaws that exist before any line of code: no tenant isolation in the data model, password reset flows that confirm which emails exist, coupons redeemable in a loop, refunds without an amount cap. Vibe-coded apps are exposed here precisely because nobody drew the trust boundaries — the assistant implemented exactly what was asked, and nobody asked "what is the worst thing a hostile logged-in user could do with this?" For each feature that moves money or data across users, write down that answer; that lightweight habit is most of "secure design" for an MVP. It is also the layer where automated tools help least and senior review helps most.
The Laravel manifestations we find, in frequency order: no rate limiting on login or OTP endpoints (credential stuffing is free); custom login controllers that skip $request->session()->regenerate() (session fixation); weak password rules; user enumeration via different error messages for "no such user" vs "wrong password"; remember-me cookies on admin panels with no second factor. The framework gives you the right defaults — starter kits throttle and regenerate — but AI-generated custom auth flows routinely drop them. Anchor the password policy centrally:
Trusting data or code that could have been tampered with. Concretely: webhook endpoints that skip signature verification (anyone can POST "payment succeeded"), unserialize() on user-controlled input (object injection → potential RCE via gadget chains), download-and-execute steps in deploy scripts, unpinned third-party GitHub Actions. Checks:
For links that grant access (unsubscribe, file download, invite), use Laravel's signed URLs (URL::temporarySignedRoute() + signed middleware) so the URL itself carries integrity.
Renamed from "monitoring" to "alerting" to make the point: logs nobody is alerted on are an archive, not a defense. Minimum viable setup for a small Laravel app: keep daily log rotation; log authentication failures and authorization denials with user/IP context; never log passwords, tokens or full card data; and wire the critical level to a human (Slack webhook channel or an uptime/error service). The test from the checklist applies: break something on purpose and check you would have noticed.
The new entry, and AI-generated code is a factory for it: assistants wrap risky calls in try/catch blocks that swallow the exception to keep the demo green. The dangerous shape is failing open:
Audit every catch block and every rescue() helper call: does the code after it behave as if the operation succeeded? Security checks must fail closed. Related habits: report exceptions instead of discarding them, monitor failed_jobs, and let APP_DEBUG=false + a custom error page handle rendering.
Do not start a compliance spreadsheet. For a typical Laravel app the leverage order is: fix access control (A01), fix configuration (A02), grep out injection sinks (A05), then put audits in CI for the supply chain (A03). That covers the categories that produce real-world Laravel breaches; the rest you harden iteratively. A professional Laravel security audit reports findings mapped to these categories with severity and a fix for each — useful both for fixing and for showing customers a recognized framework behind your security story.
2025 — it is the current edition and reorders risk to match real-world data (access control and misconfiguration on top). If a customer questionnaire still references 2021 categories, the mapping above covers both; the underlying fixes are identical.
Laravel ships secure defaults for injection (bindings, Blade escaping), CSRF and password hashing — but A01, A02 and A06 are about your code and settings, not the framework's. Those three are where almost all Laravel findings live.
No — the Top 10 is an awareness document, not a certifiable standard. Saying "audited against OWASP Top 10:2025" communicates scope honestly; for formal attestation you would look at SOC 2 or ISO 27001, which an audit report supports but does not replace.
An audit maps every finding to OWASP categories, with severity and a concrete fix.