Apr 3, 2026·6 min read·3 visits
TeleJSON < 6.0.0 passes unvalidated input from the `_constructor-name_` JSON property into a `new Function()` call during deserialization. This allows attackers to achieve arbitrary code execution via crafted JSON payloads, often delivered through cross-frame messaging.
The telejson package prior to version 6.0.0 contains a DOM-based Cross-Site Scripting (XSS) vulnerability. The package deserializer uses an unsanitized object property, `_constructor-name_`, within a dynamically generated function via `new Function()`. Attackers can supply crafted JSON payloads to achieve arbitrary JavaScript execution in the context of the vulnerable application.
The telejson library provides serialization and deserialization mechanisms for JavaScript objects, extending standard JSON capabilities to support cyclic references and custom prototypes. This functionality is heavily utilized in frontend development ecosystems, particularly within cross-frame communication channels and complex UI testing frameworks. The vulnerability, identified as GHSA-ccgf-5rwj-j3hv, resides in the deserialization routine where untrusted input dictates dynamic function creation.
By exploiting this flaw, an attacker can achieve Cross-Site Scripting (XSS) via Improper Control of Generation of Code (CWE-94). The application passes unvalidated data from a specific JSON key directly into a new Function() constructor. This pattern circumvents the inherent safety of standard JSON.parse() operations, converting a data-parsing action into arbitrary code execution.
The primary attack surface involves applications processing untrusted messages through window.postMessage using telejson.parse(). Since telejson is a foundational dependency in tools like Storybook, the blast radius encompasses numerous development environments and any production deployments exposing these parsers to untrusted origins.
The fundamental flaw exists within the reviver function utilized by telejson to reconstruct serialized objects. When the library encounters an object containing the _constructor-name_ property, it attempts to restore the object's original prototype chain. This feature allows the deserializer to yield instances of custom classes rather than plain JavaScript objects.
To achieve this dynamic instantiation, the library extracts the string value associated with _constructor-name_ and uses it to construct an anonymous function. The implementation relies on the new Function() constructor, utilizing a template literal to interpolate the class name into the function body string. The code explicitly expects this string to be a valid, benign JavaScript identifier.
The application lacks any input validation or sanitization prior to this interpolation. Because the attacker controls the complete string value of the _constructor-name_ property, they can inject arbitrary JavaScript syntax. The JavaScript engine parses the manipulated string as the body of the dynamically generated function, allowing the injected code to alter the control flow.
The vulnerability manifests precisely at the moment the new Function(...)() call is evaluated. The trailing parentheses immediately invoke the newly created function within the execution context of the hosting application, granting the attacker immediate code execution.
The vulnerable logic in telejson versions prior to 6.0.0 is isolated within the core deserialization module located in src/index.ts. The code checks for the presence of the _constructor-name_ property and proceeds to execute the vulnerable interpolation block if the value differs from the default Object type.
if (isObject<ValueContainer>(value) && value['_constructor-name_']) {
const name = value['_constructor-name_'];
if (name !== 'Object') {
const Fn = new Function(`return function ${name}(){}`)();
Object.setPrototypeOf(value, new Fn());
}
}In the snippet above, the variable name receives the unsanitized attacker payload. If the attacker supplies a payload containing function-terminating syntax followed by separate statements, the template string evaluation generates a compound script block, circumventing the intended function definition.
The patch introduced in version 6.0.0 implements a two-fold defense mechanism, combining strict sanitization with an explicit opt-in requirement. The library now requires developers to explicitly pass an allowFunction flag in the options object to enable prototype reconstruction.
if (isObject<ValueContainer>(value) && value['_constructor-name_'] && options.allowFunction) {
const name = value['_constructor-name_'];
if (name !== 'Object') {
const Fn = new Function(`return function ${name.replace(/[\W_]+/g, '')}(){}`)();
Object.setPrototypeOf(value, new Fn());
}
}The remediation strips all non-alphanumeric characters from the input string using the regex /[\W_]+/g before passing it to the new Function() constructor. This completely neutralizes the injection vector, as attackers can no longer introduce the parentheses, semicolons, or whitespace necessary to break out of the function definition context.
Exploitation requires the attacker to submit a carefully crafted JSON payload to an endpoint or event listener that subsequently processes the data using telejson.parse(). The attack vector is predominantly client-side, targeting applications that accept messages via window.postMessage from arbitrary or poorly validated origins.
The payload structure leverages the _constructor-name_ key to inject the attack string. A successful exploit payload must close the intended function declaration, inject the malicious statements, and effectively handle the trailing syntax generated by the template literal.
{
"_constructor-name_": "Exploit(){}; alert(document.domain); //"
}When the deserializer processes this object, the new Function() constructor evaluates the interpolated string return function Exploit(){}; alert(document.domain); //(){}. The JavaScript engine interprets this as a function returning a distinct, empty function, followed immediately by the execution of alert(document.domain). The // neutralizes the trailing (){} syntax that would otherwise cause a syntax error.
The vulnerability results in a direct Cross-Site Scripting (XSS) condition, operating entirely within the Document Object Model (DOM). Successful exploitation grants the attacker arbitrary JavaScript execution capabilities within the security context of the victim's application.
An attacker leveraging this flaw can access all data available to the compromised origin. This includes reading document.cookie for non-HttpOnly session tokens, accessing HTML5 storage mechanisms, and interacting with the DOM to extract sensitive user data. The attacker can also forge requests to backend APIs, assuming the identity and privileges of the authenticated user.
The CVSS v4.0 vector (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N) reflects the specific prerequisites and scope of the flaw. The Attack Requirements (AT:P) metric emphasizes that exploitation depends on the target application explicitly implementing telejson to parse untrusted, attacker-controlled input.
Despite the specific scores assigned by the context of DOM-based XSS, the practical risk is significant for applications handling sensitive session data or administrative interfaces. In contexts like shared developer environments or administrative dashboards, the flaw facilitates credential theft and extensive lateral movement within the application ecosystem.
The primary remediation for this vulnerability requires upgrading the telejson package to version 6.0.0 or later. The patch completely eliminates the underlying injection vector via regex-based sanitization and establishes a secure default posture by disabling dynamic function evaluation entirely.
Developers must explicitly opt-in to prototype reconstruction by setting the allowFunction parameter to true. This architectural change ensures that only applications strictly requiring dynamic prototype deserialization are exposed to the associated complexity, reducing the attack surface for all other use cases.
If upgrading the library is immediately unfeasible, organizations must implement stringent origin validation on all inter-frame communication. Event listeners utilizing window.postMessage must strictly verify the event.origin property against a hardcoded whitelist of trusted domains before passing the event.data to telejson.parse().
Additionally, employing strict Content Security Policy (CSP) headers mitigates the impact of successful exploitation. A CSP that explicitly omits the unsafe-eval directive prevents the JavaScript engine from evaluating strings passed to new Function(), serving as a robust compensating control against this and similar deserialization vulnerabilities.
| Product | Affected Versions | Fixed Version |
|---|---|---|
telejson storybookjs | < 6.0.0 | 6.0.0 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | DOM-based Cross-Site Scripting (XSS) |
| CWE ID | CWE-79, CWE-94 |
| Attack Vector | Network |
| Privileges Required | None |
| CVSS v4.0 Vector | CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N |
| Primary Mitigation | Upgrade to telejson >= 6.0.0 |
The application does not adequately neutralize user-controlled input before utilizing it to dynamically generate code, allowing attackers to execute arbitrary JavaScript.