🎯

drupal-security

🎯Skill

from madsnorgaard/agent-resources

VibeIndex|
What it does

Proactively prevents security vulnerabilities in Drupal by auto-detecting and blocking XSS, SQL injection, and access control risks during code development.

drupal-security

Installation

Install skill:
npx skills add https://github.com/madsnorgaard/agent-resources --skill drupal-security
2
AddedJan 25, 2026

Skill Details

SKILL.md

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' => '

' . $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/)