Blog / AI-generated code
AI-generated code · 6 min read · June 10, 2026

Top Laravel vulnerabilities in AI-generated code

Colorful code on a monitor

AI assistants are very good at producing Laravel that looks right and runs in a demo. They are also very good at reproducing the insecure patterns they were trained on. After auditing a steady stream of Cursor-, Copilot- and Claude-built apps over the past two years, the same seven flaws account for the large majority of critical findings. This article walks through each one: what it looks like, why assistants keep writing it, and the exact fix.

If you want the one-sentence summary: AI-generated Laravel fails on authorization and input boundaries, not on exotic exploits. Everything below is fixable in an afternoon once you know where to look.

1. Mass assignment from $request->all()

The classic, and still the most common critical finding. The controller passes the entire request body straight into Eloquent:

// app/Http/Controllers/ProfileController.php public function update(Request $request) { $request->user()->update($request->all()); return back(); }

If a sensitive column is fillable — and assistants love setting protected $guarded = []; on models to silence MassAssignmentException — anyone can add is_admin=1 or role=owner to the request and write it. Your Blade form not having that field is irrelevant; the attacker is not using your form.

The fix is to validate and pass only the validated data:

$data = $request->validate([ 'name' => ['required', 'string', 'max:120'], 'email' => ['required', 'email', 'max:254'], ]); $request->user()->update($data);

Keep an allow-list in $fillable as the second layer, and enable Model::shouldBeStrict() in development so silently discarded attributes throw instead of hiding bugs. We cover the whole topic in plain language in Mass assignment, explained for non-security folks.

2. Missing authorization (IDOR)

Assistants reliably add authentication — the route sits behind auth middleware — and reliably forget authorization: checking that this particular user may touch this particular record.

Route::get('/invoices/{invoice}', [InvoiceController::class, 'show']) ->middleware('auth'); public function show(Invoice $invoice) { return view('invoices.show', compact('invoice')); // any logged-in user can read any invoice by changing the ID }

This is an insecure direct object reference, and it is the single most damaging class of bug we find: it leaks customer data, and on write routes it lets users edit or delete records they do not own. Fix it with a policy and an explicit check:

// app/Policies/InvoicePolicy.php public function view(User $user, Invoice $invoice): bool { return $invoice->team_id === $user->currentTeam->id; } // controller public function show(Invoice $invoice) { Gate::authorize('view', $invoice); return view('invoices.show', compact('invoice')); }

For nested resources, scope route model bindings to the parent (->scopeBindings()) so /teams/1/invoices/99 404s when invoice 99 belongs to another team. The test is mechanical: open two accounts, swap IDs in every URL and API call, and watch what comes back.

3. SQL injection through raw query fragments

The moment a query gets slightly complex — a dynamic sort, a search box, a reporting join — assistants abandon the query builder and interpolate strings:

$users = DB::select("SELECT * FROM users WHERE name LIKE '%{$request->q}%'"); $orders = Order::orderByRaw("{$request->sort} {$request->direction}")->get();

Both are injectable. Values belong in bindings, and identifiers (column names, directions) belong in allow-lists — bindings cannot protect a column name. The safe versions, and the reasons assistants drift into raw SQL in the first place, get a full article: Why AI loves raw queries (and how to stop it).

4. XSS through unescaped Blade output

Blade's {{ }} escapes by default, which makes Laravel hard to XSS — until the assistant needs to render something "rich" and reaches for the raw echo:

{{-- resources/views/comments/show.blade.php --}} {!! $comment->body !!}

Now any user who can save a comment can run JavaScript in every other visitor's browser: session theft, fake login forms, admin actions performed with the victim's cookies. Use {{ $comment->body }} unless the content is genuinely trusted HTML. If users legitimately write rich text, sanitize on output with an HTML purifier, or render Markdown with HTML stripped:

{{ Str::markdown($comment->body, ['html_input' => 'strip', 'allow_unsafe_links' => false]) }}

5. Unsafe file uploads

AI-written upload handlers tend to trust everything the client sends: the original filename, the extension, the MIME type, the size.

$request->file('avatar')->move( public_path('uploads'), $request->file('avatar')->getClientOriginalName() );

That single snippet allows overwriting other users' files, dropping a .php file into a publicly executed directory on permissive servers, and storing 2 GB "avatars". Validate strictly, let Laravel name the file, and keep uploads out of the web root unless they must be public:

$request->validate([ 'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'], ]); $path = $request->file('avatar')->store('avatars'); // hashed name, private disk

Two extra notes: treat SVG as code, not as an image (inline scripts make it an XSS vector), and serve private files through a controller or signed URL, never by guessable path.

6. Debug mode and leaked configuration

Vibe-coded apps routinely reach production with APP_DEBUG=true, a committed .env, or env() calls sprinkled through application code. Debug error pages hand an attacker stack traces, request data and config; a .env served by a misconfigured web root hands them everything else. Two checks right now:

curl -s https://yourapp.com/.env # must be 404/403, never the file php artisan about # Environment: production, Debug Mode: OFF

The full set of production settings — sessions, cookies, CORS, Telescope gates, caching — is in Turning off the footguns: production config for Laravel.

7. No rate limiting where it hurts

Assistants wire up login, registration, password reset and OTP endpoints without any throttle. That means free credential stuffing, free user enumeration, and a mail bill from password-reset spam. Laravel's limiter makes the fix small:

// app/Providers/AppServiceProvider.php RateLimiter::for('login', function (Request $request) { return Limit::perMinute(5)->by( Str::lower($request->input('email')).'|'.$request->ip() ); }); // routes/web.php Route::post('/login', [AuthController::class, 'store']) ->middleware('throttle:login');

Throttle anything that sends email or SMS, anything that checks a credential, and any expensive endpoint a bot could hammer.

Why assistants keep making these exact mistakes

It is not randomness. Three forces push the same direction:

  • Training data skew. Tutorials and Stack Overflow answers optimize for brevity, not safety — $request->all() appears in thousands of examples.
  • Local success bias. The assistant's loop ends when the feature works in the happy path. Authorization failures don't show up when you test with one account.
  • Error-silencing reflexes. When Laravel throws a guard-rail exception (mass assignment, CSRF, strict mode), the cheapest "fix" is to disable the guard rail — and that is exactly what models suggest.

The result is code that demos perfectly and fails adversarially. None of this means you should not ship AI-written code; it means the review step that a senior engineer used to do implicitly now has to happen explicitly.

Find them in your codebase this afternoon

A grep pass catches a surprising share of the above. Run these from the project root:

grep -rn "->all()" app/Http grep -rn "guarded = \[\]" app/Models grep -rnE "DB::raw|whereRaw|orderByRaw|selectRaw|havingRaw" app grep -rn "{!!" resources/views grep -rn "getClientOriginalName" app php artisan route:list --except-vendor # every write route should name auth + a policy/can check composer audit

Every hit is not automatically a vulnerability — but every hit deserves thirty seconds of attention. For a structured pass with two-account testing and severity calls, follow our pre-launch review checklist.

Get it reviewed

If an assistant wrote a meaningful part of your app, a short, senior AI-code security audit pays for itself the first time it catches one of the seven above. Every finding we report comes with a concrete fix, and you can see a sample report before booking anything.

FAQ

Is AI-generated Laravel code less secure than human code?

It fails differently. Human teams accumulate fewer, weirder bugs; assistants produce the same handful of textbook flaws at high frequency — mass assignment, missing authorization, raw SQL. That regularity is good news: a focused review catches most of it quickly.

Which of these is the most dangerous?

Missing authorization. Injection and XSS need a payload; an IDOR needs only a changed ID in the URL, and it directly exposes other customers' data — which is usually a reportable breach, not just a bug.

Won't a static analyzer catch all this?

Tools like Larastan and security scanners flag some patterns (raw SQL, debug config). They cannot tell whether this invoice should be visible to that user — authorization is business logic, and it is exactly where AI code fails most. Pair tooling with a human pass over every route.

Audit your AI-generated Laravel app.

A senior engineer reviews every line the assistant wrote. Fixed price, every finding with a fix.