Describe the bug
When renderOptionLabelAsHtml or useSelectOptionLabelToHtml is enabled, multiple-select-vanilla renders option-controlled content with innerHTML.
If no sanitizer option is provided, the value is inserted directly into innerHTML. When useSelectOptionLabelToHtml is enabled, the selected value displayed in the main choice button is passed through stripScripts() first, but that helper is based on a blacklist regex /(\b)(on[a-z]+)(\s*)=([^>]*)|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script(>*)|(<|<)(\/*)(script|script defer)(.*)(>|>|>">)/gi and does not provide complete HTML sanitization.
This can lead to XSS when an application enables HTML rendering and the option text or value can contain untrusted content.
Relevant code paths:
MultipleSelectInstance.isRenderAsHtml returns true when renderOptionLabelAsHtml or useSelectOptionLabelToHtml is true.
applyAsTextOrHtmlWhenEnabled(...) writes to innerHTML when HTML rendering is enabled.
useSelectOptionLabelToHtml uses selected option values in the main choice button and calls stripScripts(labels) before writing the result to innerHTML.
stripScripts(...) uses a regex blacklist for sanitizing.
Reproduction
Minimal HTML reproduction:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>multiple-select-vanilla XSS reproduction</title>
<script type="module">
import { multipleSelect } from 'https://cdn.jsdelivr.net/npm/multiple-select-vanilla@5.1.0/+esm';
window.addEventListener('DOMContentLoaded', () => {
multipleSelect('.multiple-select', {
data: [
{
value: '<strong style="color: green">Safe HTML value</strong>',
text: '1. Safe HTML example'
},
{
value: '<img src="x" onerror="alert(`This should be removed by stripScripts`)">Blocked by stripScripts',
text: '2. Payload blocked by stripScripts'
},
{
value: '<iframe srcdoc="<script>alert(\'XSS\')\n<\/script>"></iframe>',
text: '3. Payload that bypasses stripScripts and executes'
}
],
filter: true,
placeholder: 'Select an option',
useSelectOptionLabelToHtml: true
// No sanitizer is configured.
});
});
</script>
</head>
<body>
<select class="multiple-select" multiple></select>
</body>
</html>
Steps:
- Save the file as an HTML file and open it in a browser.
- Open the select dropdown.
- Select
1. Safe HTML example. The main choice button renders the safe <strong> HTML value.
- Select
2. Payload blocked by stripScripts. The selected value contains an onerror event handler. stripScripts() removes the event-handler payload, so this example demonstrates a case that is successfully filtered by the current regex.
- Select
3. Payload that bypasses stripScripts and executes. The selected value contains an iframe srcdoc payload. The value is passed through stripScripts(), but this payload is not reliably removed by the regex-based filter and can execute script when inserted through innerHTML.
Observed result:
The reproduction shows three outcomes in the same configuration:
- safe HTML is rendered as intended;
- a simple event-handler payload is removed by
stripScripts();
- a different HTML payload bypasses
stripScripts() and can execute script.
This demonstrates that stripScripts() is not a reliable security boundary even though it blocks some common payloads.
Notes:
- The issue is not present in the default text-only rendering mode.
- The issue is mitigated when users provide a strict sanitizer such as DOMPurify.
- The concern is that enabling HTML rendering without a sanitizer is unsafe, and
stripScripts() may give a false impression of protection because it is a regex blacklist and can be bypassed.
Expectation
Possible fixes or mitigations:
- Do not use
innerHTML unless a sanitizer is explicitly provided.
- Make
sanitizer required when renderOptionLabelAsHtml or useSelectOptionLabelToHtml is enabled.
- Remove
stripScripts() as a security boundary, or document clearly that it is not a sanitizer.
- Consider warning in development mode when HTML rendering is enabled without
sanitizer.
Environment Info
System:
OS: Windows 11 10.0.26200
CPU: (8) x64 Intel(R) Core(TM) i5-10300H CPU @ 2.50GHz
Memory: 64 GB
Binaries:
Node: 24.11.0
npm: 11.6.1
Browsers:
Edge: Chromium (134.0.3124.51)
npmPackages:
multiple-select-vanilla: 5.1.0
Validations
Describe the bug
When
renderOptionLabelAsHtmloruseSelectOptionLabelToHtmlis enabled,multiple-select-vanillarenders option-controlled content withinnerHTML.If no
sanitizeroption is provided, the value is inserted directly intoinnerHTML. WhenuseSelectOptionLabelToHtmlis enabled, the selected value displayed in the main choice button is passed throughstripScripts()first, but that helper is based on a blacklist regex/(\b)(on[a-z]+)(\s*)=([^>]*)|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script(>*)|(<|<)(\/*)(script|script defer)(.*)(>|>|>">)/giand does not provide complete HTML sanitization.This can lead to XSS when an application enables HTML rendering and the option
textorvaluecan contain untrusted content.Relevant code paths:
MultipleSelectInstance.isRenderAsHtmlreturns true whenrenderOptionLabelAsHtmloruseSelectOptionLabelToHtmlis true.applyAsTextOrHtmlWhenEnabled(...)writes toinnerHTMLwhen HTML rendering is enabled.useSelectOptionLabelToHtmluses selected option values in the main choice button and callsstripScripts(labels)before writing the result toinnerHTML.stripScripts(...)uses a regex blacklist for sanitizing.Reproduction
Minimal HTML reproduction:
Steps:
1. Safe HTML example. The main choice button renders the safe<strong>HTML value.2. Payload blocked by stripScripts. The selected value contains anonerrorevent handler.stripScripts()removes the event-handler payload, so this example demonstrates a case that is successfully filtered by the current regex.3. Payload that bypasses stripScripts and executes. The selected value contains aniframe srcdocpayload. The value is passed throughstripScripts(), but this payload is not reliably removed by the regex-based filter and can execute script when inserted throughinnerHTML.Observed result:
The reproduction shows three outcomes in the same configuration:
stripScripts();stripScripts()and can execute script.This demonstrates that
stripScripts()is not a reliable security boundary even though it blocks some common payloads.Notes:
stripScripts()may give a false impression of protection because it is a regex blacklist and can be bypassed.Expectation
Possible fixes or mitigations:
innerHTMLunless asanitizeris explicitly provided.sanitizerrequired whenrenderOptionLabelAsHtmloruseSelectOptionLabelToHtmlis enabled.stripScripts()as a security boundary, or document clearly that it is not a sanitizer.sanitizer.Environment Info
Validations