Тема
Безопасность Laravel — лучшие практики (OWASP)
Дополняет правила безопасности в корневом
../../CLAUDE.mdи../blade.md. Проверять при любых изменениях на периметре (формы, API, публичные роуты).
SQL-инъекции
php
// ОПАСНО — конкатенация
DB::select("SELECT * FROM users WHERE email = '$email'");
User::whereRaw("name = '$name'");
// БЕЗОПАСНО — bindings / Eloquent
User::where('email', $email)->first();
DB::select('SELECT * FROM users WHERE email = ?', [$email]);
User::whereRaw('lower(name) = ?', [mb_strtolower($name)]); // bindings даже в rawEloquent/Query Builder экранируют значения. DB::raw() — только для выражений (не данных), данные — через bindings.
XSS
| Синтаксис | Поведение | Когда |
|---|---|---|
| экранирует HTML | ВСЕГДА по умолчанию |
{!! $html !!} | НЕ экранирует | только доверенный HTML из админки |
@js($var) | безопасная передача в JS | Blade → JS-переменная |
{!! !!} с пользовательским вводом = XSS. Если нужен HTML от пользователя — санитизация (mews/purifier / HTMLPurifier) перед выводом.
CSRF
- Web-формы:
@csrfв форме (middleware проверяет автоматически). - Исключения (вебхуки):
$middleware->validateCsrfTokens(except: ['stripe/*'])вbootstrap/app.php. - API на Sanctum (SPA) — CSRF через cookie; токен-API — CSRF не нужен (stateless).
Массовое присвоение
php
protected $fillable = ['name', 'email']; // whitelist
// НИКОГДА: $guarded = [] на модели с пользовательским вводом
// НИКОГДА: User::create($request->all()); → используй validated()
User::create($request->validated());Авторизация
- Policies для моделей:
php artisan make:policy PostPolicy --model=Post. Gate::authorize()/$this->authorize()/@canв Blade.- Не проверять права в middleware «вручную» — Policy/Gate централизованно.
- Route model binding по
slug(публичные) /id(admin) +scopeBindings()для вложенных (проверка принадлежности child к parent).
php
public function update(UpdatePostRequest $request, Post $post)
{
$this->authorize('update', $post); // или в FormRequest::authorize()
...
}Валидация
- Только в FormRequest, не в контроллере.
max:255на все string-поля (защита от переполнения).email:rfc,dns,exists:table,colдля FK,unique:...->ignore($id)при update.- Файлы:
file,mimes:,max:(КБ) — проверять тип и размер. Хранить вне webroot или черезStorage.
Секреты и конфигурация
php
// НИКОГДА env() вне config-файлов (ломается при config:cache)
$key = env('STRIPE_KEY'); // ПЛОХО в коде
$key = config('services.stripe.key'); // ХОРОШО.env— в.gitignore,.env.example— в git с пустыми значениями.APP_DEBUG=falseв production — без исключений (stack traces видны юзерам).APP_KEYсгенерирован; ротация черезAPP_PREVIOUS_KEYS(L11+).- Секреты — только в
.env→config(), не в коде/коммитах.
Аутентификация
- Пароли: bcrypt/argon2 (дефолт),
BCRYPT_ROUNDSрастить со временем (авто-rehash в L11+). - Rate limiting на login/API:
throttle:middleware,Limit::perMinute()/perSecond(). - 2FA для админки (Fortify/Filament).
- Sanctum для SPA/мобильных API, Passport — только для полноценного OAuth-сервера.
HTTP-периметр
- HTTPS обязателен (Caddy/Let's Encrypt — см. корневой
E:\Clients\CLAUDE.md). - За reverse proxy:
TrustProxiesmiddleware +URL::forceScheme('https')поrequest()->isSecure(). - Security headers: HSTS, X-Content-Type-Options, CSP (на nginx/Caddy или middleware).
- Не раскрывать stack traces, версии, внутренние ошибки в production-ответах (500 → generic).
API
| Код | Когда |
|---|---|
| 401 | не аутентифицирован |
| 403 | аутентифицирован, но нет прав (Policy) |
| 422 | ошибка валидации |
| 429 | rate limit |
| 500 | серверная — БЕЗ деталей в prod |
- Rate limiting на каждый POST/PUT/DELETE.
- API Resources для трансформации (не отдавать модель напрямую — утечка полей).
- Не класть PII/секреты в URL (query string логируется).
Зависимости
composer auditв CI — known CVE в пакетах.- Обновлять зависимости регулярно (minor/patch не ломают по SemVer).
- Минимум сторонних пакетов на периметре — каждый = surface attack.
Чеклист security-review
- Все SQL через Eloquent/bindings, нет конкатенации
везде,{!! !!}только доверенный HTML@csrfв формах, вебхуки вexceptvalidated(), неall(); FormRequest на каждую форму- Policy/Gate на защищённых действиях
config(), неenv()в коде;.envне в git;APP_DEBUG=falseв prod- Rate limiting на API/login
composer auditзелёный