Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Buenas prácticas de seguridad en Laravel para autenticación/autorización, validación, CSRF, asignación masiva, subida de archivos, secretos, limitación de velocidad y despliegue seguro.
.claude/skills/affaan-m-laravel-security/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-19 | ✓→✓ | = Same ✓ | 99% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 89% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 75% | 0% |
针对 Laravel 应用程序的全面安全指导,以防范常见漏洞。
VerifyCsrfToken 实现 CSRF,通过 SecurityHeaders 实现安全标头)。auth:sanctum、$this->authorize、策略中间件)。UploadInvoiceRequest)。RateLimiter::for('login'))。URL::temporarySignedRoute + signed 中间件)。APP_DEBUG=falseAPP_KEY 必须设置,并在泄露时轮换SESSION_SECURE_COOKIE=true 和 SESSION_SAME_SITE=lax(对于敏感应用,使用 strict)SESSION_HTTP_ONLY=true 以防止 JavaScript 访问SESSION_SAME_SITE=strict路由保护示例:
phpuse Illuminate\Http\Request; use Illuminate\Support\Facades\Route; Route::middleware('auth:sanctum')->get('/me', function (Request $request) { return $request->user(); });
Hash::make() 哈希密码,切勿存储明文phpuse Illuminate\Support\Facades\Hash; use Illuminate\Validation\Rules\Password; $validated = $request->validate([ 'password' => ['required', 'string', Password::min(12)->letters()->mixedCase()->numbers()->symbols()], ]); $user->update(['password' => Hash::make($validated['password'])]);
php$this->authorize('update', $project);
使用策略中间件进行路由级强制执行:
phpuse Illuminate\Support\Facades\Route; Route::put('/projects/{project}', [ProjectController::class, 'update']) ->middleware(['auth:sanctum', 'can:update,project']);
$fillable 或 $guarded,避免使用 Model::unguard()phpDB::select('select * from users where email = ?', [$email]);
{{ }}){!! !!}VerifyCsrfToken 中间件启用@csrf,并为 SPA 请求发送 XSRF 令牌对于使用 Sanctum 的 SPA 身份验证,确保配置了有状态请求:
php// config/sanctum.php 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
phpfinal class UploadInvoiceRequest extends FormRequest { public function authorize(): bool { return (bool) $this->user()?->can('upload-invoice'); } public function rules(): array { return [ 'invoice' => ['required', 'file', 'mimes:pdf', 'max:5120'], ]; } }
php$path = $request->file('invoice')->store( 'invoices', config('filesystems.private_disk', 'local') // set this to a non-public disk );
throttle 中间件phpuse Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; RateLimiter::for('login', function (Request $request) { return [ Limit::perMinute(5)->by($request->ip()), Limit::perMinute(5)->by(strtolower((string) $request->input('email'))), ]; });
对静态的敏感列使用加密转换。
phpprotected $casts = [ 'api_token' => 'encrypted', ];
设置标头的中间件示例:
phpuse Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; final class SecurityHeaders { public function handle(Request $request, \Closure $next): Response { $response = $next($request); $response->headers->add([ 'Content-Security-Policy' => "default-src 'self'", 'Strict-Transport-Security' => 'max-age=31536000', // add includeSubDomains/preload only when all subdomains are HTTPS 'X-Frame-Options' => 'DENY', 'X-Content-Type-Options' => 'nosniff', 'Referrer-Policy' => 'no-referrer', ]); return $response; } }
config/cors.php 中限制来源php// config/cors.php return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], 'allowed_origins' => ['https://app.example.com'], 'allowed_headers' => [ 'Content-Type', 'Authorization', 'X-Requested-With', 'X-XSRF-TOKEN', 'X-CSRF-TOKEN', ], 'supports_credentials' => true, ];
phpuse Illuminate\Support\Facades\Log; Log::info('User updated profile', [ 'user_id' => $user->id, 'email' => '[REDACTED]', 'token' => '[REDACTED]', ]);
composer audit使用签名路由生成临时的、防篡改的链接。
phpuse Illuminate\Support\Facades\URL; $url = URL::temporarySignedRoute( 'downloads.invoice', now()->addMinutes(15), ['invoice' => $invoice->id] );
phpuse Illuminate\Support\Facades\Route; Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download']) ->name('downloads.invoice') ->middleware('signed');
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | pass→pass | 6,859 | 3,678 | -46% | 1 | 1 | 0% | 1,318 | 2,617 | +99% | 0 | 0 | — |
case-01 | fail→fail | 11,083 | 8,047 | -27% | 1 | 1 | 0% | 2,529 | 3,844 | +52% | 0 | 0 | — |
case-02 | fail→pass | 11,954 | 9,099 | -24% | 1 | 1 | 0% | 2,429 | 3,834 | +58% | 0 | 0 | — |
case-03 | pass→pass | 8,864 | 5,569 | -37% | 1 | 1 | 0% | 1,618 | 3,064 | +89% | 0 | 0 | — |
case-04 | fail→pass | 11,501 | 10,569 | -8% | 1 | 1 | 0% | 2,344 | 4,176 | +78% | 0 | 0 | — |
case-05 | pass→pass | 9,247 | 7,540 | -18% | 1 | 1 | 0% | 2,022 | 3,546 | +75% | 0 | 0 | — |
case-06 | pass→pass | 4,223 | 3,186 | -25% | 1 | 1 | 0% | 776 | 2,580 | +232% | 0 | 0 | — |
case-07 | pass→pass | 10,205 | 7,168 | -30% | 1 | 1 | 0% | 2,147 | 3,410 | +59% | 0 | 0 | — |
case-08 | pass→pass | 7,604 | 3,673 | -52% | 1 | 1 | 0% | 1,488 | 2,651 | +78% | 0 | 0 | — |
case-09 | fail→fail | 13,029 | 12,299 | -6% | 1 | 1 | 0% | 2,592 | 4,339 | +67% | 0 | 0 | — |
case-10 | pass→pass | 4,363 | 3,100 | -29% | 1 | 1 | 0% | 932 | 2,610 | +180% | 0 | 0 | — |
case-11 | pass→pass | 4,585 | 5,113 | +12% | 1 | 1 | 0% | 835 | 3,024 | +262% | 0 | 0 | — |
case-12 | pass→pass | 9,250 | 9,141 | -1% | 1 | 1 | 0% | 1,882 | 3,937 | +109% | 0 | 0 | — |
case-13 | fail→fail | 7,455 | 6,987 | -6% | 1 | 1 | 0% | 1,586 | 3,357 | +112% | 0 | 0 | — |
case-14 | pass→pass | 3,415 | 3,213 | -6% | 1 | 1 | 0% | 566 | 2,485 | +339% | 0 | 0 | — |
case-15 | pass→pass | 3,941 | 2,813 | -29% | 1 | 1 | 0% | 716 | 2,478 | +246% | 0 | 0 | — |
case-16 | pass→pass | 7,286 | 4,062 | -44% | 1 | 1 | 0% | 1,457 | 2,723 | +87% | 0 | 0 | — |
case-17 | pass→pass | 3,625 | 3,110 | -14% | 1 | 1 | 0% | 606 | 2,209 | +265% | 0 | 0 | — |
case-18 | pass→pass | 7,246 | 5,975 | -18% | 1 | 1 | 0% | 1,267 | 3,025 | +139% | 0 | 0 | — |
case-20 | pass→pass | 3,518 | 3,490 | -1% | 1 | 1 | 0% | 733 | 2,662 | +263% | 0 | 0 | — |
case-21 | pass→pass | 5,786 | 3,819 | -34% | 1 | 1 | 0% | 1,227 | 2,788 | +127% | 0 | 0 | — |
case-22 | pass→pass | 4,951 | 6,178 | +25% | 1 | 1 | 0% | 1,032 | 3,221 | +212% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.