---
name: evolution_multiselect_audit
description: "multiselect.js widget security+fragility audit; XSS/eval/SQLi fixes applied, living doc"
metadata: 
  node_type: memory
  type: project
  originSessionId: 0af8ba17-d0fa-48dc-84ce-7a6b2b46e509
  modified: 2026-07-24T01:53:21.843Z
---

Security + fragility audit of the custom `assets/js/multiselect.js` widget (~3040 lines, hand-written, no build/tests) and its AJAX data source `app/ajax/selectFilter.php`. Living doc: `evolution/audits/MULTISELECT_AUDIT.md`.

Root data-flow risk: `selectFilter.php` returns raw DB columns via `json_encode` (JSON-safe, NOT HTML/JS-safe); the widget renders them via `innerHTML` and, historically, spliced them into `eval` strings.

Applied 2026-07-24 (working tree, NOT committed, not exercised live):
- **S1** stored XSS: single-column render `htmlText += options[ctr].text` now `.toHtmlEntities()`-escaped (matches multi-column path; helper in `global.js:2221`).
- **S2** code-exec: removed 4 server-data→`eval('… = `'+v+'`')` sinks in `updateOptionAtIndex` (~:2368) + `getMultiColumnReturnText` builder (~:2786) → bracket notation.
- **S5** SQLi: removed dead+injectable raw `$_REQUEST['search']` query in `filterquotes` (`selectFilter.php` ~:814); `$quote` was assigned-never-read.

Still OPEN: S1 residuals (`data-value` attr breakout + `allowOtherItems` self-XSS — left because data-value is round-tripped through `querySelector([data-value=…])` matching, re-encoding would desync selection); S3 broad `eval()` (33 sites → bracket notation); S4 `setTimeout(string)` at :513/:696; §2 fragility (`|| true` at :194, debug sentinel `optionIndex=9197802279`, sibling traversal, implicit globals, timing band-aids, duplicated render blocks). No minified twin in evolution (unlike [[portal_minified_no_build]]).

Relates to [[evolution_sort_injection_validator]], [[evolution_getfieldarray_deprecated]], [[plugin_security_review]].
