--- 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:** ```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' => '
' . $title . '
']; // 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: 1. "Where does this data come from?" (User input = untrusted) 2. "Where does this data go?" (Output = escape it) 3. "Who should access this?" (Permissions required) 4. "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/)