Preventing mass assignment com $fillable e $guarded
1. O que é Mass Assignment e por que é um Risco de Segurança?
Mass assignment é uma técnica que permite atribuir múltiplos campos de uma só vez a um modelo Eloquent, geralmente a partir de dados de formulário enviados pelo usuário. Em PHP, isso é comumente feito através dos métodos create() ou update() passando $request->all().
O perigo do mass assignment reside na possibilidade de um usuário malicioso manipular campos que não deveriam ser alterados por ele. Por exemplo, considere um formulário de cadastro que espera apenas name, email e password. Um invasor pode interceptar a requisição e adicionar campos extras como is_admin ou role_id:
// Código vulnerável
$user = User::create($request->all());
Se o modelo User não tiver proteção, o invasor poderia se tornar administrador simplesmente adicionando "is_admin": true ao payload JSON. As consequências incluem escalada de privilégios, corrupção de dados e falhas graves de autorização.
2. A Proteção Nativa do Eloquent contra Mass Assignment
Por padrão, o Eloquent do Laravel já vem com proteção ativa contra mass assignment. Se você tentar criar ou atualizar um modelo sem definir $fillable ou $guarded, uma exceção MassAssignmentException será lançada:
// Isso lançará MassAssignmentException se fillable/guarded não estiver definido
User::create(['name' => 'João', 'email' => 'joao@exemplo.com']);
É importante entender a diferença entre os métodos que são vulneráveis e os que são seguros:
- Susceptíveis a mass assignment:
create(),update(),firstOrCreate(),updateOrCreate() - Seguros (atribuição manual):
save(),fill(),forceFill()
// Seguro - atribuição manual
$user = new User();
$user->name = 'João';
$user->email = 'joao@exemplo.com';
$user->save();
3. Usando $fillable: A Lista de Campos Permitidos
$fillable funciona como uma whitelist (lista branca) de campos que podem ser atribuídos em massa. Apenas os campos listados aqui serão aceitos pelos métodos create() e update().
Sintaxe básica:
class User extends Model
{
protected $fillable = [
'name',
'email',
'password',
];
}
Exemplo prático de uso:
// No controller
public function store(Request $request)
{
// Apenas 'name', 'email' e 'password' serão atribuídos
// Qualquer campo extra (ex: is_admin) será ignorado
$user = User::create($request->only(['name', 'email', 'password']));
return response()->json($user, 201);
}
Mesmo que o usuário envie is_admin no request, o Eloquent simplesmente ignorará esse campo porque ele não está em $fillable.
Boas práticas: comece com $fillable vazio e vá adicionando campos conforme a necessidade real do modelo. Isso força você a pensar explicitamente sobre quais dados podem ser atribuídos em massa.
class User extends Model
{
// Comece vazio e adicione apenas o necessário
protected $fillable = [];
// Depois de validar a necessidade:
// protected $fillable = ['name', 'email'];
}
4. Usando $guarded: A Lista de Campos Bloqueados
$guarded funciona como uma blacklist (lista negra). Todos os campos são permitidos, exceto aqueles listados aqui.
Sintaxe básica:
class User extends Model
{
protected $guarded = [
'is_admin',
'password',
];
}
Exemplo prático:
class Log extends Model
{
// Bloqueia apenas campos sensíveis
protected $guarded = ['id', 'created_at', 'updated_at'];
}
Para proteção máxima, você pode usar $guarded = ['*'], que bloqueia todos os campos:
class User extends Model
{
// Proteção máxima - nenhum campo pode ser atribuído em massa
protected $guarded = ['*'];
}
Comparação direta:
| Aspecto | $fillable |
$guarded |
|---|---|---|
| Tipo | Whitelist | Blacklist |
| Segurança | Mais explícito | Menos explícito |
| Manutenção | Adicionar campos conforme necessário | Listar apenas campos sensíveis |
| Risco | Menor (esqueceu de adicionar = erro) | Maior (esqueceu de bloquear = vulnerabilidade) |
5. Estratégias para Escolher entre $fillable e $guarded
Quando usar $fillable:
- Modelos com muitos campos sensíveis (ex: User, Admin, Payment)
- Quando você precisa de controle granular sobre cada campo
- Em projetos onde segurança é prioridade máxima
Quando usar $guarded:
- Modelos com poucos campos sensíveis (ex: Log, Audit, Notification)
- Durante prototipagem rápida quando muitos campos precisam ser acessíveis
- Modelos onde quase todos os campos são "seguros" para atribuição
Recomendação geral: prefira $fillable por ser mais explícito e seguro. O esforço extra de listar campos permitidos é um pequeno preço pela segurança adicional.
Exemplo de decisão:
// Modelo User - use $fillable (muitos campos sensíveis)
class User extends Model
{
protected $fillable = ['name', 'email', 'password', 'avatar'];
// is_admin, role_id, etc. ficam de fora intencionalmente
}
// Modelo Log - use $guarded (poucos campos sensíveis)
class Log extends Model
{
protected $guarded = ['id', 'created_at'];
// Todos os outros campos podem ser atribuídos em massa
}
6. Boas Práticas e Casos Avançados
Combinar $fillable com validação de request:
use Illuminate\Foundation\Http\FormRequest;
class StoreUserRequest extends FormRequest
{
public function rules()
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
];
}
}
// No controller
public function store(StoreUserRequest $request)
{
// Validação + fillable protegem em camadas
$user = User::create($request->validated());
return response()->json($user, 201);
}
Uso de DTOs para controle refinado:
class UserDTO
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly string $password,
) {}
public function toArray(): array
{
return [
'name' => $this->name,
'email' => $this->email,
'password' => bcrypt($this->password),
];
}
}
// No controller
$dto = new UserDTO(...$request->validated());
$user = User::create($dto->toArray());
Tratamento de relacionamentos:
class Post extends Model
{
protected $fillable = ['title', 'content', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
// Criar post com usuário associado
$post = Post::create([
'title' => 'Meu Post',
'content' => 'Conteúdo...',
'user_id' => auth()->id(), // Permitido porque está em $fillable
]);
Testes automatizados:
class MassAssignmentTest extends TestCase
{
public function test_fillable_protege_campos_sensiveis()
{
$user = User::create([
'name' => 'João',
'email' => 'joao@teste.com',
'password' => 'senha123',
'is_admin' => true, // Este campo não está em $fillable
]);
$this->assertFalse($user->is_admin);
$this->assertEquals('João', $user->name);
}
}
7. Erros Comuns e Como Evitá-los
Erro 1: Esquecer de definir proteção
class User extends Model
{
// Nada definido - MassAssignmentException será lançada
}
// Solução: sempre definir $fillable ou $guarded
Erro 2: Incluir campos sensíveis no $fillable
// ERRADO - password_confirmation não deveria estar no modelo
protected $fillable = ['name', 'email', 'password', 'password_confirmation'];
// CORRETO - apenas campos que realmente vão para o banco
protected $fillable = ['name', 'email', 'password'];
Erro 3: Confundir $fillable com validação
// ERRADO - achar que $fillable substitui validação
protected $fillable = ['email']; // Apenas valida se o campo é permitido, não se é válido
// CORRETO - usar ambos em camadas
// $fillable para segurança, validação para integridade dos dados
Erro 4: Usar unguard() ou forceCreate() sem cuidado
// APENAS em cenários controlados (seeders, comandos artisan)
Model::unguard(); // Desativa proteção globalmente - use com extrema cautela
// Prefira métodos seguros
User::forceCreate([...]); // Ignora fillable/guarded - evite em produção
Referências
- Laravel Documentation - Mass Assignment — Documentação oficial do Laravel sobre proteção contra mass assignment, incluindo exemplos de $fillable e $guarded
- Laravel News - Understanding Mass Assignment — Artigo detalhado explicando o conceito de mass assignment e como proteger suas aplicações
- Stack Overflow - $fillable vs $guarded — Discussão técnica comparando as duas abordagens com exemplos práticos da comunidade
- Laracasts - Eloquent Mass Assignment Protection — Screencast gratuito demonstrando na prática como configurar proteção contra mass assignment
- Codecourse - Laravel Mass Assignment Explained — Tutorial em vídeo com exemplos avançados e boas práticas de segurança
- Medium - Preventing Mass Assignment Vulnerabilities — Artigo técnico com casos reais de vulnerabilidades e como preveni-las
- Laravel Daily - Mass Assignment Tips — Dicas práticas e exemplos de código para uso correto de $fillable e $guarded em projetos reais