Drupal is a registered trademark of Dries Buytaert
drupal 11.4.2 Update released for Drupal core (11.4.2)! drupal 11.4.1 Update released for Drupal core (11.4.1)! drupal 11.4.0 Update released for Drupal core (11.4.0)! drupal 10.6.12 Update released for Drupal core (10.6.12)! drupal 11.3.13 Update released for Drupal core (11.3.13)! drupal 10.6.11 Update released for Drupal core (10.6.11)! drupal 11.3.12 Update released for Drupal core (11.3.12)! drupal 11.2.14 Update released for Drupal core (11.2.14)! drupal 10.5.12 Update released for Drupal core (10.5.12)! cms 2.1.3 Update released for Drupal core (2.1.3)! drupal 10.5.11 Update released for Drupal core (10.5.11)! drupal 11.3.11 Update released for Drupal core (11.3.11)! drupal 11.2.13 Update released for Drupal core (11.2.13)! drupal 10.6.10 Update released for Drupal core (10.6.10)! cms 2.1.2 Update released for Drupal core (2.1.2)! drupal 11.1.10 Update released for Drupal core (11.1.10)! drupal 10.5.10 Update released for Drupal core (10.5.10)! drupal 10.4.10 Update released for Drupal core (10.4.10)! drupal 11.2.12 Update released for Drupal core (11.2.12)! drupal 11.3.10 Update released for Drupal core (11.3.10)!

security_scan

No security coverage
View on drupal.org

Security Scan reviews custom Drupal code for common security mistakes before it ships — the access-control gaps, XSS sinks, injection, unsafe deserialization and committed secrets that slip into hand-written and, increasingly, AI-generated modules.

It is a command-line developer/CI tool, not a site feature: there is no UI and nothing runs on a live request. You point it at a module (or any path), it reads the code statically, and it prints a prioritised list of things to review — with a machine-readable mode built for CI and for AI coding agents.

Why this module

AI assistants now write a large share of custom Drupal code, and they reliably reintroduce the same well-known vulnerability classes: routes with no access requirement, #markup and |raw XSS, missing CSRF tokens, unserialize() on untrusted data, and hardcoded credentials. Security Scan gives both humans and agents a fast, repeatable check for exactly these patterns, mapped to the OWASP Top 10 (2025) and grounded in real Drupal security-advisory fixes.

What it checks

Two tiers, run by one Drush command.

Deterministic checks — always on, native PHP, no external tools:

Area Detects Access control (A01) Routes with no requirements, _access: 'TRUE', login-only gating, entity queries without an explicit ->accessCheck() CSRF (A01) State-changing routes with no _csrf_token requirement XSS (A03) Twig |raw and {% autoescape false %}; #markup assigned from a variable SQL injection (A03) Queries built by string concatenation Integrity / RCE (A08) Callable render keys (#pre_render, #lazy_builder, …) assigned from a variable Deserialization (A08) unserialize() without ['allowed_classes' => FALSE] Secrets (A04) Hardcoded AWS keys, committed private keys, credential-shaped literals

Deep taint pass — optional: if the drupal-security/scanner Psalm engine is installed, the same command also traces untrusted input across functions and files to find cross-function XSS, SQL injection, command injection, path traversal, SSRF and insecure deserialization. When it is absent, the deterministic checks still run and the report says the deep pass was skipped.

Requirements

  • Drupal 10.3+ or Drupal 11
  • Drush 12 or later (the module ships a Drush command)
  • PHP 8.1 or later
  • (Optional, for the deep taint pass) drupal-security/scanner and vimeo/psalm, installed as dev dependencies

Installation

Install as you would any Drupal module. As a developer/CI tool it is typically a dev dependency:

composer require drupal/security_scan --dev
drush en security_scan

To enable the optional deep taint pass, also add the scanner engine — the module detects it automatically:

composer require --dev drupal-security/scanner

For local or monorepo development, where the engine lives outside the site's vendor/ directory, point the module at a checkout instead:

SECURITY_SCAN_PSALM_ROOT=/path/to/scanner drush security:scan my_module

The path must contain vendor/bin/psalm and the plugin's autoloader.

Usage

The module has no settings form; behaviour is controlled per run via command options.

# Scan a module by machine name.
drush security:scan my_module

# Scan any path.
drush security:scan web/modules/custom/my_module

# Machine-readable output for CI or an AI agent.
drush security:scan my_module --format=json

The command exits non-zero when any error-severity finding is present, so it drops into CI pipelines and pre-commit hooks. secscan is a shorter alias.

 ---------- ------------------- ------------------------------- ---------------------------------------------------------
  Severity   Class               Location                        Finding
 ---------- ------------------- ------------------------------- ---------------------------------------------------------
  ERROR      A01 Access control  my_module.routing.yml           Route 'my_module.report' has no access requirements.
  ERROR      A03 SQL injection   src/Controller/Report.php       SQL string built with concatenation of a variable.
  WARNING    A03 XSS             templates/report.html.twig      The '|raw' filter disables auto-escaping.
 ---------- ------------------- ------------------------------- ---------------------------------------------------------

37 file(s) scanned — 2 error, 1 warning, 0 info.

For AI coding workflows

--format=json returns each finding with a check, severity, OWASP class, file, line and a concrete recommendation. An agent can read its own scan output, apply the recommended fixes, and re-scan until the error count reaches zero — a closed correction loop. This module is the executable half of a secure-coding workflow; a companion agent skill decides when to run it and how to read the results.

An honest limit

A finding means review this, not this is broken — static review has false positives. And a clean result does not prove the code is secure: access-control correctness is a reasoning problem these syntactic checks cannot see, and it still needs human review. Treat Security Scan as a first pass that frees human attention for the judgement calls, not a certificate.

Activity

Total releases
1
First release
Jul 2026
Latest release
20 hours ago
Release cadence
Stability
100% stable

Releases

Version Type Release date
1.0.0 Stable Jul 10, 2026