drupal-security
π―Skillfrom madsnorgaard/agent-resources
Proactively prevents security vulnerabilities in Drupal by auto-detecting and blocking XSS, SQL injection, and access control risks during code development.
Installation
npx skills add https://github.com/madsnorgaard/agent-resources --skill drupal-securitySkill Details
Drupal security expertise. Auto-activates when writing forms, controllers, queries, or handling user input. Prevents XSS, SQL injection, and access bypass vulnerabilities.
Overview
# Drupal Security Expert
You proactively identify security vulnerabilities while code is being written, not after.
When This Activates
- Writing or editing forms, controllers, or plugins
- Handling user input or query parameters
- Building database queries
- Rendering user-provided content
- Implementing access control
Critical Security Patterns
SQL Injection Prevention
NEVER concatenate user input into queries:
```php
// VULNERABLE - SQL injection
$query = "SELECT * FROM users WHERE name = '" . $name . "'";
$result = $connection->query($query);
// SAFE - parameterized query
$result = $connection->select('users', 'u')
->fields('u')
->condition('name', $name)
->execute();
// SAFE - placeholder
$result = $connection->query(
'SELECT * FROM {users} WHERE name = :name',
[':name' => $name]
);
```
XSS Prevention
Always escape output. Trust the render system:
```php
// VULNERABLE - raw HTML output
return ['#markup' => $user_input];
return ['#markup' => '
// SAFE - plain text (auto-escaped)
return ['#plain_text' => $user_input];
// SAFE - use proper render elements
return [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $title, // Escaped automatically
];
// SAFE - Twig auto-escapes
{{ variable }} // Escaped
{{ variable|raw }} // DANGEROUS - only for trusted HTML
```
For admin-only content:
```php
use Drupal\Component\Utility\Xss;
// Filter but allow safe HTML tags
$safe = Xss::filterAdmin($user_html);
```
Access Control
Always verify permissions:
```php
// In routing.yml
my_module.admin:
path: '/admin/my-module'
requirements:
_permission: 'administer my_module' # Required!
// In code
if (!$this->currentUser->hasPermission('administer my_module')) {
throw new AccessDeniedHttpException();
}
// Entity queries - check access!
$query = $this->entityTypeManager
->getStorage('node')
->getQuery()
->accessCheck(TRUE) // CRITICAL - never FALSE unless intentional
->condition('type', 'article');
```
CSRF Protection
Forms automatically include CSRF tokens. For custom AJAX:
```php
// Include token in AJAX requests
$build['#attached']['drupalSettings']['myModule']['token'] =
\Drupal::csrfToken()->get('my_module_action');
// Validate in controller
if (!$this->csrfToken->validate($token, 'my_module_action')) {
throw new AccessDeniedHttpException('Invalid token');
}
```
File Upload Security
```php
$validators = [
'file_validate_extensions' => ['pdf doc docx'], // Whitelist extensions
'file_validate_size' => [25600000], // 25MB limit
'FileSecurity' => [], // Drupal 10.2+ - blocks dangerous files
];
// NEVER trust file extension alone - check MIME type
$file_mime = $file->getMimeType();
$allowed_mimes = ['application/pdf', 'application/msword'];
if (!in_array($file_mime, $allowed_mimes)) {
// Reject file
}
```
Sensitive Data
```php
// NEVER log sensitive data
$this->logger->info('User @user logged in', ['@user' => $username]);
// NOT: $this->logger->info('Login: ' . $username . ':' . $password);
// NEVER expose in error messages
throw new \Exception('Database error'); // Generic
// NOT: throw new \Exception('Query failed: ' . $query);
// Use environment variables for secrets
$api_key = getenv('MY_API_KEY');
// NOT: $api_key = 'hardcoded-secret-key';
```
Red Flags to Watch For
When you see these patterns, immediately warn:
| Pattern | Risk | Fix |
|---------|------|-----|
| String concatenation in SQL | SQL injection | Use query builder |
| #markup with variables | XSS | Use #plain_text |
| accessCheck(FALSE) | Access bypass | Use accessCheck(TRUE) |
| Missing _permission in routes | Unauthorized access | Add permission |
| {{ var\|raw }} in Twig | XSS | Remove \|raw |
| Hardcoded passwords/keys | Credential exposure | Use env vars |
| eval() or exec() | Code injection | Avoid entirely |
| unserialize() on user data | Object injection | Use JSON |
Security Review Prompts
When reviewing code, always ask:
- "Where does this data come from?" (User input = untrusted)
- "Where does this data go?" (Output = escape it)
- "Who should access this?" (Permissions required)
- "What if this contains malicious input?" (Validate/sanitize)
Quick Security Checklist
Before any code is committed:
- [ ] All user input validated/sanitized
- [ ] All output properly escaped
- [ ] Routes have permission requirements
- [ ] Entity queries use
accessCheck(TRUE) - [ ] No hardcoded credentials
- [ ] File uploads validate type AND extension
- [ ] Forms use Form API (automatic CSRF)
- [ ] Sensitive data not logged
Resources
- [Drupal Security Best Practices](https://www.drupal.org/docs/security-in-drupal)
- [Writing Secure Code](https://www.drupal.org/docs/security-in-drupal/writing-secure-code-for-drupal)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
More from this repository4
Provides expert guidance on Drupal development, module creation, theming, performance optimization, and best practices for building robust web applications
Facilitates Drupal migrations by automating data transfer from Drupal 7 to Drupal 10, supporting CSV, JSON, and custom migration workflows.
Streamlines Docker Compose local development workflows with essential commands for managing containers, running services, and debugging Docker environments.
Provides expert guidance and commands for managing DDEV local development environments, Docker containers, and PHP project configurations.