security-patterns

PHP Security Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "security-patterns" with this command: npx skills add masanao-ohba/claude-manifests/masanao-ohba-claude-manifests-security-patterns

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']) &#x26;&#x26; 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 ).

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

requirement-analyzer

No summary provided by upstream source.

Repository SourceNeeds Review
General

test-case-designer

No summary provided by upstream source.

Repository SourceNeeds Review
General

test-validator

No summary provided by upstream source.

Repository SourceNeeds Review
General

functional-designer

No summary provided by upstream source.

Repository SourceNeeds Review