CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-115910

CVE-2026-115910: OpenID Connect Session-Binding Bypass and Account Takeover in Python Social Auth

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 29, 2026·13 min read·2 visits

Executive Summary (TL;DR)

Python Social Auth (social-core < 6.2.0) fails to bind OpenID Connect nonces to the initiating user session, validating them only against a global database table. Attackers who intercept a victim's unused authorization code can inject it into their own session and bypass nonce verification, resulting in account takeover. Upgrade to social-auth-core 6.2.0 and enable PKCE to remediate.

A session-binding vulnerability in Python Social Auth (social-core) versions prior to 6.2.0 allows an attacker to perform authorization code injection against the OpenID Connect authentication backend. By storing generated OIDC nonces in a global database table without binding them to the initiating browser session, the library accepts any valid, unexpired nonce returned in an ID token regardless of which session originated the authentication transaction. When combined with the default disablement of Proof Key for Code Exchange (PKCE), an attacker who intercepts an unused authorization code can inject it into their own authentication flow to achieve full account takeover.

Vulnerability Overview

Python Social Auth (social-core, distributed on PyPI as social-auth-core) is a widely deployed authentication and authorization library used across Python web frameworks, including Django, Flask, and FastAPI. The library provides a modular architecture for integrating OAuth 1.0, OAuth 2.0, SAML, and OpenID Connect (OIDC) identity providers. Within this ecosystem, the OpenIdConnectAuth base class located in social_core/backends/open_id_connect.py implements the relying party (RP) protocol logic for generic OpenID Connect backends and serves as the foundation for provider-specific OIDC implementations.

In the OpenID Connect Authorization Code Flow, the nonce parameter is a mandatory cryptographic string value used to associate a client session with an ID Token and to mitigate replay and authorization code injection attacks. The OpenID Connect Core 1.0 specification mandates that the RP generate a nonce, bind it to the user agent's session, pass it to the OpenID Provider (OP) in the authorization request, and subsequently verify that the nonce claim inside the returned ID Token exactly matches the value stored in the active client session.

In versions of social-core prior to 6.2.0, the OpenIdConnectAuth backend failed to enforce this session-binding invariant. Instead of persisting the generated nonce within the initiating user's session state, the backend wrote the nonce solely into a global, shared database table (OpenIdConnectAssociation). During callback verification, the application queried this shared storage to confirm that the nonce returned inside the ID Token existed and had not expired, without verifying whether the nonce belonged to the browser session processing the callback.

This architectural flaw is classified under CWE-345 (Insufficient Verification of Data Authenticity) and CWE-384 (Session Fixation). Because OpenIdConnectAuth also disables Proof Key for Code Exchange (PKCE) by default (DEFAULT_USE_PKCE = False), the vulnerability exposes applications to authorization code injection. If an attacker can intercept or obtain an unredeemed authorization code generated by a victim, the attacker can substitute that code into their own authentication callback, pass both the OAuth 2.0 state validation and the global OIDC nonce check, and authenticate as the victim.

Root Cause Analysis

The root cause of CVE-2026-115910 lies in the decoupling of the OAuth 2.0 state parameter (which is bound to the user's browser session) from the OpenID Connect nonce parameter (which was stored globally). During the initiation of an OIDC login flow, the auth_params() method in OpenIdConnectAuth generates a nonce by calling self.get_and_store_nonce(self.authorization_url(), state). This method creates an association record where the handle is the random nonce string and the assoc_type is the OAuth state, saving it via self.strategy.storage.association.store().

The storage mechanism backed by strategy.storage.association maps to the OpenIdConnectAssociation model in relational databases. This table is shared globally across all users and concurrent authentication flows within the application. Prior to version 6.2.0, auth_params() did not write the generated nonce into self.strategy.session, leaving the browser session aware only of its OAuth state parameter.

When the OpenID Provider redirects the user back to the relying party's completion endpoint, social-core first validates that the state query parameter in the HTTP callback matches the state stored in the user agent's session cookie. It then exchanges the code query parameter at the OP's token endpoint to retrieve the signed ID Token. Once the JWT signature and temporal claims (exp, iat, nbf) are verified, the backend calls validate_claims(id_token) to check the nonce claim.

In the vulnerable implementation, validate_claims() extracted the nonce string from the ID Token payload and invoked self.get_nonce(nonce). This helper queried the global OpenIdConnectAssociation table filtering only by the server URL and the nonce handle. If a matching, non-expired database row was returned from any concurrent login attempt, validate_claims() deleted the row via self.remove_nonce(nonce_obj.id) and treated the claim as valid. The method never verified whether nonce_obj.assoc_type matched the current session's validated state, nor did it check the session dictionary for an expected nonce.

Consequently, the state validation and nonce validation operated on disjoint data stores. An attacker could satisfy the session check using their own State_A and satisfy the token check using the victim's Nonce_V (embedded within the ID Token redeemed from Code_V), completely bypassing the cryptographic binding between the authorization request and the token response.

Code Analysis

The vulnerability was resolved in commit c2f2ae4663bef19ee8b07f2cf5a2344f9155d826 by binding the generated nonce directly to the session transaction context and enforcing strict equality checks during token validation. In the vulnerable code prior to 6.2.0, auth_params() and validate_claims() in social_core/backends/open_id_connect.py were implemented as follows:

# Vulnerable implementation prior to 6.2.0
def auth_params(self, state=None):
    params = super().auth_params(state)
    if self.USE_NONCE:
        # Stores nonce only in global association storage
        params["nonce"] = self.get_and_store_nonce(self.authorization_url(), state)
    return params
 
def validate_claims(self, id_token) -> None:
    self.validate_temporal_claims(id_token)
 
    nonce = id_token.get("nonce")
    if not nonce:
        raise AuthResponseError(self, "Incorrect id_token: nonce", ...)
 
    # Queries global association table without checking session or state ownership
    nonce_obj = self.get_nonce(nonce)
    if nonce_obj:
        self.remove_nonce(nonce_obj.id)
    else:
        raise AuthResponseError(self, "Incorrect id_token: nonce", ...)

The patch introduces a new helper method, _store_session_nonce(self, state, nonce), which is called immediately after get_and_store_nonce() in auth_params(). This method retrieves the active transaction dictionary from the user's session (keyed either by self.transaction_key(state) for standard OAuth 2.0 flows or f"{self.name}_state" for association-only flows), verifies that the session transaction's state matches the current state, and persists the oidc_nonce value directly inside that session object:

# Patched implementation in 6.2.0
def auth_params(self, state=None):
    params = super().auth_params(state)
    if self.USE_NONCE:
        nonce = self.get_and_store_nonce(self.authorization_url(), state)
        # Bind the generated nonce to the browser session transaction
        self._store_session_nonce(state, nonce)
        params["nonce"] = nonce
    ...
 
def _store_session_nonce(self, state: str | None, nonce: str) -> None:
    """Bind the nonce to the browser's pending authorization context."""
    if self.ASSOCIATION_ONLY:
        key = f"{self.name}_state"
        transaction = self.strategy.session_get(key)
    elif isinstance(state, str):
        key = self.transaction_key(state)
        transaction = self.strategy.session_get(key)
    else:
        transaction = None
    if not isinstance(transaction, dict) or transaction.get("state") != state:
        raise AuthSessionError(
            self, "state", code="session_context_missing", stage="begin"
        )
    self.strategy.session_set(key, {**transaction, "oidc_nonce": nonce})

During the callback phase, validate_claims() was rewritten to extract the expected nonce and state from the validated session transaction (self._oauth2_transaction or self._oidc_association_transaction). It enforces type safety, verifies that all values are non-empty strings, and executes a constant-time comparison (constant_time_compare(nonce, expected)) between the ID Token's nonce claim and the session's oidc_nonce. Furthermore, when looking up the association record for cleanup via self.get_nonce(nonce), the patch adds a secondary check verifying that nonce_obj.assoc_type == state:

# Patched claim validation in 6.2.0
def validate_claims(self, id_token) -> None:
    self.validate_temporal_claims(id_token)
 
    # The token nonce must belong to the state validated for this browser.
    nonce = id_token.get("nonce")
    transaction = (
        self._oidc_association_transaction
        if self.ASSOCIATION_ONLY
        else self._oauth2_transaction
    )
    expected = (
        transaction.get("oidc_nonce") if isinstance(transaction, dict) else None
    )
    state = transaction.get("state") if isinstance(transaction, dict) else None
    if not isinstance(nonce, str):
        nonce = ""
    if not isinstance(expected, str):
        expected = ""
    if not isinstance(state, str):
        state = ""
    if not (
        nonce and expected and state and constant_time_compare(nonce, expected)
    ):
        raise AuthResponseError(
            self,
            "Incorrect id_token: nonce",
            code="nonce_mismatch",
            stage="token_validation",
        )
 
    nonce_obj = self.get_nonce(nonce)
    if nonce_obj and nonce_obj.assoc_type == state:
        self.remove_nonce(nonce_obj.id)
    else:
        raise AuthResponseError(
            self,
            "Incorrect id_token: nonce",
            code="nonce_mismatch",
            stage="token_validation",
        )

This dual verification ensures that the fix is complete. Even if an attacker manipulates session structures or attempts a timing attack, both the session-bound oidc_nonce and the database association's assoc_type must strictly match the active session's state. In addition, if an authorization code injection attempt fails the first check, self.remove_nonce() is not reached, preserving the legitimate victim's nonce in the database so their login attempt is not denied by a premature deletion.

Exploitation

Exploiting CVE-2026-115910 requires an attacker to obtain a valid, unredeemed OAuth 2.0 authorization code issued to a victim user for the target relying party. Because authorization codes are transmitted as URL query parameters during the browser redirect from the OpenID Provider to the relying party, interception can occur through several established vectors. Common leakage paths include HTTP Referer headers sent when the callback page loads external third-party resources before redeeming the code, open redirect chains on the relying party domain, shared proxy or load balancer access logs, or malware/browser extensions reading navigation history.

Once the victim initiates a login flow, social-core generates Nonce_V and writes it to the global OpenIdConnectAssociation table. The victim authenticates at the OP, which generates Code_V and binds it internally to Nonce_V. Before the victim's browser completes the callback to redeem Code_V (or if the callback request is blocked or dropped prior to token exchange), the attacker captures Code_V.

To execute the injection, the attacker initiates an independent OpenID Connect login flow against the target application using their own browser. The application initializes a valid session cookie for the attacker, stores a transaction containing State_A in the attacker's session, and redirects the attacker to the OP. The attacker does not need to authenticate at the OP; instead, they intercept their own redirect URL to extract State_A.

The attacker then sends a crafted HTTP GET request to the application's OIDC completion endpoint (/complete/<backend>/), combining their own session cookie and State_A with the victim's stolen authorization code Code_V:

GET /complete/oidc/?state=State_A&code=Code_V HTTP/1.1
Host: relying-party.example.com
Cookie: sessionid=attacker_valid_session_id

Upon receiving this request, social-core verifies that state=State_A matches the State_A stored in attacker_valid_session_id. Next, the backend makes a back-channel POST request to the OP's token endpoint to exchange Code_V. Because PKCE is disabled by default (DEFAULT_USE_PKCE = False), the OP does not require a code_verifier and returns the victim's Access Token and signed ID Token (which contains the victim's subject identifier sub and `

Exploitation Walkthrough & Test Verification

When the back-channel token exchange returns the victim's ID Token containing "nonce": "Nonce_V", the vulnerable validate_claims() method queries the database for Nonce_V. Because Nonce_V was stored globally when the victim began their login flow, the database query returns a valid association object. social-core deletes Nonce_V from the table, accepts the ID Token claims as authentic, and completes the authentication pipeline—logging the attacker's browser session directly into the victim's account.

The mechanics of this exploit are demonstrated in the upstream regression test added in commit c2f2ae4663bef19ee8b07f2cf5a2344f9155d826 within social_core/tests/backends/open_id_connect_nonce.py. The test simulates two separate strategies (self.backend for the victim and attacker_backend for the attacker) sharing the same underlying association storage:

def test_victim_code_rejected_in_other_browser(
    self: OpenIdConnectNonceAssertionsCapable,
) -> None:
    # 1. Victim starts auth flow; Nonce_V is stored in shared TestAssociation storage
    victim_params = get_querystring(self.backend.auth_url())
    
    # 2. Attacker starts separate auth flow in their own session strategy
    attacker_strategy = TestStrategy(TestStorage)
    attacker_strategy.set_settings(self.extra_settings())
    attacker_backend = type(self.backend)(
        attacker_strategy, redirect_uri=self.backend.redirect_uri
    )
    attacker_params = get_querystring(attacker_backend.auth_url())
    
    # 3. Mock OP token endpoint returning Victim's ID Token (with Nonce_V)
    responses.add(
        responses.POST,
        attacker_backend.access_token_url(),
        json=json.loads(
            self.prepare_access_token_body(
                nonce=victim_params["nonce"], subject="victim"
            )
        ),
    )
    
    # 4. Attacker submits Victim's code with Attacker's valid state
    attacker_strategy.set_request_data(
        {"code": "victim-code", "state": attacker_params["state"]},
        attacker_backend,
    )
    
    # 5. Patched backend rejects the injection with nonce_mismatch
    with assert_auth_error(self, AuthResponseError, "nonce_mismatch") as error:
        attacker_backend.complete()
    self.assertEqual(error.exception.stage, "token_validation")
    self.assertEqual(len(TestAssociation.get(handle=victim_params["nonce"])), 1)

Prior to version 6.2.0, calling attacker_backend.complete() in this test scenario succeeded without raising AuthResponseError, returning the authenticated user object corresponding to subject="victim".

Impact Assessment

The primary security impact of CVE-2026-115910 is full account takeover (ATO) of any user authenticating via an affected OpenID Connect backend. When an attacker successfully injects a victim's authorization code, social-core associates the authenticated identity from the ID Token with the attacker's active session. This grants the attacker complete read and write access to the victim's account data, application privileges, and any linked OAuth access or refresh tokens persisted by the social authentication pipeline.

Furthermore, in configurations where social-core is used to link additional external social accounts to an already authenticated local user profile, a related session-binding weakness could allow an attacker to forcibly link a victim's external identity to the attacker's account, or vice versa. This compromises both the confidentiality (C:H) and integrity (I:H) of user accounts within the relying party application.

The vulnerability is assigned a CVSS v3.1 base score of 6.8 (Medium) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N. While the attack occurs remotely over the network (AV:N) without requiring prior privileges (PR:N), the Attack Complexity is rated High (AC:H) and User Interaction is Required (UI:R). This reflects the prerequisite that a victim must actively initiate an OIDC login flow and that the attacker must have a positioned mechanism (such as a network tap, compromised proxy, open redirect, or referrer leak) to capture the authorization code before the victim's browser redeems it.

From a threat intelligence perspective, CVE-2026-115910 is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no public weaponized exploit tools exist outside of the unit tests included in the official patch commit. The Exploit Prediction Scoring System (EPSS) assigns the flaw a score of 0.00157 (0.16%, 4.78th percentile), indicating a low probability of automated mass exploitation due to the requirement for authorization code interception.

Remediation

To remediate CVE-2026-115910, administrators and developers must upgrade social-auth-core to version 6.2.0 or later. This version enforces strict session binding for OpenID Connect nonces across both standard OAuth 2.0 login flows and association-only flows.

pip install --upgrade "social-auth-core>=6.2.0"

> [!NOTE] > Upgrading to social-auth-core 6.2.0 alters the internal structure of pending OIDC transaction dictionaries stored in user sessions (by requiring the oidc_nonce key). Any in-progress OpenID Connect login attempts initiated prior to the deployment will fail upon callback with nonce_mismatch and must be restarted by the user.

As a defense-in-depth measure, and as an effective workaround for environments where an immediate library upgrade cannot be deployed, operators should explicitly enable Proof Key for Code Exchange (PKCE) for all OpenID Connect backends. When PKCE (RFC 7636) is enforced by both the relying party and the OpenID Provider, the authorization request includes a cryptographic code_challenge derived from a session-specific code_verifier. Even if an attacker intercepts Code_V and bypasses the global nonce check, the token exchange will fail at the OpenID Provider because the attacker's session does not possess the victim's code_verifier.

In Django applications using social-auth-app-django, PKCE can be enabled globally for the generic OIDC backend or per specific backend in settings.py:

# Enable PKCE for the generic OIDC backend
SOCIAL_AUTH_OIDC_USE_PKCE = True
 
# Or enable PKCE for a custom/named OIDC backend
SOCIAL_AUTH_<BACKEND_NAME>_USE_PKCE = True

Alternatively, developers maintaining custom subclasses of OpenIdConnectAuth can enable PKCE directly on the class definition:

from social_core.backends.open_id_connect import OpenIdConnectAuth
 
class HardenedOIDCBackend(OpenIdConnectAuth):
    DEFAULT_USE_PKCE = True

Security teams should also configure application logging and SIEM alerts to monitor for AuthResponseError exceptions raised with the error code nonce_mismatch during the token_validation stage. Repeated occurrences of this exception indicate either a misconfigured client session store or active attempts to inject foreign authorization codes.

Official Patches

Python Social AuthOfficial patch commit binding OIDC nonces to browser session and state
Python Social Authsocial-core 6.2.0 release tag
Python Social AuthGitHub Pull Request #2141 addressing OIDC session binding

Fix Analysis (1)

Technical Appendix

CVSS Score
6.8/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N
EPSS Probability
0.16%
Top 95% most exploited

Affected Systems

Python Social Auth (social-core / social-auth-core)

Affected Versions Detail

Product
Affected Versions
Fixed Version
social-core (social-auth-core)
python-social-auth
< 6.2.06.2.0
AttributeDetail
Vulnerability TypeCWE-345 (Insufficient Verification of Data Authenticity) / CWE-384 (Session Fixation)
Affected ComponentOpenIdConnectAuth (social_core/backends/open_id_connect.py)
Attack VectorNetwork (Authorization Code Injection via OIDC Callback)
CVSS v3.1 Score6.8 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N)
EPSS Score0.16% (4.78th Percentile)
ImpactAccount Takeover (High Confidentiality and Integrity Impact)
Exploit StatusProof-of-Concept (Upstream Unit Test Suite)
Patched Versionsocial-auth-core 6.2.0

MITRE ATT&CK Mapping

T1556Modify Authentication Process
Credential Access
T1539Steal Web Session Cookie
Credential Access
T1078Valid Accounts
Initial Access
CWE-345
Insufficient Verification of Data Authenticity

The software does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.

Vulnerability Timeline

Security patch committed to python-social-auth/social-core (c2f2ae4663bef19ee8b07f2cf5a2344f9155d826)
2026-10-20
GitHub Security Advisory GHSA-h2r5-x5m7-3935 and CVE-2026-115910 published
2026-10-26
Python Social Auth social-core version 6.2.0 released to PyPI
2026-10-26
CVE-2026-115910 published in the National Vulnerability Database (NVD)
2026-10-26
EPSS score calculated and recorded in NVD and OSV catalogs
2026-10-28

References & Sources

  • [1]NVD - CVE-2026-115910
  • [2]CVE.org Record - CVE-2026-115910
  • [3]Google OSV - GHSA-h2r5-x5m7-3935
  • [4]CVEListV5 Record - CVE-2026-115910
  • [5]GitHub Pull Request #2141 - python-social-auth/social-core
  • [6]GitHub Commit c2f2ae4663bef19ee8b07f2cf5a2344f9155d826
  • [7]Python Social Auth social-core 6.2.0 Release

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 1 hour ago•CVE-2026-111497
9.8

CVE-2026-111497: Out-of-Bounds Heap Write and Remote Code Execution in Google Brotli Decoder (Node2Shell)

CVE-2026-111497, tracked in threat intelligence under the aliases Node2Shell, Broccoli2Shell, and HTTP2Shell, is a critical out-of-bounds heap write and denial-of-service vulnerability in the reference Google Brotli decompression library (c/dec/decode.c). The flaw originates from an unchecked return value and error-code conflation in DecodeBlockTypeAndLength() when processing malformed streams with single-block-type declarations (max_block_type <= 1). On the fast decoding path, the ignored error return causes the 32-bit unsigned block_length counter to underflow from 0 to UINT32_MAX (0xFFFFFFFF), corrupting internal decoder tables and enabling arbitrary heap memory overwrites via memmove16(). On the streaming path, the error return is misinterpreted as an incomplete bit-stream, trapping the worker process in an infinite 100% CPU loop. Because Google Brotli is embedded across major runtimes and infrastructure components—including Node.js, Envoy Proxy, Microsoft .NET, Istio, and Python—the vulnerability exposes a broad attack surface via standard HTTP Content-Encoding: br request and response bodies.

Amit Schendel
Amit Schendel
3 views•12 min read
•about 2 hours ago•CVE-2026-116575
6.3

CVE-2026-116575: Cross-Tenant Conversation Disclosure via Delimiter Injection in Mem0

CVE-2026-116575 (GHSA-5324-3w58-qpp2) is an unescaped delimiter injection and canonicalization flaw in the Mem0 persistent memory platform for AI agents and applications. Prior to Python SDK version 2.0.18 and TypeScript SDK version 3.1.6, the session scope key construction functions join caller-controlled user_id, agent_id, and run_id identifiers using raw '=' and '&' delimiters without sanitization. An attacker who controls one of these identifiers in a multi-tenant deployment can inject delimiter sequences to force a string collision with a target tenant's session scope key, resulting in unauthorized disclosure, poisoning, or eviction of the target tenant's recent conversation buffer.

Alon Barad
Alon Barad
3 views•9 min read
•about 3 hours ago•CVE-2026-116580
7.3

CVE-2026-116580: Unvalidated Method Invocation via Identifier Reuse in Seroval Deserializer

Seroval versions prior to 1.6.8 contain an insecure deserialization vulnerability (CWE-502) in the `fromJSON` parser. When reconstructing `AsyncIteratorFactoryInstance` or `IteratorFactoryInstance` nodes, the deserializer verifies the source node type using its identifier in the AST lookup table rather than verifying the runtime identity of the deserialized object. Because unmarked node identifiers omitted from the `m` reference array can be reused in vanilla deserialization mode, an attacker can substitute an arbitrary plain object or plugin-produced value as the stream source. This causes the runtime to invoke `.on(listener)` on an attacker-controlled target.

Amit Schendel
Amit Schendel
2 views•12 min read
•about 4 hours ago•CVE-2026-116584
7.5

CVE-2026-116584: Denial of Service via Unvalidated Node Element Lists in Seroval

A Denial of Service (DoS) vulnerability exists in the Seroval JavaScript serialization library affecting versions 1.4.1 through 1.6.7. The deserialization routines fromJSON and fromCrossJSON fail to verify that node element lists are actual JavaScript Array primitives before reading their length property. An unauthenticated remote attacker can supply a compact, malformed JSON payload containing an array-like object with an arbitrary length value, causing unbounded memory allocation and synchronous loop iterations that block the event loop or crash the process via heap exhaustion.

Amit Schendel
Amit Schendel
3 views•10 min read
•about 5 hours ago•CVE-2026-116581
7.5

CVE-2026-116581: Unauthenticated Event-Loop Denial of Service via Unvalidated Sequence Indices in Seroval

Seroval, a JavaScript serialization library used for transferring complex structures across network boundaries, contains an input validation vulnerability in versions prior to 1.6.8. The `fromJSON` and `fromCrossJSON` deserializers fail to validate array element lists and the termination boundaries (`done` and `throw` indices) of sequence nodes. By supplying out-of-bounds indices or forged array-like structures, an unauthenticated remote attacker can construct an iterator with an unreachable exit condition or trigger excessive loop iterations, halting the single-threaded JavaScript event loop and causing a denial of service.

Amit Schendel
Amit Schendel
7 views•10 min read
•about 6 hours ago•CVE-2026-116582
4.7

CVE-2026-116582: Cross-Site Scripting via Unescaped RegExp Serialization in Seroval

CVE-2026-116582 is an improper output escaping vulnerability in the Seroval JavaScript serialization library that leads to cross-site scripting (XSS). When Seroval serializes RegExp objects into executable JavaScript literals for embedding in HTML script elements, it fails to neutralize the less-than character ('<'). If an application constructs a regular expression from untrusted input and embeds the serialized output inside an inline script block during server-side rendering (SSR), an attacker can inject a closing script tag to break out of the JavaScript context and execute arbitrary client-side code.

Amit Schendel
Amit Schendel
4 views•11 min read