Skip to content

Sanitize HTML icon options in Chips and NavOverflow - #42805

Merged
mdo merged 5 commits into
twbs:v6-devfrom
aljojoby9:fix/sanitize-icon-html-options
Aug 11, 2026
Merged

Sanitize HTML icon options in Chips and NavOverflow#42805
mdo merged 5 commits into
twbs:v6-devfrom
aljojoby9:fix/sanitize-icon-html-options

Conversation

@aljojoby9

Copy link
Copy Markdown

Description

Chips and NavOverflow accept HTML for their icon options, and those values can also come from the data API. They were written straight into the DOM with innerHTML / string templates, so a crafted dismissIcon, moreIcon, moreText, menuPlacement, or [data-bs-overflow-icon] value could inject markup.

  • Route dismissIcon, moreIcon, and [data-bs-overflow-icon] HTML through a new DefaultIconAllowlist in the sanitizer (covers the default SVGs and common icon markup)
  • Build the NavOverflow overflow toggle with DOM APIs instead of an HTML string template
  • Insert moreText with textContent and set menuPlacement with setAttribute
  • Document the icon allowlist and note sanitization on the Chips / NavOverflow option tables
  • Add unit coverage for the XSS-style payloads and for the default SVG icons still rendering

Checklist

dismissIcon, moreIcon, and [data-bs-overflow-icon] markup were inserted
via innerHTML without going through the sanitizer. moreText and
menuPlacement were also interpolated into HTML string templates.

Route icon HTML through a dedicated DefaultIconAllowlist, insert
moreText with textContent, and build the NavOverflow toggle with DOM
APIs so those values cannot break out of their slots.
@aljojoby9
aljojoby9 requested a review from a team as a code owner August 6, 2026 19:14
Copilot AI lite review requested due to automatic review settings August 6, 2026 19:14
The icon allowlist and sanitizer calls in Chips and NavOverflow push the
gzipped min builds a few dozen bytes over the previous caps.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces XSS risk in Bootstrap v6 by sanitizing icon HTML options in Chips and NavOverflow.

Changes:

  • Add a new DefaultIconAllowlist for icon-related HTML sanitization.
  • Replace NavOverflow toggle string templates with DOM API construction, and insert moreText as plain text.
  • Add docs and unit tests for XSS-style payloads and for default SVG icon rendering.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.

Show a summary per file
File Description
site/src/content/docs/getting-started/javascript.mdx Document the icon allowlist and clarify sanitizer scope.
site/src/content/docs/forms/chips.mdx Note that dismissIcon is sanitized before DOM insertion.
site/src/content/docs/components/nav-overflow.mdx Note that moreText is plain text and moreIcon / markup icons are sanitized.
js/tests/unit/util/sanitizer.spec.js Add tests for DefaultIconAllowlist SVG retention and XSS stripping.
js/tests/unit/nav-overflow.spec.js Add tests for moreText, moreIcon, menuPlacement, and markup icon sanitization.
js/tests/unit/chips.spec.js Add tests for default dismiss SVG retention and dismissIcon sanitization (JS and data API).
js/src/util/sanitizer.ts Introduce DefaultIconAllowlist and expose it for docs and components.
js/src/nav-overflow.ts Build the overflow toggle with DOM APIs and sanitize icon HTML before insertion.
js/src/chips.ts Sanitize dismissIcon HTML before inserting it into the dismiss button.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mdo mdo added this to v6.0.0 Aug 10, 2026
@github-project-automation github-project-automation Bot moved this to Inbox in v6.0.0 Aug 10, 2026
mdo and others added 2 commits August 10, 2026 11:04
Clarified the explanation of components that can render or accept HTML, emphasizing the use of the built-in content sanitizer to prevent XSS attacks.
The icon allowlist uses the lowercased SVG attribute name that the
sanitizer matches on, so cspell flagged it as unknown.
daltino

This comment was marked as low quality.

Document that viewbox is lowercased on purpose for attribute matching,
and drop <use> so icon HTML cannot load external SVG fragments via href.
@aljojoby9

Copy link
Copy Markdown
Author

Thanks for the review.

viewbox casing: intentional. allowedAttribute compares attribute.nodeName.toLowerCase() against the allowlist, so the entry has to be lowercased. I added a short comment next to the allowlist entry and a unit assertion that viewBox survives sanitization for the default icon markup.

<use href>: good catch. Even with URI checks, SAFE_URL_PATTERN still allows external https: URLs, so external SVG fragment loads were possible. I removed use from DefaultIconAllowlist in 6781afc and added a test that strips external <use>. Apps that need sprite references can extend the allowlist deliberately.

@mdo
mdo merged commit e268909 into twbs:v6-dev Aug 11, 2026
12 checks passed
mdo added a commit that referenced this pull request Aug 11, 2026
The #42805 XSS specs still put data-bs-toggle on the nav itself. Point
them at the wrapper markup. Raise the minified JS caps by a quarter
kilobyte for the wrapper path growth.
mdo added a commit that referenced this pull request Aug 11, 2026
* Make nav overflow a wrapper around the nav

The component measured and observed the same .nav that it collapsed, so each
pass changed the width that produced it. Where the nav width tracked its
content, the two states straddled the collapseBelow threshold and the
ResizeObserver ran hundreds of times a second. That read as items that never
collapse, plus flicker on load.

The nav now lives inside a .nav-overflow wrapper that carries the data
attribute. The component measures and observes the wrapper, which its own output
cannot change. Inline-size containment on the wrapper closes the last path back
in: the min-content width of the items otherwise leaks through a flex ancestor
and widens the wrapper whenever the items are visible.

Items no longer shrink, so the measured widths are the widths the nav really
needs, and the nav gap is counted instead of a 10px fudge.

Fixes #42639

* Let moreText be false for an icon-only overflow toggle

An empty string already hid the label, but it left an empty element behind and
took the accessible name of the button with it. false says the intent, writes no
text element, and falls back to aria-label="More" so the button keeps a name.
An empty string now behaves the same way.

Closes #42639

* Use an English label in the custom toggle example

* Fix sanitizer specs for the wrapper API and bump JS budgets

The #42805 XSS specs still put data-bs-toggle on the nav itself. Point
them at the wrapper markup. Raise the minified JS caps by a quarter
kilobyte for the wrapper path growth.

* Document the nav overflow wrapper in the migration guide
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Inbox

Development

Successfully merging this pull request may close these issues.

4 participants