From drupal-workflow
Provides OWASP security patterns for Drupal 10/11 including SQL injection prevention, XSS filtering, route access control, and custom checkers. Use for code security reviews and hardening.
How this skill is triggered — by the user, by Claude, or both
Slash command
/drupal-workflow:drupal-security-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```php
// Bad: string concatenation.
$result = $connection->query("SELECT * FROM {node} WHERE title = '$title'");
// Good: parameterized query.
$result = $connection->query(
"SELECT * FROM {node} WHERE title = :title",
[':title' => $title]
);
// Best: Entity API.
$nids = \Drupal::entityQuery('node')
->condition('title', $title)
->accessCheck(TRUE)
->execute();
// Twig auto-escapes by default - safe.
{{ node.title }}
// Explicit escaping for raw output.
use Drupal\Component\Utility\Html;
$safe = Html::escape($user_input);
// Xss filter for allowed HTML.
use Drupal\Component\Utility\Xss;
$filtered = Xss::filter($user_input);
// Admin filter (more tags allowed).
$filtered = Xss::filterAdmin($content);
# my_module.routing.yml
my_module.admin:
path: '/admin/my-module'
defaults:
_controller: '\Drupal\my_module\Controller\AdminController::page'
requirements:
_permission: 'administer my_module'
my_module.content:
path: '/my-module/{node}'
defaults:
_controller: '\Drupal\my_module\Controller\ContentController::view'
requirements:
_entity_access: 'node.view'
declare(strict_types=1);
namespace Drupal\my_module\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
final class MyAccessChecker implements AccessInterface {
public function access(AccountInterface $account): AccessResult {
return AccessResult::allowedIfHasPermission($account, 'access my_module')
->cachePerPermissions();
}
}
Routes can have multiple _*_access* requirements that ALL must pass (AND logic). Don't assume _permission is the only gate.
# Example: three access checkers on one route
my_module.entity_edit:
path: '/entity/{entity}/edit'
requirements:
_entity_access: entity.update # Entity-level check
_custom_archived_check: 'TRUE' # Custom: is entity archived?
_custom_status_check: 'TRUE' # Custom: additional gate
Debugging 403s when entity access passes:
requirements in *.routing.yml_*_access* checker service in *.services.ymlDuring security review: Examine ALL route requirements, not just _permission. Custom access checkers may silently block access even when entity-level access is granted.
// Form API handles CSRF automatically via form tokens.
// For custom AJAX endpoints:
use Drupal\Core\Access\CsrfTokenGenerator;
// Generate token.
$token = \Drupal::csrfToken()->get('my_module_action');
// Validate token.
if (!\Drupal::csrfToken()->validate($token, 'my_module_action')) {
throw new AccessDeniedHttpException();
}
$validators = [
'file_validate_extensions' => ['pdf doc docx'],
'file_validate_size' => [25 * 1024 * 1024], // 25MB
'file_validate_name_length' => [],
];
Html::escape).\Drupal:: in classes).\Drupal:: static calls in service classes.npx claudepluginhub gkastanis/drupal-workflow --plugin drupal-workflowEnforces core Drupal 10+ rules for services, dependency injection, security including sanitization and access control, code quality, and testing verification. Always use when writing Drupal code.
Automatically scans code for security vulnerabilities like XSS, SQL injection, CSRF, and authentication flaws.
Reviews project code against OWASP Top 10 vulnerabilities: broken access control, injections (SQL, XSS, CSRF), cryptographic failures, insecure design, misconfigurations, and authentication issues.