Aug 1, 2026·5 min read·35 visits
Authenticated editors can execute stored Cross-Site Scripting (XSS) across all site visitors by injecting malicious payloads into Google Analytics/Tag Manager configuration fields in @apostrophecms/seo <= 1.4.2.
A stored Cross-Site Scripting (XSS) vulnerability exists in the @apostrophecms/seo package of the ApostropheCMS ecosystem up to and including version 1.4.2. Unsanitized user inputs for Google Analytics and Google Tag Manager IDs are injected directly into script elements within the document header, enabling authenticated editors to execute arbitrary JavaScript in the context of all site visitors.
The @apostrophecms/seo module is an extension for the Node.js-based ApostropheCMS that enables content editors to manage search engine optimization configurations globally and on a per-page basis. Among its core features is the integration of external analytics packages, specifically Google Analytics and Google Tag Manager. To accomplish this, the module dynamically inserts tracking script blocks into the document head layout configuration.
This architecture exposes a persistent attack surface. Because these settings are intended to be configurable by authenticated content editors or managers, any input fields dedicated to storing these tracking identifiers are exposed to authorized administrative interfaces.
When the application retrieves these configurations and injects them without sanitization, it creates a classic Stored Cross-Site Scripting (XSS) condition. The injected payload is executed transparently in the browsers of all subsequent site visitors, changing the overall security scope.
The root cause of the vulnerability lies in the node definition mechanism located in packages/seo/lib/nodes.js. When constructing the <head> of a rendered webpage, the module builds an array of node objects that represent the meta elements and script tags to be appended to the Document Object Model (DOM).
To generate the scripts for Google Analytics and Google Tag Manager, the application utilized ES6 template literals to embed the configuration fields seoGoogleTrackingId and seoGoogleTagManager into static JavaScript blocks. The engine pushed these template literal blocks directly into the array under a raw property block.
This pattern assumes that the input variables will exclusively contain valid tracking IDs conforming to standard formats. The template rendering engine did not perform character escaping, type validation, or sanitization on these configuration values before generating the raw string block. As a consequence, any characters capable of terminating a JavaScript string literal or an HTML script tag are preserved verbatim in the final HTTP response.
Below is a comparison of the vulnerable implementation versus the patched implementation in packages/seo/lib/nodes.js.
// VULNERABLE: Direct string interpolation into raw body
nodes.push({
name: 'script',
body: [ {
raw: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', '${global.seoGoogleTrackingId}');\n`
} ]
});The patch replaces the single monolithic raw string literal with an array of structured segments. The user-controlled variable is moved out of the raw string and encapsulated in an object containing a json key.
// PATCHED: Extraction of the tracking ID into an escaped JSON node
body: [
{
raw: `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', `
},
{ json: global.seoGoogleTrackingId },
{ raw: ');\\n' }
]During the generation phase, the rendering engine processes elements with the json key by running them through safeJsonForScript. This helper function serializes the value and escapes HTML-sensitive characters. This completely prevents structural escaping of the script tag block or inline script injection.
Exploitation of this vulnerability requires an authenticated attacker with permissions to modify global SEO properties, typically associated with the 'Editor' role. The attacker inputs a crafted payload into the Google Analytics Tracking ID field within the administrative dashboard.
The execution depends on a multi-stage breakout technique. First, the attacker terminates the active string literal parameter in the static JavaScript template. Second, they insert arbitrary payload instructions. Finally, they close the existing <script> block and open a new one to guarantee execution regardless of how the subsequent block of the original template is compiled.
A typical payload structure is: G-FAKE'); alert(document.cookie); </script><script>alert(1)//. When processed, the output in the HTML document parses as two separate executable script statements. The trailing characters from the original template are negated by appending a comment indicator (//).
Because the payload is stored directly in the global document template, the arbitrary JavaScript is served to and executed by every single client visiting any page on the affected ApostropheCMS site. This includes anonymous visitors, standard users, and high-privilege administrators.
In the context of an administrator session, the execution of arbitrary JavaScript allows the attacker to perform actions on behalf of the administrator. This includes administrative account takeover, modification of application configurations, or the exfiltration of sensitive session cookies.
If session cookies do not use the HttpOnly attribute, the script can exfiltrate session data to an attacker-controlled listener. In scenarios involving drive-by exploitation, this can lead to total compromise of the application management plane.
Remediation of this vulnerability requires upgrading the @apostrophecms/seo dependency to a version containing the official patch commit 5a88e9630cbbdde33154ef8abe7557ddf7be418b. Developers should verify that all installations of @apostrophecms/seo exceed version 1.4.2.
If an immediate upgrade is not possible, administrators should clean existing databases by executing a query targeting the seoGoogleTrackingId and seoGoogleTagManager values. These values should be verified against a strict regular expression such as ^(G|UA|GTM)-[A-Z0-9\\-]+$ to ensure no special characters or script segments are preserved.
Additionally, deploying a Content Security Policy (CSP) that restricts inline script execution (script-src 'self') or requires nonces can mitigate the impact of stored XSS by preventing unauthorized inline blocks from executing.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
@apostrophecms/seo ApostropheCMS | <= 1.4.2 | 1.4.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Severity Score | 8.7 |
| EPSS Score | 0.0021 |
| Impact Category | Stored Cross-Site Scripting (XSS) |
| Exploit Maturity | Proof-of-Concept |
| CISA KEV Status | Not Listed |
The software does not neutralize or incorrectly neutralizes user-controlled input before it is placed in output that is used as a web page that is served to other users.
A critical security vulnerability has been identified and patched in the WebUI component of pyLoad, a popular Python-based open-source download manager. This vulnerability allows attackers to completely bypass API rate limits and spoof client IP addresses in audit logs due to unsafe parsing of the X-Forwarded-For HTTP header.
An authorization bypass and credential oracle vulnerability in pyload-ng allows authenticated users with minimal or no privileges to brute-force the administrator password. This is achieved through sensitive API methods exposed globally combined with a non-constant-time password hash comparison algorithm.
An authentication bypass vulnerability in pyLoad allows unauthenticated remote attackers to gain administrative API access. The vulnerability is caused by a logical flaw in the API key cache validation lookup, where authentication states are cached using only the public key identifier, skipping cryptographic token verification on cache hits.
An authorization bypass vulnerability in Strawberry GraphQL between versions 0.217.0 and 0.326.1 occurs when a synchronous permission handler returns an awaitable object (such as an unawaited coroutine). Due to Python's truthiness rules, the unawaited coroutine is evaluated as True, leading to an immediate bypass of security policies.
An uncontrolled resource consumption vulnerability in Strawberry GraphQL allows unauthenticated remote attackers to trigger a Denial of Service on persistent WebSocket connections using the legacy graphql-ws protocol. When the server enforces max_subscriptions_per_connection, naturally terminating subscriptions are not cleared from memory registries, leading to exhaustion of connection slots.
A high-severity type-confusion vulnerability exists in NearForm fast-jwt prior to version 6.3.0. The vulnerability allows attackers to bypass crucial claim validation steps (such as expiration, issuer, audience, and subject validations) by presenting a validly signed JSON Web Token structured as a JSON array instead of a JSON object. This occurs because the library's decoder fails to reject JSON arrays during type evaluation.