]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Sanitizer: block data:/vbscript: URLs (XSS hardening) (#42549)
authorMark Otto <markd.otto@gmail.com>
Sun, 28 Jun 2026 02:29:41 +0000 (19:29 -0700)
committerGitHub <noreply@github.com>
Sun, 28 Jun 2026 02:29:41 +0000 (19:29 -0700)
* Sanitizer: block data:/vbscript: URLs in allowed attributes

The sanitizer's SAFE_URL_PATTERN only rejected javascript:, so a
data:text/html (or vbscript:) URL in an href/src passed the allowList —
an XSS vector via data-bs-title/data-bs-content. Reject data: and
vbscript: in SAFE_URL_PATTERN and re-allow only safe base64 image/video/
audio data URLs via a restored DATA_URL_PATTERN. Fixes #42443.

* Bump bundlewatch size thresholds

* Sanitizer: drop redundant 0-9 from base64 data-URI char class

\d already matches 0-9, so the explicit 0-9 in the same class was dead
weight. Functionally identical; clears the CodeQL overly-permissive-range
alert on the overlap.

* Build: bump bundle.js bundlewatch threshold for sanitizer additions

.bundlewatch.config.json
js/src/util/sanitizer.js
js/tests/unit/util/sanitizer.spec.js

index 8986f6b95373ca0aa52b31a22256ada237b28353..e8991a633756aef53536751bc185a6da991368e9 100644 (file)
@@ -34,7 +34,7 @@
     },
     {
       "path": "./dist/js/bootstrap.bundle.js",
-      "maxSize": "83.5 kB"
+      "maxSize": "83.75 kB"
     },
     {
       "path": "./dist/js/bootstrap.bundle.min.js",
index cb33633ffd39d99e79b3865443baea1df7ad2cc7..f0482eb94ed97ab5c91f044d88575ce897f3d13c 100644 (file)
@@ -63,14 +63,22 @@ const uriAttributes = new Set([
  *
  * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
  */
-const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
+const SAFE_URL_PATTERN = /^(?!(?:javascript|data|vbscript):)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i
+
+/**
+ * A pattern that matches safe data URLs. Only matches image, video and audio
+ * types — notably NOT `data:text/html`, which is an XSS vector.
+ *
+ * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L49
+ */
+const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z=]+$/i
 
 const allowedAttribute = (attribute, allowedAttributeList) => {
   const attributeName = attribute.nodeName.toLowerCase()
 
   if (allowedAttributeList.includes(attributeName)) {
     if (uriAttributes.has(attributeName)) {
-      return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue))
+      return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue))
     }
 
     return true
index 2b21ef2e19672fe4878347b8a8da90f7870b2139..ccf5c5cf6dd4e5e94a42f4bcfe4e9c1ba1899c74 100644 (file)
@@ -67,7 +67,13 @@ describe('Sanitizer', () => {
         'jav\u0000ascript:alert();'
       ]
 
-      for (const url of invalidUrls) {
+      const dangerousDataUrls = [
+        'data:text/html,hello',
+        'data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==',
+        'vbscript:msgbox(1)'
+      ]
+
+      for (const url of [...invalidUrls, ...dangerousDataUrls]) {
         const template = [
           '<div>',
           `  <a href="${url}">Click me</a>`,