In index.html, three places build DOM with innerHTML and interpolate exercise-derived strings unescaped:
- Instruction step text (
step in the modal): li.innerHTML = \<span class="step-num">${i + 1}<span class="step-text">${step}`;`
- Modal meta chip value (body part / equipment / target):
chip.innerHTML = \<span class="meta-chip-label">${label}<span class="meta-chip-value">${value}`;`
- Active filter badge:
badge.innerHTML = \${val}<button ... data-value="${val}" aria-label="Remove ${val}">×`;`
Any exercise field containing <, >, &, or quotes would render as HTML — a step like <img src=x onerror=...> executes. The same code paths break rendering for benign text with & or <.
The fix is to build the nodes with textContent (same pattern already used elsewhere in the file for card-name, muscle-tag, etc.).
In
index.html, three places build DOM withinnerHTMLand interpolate exercise-derived strings unescaped:stepin the modal):li.innerHTML = \<span class="step-num">${i + 1}<span class="step-text">${step}`;`chip.innerHTML = \<span class="meta-chip-label">${label}<span class="meta-chip-value">${value}`;`badge.innerHTML = \${val}<button ... data-value="${val}" aria-label="Remove ${val}">×`;`Any exercise field containing
<,>,&, or quotes would render as HTML — a step like<img src=x onerror=...>executes. The same code paths break rendering for benign text with&or<.The fix is to build the nodes with
textContent(same pattern already used elsewhere in the file forcard-name,muscle-tag, etc.).