<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
БЕЗПЕКА · JUNIOR ЧАСТО ПИТАЮТЬ

Що таке mass assignment і як `$fillable`, `$guarded` і `validated()` від нього захищають?

Mass assignment трапляється, коли модель заповнюється масивом із запиту цілком: `User::create($request->all())` запише і `is_admin`, і `company_id`, якщо користувач їх дописав. Захист у два шари: модель дозволяє масове заповнення лише полів із `$fillable` (або `#[Fillable]` у Laravel 13), а контролер передає в неї `$request->validated()`, тобто лише поля, для яких є правила. Значення, які виставляє сервер (роль, власник, статус), присвоюють явно.

Що не так з рядком `$user->update($request->all())`?
Користувач додав у форму профілю поле `is_admin=1` і став адміном. Як так вийшло?
Що краще: `$fillable` чи `$guarded = []` разом із валідацією?
Ми ж валідуємо запит через Form Request. Навіщо тоді ще `$fillable`?
mass assignment Eloquent fillable guarded validated Form Request

User::create($request->all()) виглядає зручно, поки не згадати, що запит формує клієнт. HTML-форма може мати три поля, але ніщо не заважає відправити POST руками з четвертим, is_admin=1, або з company_id чужої компанії. Якщо модель приймає масив цілком, Eloquent запише все, що збігається з колонками таблиці. Саме так у 2012 році Єгор Хомаков додав свій SSH-ключ до організації rails на GitHub і зробив коміт у репозиторій Rails, після чого Rails спершу закрив масове присвоєння за замовчуванням у нових застосунках, а в Rails 4 перейшов на strong parameters.

Eloquent закритий так само: модель без налаштувань має $guarded = ['*'], і create() з будь-яким полем кидає MassAssignmentException. Відкривають її білим списком $fillable (у Laravel 13 ще й атрибутом #[Fillable([...])]) або чорним $guarded. Білий список надійніший: нова колонка is_verified, додана міграцією через пів року, лишається закритою, поки її свідомо не додадуть. Із чорним списком вона відкрита, і про це ніхто не подумає. Поля поза $fillable за замовчуванням мовчки відкидаються, тому в розробці вмикають Model::preventSilentlyDiscardingAttributes(), щоб бачити такі місця винятком, а не загадковим «поле не зберігається».

Другий шар у контролері. $request->validated() повертає лише ключі, для яких у Form Request є правила, і дописане поле туди не потрапить навіть з $guarded = []. Але валідація перевіряє, чи значення коректне, а не чи має користувач право його змінювати. Поле role з правилом in:user,admin пройде перевірку зі значенням admin, тому для форми профілю і для адмінки потрібні різні запити з різними правилами. Вкладені масиви теж треба обмежувати: 'meta' => 'array' пропустить будь-які ключі в JSON-колонку, а 'meta' => 'array:city,remote' лише перелічені.

Поля, які визначає сервер, узагалі не мають приходити з форми. Власника ставлять через звʼязок ($request->user()->resumes()->create(...)), статус і роль присвоюють явно або через forceFill() зі значеннями, сформованими в коді. Тоді в $fillable лишаються лише поля, які користувач справді редагує сам, і дві помилки, mass assignment і підстановка чужого company_id, закриваються одним рішенням.

// Було: у модель летить усе, що прислав клієнт, зокрема is_admin=1.
$user->update($request->all());

// Модель: білий список полів для масового заповнення (атрибут Laravel 13).
#[Fillable(['name', 'city', 'about'])]
class User extends Authenticatable {}

// Form Request: лише поля, які користувач має право змінювати сам.
public function rules(): array
{
    return [
        'name' => ['required', 'string', 'max:100'],
        'city' => ['nullable', 'string', 'max:60'],
        'about' => ['nullable', 'string', 'max:2000'],
        // 'role' тут немає: роль змінює адмінка іншим запитом з іншою перевіркою.
    ];
}

// Контролер: validated() замість all().
$request->user()->update($request->validated());

// Службові поля ставить сервер: власника через звʼязок, статус явно.
$vacancy = $company->vacancies()->make($request->validated());
$vacancy->status = VacancyStatus::Draft;
$vacancy->save();

// AppServiceProvider::boot(): у розробці падати на полях поза $fillable.
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
Що атака не потребує нічого, крім зайвого поля в запиті: HTML-форму не треба змінювати, достатньо відправити POST руками.
Що `$fillable` це білий список, а `$guarded` чорний, і білий надійніший, бо нова колонка за замовчуванням закрита.
Що `$request->validated()` повертає лише поля з правилами, а `$request->all()` усе, що прислав клієнт.
Що валідація відповідає на питання «чи коректне значення», а не «чи має право цей користувач його змінювати»: поле `role` з правилом `in:user,admin` пройде валідацію.
Що поля, які визначає сервер, присвоюють явно або через звʼязок (`$company->vacancies()->create(...)`), а не беруть з форми.
Ставити `protected $guarded = [];` на всі моделі «щоб не заважало» і передавати туди `$request->all()`: захисту не лишається жодного.
Додати `role` у правила Form Request для форми профілю, бо адмінка використовує той самий запит: тепер кожен користувач може призначити собі роль валідним запитом.
Брати `user_id` чи `company_id` з тіла запиту замість `$request->user()`: це одночасно mass assignment і IDOR.
Вважати, що `$fillable` заважає записати службові поля, і через це додавати їх у `$fillable`: для них є явне присвоєння, `forceFill()` і звʼязки.
Валідувати вкладений масив правилом `'meta' => 'array'` і передавати його в JSON-колонку: усередину пройдуть будь-які ключі.
ПОРАДА

Скажіть, що захист має два незалежні шари: модель вирішує, які поля взагалі можна заповнювати масово, а запит вирішує, які поля прийшли від цього користувача. І окремо назвіть поля, які ніколи не приходять із форми: власник, роль, статус оплати.

оновлено 8 жовтня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

`validated()` збирає дані лише за ключами з `rules()`, тож поле без правила відкидається. `all()` після валідації не змінюється і містить усе, що прийшло. Модель без налаштувань має `$guarded = ['*']` і кидає `MassAssignmentException`. Правило `in:user,admin` пропустить `admin`, бо це коректне значення; питання про право змінювати роль валідація не вирішує.