By framework
Laravel security — .env exposure, APP_DEBUG, and authorization
Laravel's big three security pitfalls — .env exposed from the public directory, APP_DEBUG on in production, and missing authorization (IDOR / Mass Assignment) — and how to close each. Defensive, no attack steps.
For: anyone running a Laravel app. No attack steps here — just the big three operational incidents and how to close them. For the full picture, see the security-by-framework hub.
The big three pitfalls (where defaults won't save you)
Laravel's escaping and authentication are excellent, but these three are yours to close.
① Exposed secrets
.env, backups, or keys reachable by URL from public/. A placement slip = instant leak.
② Debug on in production
APP_DEBUG=true exposes env vars and connection info on the error page. Extracted via deliberate errors.
③ Missing authorization
Authenticated but no owner scope (IDOR) / Mass Assignment overwriting is_admin, etc.
How to close them (3 steps)
Move secrets outside the public directory (perms 600)
.env outside the document root, but don't accidentally drop backups, exports, or key files into public/. Keep secrets outside the app root at perms 600 (owner-only). (→ keep secrets out of public directories · a full .env exposure case)Disable debug + cache config in production
APP_DEBUG=false and APP_ENV=production. Pin it with a config cache and stop detailed errors from showing externally. Bake it into the deploy steps and verify every time.Build authorization (Policy/Gate + $fillable)
$fillable to prevent unintended fields from being overwritten. (→ what IDOR is)Common (dangerous)
- backups or keys placed in
public/, reachable by URL - production left at
APP_DEBUG=true - no authorization — "logged in = can view"
Model::create($request->all())accepting every field
Correct
- secrets outside the document root at perms 600
- production debug off + config cache
- Policy/Gate owner scoping
- declare
$fillableto block Mass Assignment
This site's view: solid up to the defaults; authorization is on you
Laravel has many good defaults, but authorization — who may do what — is app-specific, so no framework can guard it automatically. The incidents we keep seeing are less SQLi or XSS than the "authenticated, but no ownership check" type. So the center of gravity isn't flashy config but the unglamorous work of scoping every read/update by owner. Add keeping secrets off the public surface and turning off production debug, and you prevent most real-world Laravel incidents with these three.
Read next
- Hub: security by framework · WordPress security
- Secrets: keep secrets out of public directories · Case: a full .env exposure
- Authorization: what IDOR is · Practice: the vulnerability-response playbook
FAQ
QIs Laravel a secure framework?
Laravel ships many safe defaults (escaping, authentication) and is fairly solid out of the box. But 'safe defaults' and 'safe operations' are different things, and real incidents come not from the core but from configuration and operations. Exposed .env/secrets, debug left on in production, and thin authorization are areas the framework won't cover for you. You can't rely on defaults alone — you have to close these three yourself.
QWhat's dangerous about shipping with APP_DEBUG=true?
With debug on, an error page can show not just a stack trace but internal secrets like environment variables and connection info. An attacker can trigger errors on purpose to extract them. In production, always set APP_DEBUG=false and cache the config (config cache) to make it stick, and stop detailed errors from being shown externally.
QWhy is 'if you're logged in, you can see it' dangerous?
That's authentication without authorization. Even when logged in, if the data isn't scoped to the user (by user_id, etc.), swapping the ID in the URL can reveal someone else's data (IDOR). In Laravel, implement ownership checks with Policy/Gate, and declare $fillable for Mass Assignment to prevent unintended fields from being overwritten.