All skills
asyrafhussin avatar

/laravel-owasp-security

@ac26821

OWASP Top 10 security audit and secure coding guidelines for Laravel + React/Inertia.js applications. Use when auditing for vulnerabilities ("run OWASP audit", "security review", "check my app security") or writing secure Laravel code involving auth, payments, file uploads, or API design. Triggers on security-related tasks, payment handling, authentication, or any request to audit a Laravel codebase.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/laravel-owasp-security

This session only. Nothing lands on disk.

rulessec-cryptographic-failures.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Prevent Cryptographic Failures

Impact: CRITICAL (Prevents plaintext credential storage and sensitive data exposure)

Why It Matters

  • Risk: Plaintext or weakly hashed passwords are trivially cracked on any data breach. Unencrypted API keys in the database are exposed to any SQL dump
  • Impact: Full account takeover, payment gateway compromise, third-party service abuse
  • OWASP: A02:2021 — covers all forms of weak, missing, or misapplied cryptography

Incorrect

<?php

// ❌ Password stored as plaintext
class RegisteredUserController extends Controller
{
    public function store(Request $request)
    {
        User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => $request->password,  // Plaintext — NEVER do this
        ]);
    }
}
<?php

// ❌ Weak MD5 or SHA1 hashing — trivially cracked via rainbow tables
$hashedPassword = md5($request->password);
$hashedPassword = sha1($request->password);
<?php

// ❌ API secret stored as plaintext in the database
Setting::set('toyyibpay_secret_key', $request->secret_key);  // Plaintext in DB
// Now any SQL dump or DB read exposes the secret key
<?php

// ❌ Sensitive one-time link without signing — ID can be tampered
Route::get('/email/verify/{id}', function ($id) {
    User::findOrFail($id)->markEmailAsVerified();
});
// Attacker can verify any account by guessing or incrementing the ID

Problems:

  • Plaintext passwords are exposed in any database breach
  • MD5/SHA1 hashes are pre-computed in rainbow tables and cracked in seconds
  • Plaintext API keys in the DB are exposed in any DB dump or admin query
  • Unsigned verification links allow account takeover by ID manipulation

Correct

Always Hash Passwords with Bcrypt

<?php

declare(strict_types=1);

// ✅ Use Laravel's 'hashed' cast — automatically bcrypt on set
class User extends Authenticatable
{
    protected $casts = [
        'password' => 'hashed',  // Automatically hashed via Hash::make()
    ];
}

// ✅ Or use Hash::make() explicitly
User::create([
    'name'     => $request->name,
    'email'    => $request->email,
    'password' => Hash::make($request->password),
]);

Encrypt Sensitive Fields at Rest

<?php

declare(strict_types=1);

// ✅ Use Laravel's 'encrypted' cast for sensitive DB columns
class Setting extends Model
{
    protected $casts = [
        'value' => 'encrypted',  // Auto-encrypts using APP_KEY via AES-256-CBC
    ];
}

// ✅ Or use Crypt facade manually
Setting::set('toyyibpay_secret_key', Crypt::encryptString($request->secret_key));

// Decrypt on read
$secretKey = Crypt::decryptString(Setting::get('toyyibpay_secret_key'));

Use Signed URLs for Sensitive One-Time Actions

<?php

declare(strict_types=1);

// ✅ Signed URL for email verification — cannot be tampered
Route::get('/email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
    ->middleware(['auth', 'signed', 'throttle:6,1'])
    ->name('verification.verify');

// ✅ Generate signed URL with expiry
$url = URL::temporarySignedRoute(
    'verification.verify',
    now()->addMinutes(60),
    ['id' => $user->id, 'hash' => sha1($user->email)],
);

// ✅ Generate signed URL for password reset
$url = URL::signedRoute('password.reset', ['token' => $token, 'email' => $email]);

Mask Secrets in UI — Never Send Full Keys to Frontend

<?php

declare(strict_types=1);

// ✅ Mask secret in Inertia prop — frontend only needs to know if key is set
class SettingsController extends Controller
{
    public function index(): Response
    {
        $secretKey = Crypt::decryptString(Setting::get('toyyibpay_secret_key', ''));

        return Inertia::render('admin/settings/index', [
            'toyyibpay' => [
                'secret_key_set'    => $secretKey !== '',
                'secret_key_masked' => $secretKey !== ''
                    ? substr($secretKey, 0, 4) . str_repeat('*', 12)
                    : '',
            ],
        ]);
    }
}

Recommended Patterns

Pattern Use Case
'password' => 'hashed' cast User password fields
'value' => 'encrypted' cast API keys, secrets, tokens in DB
Crypt::encryptString() / decryptString() Manual encrypt/decrypt
URL::signedRoute() Password reset, email verify links
URL::temporarySignedRoute() Time-limited one-time links

Reference: Laravel Encryption | OWASP A02:2021

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The analyzed skill is a security auditing and secure coding reference toolkit designed specifically for Laravel 13 + React/Inertia.js environments. It operates using standard pattern matching and reference documentation to provide static code reviews without executing external code or performing suspicious administrative operations. No safety violations or malicious behaviors were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    6/14 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at ac26821. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
Other metadata
metadata
{
  "author": "AsyrafHussin",
  "version": "1.0.3",
  "laravelVersion": "13.x",
  "phpVersion": "8.3+"
}

README badge

README badge for asyrafhussin/agent-skills/laravel-owasp-security