| name | drupal-security |
| description | Drupal security expertise. Auto-activates when writing forms, controllers, queries, or handling user input. Prevents XSS, SQL injection, and access bypass vulnerabilities. |
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:
$query = "SELECT * FROM users WHERE name = '" . $name . "'";
$result = $connection->query($query);
$result = $connection->select('users', 'u')
->fields('u')
->condition('name', $name)
->execute();
$result = $connection->query(
'SELECT * FROM {users} WHERE name = :name',
[':name' => $name]
);
XSS Prevention
Always escape output. Trust the render system:
return ['#markup' => $user_input];
return ['#markup' => '<div>' . $title . '</div>'];
return ['#plain_text' => $user_input];
return [
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => $title,
];
{{ variable }}
{{ variable|raw }}
For admin-only content:
use Drupal\Component\Utility\Xss;
$safe = Xss::filterAdmin($user_html);
Access Control
Always verify permissions:
my_module.admin:
path: '/admin/my-module'
requirements:
_permission: 'administer my_module'
if (!$this->currentUser->hasPermission('administer my_module')) {
throw new AccessDeniedHttpException();
}
$query = $this->entityTypeManager
->getStorage('node')
->getQuery()
->accessCheck(TRUE)
->condition('type', 'article');
CSRF Protection
Forms automatically include CSRF tokens. For custom AJAX:
$build['#attached']['drupalSettings']['myModule']['token'] =
\Drupal::csrfToken()->get('my_module_action');
if (!$this->csrfToken->validate($token, 'my_module_action')) {
throw new AccessDeniedHttpException('Invalid token');
}
File Upload Security
$validators = [
'file_validate_extensions' => ['pdf doc docx'],
'file_validate_size' => [25600000],
'FileSecurity' => [],
];
$file_mime = $file->getMimeType();
$allowed_mimes = ['application/pdf', 'application/msword'];
if (!in_array($file_mime, $allowed_mimes)) {
}
Sensitive Data
$this->logger->info('User @user logged in', ['@user' => $username]);
throw new \Exception('Database error');
$api_key = getenv('MY_API_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:
Resources