Drupal 10/11 Contrib Security Pitfalls: A Hardening Checklist for Maintainers
If you maintain a Drupal 10/11 contrib module, the biggest security misses are still predictable: missing access checks, weak route protection, unsafe output, and incomplete release hygiene. The fastest hardening path is to enforce explicit access decisions (entityQuery()->accessCheck()), protect state-changing routes with CSRF requirements, ban unsafe rendering patterns, and ship every release with a repeatable security gate.
The Problem
Contrib maintainers usually do not get breached by exotic 0-days. They get burned by small, repeatable mistakes under release pressure:
- Querying entities without explicit access intent.
- Exposing privileged routes with weak permission or CSRF coverage.
- Letting untrusted data hit output without strict escaping/sanitization.
- Shipping releases without a structured security review checkpoint.
On modern Drupal, these gaps are avoidable, but only if the checklist is explicit and enforced in CI/review.
The Solution
Use this hardening checklist before every tagged release.
| Pitfall | Hardening action for D10/D11 | How to verify quickly |
|---|---|---|
| Implicit access behavior in entity queries | Always call ->accessCheck(TRUE) (or FALSE only with a documented reason) on entity queries. | rg "entityQuery\\(" and confirm paired accessCheck(...) in each path. |
| Weak route protection | Require route permissions and add CSRF protection for state-changing routes. | Review *.routing.yml for _permission and CSRF requirements where applicable. |
| XSS through rendering shortcuts | Prefer render arrays/Twig auto-escaping; do not output untrusted HTML directly. Avoid casual ` | raw` usage in templates. |
| SQL injection risk in custom queries | Use Drupal DB API placeholders and never concatenate untrusted input into SQL. | `rg "->query\( |
| Upload/extension abuse | Restrict allowed extensions/MIME, validate uploads, and enforce destination/access rules. | Review upload validators and file field constraints in form/entity handlers. |
| Missing release-time security gate | Add a pre-release checklist item for security advisories, access regressions, and deprecation impact. | Gate release tags on checklist completion in issue template/CI workflow. |
Deprecation-Aware Security Notes
- Older code paths that relied on implicit entity query behavior are now unsafe from a maintenance perspective; modern Drupal requires explicit access intent.
- Legacy patterns from older Drupal generations (for example raw SQL string building) should be treated as migration debt, not "good enough" compatibility code.
- Keep dependency and API usage current to avoid silent drift into unsupported patterns during D10 to D11 transitions.
Do not mark a release "security reviewed" unless you can point to concrete checks in code or CI. Checklist theater is not hardening.
For adjacent upgrade planning and change tracking, see:
What I Learned
- Enforcing explicit access intent is one of the highest ROI safeguards for contrib maintainers.
- Route-level permission and CSRF checks catch many "small" mistakes before they become advisories.
- Security hardening is most reliable when embedded in release operations, not left as ad hoc reviewer memory.
- Deprecated and legacy coding patterns are not just upgrade problems; they are security risk multipliers over time.
