Aug 1, 2026·6 min read·19 visits
The sanitize-html library bypassed URI scheme validation for standard attributes such as 'action' or 'formaction' when customized configurations allowed them, enabling Stored Cross-Site Scripting.
An incomplete default configuration vulnerability in sanitize-html prior to version 2.17.5 allows remote attackers to execute arbitrary JavaScript code via crafted HTML payloads containing neglected URI-bearing attributes (e.g., action, formaction, data, xlink:href) that bypass input validation logic.
The Node.js library sanitize-html is widely utilized to parse and clean untrusted HTML inputs, stripping hazardous tags, attributes, and malicious URI schemes before rendering. Applications use this package to permit safe rich-text formatting while mitigating browser exploitation vectors. The security model relies on strict control over allowed elements and the evaluation of URIs inside specific attributes.
Prior to version 2.17.5, sanitize-html contained a default configuration gap that restricted URI protocol filtering to a small default subset of attributes. If developers customized their allowed elements list to include other standard HTML5 elements with URI-bearing properties, the library failed to strip harmful schemes like javascript: or vbscript:. This architectural gap resulted in a Stored Cross-Site Scripting (XSS) vulnerability tracked as CVE-2026-53606.
The vulnerability is classified under CWE-79: Improper Neutralization of Input During Web Page Generation. The lack of standard validation for alternate URI-bearing attributes allowed malicious payloads to survive parsing and persist in the application database, executing arbitrary JavaScript in the victim's session upon subsequent page render.
The vulnerability stems from the implementation of the allowedSchemesAppliedToAttributes configuration property. To detect and strip hazardous URI schemes, sanitize-html employs an internal safety check called naughtyHref(). This function parses the target URI and discards the attribute value if it contains unauthorized protocols such as javascript:, data:, or vbscript:.
To balance execution performance and avoid corrupting non-URI string properties, the library does not pass every single HTML attribute through the naughtyHref() engine. Instead, it references allowedSchemesAppliedToAttributes as a gatekeeper list. Prior to version 2.17.5, this list was hardcoded by default to only contain three attributes: href, src, and cite.
When a developer customized the sanitizer configuration to allow tags and attributes beyond the default safe-list, the parser analyzed the configured elements. If an allowed attribute was not explicitly included in the default allowedSchemesAppliedToAttributes array, the sanitizer skipped protocol validation. Attributes like action on <form>, formaction on <button>, or data on <object> bypassed security verification entirely.
To understand the code-level flaw, we must examine the validation loop inside packages/sanitize-html/index.js. The parser loops through attributes and checks whether they should be validated based on their presence in the allowedSchemesAppliedToAttributes array.
In the vulnerable implementation, the default options block was defined as follows:
// VULNERABLE CODE (Prior to 2.17.5)
sanitizeHtml.defaults = {
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ],
allowedSchemesByTag: {},
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ], // Highly restricted list
allowProtocolRelative: true,
enforceHtmlBoundary: false
};When the library evaluated an attribute like formaction, it checked whether the name was present in the allowedSchemesAppliedToAttributes array. Because formaction was missing, the logic skipped the naughtyHref validation path. The patched version expands this array to cover all HTML5 specifications where a browser would parse and execute a URI:
// PATCHED CODE (Version 2.17.5)
sanitizeHtml.defaults = {
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto', 'tel' ],
allowedSchemesByTag: {},
allowedSchemesAppliedToAttributes: [
'href', 'src', 'cite',
'action', 'formaction', 'data', 'xlink:href',
'poster', 'background', 'ping',
'longdesc', 'usemap', 'codebase', 'classid', 'archive',
'profile', 'manifest', 'itemid',
'dynsrc', 'lowsrc'
],
allowProtocolRelative: true,
enforceHtmlBoundary: false
};Additionally, a flaw in the srcset parser logic was corrected. The original parser evaluated imagesrcset values using the string literal 'srcset' in the inner loop, preventing correct validation of imagesrcset schemes. The patch resolved this by passing the variable attribute name a dynamically to naughtyHref(a, value.url) rather than a hardcoded string.
Exploitation of CVE-2026-53606 is straightforward and relies on typical input vectors where standard attributes are allowed by the target application configuration. For instance, if an application supports custom form generation and allows users to define <form> or <button> elements, an attacker can input an executable javascript payload within the form's target action.
An attacker crafts a payload utilizing the <form> tag and the action attribute. When parsed by vulnerable versions of sanitize-html configured to allow <form> and action, the input <form action="javascript:alert(document.cookie)"> remains unmodified. When a user interacts with the form or submits it, the browser parses the javascript: pseudo-protocol and executes the trailing payload within the context of the vulnerable origin.
Another highly effective vector targets the <object> element using the data attribute. If the sanitizer configuration allows the <object> tag with the data attribute, an attacker can supply <object data="javascript:alert(document.domain)"></object>. The browser automatically parses and executes the data property upon loading the element in the DOM, executing the script with no user interaction required.
The security impact of CVE-2026-53606 is characterized by Stored Cross-Site Scripting (XSS). Because the input parsing is executed server-side prior to database entry, malicious payloads are stored statically. Any user requesting the associated page or rendering the sanitized output executes the injected JavaScript code immediately in their browser context.
An attacker can exploit this flaw to execute arbitrary client-side code, hijack user sessions, steal session identifiers stored in non-HttpOnly cookies, and capture local storage tokens. In a scenario involving administrator victims, this execution path can lead to administrative panel compromise, configuration modification, or full application takeover.
The vulnerability is assessed with a CVSS v3.1 score of 5.4. While the attack complexity is low and exploitation is straightforward, it requires some user interaction and relies on custom configurations where specific attributes are allowed. The scope is changed because execution bypasses the sandbox constraints of the HTML parser and impacts the broader browser context.
The primary remediation for this vulnerability is upgrading sanitize-html to version 2.17.5 or above. This release updates the default allowedSchemesAppliedToAttributes array to include all standard HTML5 URI-accepting attributes, mitigating the vector without requiring manual configuration adjustments.
If immediate dependency updates are not possible due to deployment freezes, a manual configuration override serves as an effective hotfix. Developers must explicitly define the expanded allowedSchemesAppliedToAttributes array when initializing the sanitizer in their application code, replicating the updated list of 20 attributes introduced in the official patch.
// Remediation Hotfix Workaround
const sanitizeHtml = require('sanitize-html');
const cleanHtml = sanitizeHtml(dirtyInput, {
allowedSchemesAppliedToAttributes: [
'href', 'src', 'cite',
'action', 'formaction', 'data', 'xlink:href',
'poster', 'background', 'ping',
'longdesc', 'usemap', 'codebase', 'classid', 'archive',
'profile', 'manifest', 'itemid',
'dynsrc', 'lowsrc'
]
});The fix is complete regarding standard attributes defined in modern browser execution environments. However, applications should also deploy a robust Content Security Policy (CSP) to restrict inline script execution and limit connections to trusted domains, providing defense-in-depth protection against parsing or sanitization bypasses.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
sanitize-html ApostropheCMS | < 2.17.5 | 2.17.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Severity | Medium (5.4) |
| EPSS Score | 0.00136 |
| Impact | Stored Cross-Site Scripting (XSS) |
| Exploit Status | Proof of Concept |
| 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.
Vikunja versions 2.3.0 through 2.6.0 contain an insufficient session expiration vulnerability (CWE-613) within the WebSocket authentication handler. Although Vikunja enforces server-side session tracking and revocation for REST API routes, the WebSocket handshake handler validates cryptographic JWT signatures without querying the database session state. Consequently, revoked JWT tokens can establish new real-time WebSocket connections, and existing connections persist after session revocation.
A cross-tenant boundary breach vulnerability in Vikunja allows an authenticated user to trigger global task position recalculations across all tenant instances by creating a saved filter with an empty filter string payload.
An access revocation flaw in Vikunja allows removed collaborators to retain outbound webhooks and link shares created prior to revocation, enabling persistent exfiltration of sensitive task data.
Vikunja v2.6.0 contains a permission inheritance regression in pkg/models/project_access.go where explicit down-restrictions on sub-projects are overridden by higher parent project permissions due to MAX aggregation across project tree nodes.
An authorization bypass vulnerability in Vikunja's link share deletion handlers allows project members with Write privileges to delete Admin-tier link shares. The handler passes an unpopulated struct to the authorization check, causing the permission evaluation to fall back to default Write permissions instead of requiring Admin privileges.
Vikunja versions 2.2.0 through 2.6.0 contain a Cross-Origin Resource Sharing (CORS) misconfiguration flaw in `code.vikunja.io/api`. Default configurations permit wildcard origins for localhost (`http://127.0.0.1:*` and `http://localhost:*`) with credentialed requests (`Access-Control-Allow-Credentials: true`). Because configuring a public service URL appends to this default list rather than overriding it, production environments inadvertently trust all local origins. A local page or application on a user's machine can execute a credentialed cross-origin request to the token refresh endpoint, extract the returned JWT access token, and achieve complete account takeover.