Skip to content

test(): preserve raw option values with secure text - #482

Merged
gnbm merged 6 commits into
sa-si-dev:masterfrom
oiahoon:fix/preserve-secure-option-values
Aug 10, 2026
Merged

test(): preserve raw option values with secure text#482
gnbm merged 6 commits into
sa-si-dev:masterfrom
oiahoon:fix/preserve-secure-option-values

Conversation

@oiahoon

@oiahoon oiahoon commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Issue number: resolves #455


What is the current behavior?

When enableSecureText is enabled, option values are converted to HTML entities while options are prepared. A later dataset update preserves that entity string, so selecting a value such as < writes ["&lt;"] to the hidden form input.

What is the new behavior?

  • keep option values in their original form for selection, API, and form submission
  • escape values only when interpolating them into option HTML attributes
  • preserve secure label rendering and attribute-injection protection
  • cover existing and dynamically added values with Cypress regression tests

Does this introduce a breaking change?

  • Yes
  • No

Other information

  • npm run build
  • npm run validate
  • complete Cypress suite: 237 passing tests across 8 specs
  • npm pack --dry-run
  • browser verification on desktop and 390x844: hidden input contains ["<"], label renders as text, no injected image, no horizontal overflow, and no console errors

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an encoding bug when enableSecureText is enabled by preserving raw option values for selection/API/form submission while escaping values only at HTML attribute interpolation time (preventing attribute-breakout injection).

Changes:

  • Store option value in its original (raw) form during option preparation and when creating/updating “new” options.
  • Escape data-value only when generating option HTML, keeping attribute-injection protection while avoiding double-encoding in state/dataset updates.
  • Add Cypress regression coverage for existing unsafe-character values and dynamically added (new) values; update dist/docs bundles accordingly.

Reviewed changes

Copilot reviewed 3 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/virtual-select.js Keeps raw values in option data, escapes only when interpolating data-value into HTML.
dist/virtual-select.js Built bundle update reflecting the raw-value + attribute-escape behavior.
dist/virtual-select.min.js Minified bundle update reflecting the same behavior.
docs/assets/virtual-select.js Docs bundle update reflecting the raw-value + attribute-escape behavior.
docs/assets/virtual-select.min.js Minified docs bundle update reflecting the same behavior.
cypress/e2e/secure-option-values.cy.ts Cypress regression tests for raw hidden-input values and XSS-safe rendering.

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

@@ -0,0 +1,58 @@
describe('Secure option values', () => {
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

PR Test Results — ✅ all checks passed

Check Result Time
Typecheck 2s
ESLint 2s
Stylelint 1s
CI Scripts 1s
Build 3s
a11y-aria-label.cy.ts ✅ 10/10 5s
a11y-close-clears-highlight.cy.ts ✅ 7/7 5s
a11y-escape-close.cy.ts ✅ 6/6 2s
a11y-listbox-multiselectable.cy.ts ✅ 3/3 1s
a11y-live-region.cy.ts ✅ 18/18 6s
a11y-reduced-motion.cy.ts ✅ 3/3 1s
a11y-required-error.cy.ts ✅ 20/20 4s
a11y-search-arrow-navigation.cy.ts ✅ 13/13 7s
a11y-select-all.cy.ts ✅ 7/7 3s
a11y-server-search-announcements.cy.ts ✅ 6/6 4s
a11y-target-size.cy.ts ✅ 5/5 2s
build-stylesheet-integrity.cy.ts ✅ 4/4 1s
examples.cy.ts ✅ 219/219 1m43s
observer-listener-lifecycle.cy.ts ✅ 7/7 1s
perf-resize-throttle.cy.ts ✅ 3/3 1s
perf-scroll-aria.cy.ts ✅ 6/6 4s
secure-option-values.cy.ts ✅ 2/2 1s
secure-text-warning.cy.ts ✅ 4/4 1s
security-ampersand-storage.cy.ts ✅ 16/16 3s
security-chrome-label-props.cy.ts ✅ 10/10 1s
security-classnames-xss.cy.ts ✅ 2/2 1s
security-customdata-xss.cy.ts ✅ 2/2 1s
security-global-defaults.cy.ts ✅ 11/11 1s
security-hidden-input-name.cy.ts ✅ 7/7 1s
security-proto-value.cy.ts ✅ 9/9 1s
security-quote-escaping.cy.ts ✅ 12/12 2s
tag-tooltip-overflow.cy.ts ✅ 6/6 11s
timer-cleanup.cy.ts ✅ 2/2 1s

Tested commit: d53f3e6 · Run #20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

Comment thread docs/assets/virtual-select.js Outdated
gnbm added 3 commits August 10, 2026 11:34
Both sides had independently fixed sa-si-dev#455 (option values reaching the form
HTML-encoded), so all four conflicts in src/virtual-select.js asked the same
question: is the option value escaped? Every one is resolved to master's
side, which is a superset of this branch's fix.

- data-value uses Utils.escapeAttributeValue() rather than this.secureText().
  secureText() deliberately does not escape quotes and is a no-op when
  enableSecureText is off, so this branch's version still let a value such as
  `x" data-pwned="1` break out of the attribute in both modes.
- setOptions() and getOptionObj() keep master's rawValue/rawLabel/
  rawDescription locals; the already-merged code below both hunks refers to
  them, so this branch's naming would not have compiled.
- addNewOption() differed only by an explanatory comment.

src/virtual-select.js is now identical to master's. This branch's regression
test (cypress/e2e/secure-option-values.cy.ts) is untouched, and the issue's
own repro now yields ["<"] in the hidden input instead of ["&lt;"].

Build files are left out per the pull request rule in .github/README.md.
.github/README.md asks that build files not be committed in a PR - they are
generated before a release only. fcaaa8a shipped the rebuilt bundle along with
its source fix, and the earlier merge already resolved the minified artifacts
back to master's copies, leaving dist/virtual-select.js and
docs/assets/virtual-select.js as the last two carrying branch-local output.

Both are restored to master's version, so this branch now changes no build
files at all. The bundle is regenerated at release time from the source change
that is still here.
@gnbm

gnbm commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this, @oiahoon — your diagnosis on #455 was spot on, and the shape of the fix you proposed is the one that shipped.

Heads up on what happened while this was open: the same root cause was fixed independently on master in #485, as part of a wider accessibility/security pass. After merging master into this branch, the source change here turns out to be fully redundant — src/virtual-select.js is now identical to master's, and the net diff of this PR is just your regression test.

Two notes on the merge, so nothing looks like it quietly went missing:

  • data-value escaping. Your version used this.secureText(d.value); the merge kept master's Utils.escapeAttributeValue(). The reason is that secureText() deliberately leaves quotes alone, and is a no-op when enableSecureText is false — so a value like x" data-pwned="1 could still break out of the attribute, in both modes. escapeAttributeValue() escapes & and then ", which is what a quoted attribute needs and round-trips exactly.
  • cypress/e2e/secure-option-values.cy.ts stays. It is the one thing master did not already have, and it is a genuinely useful regression guard — it is the reason this PR is still worth landing. Your original repro passes against the merged branch: the hidden input reads ["<"].

One housekeeping note, no action needed from you: .github/README.md asks that build files not be committed in a PR, and fcaaa8a carries rebuilt dist/ and docs/assets/ output. Those are reverted on the merge branch, so nothing build-related lands; the bundle is regenerated at release time.

Since the source fix arrived by another route, this is now effectively a test-only PR — you may want to retitle it to match. You are credited for #455 in the 1.4.0 release notes alongside #485.

@gnbm gnbm changed the title fix: preserve raw option values with secure text test(): preserve raw option values with secure text Aug 10, 2026

@gnbm gnbm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@gnbm
gnbm merged commit e4ead5e into sa-si-dev:master Aug 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Values become HTML-encoded

3 participants