Using Claude to find security issues in Drupal modules

One report became a published advisory in Drupal Commerce. Most of the others never surface, and that is exactly how the process is meant to work.

Part of looking after a Drupal site is reading code that someone else wrote: the contributed modules a site depends on. I have started using Claude Code as a first pass over that code, specifically to flag the places where untrusted input reaches the screen or an email without being escaped. The honest result is a lot of noise, a handful of real findings, and a careful process of verification and responsible disclosure in between. One of those findings is now a public Drupal security advisory. This is how that actually works, and why the visible advisory is the small tip of a much larger, mostly invisible pile.

Drupal’s core is well audited and has a serious security team behind it. The contributed modules that turn core into a real website are a different picture. There are tens of thousands of them, written by volunteers of wildly varying experience, and only a subset are covered by the security advisory policy at all. When you install a contrib module you are trusting code that, in many cases, no security reviewer has ever looked at line by line. On a site that takes payments or holds customer data, that trust is worth checking.

That checking is slow and unglamorous work, and it is exactly the kind of task an LLM is genuinely good at: reading a lot of unfamiliar code quickly and pattern-matching it against a known class of mistake. So I started pointing Claude Code at module code with a narrow brief, not “is this good code” but “where does data that a user controls end up rendered without being sanitised first.”

What I actually ask it to look for

Cross-site scripting in Drupal usually comes from one root cause: a value that a user can influence is printed into a page or an email as raw markup instead of escaped text. Drupal gives you good tools to avoid this. Twig templates autoescape by default, render arrays carry their own safety, and there are helpers like Html::escape() and Xss::filter() for the cases that need them. XSS creeps in at the seams: a string concatenated into #markup, a value marked as safe that should not have been, a template that handles a field as raw HTML, or output that escapes correctly on the web page but is then reused somewhere that does not escape, such as an email body.

So the prompt is concrete. Trace user-controllable inputs. Follow each one to where it is output. At each output point, decide whether it is escaped for that exact context. Flag any path where it is not, and tell me the input, the sink, and why the escaping is missing or wrong. Claude is good at the tracing and the pattern recognition. It is not the judge of whether a finding is real.

The tool finds candidates; a human confirms vulnerabilities. Most of what comes back is not exploitable: the value turns out to be admin-only, or it is escaped one layer up, or the context autoescapes after all. Treating Claude’s output as a list of leads to investigate, rather than a list of bugs, is the whole discipline. Reporting an unverified finding to a security team wastes their time and erodes your credibility. Every report I file, I have reproduced first.

The one that became public

The clearest finding was in Drupal Commerce Core, the e-commerce suite a great many Drupal shops run on. Commerce has an optional checkout step that lets a customer leave a comment with their order. That comment is later included in the order receipt email. The value the customer typed was placed into the receipt without being adequately sanitised, which means a customer could put markup into that field and have it rendered, rather than shown as plain text, when the receipt was generated.

I reproduced it, wrote it up plainly, and reported it to the Drupal Security Team through the proper private channel rather than posting it anywhere public. They confirmed it, a fix was written and released, and a coordinated advisory was published once the fixed version was available. Here is the result.

Published advisory

Commerce Core: Cross-site scripting

Advisory
SA-CONTRIB-2026-041
CVE
CVE-2026-10769
Project
Commerce Core (Drupal Commerce)
Type
Cross-site scripting (XSS), via the customer comments checkout pane in order receipt emails
Severity
Moderately critical (14/25). Exploitability rated theoretical
Affected
Commerce Core 3.3.0 up to, but not including, 3.3.6
Fixed in
Commerce Core 3.3.6
Credited
Reported by Brian Willows; fixed by the maintainer; coordinated by the Drupal Security Team

Why it is rated low, and why that is the honest read

I want to be straight about the severity, because it would be easy to dress this up as more than it is. The advisory is rated moderately critical, with exploitability marked as theoretical, and there is a good reason for that. The vulnerable feature is the “Comments” checkout pane, and that pane is disabled by default. A site is only affected if it has checkout enabled and has deliberately turned that specific pane on. Many sites never do. So this is a real bug that was worth fixing, not a five-alarm fire, and the scoring reflects that honestly.

That is the right outcome. The point of the security process is not drama, it is getting the fix shipped and the credit recorded accurately. A correctly-scored low-to-moderate finding that is fixed quietly is a success, not an anticlimax.

Why most of them never show

If you only ever see published advisories, you see a fraction of the work, and a skewed one. Most reports never become a public advisory, for entirely legitimate reasons:

So a single published advisory sits on top of a stack of verified-but-uncredited fixes, judgement calls, dead ends, and reports still under wraps. That is not a complaint; it is how responsible disclosure is supposed to look. The visible credit is real, but it under-counts the work, and it should.

The honest split

Claude Code did the heavy reading: tracing user-controllable inputs through unfamiliar module code to their output points, and flagging the ones where escaping looked absent or wrong. It is fast and tireless at exactly the part of the job that is tedious for a person.

I did the judgement: deciding which flags were worth chasing, building a working reproduction, scoping the real-world impact, writing the report, and handling disclosure through the proper private channel. The model produced leads. Confirming a vulnerability, and reporting it responsibly, is human work.

This is the same division of labour I describe in my other write-ups, applied to security review rather than building. The AI is leverage on the laborious part. It does not remove the need to know what a real vulnerability looks like, how Drupal’s sanitisation actually behaves, or how to disclose without doing harm. If anything it raises the value of that knowledge, because the bottleneck moves from finding candidates to judging them.

What this means if you run a Drupal site

Every contrib module on your site is code you are trusting, and not all of it has been reviewed by anyone with security in mind. You do not need to audit all of it yourself, but it is worth knowing that the gap exists, keeping modules updated promptly when advisories land, and having someone who can read the code when something looks off. Reviewing the contrib a site depends on, reporting what is wrong through the right channels, and applying the fixes is part of what a proper Drupal support arrangement should cover.

If you would like that kind of attention on a site that handles payments or customer data, that is the work I do. The contact details are below.

© 2026 Graith Internet.

Graith Internet is a UK web development company specialising in PHP, Drupal, and AI-assisted development.