Feb 28, 2026·6 min read·28 visits
CVE-2025-65110 is a high-severity XSS flaw in Vega allowing arbitrary code execution. Attackers can bypass the expression sandbox by passing a malicious object to `vlSelectionTuples`, triggering a gadget chain via the `.map()` method. Patched in vega-selections 5.6.3 and 6.1.2.
A critical Cross-Site Scripting (XSS) vulnerability has been identified in the `vega-selections` package, a core dependency of the Vega visualization grammar. The vulnerability arises from improper type validation in the `vlSelectionTuples` function, which is exposed to Vega's expression interpreter. By supplying a crafted object instead of the expected array, an attacker can hijack the control flow via a method gadget chain, leading to arbitrary JavaScript execution within the victim's browser context. This issue affects Vega versions prior to 5.6.3 and 6.1.2.
Vega is a widely adopted declarative language for creating, saving, and sharing interactive visualization designs. While Vega implements a secure, sandboxed environment for evaluating expressions within visualization specifications, this security model relies on the integrity of exposed internal functions. The vulnerability (CVE-2025-65110) resides in vega-selections, a package responsible for handling interactive selections.
The specific flaw exists in the vlSelectionTuples function. This function is exposed to the runtime expression evaluator but lacks necessary type enforcement on its arguments. In JavaScript, method calls are dynamic; if a function expects an array and calls .map() on it, passing an object with a map property pointing to a function will execute that function instead. This behavior allows an attacker to break out of the intended logic flow.
By injecting a malicious Vega specification, an attacker can invoke vlSelectionTuples with a crafted JavaScript object. If the application environment exposes certain global variables (common in debugging scenarios), this object can reference existing functions (gadgets) to execute arbitrary code, effectively bypassing the sandbox and resulting in DOM-based Cross-Site Scripting (XSS).
The root cause of this vulnerability is Improper Input Validation (CWE-20) leading to Improper Neutralization of Input During Web Page Generation (CWE-79). Specifically, the vlSelectionTuples function assumes its input is an array without verification.
The vulnerable code pattern performs an operation similar to return input.map(...). In a secure context, the developer assumes input is data generated by the visualization engine. However, because vlSelectionTuples is accessible via Vega signals, an attacker can control the input argument.
This vulnerability exploits JavaScript's duck-typing. When the engine executes input.map(), it looks up the map property on input. If input is a plain JSON object constructed by an attacker—{"map": ...}—the engine executes whatever function is referenced by that property. The this context inside that function becomes the attacker's object. If the attacker references a "gadget" function (a function that performs dangerous actions based on this properties), they can manipulate the execution flow to invoke sinks like eval() or Function().
The vulnerability is located in the vega-selections package. Below is a comparison of the vulnerable logic and the remediation applied in versions 5.6.3 and 6.1.2.
In the unpatched version, the code directly invokes .map() on the first argument, blindly trusting it is an array.
// vega-selections/src/selection.js (Simplified)
export function vlSelectionTuples(input, ...) {
// VULNERABILITY: No check to ensure 'input' is actually an array.
// An attacker can pass an object with a 'map' function.
return input.map(function(d) {
return { ... };
});
}The fix introduces an explicit type check using Array.isArray(). This simple validation ensures that only genuine arrays are processed, neutralizing the gadget attack vector because plain objects will fail this check.
// vega-selections/src/selection.js (Patched)
export function vlSelectionTuples(input, ...) {
// FIX: Explicitly validate that input is an array.
if (!Array.isArray(input)) {
return [];
}
// Safe to call .map() now that we know it's an array.
return input.map(function(d) {
return { ... };
});
}Exploiting this vulnerability requires a "gadget chain." A gadget is a piece of existing code in the application that can be repurposed for malicious ends. For Vega, a common gadget is found when debugging tools or the vega object itself are exposed globally.
signals definition, they define a signal that calls vlSelectionTuples.map property of this object is set to a function that executes logic on this. A known gadget in Vega ecosystems is VEGA_DEBUG.vega.CanvasHandler.prototype.on.on function iterates over handlers defined in this. By controlling properties like _handlers and _handlerIndex on the malicious object, the attacker directs the gadget to call window.eval or fetch.{
"signals": [
{
"name": "exploit",
"update": "vlSelectionTuples({'map': VEGA_DEBUG.vega.CanvasHandler.prototype.on, 'eventName': 'log', '_handlers': ['alert(document.domain)'], '_handlerIndex': window.eval})"
}
]
}> [!WARNING]
> This exploit relies on the availability of a suitable gadget in the global scope. If VEGA_DEBUG or similar objects are not exposed, exploitation becomes significantly harder, though not theoretically impossible if other gadgets exist.
The impact of CVE-2025-65110 is classified as High (CVSS 8.1). Successful exploitation results in arbitrary JavaScript execution within the context of the user's browser.
HttpOnly), local storage tokens, and user data displayed in the application.While the EPSS score is currently low (0.08%), the availability of a clear PoC and the popularity of Vega in data science dashboards increases the likelihood of targeted exploitation.
Immediate patching is the primary recommendation. The vulnerability is resolved in vega-selections versions 5.6.3 (for Vega 5.x) and 6.1.2 (for Vega 6.x).
Developers using npm or yarn should update their lockfiles to ensure the transitive dependency vega-selections is resolved to a safe version.
# Check current version
npm list vega-selections
# Update dependencies
npm update vega vega-selectionsIf immediate patching is not feasible, the following mitigations reduce risk:
VEGA_DEBUG, vega, and view instances are not attached to the window object in production environments. This breaks the specific gadget chain used in the known PoC.unsafe-eval. This prevents the window.eval sink from executing the payload, even if the gadget chain is triggered.CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
vega-selections Vega | < 5.6.3 | 5.6.3 |
vega-selections Vega | >= 6.0.0 < 6.1.2 | 6.1.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 (Cross-site Scripting) |
| CVSS v3.1 | 8.1 (High) |
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Required | None |
| User Interaction | Required |
| Exploit Status | Proof-of-Concept Available |
The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.