Skip to content

Build1 publisher2 min readPublished

A Raxis pentester walked attacker JavaScript past a nonce-based CSP through googletagmanager.com

The strict-dynamic directive passes a script's trust to everything that script loads, and Ryan Chaplin of Raxis used that rule to get an XSS payload executing under a nonce-based policy that had blocked it.

The Engineer · Build desk

Photograph accompanying A Raxis pentester walked attacker JavaScript past a nonce-based CSP through googletagmanager.com
Photo: dev.to

What happened

  • Ryan Chaplin published the research on the Raxis blog on February 10, 2026, and dev.to republished it under his byline.
  • He wrote that during several penetration tests last year, clients often set unsafe CSP directives for Google Tag Manager, usually to supplement data collection through Google Analytics and third-party vendors.
  • The post documents two bypasses built on the tag-manager origin, one against a Content-Security-Policy and one against a Cloudflare WAF, followed by remediation advice.
  • Chaplin submitted parts of the work to Google's bug bounty program, and Google awarded him an honorable mention and swag.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Anyone keeping googletagmanager.com trusted in script-src now has to decide what the policy is for, because a nonced loader admits whatever the container fetches next.
  • exposure Reflected XSS bugs that teams have been comfortable deferring on the grounds that the CSP would catch them are reachable again wherever a user-content origin is trusted.
  • precedent Chaplin scopes the technique to any platform that lets users upload or host content, so every CDN and storage origin in a policy invites the same review as an inline script.

The nonce in Chaplin's example holds because the server mints a fresh random value for every response, and a script injected through the URL cannot carry it [7]. The page he tested takes the `name` parameter, skips sanitising, and appends it straight to the body, which is textbook reflected XSS [6]. A nonce authenticates the response it was minted for. Authorship of the file it admits is out of scope.

strict-dynamic widens that gap by design. "Using the strict-dynamic directive propagates trust. If a script is trusted (via nonce or hash), then any scripts it loads are automatically trusted too," Chaplin wrote [10]. The policy in his post is one line: `script-src 'nonce-random123' 'strict-dynamic'` [11]. Put that nonce on a tag-manager loader and every script the container fetches inherits the loader's trust [16].

The older behaviour is why so many policies ended up this way. Before strict-dynamic, a nonced inline script that created a `script` element pointing at https://cdn.example.com/library.js was blocked, unless the URL was in the allowlist or the loaded script could be given a nonce, which Chaplin wrote was nearly impossible for frequently changing third-party scripts [8][9]. Teams also had to add build-cycle steps to regenerate hashes as third-party libraries changed, and coordinate across teams for every script on the server [12]. Safari was the last browser to adopt strict-dynamic [13].

Whether this reaches a given application depends on three conditions holding together: script-src trusts googletagmanager.com or trusts a script that loads it, the application has an injection point that can create a script tag, and an attacker can get a JavaScript file served from that origin [17]. The third condition is the one Chaplin asserts for Google's domain. "Aside from the obvious problem of using unsafe directives, this threat was amplified since Google hosts malicious JavaScript from googletagmanager.com," he wrote [3].

Before editing a header, I would enumerate which origins in the current policy accept third-party or user-supplied content, because that list is what the allowlist actually grants. Chaplin's remediation section provides a .NET Blazor server configuration and an nginx.conf snippet [14].

What to watch

  • Whether Google restricts what JavaScript can be served from googletagmanager.com, or answers the submission with something beyond the honorable mention.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories