Claude Code Plugins

Community-maintained marketplace

Feedback

PHP security best practices and patterns for preventing common vulnerabilities

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name security-patterns
description PHP security best practices and patterns for preventing common vulnerabilities

PHP Security Patterns

Language-level security patterns for PHP, applicable to any PHP project.

Input Validation

Type Validation

// Always validate and sanitize input
public function processUserId(mixed $input): int
{
    if (!is_numeric($input)) {
        throw new \InvalidArgumentException('User ID must be numeric');
    }

    $id = (int)$input;

    if ($id <= 0) {
        throw new \InvalidArgumentException('User ID must be positive');
    }

    return $id;
}

Email Validation

public function validateEmail(string $email): bool
{
    // Use built-in filter
    $filtered = filter_var($email, FILTER_VALIDATE_EMAIL);

    if ($filtered === false) {
        throw new \InvalidArgumentException('Invalid email format');
    }

    return true;
}

URL Validation

public function validateUrl(string $url): bool
{
    $filtered = filter_var($url, FILTER_VALIDATE_URL);

    if ($filtered === false) {
        throw new \InvalidArgumentException('Invalid URL format');
    }

    // Additional checks
    $parsed = parse_url($url);

    // Only allow http/https
    if (!in_array($parsed['scheme'] ?? '', ['http', 'https'])) {
        throw new \InvalidArgumentException('URL must use http or https');
    }

    return true;
}

SQL Injection Prevention

Use ORM Query Builder (Recommended)

// ✅ CORRECT: Use ORM (CakePHP example)
public function findUserByEmail(string $email): ?User
{
    return $this->Users->find()
        ->where(['email' => $email])  // Automatically parameterized
        ->first();
}

// ✅ CORRECT: ORM with conditions array
public function findActiveUsers(int $companyId): array
{
    return $this->Users->find()
        ->where([
            'company_id' => $companyId,
            'status' => 1,
            'del_flg' => 0,
        ])
        ->toArray();
}

// ❌ WRONG: Raw SQL with string concatenation
public function findUserUnsafe(string $email): ?User
{
    $query = "SELECT * FROM users WHERE email = '" . $email . "'";  // VULNERABLE!
    return $this->getConnection()->query($query)->fetch();
}

Use Query Expressions for Complex Conditions

// ✅ CORRECT: Use Query expressions for complex logic
public function findUsersWithComplexCondition(string $searchTerm): array
{
    return $this->Users->find()
        ->where(function ($exp, $q) use ($searchTerm) {
            return $exp->or([
                'email LIKE' => '%' . $searchTerm . '%',
                'name LIKE' => '%' . $searchTerm . '%',
            ]);
        })
        ->toArray();
}

Only Use Raw SQL When Absolutely Necessary

// If raw SQL is unavoidable, ALWAYS use parameter binding
public function executeRawQuery(int $userId): array
{
    $conn = $this->getConnection();

    // ✅ CORRECT: Named parameters
    $stmt = $conn->execute(
        'SELECT * FROM users WHERE id = :id',
        ['id' => $userId],
        ['id' => 'integer']
    );

    return $stmt->fetchAll();
}

// ❌ WRONG: Never concatenate user input
public function unsafeRawQuery(string $email): array
{
    $sql = "SELECT * FROM users WHERE email = '$email'";  // VULNERABLE!
    return $this->getConnection()->execute($sql)->fetchAll();
}

XSS (Cross-Site Scripting) Prevention

Output Escaping

// ✅ CORRECT: Escape output
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

// For HTML attributes
echo '<input value="' . htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '">';

// For JavaScript
echo '<script>var name = "' . json_encode($name, JSON_HEX_TAG | JSON_HEX_AMP) . '";</script>';

// For URLs
echo '<a href="' . urlencode($url) . '">Link</a>';

Content Security Policy

// Set CSP headers
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");

CSRF (Cross-Site Request Forgery) Prevention

Token Generation

class CsrfToken
{
    public static function generate(): string
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        $token = bin2hex(random_bytes(32));
        $_SESSION['csrf_token'] = $token;

        return $token;
    }

    public static function validate(string $token): bool
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
    }
}

Form Implementation

// In form
<form method="POST">
    <input type="hidden" name="csrf_token" value="<?= CsrfToken::generate() ?>">
    <!-- Other fields -->
</form>

// In handler
if (!CsrfToken::validate($_POST['csrf_token'] ?? '')) {
    throw new SecurityException('Invalid CSRF token');
}

Password Security

Hashing

// ✅ CORRECT: Use password_hash()
public function hashPassword(string $password): string
{
    return password_hash($password, PASSWORD_ARGON2ID, [
        'memory_cost' => 65536,
        'time_cost' => 4,
        'threads' => 3,
    ]);
}

// ❌ WRONG: Never use md5, sha1, or plain text
public function insecureHash(string $password): string
{
    return md5($password);  // VULNERABLE!
}

Verification

public function verifyPassword(string $password, string $hash): bool
{
    return password_verify($password, $hash);
}

public function needsRehash(string $hash): bool
{
    return password_needs_rehash($hash, PASSWORD_ARGON2ID, [
        'memory_cost' => 65536,
        'time_cost' => 4,
        'threads' => 3,
    ]);
}

Session Security

Secure Session Configuration

// Configure secure sessions
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1');  // HTTPS only
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', '1');

// Regenerate session ID on login
session_start();
if ($loginSuccessful) {
    session_regenerate_id(true);
}

Session Fixation Prevention

public function login(string $username, string $password): bool
{
    if ($this->authenticate($username, $password)) {
        // Regenerate session ID to prevent fixation
        session_regenerate_id(true);

        $_SESSION['user_id'] = $userId;
        $_SESSION['last_activity'] = time();

        return true;
    }

    return false;
}

Session Timeout

public function checkSessionTimeout(): bool
{
    $timeout = 1800; // 30 minutes

    if (isset($_SESSION['last_activity'])) {
        if (time() - $_SESSION['last_activity'] > $timeout) {
            session_destroy();
            return false;
        }
    }

    $_SESSION['last_activity'] = time();
    return true;
}

File Upload Security

Validation

public function validateUpload(array $file): bool
{
    // Check for upload errors
    if ($file['error'] !== UPLOAD_ERR_OK) {
        throw new \RuntimeException('Upload failed');
    }

    // Validate MIME type
    $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);

    if (!in_array($mimeType, $allowedTypes)) {
        throw new \InvalidArgumentException('Invalid file type');
    }

    // Validate file size (max 5MB)
    if ($file['size'] > 5 * 1024 * 1024) {
        throw new \InvalidArgumentException('File too large');
    }

    return true;
}

public function saveUpload(array $file): string
{
    $this->validateUpload($file);

    // Generate safe filename
    $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $filename = bin2hex(random_bytes(16)) . '.' . $extension;

    // Save outside web root
    $uploadDir = '/var/uploads/';
    $destination = $uploadDir . $filename;

    if (!move_uploaded_file($file['tmp_name'], $destination)) {
        throw new \RuntimeException('Failed to save file');
    }

    return $filename;
}

Directory Traversal Prevention

Path Sanitization

public function getSecurePath(string $userPath): string
{
    // Define safe base directory
    $baseDir = realpath('/var/www/uploads/');

    // Resolve user path
    $requestedPath = realpath($baseDir . '/' . $userPath);

    // Ensure path is within base directory
    if ($requestedPath === false || strpos($requestedPath, $baseDir) !== 0) {
        throw new \InvalidArgumentException('Invalid path');
    }

    return $requestedPath;
}

Command Injection Prevention

Avoid shell execution

// ✅ CORRECT: Use native PHP functions
$files = scandir($directory);

// ✅ CORRECT: If shell needed, use escapeshellarg()
$filename = escapeshellarg($userFilename);
exec("ls -la " . $filename, $output);

// ❌ WRONG: Direct shell execution with user input
exec("ls -la " . $userFilename);  // VULNERABLE!

XML External Entity (XXE) Prevention

Disable external entities

// ✅ CORRECT: Disable external entity loading
libxml_disable_entity_loader(true);

$dom = new DOMDocument();
$dom->loadXML($xmlString, LIBXML_NOENT | LIBXML_DTDLOAD);

// Or use SimpleXML
$xml = simplexml_load_string($xmlString, 'SimpleXMLElement', LIBXML_NOENT);

Cryptographic Random Numbers

Use secure random

// ✅ CORRECT: Use random_bytes() or random_int()
$token = bin2hex(random_bytes(32));
$randomNumber = random_int(1, 100);

// ❌ WRONG: Never use rand() or mt_rand() for security
$insecureToken = mt_rand();  // VULNERABLE!

HTTP Headers

Security Headers

// X-Frame-Options
header('X-Frame-Options: SAMEORIGIN');

// X-Content-Type-Options
header('X-Content-Type-Options: nosniff');

// X-XSS-Protection
header('X-XSS-Protection: 1; mode=block');

// Strict-Transport-Security (HTTPS only)
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');

// Referrer-Policy
header('Referrer-Policy: strict-origin-when-cross-origin');

Error Handling

Don't expose sensitive information

// ✅ CORRECT: Log detailed errors, show generic message
try {
    $this->processPayment($amount);
} catch (\Exception $e) {
    // Log full error for debugging
    error_log('Payment error: ' . $e->getMessage());

    // Show generic message to user
    throw new \RuntimeException('Payment processing failed');
}

// ❌ WRONG: Expose stack traces in production
ini_set('display_errors', '1');  // VULNERABLE in production!

Framework-Agnostic

These security patterns apply to:

  • CakePHP projects
  • Laravel projects
  • Symfony projects
  • Any PHP application

Framework-specific security features (Authentication, Authorization, CSRF middleware, etc.) should be defined in framework-level skills (e.g., php-cakephp/security-patterns).