- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Mass Assignment in Laravel: The Bug Hiding in Your create() Call
About Post
Here's a registration endpoint. It validates its input. It looks fine. It passes code review in a lot of teams.
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users'],
'password' => ['required', 'string', 'min:8'],
]);
$user = User::create($request->all());
return response()->json($user, 201);
}
And here's how someone becomes an administrator of your app without ever seeing an admin screen:
curl -X POST https://example.com/api/register \
-d name=Eve -d [email protected] \
-d password=secret123 -d is_admin=1
If the users table has an is_admin column and the model allows it to be filled, Eve is now an admin. That's mass assignment: the request decides which columns get written, instead of your code.
Why this is possible
User::create($array) sets every attribute in the array at once. Convenient, and dangerous, because the array came from the client. Your form only has three fields, but the client can send anything. Browser dev tools, curl or Postman make adding a field trivial.
This isn't theoretical, and it isn't just a Laravel thing. In 2012, a developer used a mass assignment flaw in GitHub, a Rails app, to push a commit to the Rails repository itself. It was a big reason Rails changed how it handles request parameters.
Eloquent protects you by default. A model with neither $fillable nor $guarded refuses mass assignment and throws a MassAssignmentException. The trouble starts with how we make that exception go away.
Mistake 1: $guarded = [] to silence the error
class User extends Authenticatable
{
protected $guarded = []; // "fixes" the exception, removes the protection
}
An empty $guarded means every column is fillable. On its own, that's a choice some teams make deliberately, if they never pass raw request data to create() or update(). Combined with $request->all(), it's the exact hole from the example above.
Fix: prefer an explicit $fillable list. It's a little more typing and much easier to review:
protected $fillable = ['name', 'email', 'password'];
With $fillable set, extra keys like is_admin are silently dropped instead of saved.
Mistake 2: believing validation removes extra fields
This is the one that catches experienced developers. Look at the first example again: it does validate. But validate() checks the fields you listed. It doesn't remove the ones you didn't. $request->all() still contains is_admin.
Fix: use only validated data. $request->validate() returns it, and a Form Request gives you validated():
public function store(RegisterRequest $request)
{
$user = User::create($request->validated());
return response()->json($user, 201);
}
Need a subset? $request->safe()->only(['name', 'email']). Now there are two layers: validation decides which keys exist, and $fillable decides which columns can be written.
One subtle gap: a rule like 'settings' => ['array'] lets the whole array through, with whatever keys the client chose. Use 'settings' => ['array:theme,language'] to allow only specific keys, or validate each child field.
Mistake 3: sensitive columns in $fillable
The admin panel needs to change a user's role, so role goes into $fillable. Months later, someone builds a "update my profile" endpoint with $user->update($request->all()). Now every user can change their own role.
Fix: keep privilege and ownership columns out of $fillable entirely: is_admin, role, user_id, tenant_id, status, prices and balances. Set them explicitly, in code that has already checked permission:
// Only in an action that has authorized the change
$this->authorize('changeRole', $user);
$user->forceFill(['role' => $request->validated('role')])->save();
forceFill() bypasses mass assignment protection on purpose. That's fine when it's visible, deliberate and guarded by authorization, which is the point.
Mistake 4: taking ownership from the request
A user creates a maintenance request, and the client sends user_id along with it. Even if user_id is fillable for good reasons, it should never come from the client. Take it from the authenticated user instead, ideally through the relationship:
$request->user()->maintenanceRequests()->create($request->validated());
The relationship sets the foreign key for you. The client can't create records on someone else's behalf, whatever it sends.
The rule: the client chooses values, your code chooses columns. Validated data in, explicit $fillable on the model, and anything to do with permissions or ownership set by code, never by the request.
A safety net for development
Silently dropping unknown attributes is safe, but it also hides typos: you send phone_numer and wonder why nothing saves. Laravel can make that loud while you develop:
// AppServiceProvider::boot()
Model::preventSilentlyDiscardingAttributes(! app()->isProduction());
Outside production, filling a non-fillable attribute now throws an exception. Model::shouldBeStrict() turns this on together with lazy-loading prevention and missing-attribute checks. The Eloquent docs on mass assignment cover the details.
Review checklist
- Search the codebase for
->all()passed intocreate,update,fillorfirstOrCreate. - Search for
$guarded = []and check every write path for that model. - Make sure
$fillablelists have no privilege, ownership or money columns. - Use
validated()orsafe()->only()everywhere data reaches Eloquent. - Set ownership from
$request->user(), never from input.
This search doesn't take long, and it's one of the most useful things you can do in a Laravel security review. Have you ever found a $request->all() hiding somewhere it shouldn't be? What was the column that worried you most?

Be first to comment it...