Install
$ agentstack add skill-edutrul-drupal-ai-drupal-security Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Dangerous shell/eval execution.
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Drupal Security
Critical Security Patterns
SQL Injection Prevention
NEVER concatenate user input into queries:
// 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:
// 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:
use Drupal\Component\Utility\Xss;
// Filter but allow safe HTML tags
$safe = Xss::filterAdmin($user_html);
Note: Never pass untrusted user input into #markup. Prefer #plain_text, Twig auto-escaping, or safe render elements.
Access Control
Always verify permissions:
// 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:
// Include token in AJAX requests
$build['#attached']['drupalSettings']['myModule']['token'] =
\Drupal::csrfToken()->get('my_module_action');
Note: Prefer dependency injection over direct \Drupal::service() or \Drupal:: calls in classes.
// Validate in controller
if (!$this->csrfToken->validate($token, 'my_module_action')) {
throw new AccessDeniedHttpException('Invalid token');
}
File Upload Security
$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
// 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 | | Custom controller with no access check | Unauthorized access | Add route permission or access callback | | {{ 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 | | Missing CSRF validation in custom endpoints | CSRF attack | Validate token using csrf_token service | | File upload without validation | Malicious file upload | Validate extension, size, and MIME type | | Direct \Drupal:: usage in classes | Hard to test / bad practice | Use dependency injection | | Logging sensitive data | Information disclosure | Mask or avoid logging sensitive fields |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: edutrul
- Source: edutrul/drupal-ai
- License: MIT
- Homepage: https://eduardotelaya.com/drupal-ai/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.