Fix stored XSS in retune modal - #44
Conversation
retuneSong() built the retune-progress modal via innerHTML template literals that interpolated the song title, target tuning, and the websocket's echoed filename/error text without HTML-escaping. A song title is attacker-influenced (e.g. crafted metadata in an imported Guitar Pro / MusicXML / sloppak file), and the "Convert to E Standard" library-card action (card-actions-core.js) passes song.title straight into this function — so a malicious title like `<img src=x onerror=...>` executes as stored XSS the moment the modal renders, before any network round-trip. Since the app has no auth model (single-user, all endpoints trusted-local per the constitution), script execution here means full read/write access to the library via the same-origin API. Fix: route all four interpolated values through the existing esc() helper (already imported from ./js/dom.js in this file and used elsewhere), matching the escaping discipline used by every other innerHTML template in the codebase.
VERSION isn't hand-bumped here — per this repo's CLAUDE.md it's auto-synced from feedBack-desktop's release job via repository_dispatch, not touched by feature/fix PRs. CHANGELOG.md's Unreleased section is the part PRs are expected to keep current. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018J1NvPtPQZd3cEt6aAbGzG
✅ Action performedReview finished.
|
|
The CI failures may require manual intervention. View agent analysis |
|
Oops, something went wrong! Please try again later. 🐰 💔 |
…ng headers Three MEDIUM issues documented (not fixed) during the security audit that produced PR #44, now fixed: - XML entity-expansion ("billion laughs") DoS (#45): xml.etree.ElementTree has no built-in protection on untrusted input. Added lib/safe_xml.py, a shared hardened-parse helper using defusedxml (now a requirements.txt dependency; falls back to stdlib with a logged warning if absent), and switched every untrusted-XML parse call site (gp2rs_gpx.py, loosefolder.py, song.py, routers/ws_highway.py) to use it. Rejections normalize to ET.ParseError so existing except clauses keep working. - No decompressed-size cap on sloppak zip extraction (#46): _unpack_zip() now sums each member's declared size against an 8 GB default cap before extracting (no decompression needed to check it), aborting and cleaning up any partial extraction if exceeded. FEEDBACK_SLOPPAK_MAX_UNPACK_MB overrides (0 disables), independent of the unpack-cache LRU eviction which only bounds the aggregate cache after the fact. - No security headers (#47): added X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and a Content-Security-Policy to every response. script-src/style-src keep 'unsafe-inline' since the v3 UI's inline onclick="..." attributes and inline <script>/<style> blocks aren't nonce'd or externalized — but non-self/non-https origins are blocked, meaningful defense-in-depth against the retune-modal XSS class (PR #44) even before that fix landed. Regression tests added for all three (tests/test_safe_xml.py, tests/test_sloppak_unpack_size_cap.py, tests/test_security_headers.py) — verified failing against pre-fix code, passing after. Full existing suite (2801 tests, minus the pre-existing midiutil-blocked module) passes unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018J1NvPtPQZd3cEt6aAbGzG
…ders Three MEDIUM issues documented (not fixed) during the security audit that produced PR #44, now fixed: - XML entity-expansion ("billion laughs") DoS (#45): xml.etree.ElementTree has no built-in protection on untrusted input. Added lib/safe_xml.py, a shared hardened-parse helper using defusedxml (now a requirements.txt dependency; falls back to stdlib with a logged warning if absent), and switched every untrusted-XML parse call site (gp2rs_gpx.py, loosefolder.py, song.py, routers/ws_highway.py) to use it. Rejections normalize to ET.ParseError so existing except clauses keep working. - No decompressed-size cap on sloppak zip extraction (#46): _unpack_zip() now sums each member's declared size against an 8 GB default cap before extracting (no decompression needed to check it), aborting and cleaning up any partial extraction if exceeded. FEEDBACK_SLOPPAK_MAX_UNPACK_MB overrides (0 disables), independent of the unpack-cache LRU eviction which only bounds the aggregate cache after the fact. - No security headers (#47): added X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and a Content-Security-Policy to every response. script-src/style-src keep 'unsafe-inline' since the v3 UI's inline onclick="..." attributes and inline <script>/<style> blocks aren't nonce'd or externalized — but non-self/non-https origins are blocked, meaningful defense-in-depth against the retune-modal XSS class (PR #44) even before that fix landed. Regression tests added for all three (tests/test_safe_xml.py, tests/test_sloppak_unpack_size_cap.py, tests/test_security_headers.py) — verified failing against pre-fix code, passing after. Full existing suite (2801 tests, minus the pre-existing midiutil-blocked module) passes unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018J1NvPtPQZd3cEt6aAbGzG
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
Fixed the failing Diagnosis — the check-suite failure (job
( Fix — rewrote the two offending commit subjects to carry valid prefixes and force-pushed the feature branch (history rewrite is the only way to satisfy this check; adding a commit can't retrofit earlier ones):
Verification — re-ran the exact CI step (rev-list Task list (7/7 completed)
|
071a797 to
ec713b4
Compare

What
Security audit fix:
retuneSong(filename, title, tuning, target)instatic/app.jsinjectedtitle/target/msg.filename/msg.errorinto a modal'sinnerHTMLvia unescaped template literals.song.titleis attacker-influenceable (imported GP/MusicXML/sloppak metadata) and reaches this sink directly from the library card's "Convert to E Standard" menu action, executing before any network round-trip. Since the app has no auth model (single-user, all endpoints trusted-local by design), script execution here grants full read/write access to the library via same-origin API calls.Fixed by wrapping all four values in the codebase's existing
esc()helper, matching the escaping pattern already used everywhere else in the file.feedpak surface
Checklist
CHANGELOG.md[Unreleased]updated (user-visible changes)git commit -s)Generated by Claude Code
Greptile Summary
The PR closes the stored-XSS path in the retune modal and documents the security fix.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Reviews (2): Last reviewed commit: "Update changelog for the retune-modal XS..." | Re-trigger Greptile