fix(settings-ui): reject empty normalized search queries #50531

Open
niels9001 opened 1:21pm on September 11, 2026 wants to merge 201 Ξ” into microsoft/powertoys main from
niels9001-unicode-search-safety
Diff Delta:
201
About 71 Diff Delta/hour
Classified as:  Feature Add

niels9001's Description of Work

Summary of the Pull Request

Prevent Settings search from passing queries that normalize to empty or whitespace-only text into fuzzy matching, and make the shared matcher return its existing no-match result for whitespace-only queries.

PR Checklist


  • [x] Communication: Scoped to the requested atomic Settings search-safety audit follow-up; submitted as a draft.

  • [x] Tests: Added focused regression coverage; all 46 selected tests pass.

  • [x] Localization: No new user-facing strings; normalization and existing localized search behavior are unchanged.

Issue closure, development/user documentation, and new-binary checklist items are N/A. No new projects, dependencies, binaries, or persisted settings changes.

Detailed Description of the Pull Request / Additional comments

Trigger: A query consisting of U+00A8 (spacing diaeresis) passes the initial whitespace check in src\settings-ui\Settings.UI\Services\SearchIndexService.cs. FormKD normalization decomposes it into U+0020 plus a combining mark; removing nonspacing marks leaves a single space. Previously, src\common\Common.Search\FuzzSearch\StringMatcher.cs checked only for null/empty input before trimming, then indexed the first element of an empty token array. With a populated search index, the resulting IndexOutOfRangeException can propagate through Parallel.ForEach to the live search handler, which catches cancellation only. This is a source/unit-test finding, not a claim of a reproduced live application crash.

The Settings service now returns no results after normalization when no searchable text remains. The shared matcher rejects null/empty/whitespace queries before its per-start-index matching loop, preventing empty trimmed/tokenized input from reaching the indexed access. Both use existing no-match behavior rather than adding an exception catch.

Shared-caller risk: Common.Search is also used by Shortcut Guide and the Command Palette PowerToys extension. Their existing blank-query handling remains unchanged, as do the matching algorithm, separators, scoring, highlighting, and case options for nonempty queries. No public API signature changes. NormalizeString becomes internal solely for direct coverage through the existing Settings test friend assembly.

Tests are in the existing src\settings-ui\Settings.UI.UnitTests\Search\StringMatcherTests.cs and src\settings-ui\Settings.UI.UnitTests\Search\SearchIndexServiceTests.cs; there is no standalone Common.Search test project on current main. The service fixture supplies a nonempty in-memory index, restores it afterward, and does not load Settings resources or change user settings.

This PR deliberately excludes expander-result filtering and prebuilt search-index generation/embedding changes. It does not auto-close the different search-crash reports #41817 or #47118.

Validation Steps Performed


  • Ran the repository first-build prerequisite tools\build\build-essentials.cmd -Platform x64 -Configuration Release after the fresh worktree reported missing NuGet assets; restore and essentials completed successfully.

  • Built Settings.UI.UnitTests in Release x64 using tools\build\build.ps1 with the worktree SolutionDir; exit code 0.

  • Ran the existing Microsoft.Testing.Platform runner with --filter "FullyQualifiedName~Settings.UI.UnitTests.Search" --minimum-expected-tests 46 --report-trx: 46 passed, 0 failed, 0 skipped.

  • Covered null/empty/ASCII whitespace, Unicode spaces, U+00A8 compatibility normalization, combining-only queries, both Search overloads with a nonempty index, both case options, ordinary/padded/multiword queries, unchanged scores/highlights, exact-match ranking, and accented/fullwidth normalization.

  • Did not launch or modify the installed/running PowerToys instance or user settings.

4 total changed files
Loading changes...
Loading changes...
Loading changes...
Loading changes...
You’ve reached the end
Approve, request changes, or stare into the void (reviewer's choice) πŸ˜