Skip to content

Optional integrations build inline <script> elements, forcing script-src 'unsafe-inline' #6078

Description

@bram-atmire

Is your feature request related to a problem? Please describe.

Two of the optional integrations construct <script> elements with inline content at runtime.
Any deployment that enables them therefore requires script-src 'unsafe-inline', which is the
single directive value that most undermines a Content-Security-Policy.

Google Analytics (src/app/statistics/google-analytics.service.ts:71-85) builds the config
snippet as inline script for both the GTag and the legacy variant:

const libScript = this.document.createElement('script');
libScript.innerHTML = `window.dataLayer = window.dataLayer || [];function gtag(){window.dataLayer.push(arguments);}
                     gtag('js', new Date());gtag('config', '${trackingId}');`;
this.document.body.appendChild(libScript);

MathJax (src/app/core/shared/client-math.service.ts:83-86) does the same for its options
object:

const optionsScript: HTMLScriptElement = this._document.createElement('script');
optionsScript.type = 'text/javascript';
optionsScript.text = `MathJax = ${JSON.stringify(this.mathJaxOptions)};`;
this._document.head.appendChild(optionsScript);

In both cases the inline script does nothing that could not be done directly in TypeScript. It
assigns a global. Routing it through a <script> element buys nothing and costs the ability to
run under a meaningful CSP.

Separately, several services inject external scripts at runtime, which is fine in itself but
becomes relevant under a nonce-based policy that does not use 'strict-dynamic', since a
dynamically created script element carries no nonce:

Service Location
Google Analytics / GTag src/app/statistics/google-analytics.service.ts:67
Google reCAPTCHA src/app/core/google-recaptcha/google-recaptcha.service.ts:182
MathJax src/app/core/shared/client-math.service.ts:88
AddToAny social sharing src/app/social/social.service.ts:82
Matomo src/app/statistics/matomo.factory.ts:19-24

Describe the solution you'd like

1. Remove the two inline script blocks. Assign the globals directly instead of via a script
element:

// MathJax: set the global before loading the library, no script element needed
(window as any).MathJax = this.mathJaxOptions;

// Google Analytics: same, define dataLayer and gtag in TypeScript

This alone removes the need for script-src 'unsafe-inline' on deployments that enable Analytics
or MathJax, and is worth doing independently of any nonce work.

2. Apply the nonce to dynamically created script elements. Once #6077 provides CSP_NONCE,
each of the services above should set script.nonce = inject(CSP_NONCE) on the element it creates.
This is a no-op when no nonce is configured, so it is backwards compatible, and it makes the
integrations work under an explicit-allowlist policy rather than depending on 'strict-dynamic'.

A small shared helper (for example createScriptElement(doc, src, nonce)) would avoid repeating
this in five places and would make it harder for a future integration to reintroduce the problem.

Acceptance criteria

  • No innerHTML or .text assignment on a <script> element remains in src/.
  • Every dynamically created <script> element carries the nonce when one is configured,
    verifiable in the DOM with Analytics, Matomo, MathJax, reCAPTCHA and AddToAny all enabled.
  • With those five enabled and a policy of script-src 'nonce-X' 'strict-dynamic' (no
    'unsafe-inline', no 'unsafe-eval'), all five load and function with zero CSP violations.
  • Existing behaviour is unchanged when no nonce is configured.

Describe alternatives or workarounds you've considered

Additional information

Umbrella: #6079. Depends on #6077 only for part 2; part 1 is independent and can be merged
first.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    • Status
      👀 Needs Discussion / Analysis

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions