Stored XSS via Analytics ID in Cal.com
CVE-2026-57858 is a stored XSS flaw in Cal.com's BookingPageTagManager letting event owners inject scripts into public booking pages.
Overview
CVE-2026-57858 is a stored cross-site scripting (XSS) vulnerability affecting Cal.com’s self-hosted distribution, Cal.diy, across versions 2.1.1 through 6.2.0. The flaw resides in the BookingPageTagManager component, which is responsible for injecting analytics and tag manager tracking IDs — such as Google Tag Manager container IDs or similar identifiers — directly into inline <script> blocks rendered on public-facing booking pages. Because the tracking ID value is written into a JavaScript string literal without sanitization or escaping, an authenticated event owner can supply a crafted payload that breaks out of the string context and executes arbitrary JavaScript in the browsers of all visitors to that booking page.
The vulnerability was identified by security researchers analyzing the analytics configuration flow in Cal.com’s booking infrastructure. Its significance extends beyond a typical reflected XSS: payloads are persisted server-side as part of the event owner’s configuration, meaning every subsequent visitor to the affected public booking URL triggers execution without any further attacker interaction. This persistence, combined with the unauthenticated nature of the victim-facing surface, substantially elevates the real-world risk compared with a transient reflected variant.
Organizations running self-hosted Cal.diy instances — including enterprises, agencies, and individual consultants who manage scheduling infrastructure independently — are the primary affected population. SaaS-hosted Cal.com deployments may have independent controls that mitigate the issue, but any operator within the stated version range should treat this as a critical remediation priority given the public exposure of booking pages and the wormability potential described below.
Technical Analysis
The root cause is unsanitized interpolation of user-controlled input into an inline JavaScript string literal. Within the BookingPageTagManager component, the analytics tracking ID collected from the event owner’s settings is embedded directly into a <script> tag rendered server-side or during React’s SSR pass. A representative simplified version of the vulnerable pattern looks like this:
// VULNERABLE — packages/features/bookings/BookingPageTagManager.tsx (illustrative)
export function BookingPageTagManager({ trackingId }: { trackingId: string }) {
// trackingId sourced from event owner's analytics configuration, stored in DB
const scriptContent = `
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${trackingId}');
`;
return (
<script
dangerouslySetInnerHTML={{ __html: scriptContent }}
/>
);
}
The critical line is the template literal interpolation: '${trackingId}'. An attacker-controlled trackingId value such as:
GTM-XXXX');alert(document.cookie);//
causes the rendered inline script to become:
})(window,document,'script','dataLayer','GTM-XXXX');alert(document.cookie);//');
The single quote closes the string literal, the closing parenthesis and semicolon terminate the IIFE invocation, and the attacker-supplied JavaScript executes immediately. The trailing // comments out the remainder of the original template to prevent syntax errors that might trigger browser console warnings and alert defenders.
Because dangerouslySetInnerHTML bypasses React’s built-in output encoding — which is appropriate only when content is already trusted — and because no upstream validation or allow-list enforcement is applied to the tracking ID field before it reaches this component, the injection succeeds unconditionally. The value is persisted in the database at configuration time and replayed verbatim to every unauthenticated visitor thereafter.
Impact
The practical impact of this vulnerability is severe across multiple dimensions. At the most basic level, an attacker can steal session cookies from visitors who are authenticated to Cal.com — consultants reviewing their own bookings, administrators, or shared-calendar participants — enabling session hijacking. The HttpOnly flag on session cookies would limit this specific vector, but the XSS surface also allows forged authenticated requests (CSRF chaining), keylogging of form inputs on the booking page (including names, emails, and custom question responses), and exfiltration of page content to attacker-controlled infrastructure.
More critically, the CVSS score of 8.9 partially reflects the wormability potential described in the CVE. An attacker can embed a payload that, upon execution in a victim’s browser, uses the victim’s own authenticated session to make API calls that write the same malicious tracking ID into additional events owned by the victim — or, in a multi-tenant shared environment, leverages any CSRF-able endpoints to propagate the payload to other event owners. This self-replicating behavior transforms an initial single-event compromise into an organization-wide or platform-wide incident with minimal ongoing attacker involvement.
The attack requires only that the victim be an authenticated event owner with the ability to configure analytics integrations — a privilege level available to any standard Cal.diy user — and that at least one visitor access the public booking page. No additional prerequisites exist on the victim side.
How to Fix It
The fix requires two complementary controls: strict input validation at write time and safe output handling at render time.
Input validation: Enforce an allow-list on the tracking ID field at the API layer before the value is persisted. Legitimate GTM container IDs follow the pattern GTM-[A-Z0-9]{4,10}. Reject any value that does not match:
// Validation at the API / form handler layer
const GTM_ID_PATTERN = /^GTM-[A-Z0-9]{4,10}$/;
function validateTrackingId(trackingId: string): string {
if (!GTM_ID_PATTERN.test(trackingId)) {
throw new Error("Invalid analytics tracking ID format.");
}
return trackingId;
}
Safe rendering: Avoid constructing inline script content via string interpolation entirely. Use the validated ID only as an attribute value or pass it through a safe, structured API. If inline script generation is unavoidable, at minimum JSON-encode the value before interpolation:
// FIXED — safe interpolation using JSON.stringify
export function BookingPageTagManager({ trackingId }: { trackingId: string }) {
// JSON.stringify ensures the value is a properly escaped JS string literal
const safeId = JSON.stringify(trackingId); // produces '"GTM-XXXX"' with quotes
const scriptContent = `
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
j.async=true;
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer',${safeId});
`;
return (
<script dangerouslySetInnerHTML={{ __html: scriptContent }} />
);
}
Apply the patch by upgrading to a fixed release once available from the Cal.com project. For npm-based self-hosted deployments, track the release channel:
npm install @calcom/cal.diy@latest
# or if using the monorepo directly:
git pull origin main && yarn install
Until a patch is released, operators should disable the analytics tracking ID configuration field for non-administrative users or strip the field value to an empty string at the rendering layer as a temporary mitigation.
Our Take
Stored XSS through analytics and tag manager integration points is a recurring vulnerability class that the industry consistently underestimates. Developers treating tracking IDs as inert configuration strings — rather than as user-controlled data that will be written into executable script contexts — make an implicit trust assumption that attackers are well aware of. The dangerouslySetInnerHTML escape hatch in React is particularly hazardous in this pattern: it signals to the developer that they have accepted responsibility for content safety, but that responsibility is easily forgotten once the code path is abstracted into a utility component.
For enterprise operators, this vulnerability class underscores the importance of defense-in-depth beyond perimeter controls. A Content Security Policy (CSP) with a strict script-src directive — ideally using nonces — would prevent inline script execution entirely, rendering this vulnerability unexploitable even in the absence of a code fix. Enterprises running Cal.diy should treat CSP configuration as a mandatory baseline control, not an optional hardening measure.
Detection with SAST
This vulnerability falls under CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting), specifically the stored variant. In a React/TypeScript codebase, SAST detection focuses on data-flow analysis tracing tainted sources — database-read fields, API response properties, user configuration values — through to dangerous sinks.
The primary sink pattern to flag is any use of dangerouslySetInnerHTML where the __html property is constructed via string concatenation or template literal interpolation involving a non-literal variable. Offensive360’s SAST engine identifies:
- Source: ORM/database reads into component props (e.g.,
event.trackingId,booking.analyticsId) - Propagation: Template literal or string concatenation in component render scope
- Sink:
dangerouslySetInnerHTML={{ __html: ... }}or directinnerHTMLassignment - Missing sanitizer: Absence of
JSON.stringify, a content security encoder, or an allow-list validation call in the data path
Secondary detection rules target <script> element construction patterns in JSX/TSX where prop values or state variables are interpolated without provable sanitization. Taint tracking across component boundaries — where the vulnerable interpolation occurs in a child component receiving props from a parent that fetched data from the network — requires interprocedural analysis, which shallow linting tools miss but proper SAST data-flow engines handle correctly.
References
Detect this vulnerability class in your codebase
Offensive360 SAST scans your source code for CVE-2026-57858-class vulnerabilities and thousands of other patterns — across 60+ languages.