Communitygithub.com

laravel-security

Laravelセキュリティのベストプラクティス — 認証、認可、Eloquentの安全性、CSRF、XSS対策、APIセキュリティ、安全なデプロイ構成。Laravelの認証、Eloquentの安全性、CSRF、XSS、APIセキュリティ、デプロイ構成をレビューする際に使用します。

laravel-security とは?

このスキルは、Laravelアプリケーションのセキュリティ強化に関する包括的なガイドラインを提供します。認証(Sanctum、Passport)、認可(ゲート、ポリシー、ミドルウェア)、Eloquentの安全性(マスアサインメント保護、SQLインジェクション防止、属性キャスト)、CSRF保護、XSS防止、安全なデプロイ構成など、主要な脆弱性対策を網羅しています。Claude CodeやCursorなど、AIコーディングエージェント向けに設計されており、Laravelアプリケーションのコードレビューや設定時に、セキュリティ上の問題を検出・修正するための具体的なコード例と設定スニペットを提供します。本番環境向けの具体的な設定(HTTPS強制、環境変数検証、セッション管理)から、詳細なコード実装(ポリシークラス、ミドルウェア、バリデーションルール)まで、実践的なセキュリティ知識を段階的に学習・適用できます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/laravel-security

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Laravelセキュリティのベストプラクティス

Laravelアプリケーションを一般的な脆弱性から保護するための包括的なセキュリティガイドライン。

アクティベーションのタイミング

  • Laravelの認証と認可(Sanctum、Passport、Jetstream、Breeze)のセットアップ
  • ユーザーロール、権限、ポリシーの実装
  • 本番環境のセキュリティ設定と環境変数の構成
  • Laravelアプリケーションの脆弱性レビュー
  • Laravelアプリケーションの本番環境へのデプロイ
  • 安全なEloquentクエリとマイグレーションの作成

本番環境の構成

重要な本番環境設定

// config/app.php
'env' => env('APP_ENV', 'production'),
'debug' => (bool) env('APP_DEBUG', false), // 重要: 本番環境では絶対にtrueにしない
'key' => env('APP_KEY'), // 必須: php artisan key:generate

// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax',

// 起動時にAPP_KEYが設定されているか確認
// bootstrap/app.php またはサービスプロバイダ
if (empty(config('app.key'))) {
    throw new RuntimeException('APP_KEYが設定されていません。実行: php artisan key:generate');
}

環境ファイルのセキュリティ

# .env をバージョン管理にコミットしてはいけません
# .gitignore にはデフォルトで .env が含まれています

# 代わりに.env.exampleをプレースホルダー付きで使用
DB_PASSWORD=
APP_KEY=
SANCTUM_TOKEN_PREFIX=

# 起動時に必要な変数を検証
// AppServiceProvider::boot() 内
$requiredKeys = ['app.key', 'database.connections.mysql.database', 'database.connections.mysql.username'];
foreach ($requiredKeys as $key) {
    if (empty(config($key))) {
        throw new RuntimeException("必須の設定キーが不足しています: {$key}");
    }
}

HTTPSの強制

// AppServiceProvider::boot() またはミドルウェア
if (app()->environment('production')) {
    URL::forceScheme('https');
    request()->server->set('HTTPS', 'on');
}

// config/app.php の信頼できるプロキシ(ロードバランサー)
// 特定のIP範囲を使用 — * はすべてを信頼し、X-Forwarded-*のなりすましを許します
// AWS: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],

// ミドルウェアを介して本番環境でHTTPSを強制
// app/Http/Middleware/ForceHttps.php
public function handle($request, Closure $next)
{
    if (!$request->secure() && app()->environment('production')) {
        return redirect()->secure($request->getRequestUri());
    }
    return $next($request);
}

認証

Sanctum(APIトークン認証)

// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
    env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
)));

'expiration' => 60 * 24, // トークンの有効期限(分単位、null = 無期限)
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),

// アビリティを持つトークンの発行
$token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;

// ルートでのアビリティ検証
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/orders', function () {
        // ユーザーは'read'アビリティを持っている必要があります
        abort_unless(Auth::user()->tokenCan('read'), 403);
        // ...
    })->middleware('abilities:read');

    Route::post('/orders', function () {
        // ユーザーは'write'アビリティを持っている必要があります
        abort_unless(Auth::user()->tokenCan('write'), 403);
        // ...
    })->middleware('abilities:write');
});

パスワードセキュリティ

// config/hashing.php
// デフォルトはbcrypt。Argon2idの方が強力です。
'bcrypt' => [
    'rounds' => env('BCRYPT_ROUNDS', 12), // より強力なハッシュにするために増やす
],

'argon' => [
    'memory' => 65536,
    'threads' => 4,
    'time' => 4,
],

// RegisterRequestでのパスワードバリデーション
public function rules(): array
{
    return [
        'password' => [
            'required',
            'confirmed',
            Password::min(12)
                ->letters()
                ->mixedCase()
                ->numbers()
                ->symbols()
                ->uncompromised(), // haveibeenpwnedをチェック
        ],
    ];
}

// ログイン試行のレート制限
// App\Http\Controllers\Auth\AuthenticatedSessionController
protected function authenticated(Request $request, $user)
{
    if ($user->wasRecentlyLockedOut()) {
        // 不審なログインをユーザーに通知
        $user->notify(new SuspiciousLoginNotification($request->ip()));
    }
}

セッション管理

// config/session.php
'driver' => env('SESSION_DRIVER', 'database'), // database/redis > file
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
'encrypt' => env('SESSION_ENCRYPT', false),

// ログイン時にセッションを再生成
// App\Http\Controllers\Auth\AuthenticatedSessionController
public function store(LoginRequest $request): RedirectResponse
{
    $request->authenticate();
    $request->session()->regenerate(); // 重要: セッション固定化を防ぎます
    return redirect()->intended(RouteServiceProvider::HOME);
}

// ログアウト時にセッションを無効化
public function destroy(Request $request): RedirectResponse
{
    Auth::guard('web')->logout();
    $request->session()->invalidate();
    $request->session()->regenerateToken();
    return redirect('/');
}

認可

ゲート

// App\Providers\AuthServiceProvider
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::define('update-post', function (User $user, Post $post): bool {
        return $user->id === $post->user_id;
    });

    Gate::define('publish-post', function (User $user): bool {
        return $user->role === 'editor' || $user->role === 'admin';
    });

    // before()を使用したスーパー管理者のオーバーライド
    Gate::before(function (User $user, string $ability): ?bool {
        if ($user->role === 'super-admin') {
            return true; // すべてのアビリティを付与
        }
        return null; // 通常のチェックにフォールスルー
    });
}

// コントローラーでの使用
public function update(Request $request, Post $post): RedirectResponse
{
    Gate::authorize('update-post', $post);
    // または: $this->authorize('update-post', $post);
    // または: abort_unless(Auth::user()->can('update-post', $post), 403);
    // ...
}

ポリシー

// App\Policies\PostPolicy
class PostPolicy
{
    use HandlesAuthorization;

    public function viewAny(?User $user): bool
    {
        return true; // 公開リスト
    }

    public function view(?User $user, Post $post): bool
    {
        return $post->is_published || ($user && $user->id === $post->user_id);
    }

    public function create(User $user): bool
    {
        return $user->hasVerifiedEmail(); // 最初にメールを確認する必要があります
    }

    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id && $post->created_at->diffInDays(now()) <= 30;
    }

    public function restore(User $user, Post $post): bool
    {
        return $user->role === 'admin';
    }

    public function forceDelete(User $user, Post $post): bool
    {
        return $user->role === 'super-admin';
    }
}

// AuthServiceProviderに登録
protected $policies = [
    Post::class => PostPolicy::class,
];

// コントローラーでの使用
public function show(Post $post): View
{
    $this->authorize('view', $post);
    return view('posts.show', compact('post'));
}

// Bladeでの使用
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}">編集</a>
@endcan

@cannot('update', $post)
    <span>この投稿は編集できません</span>
@endcannot

ミドルウェアによる認可

// ルートでのミドルウェアの使用
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('can:update,post');

Route::get('/posts/create', [PostController::class, 'create'])
    ->middleware('can:create,App\Models\Post');

// カスタム認可ミドルウェア
// app/Http/Middleware/CheckRole.php
class CheckRole
{
    public function handle(Request $request, Closure $next, string $role): mixed
    {
        if (!$request->user() || $request->user()->role !== $role) {
            abort(403, 'Unauthorized. This area requires role: ' . $role);
        }
        return $next($request);
    }
}

// Kernelに登録
protected $routeMiddleware = [
    'role' => \App\Http\Middleware\CheckRole::class,
];

// ルートでの使用
Route::middleware(['auth', 'role:admin'])->group(function () {
    Route::get('/admin', [AdminController::class, 'index']);
});

Eloquentセキュリティ

マスアサインメント保護

// 悪い例: $guarded = [] はすべてのカラムへのマスアサインメントを許可します
// 本番環境では決して $guarded = [] を使用しないでください

// 良い例: フィラブル属性をホワイトリスト化
final class User extends Authenticatable
{
    protected $fillable = [
        'name',
        'email',
        'phone',
        'avatar',
    ];
    // 'role', 'is_admin', 'is_verified' は絶対にここに追加しないでください
}

// 良い例: リクエストでどのフィールドを入力できるかを明示的に制御
public function store(StoreUserRequest $request): RedirectResponse
{
    $user = User::create($request->safe()->only([
        'name', 'email', 'phone', 'avatar'
    ]));
    // $request->safe() は検証済みデータのみを使用します
    // $request->only() は検証ルールなしでは安全ではありません
}

// 悪い例: リクエストデータで直接ユーザーを作成
User::create($request->all()); // マスアサインメントに対して脆弱!

// より良い例: 作成にDTOを使用
$user = User::create($request->validated()); // 検証済みフィールドのみ

SQLインジェクション防止

// 良い例: Eloquentは自動的にパラメータ化クエリを実行します
User::where('email', $userInput)->first();
User::whereRaw('email = ?', [$userInput])->first();

// 良い例: クエリビルダーもパラメータ化します
DB::table('users')->where('email', $userInput)->first();
DB::select('SELECT * FROM users WHERE email = ?', [$userInput]);

// 悪い例: 生の文字列補間
DB::select("SELECT * FROM users WHERE email = '{$userInput}'"); // 脆弱!
User::whereRaw("email = '{$userInput}'")->first(); // 脆弱!

// 悪い例: エスケープされていない入力での whereRaw/orderByRaw
User::orderByRaw($userInput); // 脆弱!
User::groupByRaw($userInput); // 脆弱!

// 悪い例: 連結での DB::statement
DB::statement("INSERT INTO users (email) VALUES ('{$userInput}')"); // 脆弱!

属性キャスト

final class User extends Authenticatable
{
    protected $casts = [
        'email_verified_at' => 'datetime',
        'is_admin' => 'boolean', // booleanにキャストすると文字列インジェクションを防ぎます
        'settings' => 'array', // 自動的にjson_encode/json_decode
        'metadata' => 'encrypted:array', // Laravel 11+ 暗号化キャスト
        'password' => 'hashed', // Laravel 10+ 設定時に自動ハッシュ化
    ];
}

モデルセキュリティ

final class User extends Authenticatable
{
    // 機密属性をJSON/APIレスポンスから隠す
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_secret',
        'two_factor_recovery_codes',
    ];

    // 安全な計算済み属性のみ追加
    protected $appends = ['full_name']; // 安全
    // 機密の計算済みデータは絶対に追加しない
}

final class Post extends Model
{
    // ソフトデリートされたレコードをフィルタリングするグローバルスコープ
    use SoftDeletes;

    // 遅延読み込みを制限してN+1を防ぐ(オプションの厳格モード)
    // AppServiceProvider::boot()
    // Model::preventLazyLoading(!app()->isProduction());
}

CSRF対策

デフォルトの保護

// Laravel CSRFはVerifyCsrfTokenミドルウェアによってデフォルトで有効
// app/Http/Kernel.php (protected $middlewareGroups['web'])

// すべてのPOST/PUT/PATCH/DELETEフォームには @csrf を含める必要があります
<form method="POST" action="/posts">
    @csrf
    <input type="text" name="title">
    <button type="submit">作成</button>
</form>

ルートの除外(慎重に)

// app/Http/Middleware/VerifyCsrfToken.php
class VerifyCsrfToken extends Middleware
{
    // 外部のCSRF対策があるルートのみ除外(webhookなど)
    protected $except = [
        'stripe/*', // Stripe webhookは独自の署名検証を使用
        // 包括的な 'api/*' は避けてください — ステートフルなSanctumルートはCSRFが必要です。
        // 特定のステートレスなwebhook/エンドポイントルートのみを除外します。
    ];
}

JavaScriptでのCSRF

<meta name="csrf-token" content="{{ csrf_token() }}">

<script>
// Axiosの例(LaravelにはAxiosが付属)
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector(
    'meta[name="csrf-token"]'
).getAttribute('content');

// Fetchの例
fetch('/posts', {
    method: 'POST',
    headers: {
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
});
</script>

XSS防止

Bladeテンプレートセキュリティ

{{-- 安全: Bladeによって自動エスケープ --}}
{{ $userInput }}

{{-- 危険: 生の出力 — ユーザー入力では絶対に使用しないでください --}}
{!! $userInput !!}

{{-- 安全: 制御された信頼できるコンテンツでのみ {!! !!} を使用 --}}
{!! $trustedHtmlFromYourServer !!}

{{-- 良い例: 特定のエスケープディレクティブを使用 --}}
@js($data) {{-- JavaScript用にJSONエンコード --}}
@json($data) {{-- テンプレート内でJSONエンコード --}}

{{-- 悪い例: 生のHTML内の直接のユーザー入力 --}}
<div>{!! $user->bio !!}</div> {{-- ユーザーがbioを提供する場合、脆弱 --}}

安全なHTML処理

// 一部のHTMLを許可する必要がある場合は、ホワイトリストアプローチを使用
use HTMLPurifier; // 必要: composer require ezyang/htmlpurifier

public function sanitizeHtml(string $dirty): string
{
    $config = \HTMLPurifier_Config::createDefault();
    $config->set('HTML.Allowed', 'p,b,i,a[href],ul,ol,li,br');
    $config->set('URI.AllowedSchemes', ['http', 'https', 'mailto']);
    $purifier = new \HTMLPurifier($config);
    return $purifier->purify($dirty);
}

// Blade内:
<div>{!! $sanitizedHtml !!}</div>

APIセキュリティ

APIレート制限

// app/Http/Kernel.php
protected $middlewareGroups = [
    'api' => [
        // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        'throttle:60,1', // 1分あたり60リクエスト
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

// 特定のルートのカスタムレート制限
Route::middleware('throttle:20,1')->group(function () {
    Route::post('/login', [AuthController::class, 'login']);
});

// ユーザーベースのレート制限(ログイン後)
Route::middleware(['auth:sanctum', 'throttle:100,1'])->group(function () {
    Route::get('/orders', [OrderController::class, 'index']);
});

CORS設定

// config/cors.php または config/cors.php(Laravel 9+)
return [
    // 許可するパス — APIルートのみ制限
    'paths' => ['api/*', 'sanctum/csrf-cookie'],

    // 許可するオリジン — 本番環境ではカンマ区切りリストを指定
    'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],

    // 許可するメソッド
    'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],

    // 許可するヘッダー
    'allowed_headers' => ['Content-Type', 'Authorization', 'X-Requested-With', 'X-CSRF-TOKEN'],

    // 公開するヘッダー
    'exposed_headers' => ['X-Pagination-Total'],

    // 認証情報
    'supports_credentials' => true, // Sanctumには必須

    // プリフライトリクエストの最大有効期間(秒)
    'max_age' => 86400,
];

APIリクエストバリデーション

// App/Http/Requests/StoreOrderRequest
class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // 認可はポリシー/ゲートで別途処理
    }

    public function rules(): array
    {
        return [
            'items' => 'required|array|min:1|max:50',
            'items.*.product_id' => 'required|exists:products,id',
            'items.*.quantity' => 'required|integer|min:1|max:100',
            'shipping_address' => 'required|string|max:500',
            'payment_method' => 'required|in:credit_card,paypal',
        ];
    }

    // 機密フィールドをログに残さない
    protected function passedValidation(): void
    {
        $this->replace($this->except(['credit_card_number', 'cvv']));
    }

    // 機密データはリクエストクエリに含めない(URLに表示される)
}

デプロイ設定

安全な環境変数

# 本番環境の .env 設定
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:... # 必ず設定

DB_HOST=your-database-host
DB_DATABASE=your-database
DB_USERNAME=your-username
DB_PASSWORD=your-strong-database-password # 強力なパスワードを使用

REDIS_PASSWORD=your-redis-password # Redisに認証が必要な場合
MAIL_PASSWORD=your-mail-password

# サードパーティサービスキー
STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...

# 機密性の高いサービス URL には本番ドメインの HTTPS を使用
APP_URL=https://your-production-domain.com
SANCTUM_STATEFUL_DOMAINS=your-production-domain.com

サーバー設定

# nginx 設定
server {
    listen 443 ssl http2;
    server_name your-production-domain.com;

    ssl_certificate /etc/ssl/certs/your-cert.crt;
    ssl_certificate_key /etc/ssl/private/your-key.key;

    root /var/www/html/public;
    index index.php;

    # セキュリティヘッダー
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    # 機密ファイルへのアクセスを拒否
    location ~ \.env {
        deny all;
    }

    location ~ \.git {
        deny all;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # 悪意のある入力をブロック
        if ($query_string ~* "<script>") {
            return 403;
        }
    }

    # ログ記録と監視
    access_log /var/log/nginx/your-app-access.log;
    error_log  /var/log/nginx/your-app-error.log;

    # ファイルアップロードの制限
    client_max_body_size 10M;
}

依存関係のセキュリティ

Composerセキュリティ監査

# 知られている脆弱性がないか依存関係を監査
composer audit

# すべての依存関係を最新に保つ
composer update

# ローカル開発のためのセキュリティ脆弱性スキャナー
composer require --dev enlightn/enlightn
php artisan enlightn

サードパーティパッケージのセキュリティ

// services.json 内の信頼できるパッケージのみを使用
// 定期的に監査: composer audit

// パッケージがアクティブにメンテナンスされているか確認
composer show --latest your/package

// 可能性のある悪意のあるコードがないかパッケージのソースを確認
// パッケージのGitHubリポジトリ、スター、フォーク、最終更新日を確認

ロギングと監視

安全なロギング

// config/logging.php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily'], // 毎日のローテーション
        'ignore_exceptions' => false,
    ],

    'daily' => [
        'driver' => 'daily',
        'path' => storage_path('logs/laravel.log'),
        'level' => env('LOG_LEVEL', 'error'), // 本番環境では error 以上
        'days' => 14, // 14日後にログをローテーション
    ],

    // セキュリティイベント専用チャンネル
    'security' => [
        'driver' => 'daily',
        'path' => storage_path('logs/security.log'),
        'level' => 'info',
        'days' => 90,
    ],
];

// ログに機密情報を含めない
Log::info('ユーザーがログインしました', ['user_id' => $user->id]); // 安全
Log::info('ユーザーがログインしました', ['password' => $request->password]); // 危険

// セキュリティイベントのログ記録
public function store(LoginRequest $request): RedirectResponse
{
    // ...
    Log::channel('security')->info('ログイン成功', [
        'user_id' => $user->id,
        'ip' => $request->ip(),
        'user_agent' => $request->userAgent(),
        'timestamp' => now(),
    ]);
    // ...
}

セキュリティヘッダー

Laravelでのセキュリティヘッダー

// config/secure-headers.php を作成するか、ミドルウェアを使用

// App\Http\Middleware\SecurityHeaders.php
class SecurityHeaders
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

        // Content-Security-Policy(アプリケーションのニーズに合わせて調整)
        $csp = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';";
        $response->headers->set('Content-Security-Policy', $csp);

        return $response;
    }
}

// Kernel.phpのミドルウェアグループに登録
protected $middlewareGroups = [
    'web' => [
        // ...
        \App\Http\Middleware\SecurityHeaders::class,
    ],
];

メンテナンス

安全なメンテナンスモード

# Laravelメンテナンスモードを有効化
php artisan down --secret="your-secret-token" --render="errors::503"

# 特定IPのバイパスを許可
php artisan down --allow=192.168.1.1 --allow=10.0.0.0/8

# メンテナンスモードを終了
php artisan up

定期的なセキュリティタスク

// App\Console\Kernel.php
protected function schedule(Schedule $schedule): void
{
    // 依存関係の脆弱性スキャン
    $schedule->exec('composer audit')->daily()->emailOutputTo('[email protected]');

    // データベースバックアップ(暗号化)
    $schedule->command('backup:run --only-db')->daily()->at('02:00');

    // セッションテーブルのクリーンアップ
    $schedule->command('session:gc')->hourly();

    // 期限切れトークンのクリーンアップ
    $schedule->command('sanctum:prune-expired --hours=24')->daily();
}

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

関連スキル