Skip to content

Fix stored XSS in retune modal - #44

Open
carochacs wants to merge 3 commits into
mainfrom
claude/security-issues-jdpj26
Open

Fix stored XSS in retune modal#44
carochacs wants to merge 3 commits into
mainfrom
claude/security-issues-jdpj26

Conversation

@carochacs

@carochacs carochacs commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

What

Security audit fix: retuneSong(filename, title, tuning, target) in static/app.js injected title/target/msg.filename/msg.error into a modal's innerHTML via unescaped template literals.

song.title is 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

  • This PR does not change how the app reads/writes feedpaks (manifest keys, pack files, folder layout)

Checklist

  • CHANGELOG.md [Unreleased] updated (user-visible changes)
  • No new behavior — pure security fix, existing tests cover the affected code path
  • Commits are DCO signed off (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.

  • Escapes the song title and target before rendering the initial modal.
  • Escapes server-provided filenames and errors before rendering completion states.
  • Adds an Unreleased Security changelog entry describing the affected flow and remediation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
static/app.js Applies the existing HTML text-context escaping helper to all four dynamic values inserted into the retune modal.
CHANGELOG.md Adds an accurate Security entry describing the stored-XSS vulnerability and fix.

Reviews (2): Last reviewed commit: "Update changelog for the retune-modal XS..." | Re-trigger Greptile

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.
@carochacs carochacs added the bug Something isn't working label Aug 6, 2026
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
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

⚠️ Agent ran but produced no code changes.

The CI failures may require manual intervention.

View agent analysis
Now let's check the commit subjects for the core-commit-labeling rule.

I investigated the failing check and the root cause is **not** a code/test bug — it's a commit-message policy gate.

## Root cause

This repo enforces a fork-maintenance policy (`docs/fork-maintenance.md`, Rule 2) via `.github/workflows/fork-audit.yml`'s `core-commit-labeling` job: **any commit introduced by a PR that touches a "core" path** (everything except `plugins/**`, `docs/**`, `tests/**`, `scripts/**`, `specs/**`, `.specify/**`, `CHANGELOG.md`, `VERSION`) **must have a subject line prefixed `core:`, `hook:`, or `sync:`**.

The PR has two commits ahead of `origin/main`:

| Commit | Files | Subject | Verdict |
|---|---|---|---|
| `2afd464` | `static/app.js` | `fix(security): escape song title/filename/error in retune modal HTML` | Touches a core path (`static/app.js`), subject doesn't start with `core:`/`hook:`/`sync:` → **fails the gate** |
| `33962cd` | `CHANGELOG.md` | `Update changelog for the retune-modal XSS fix` | Only touches an excluded path → exempt |

I verified `static/app.js`'s content is already correct and complete (all four values — `target`, `title`, `msg.filename`, `msg.error` — are properly routed through `esc()`, matching the PR's stated intent). There is no code defect to fix here.

Per `docs/fork-maintenance.md`'s own decision tree, a direct edit to a core file like this is the explicit **P3 exception** ("a real bug fix... label the commit `core:`"), which is exactly this case (a security bug fix, not a code smell to revert).

## Why I'm not making a file edit

The fix this check requires is **rewording the commit subject** of `2afd464` (e.g. to `core: fix(security): escape song title/filename/error in retune modal HTML`), not a change to any file's contents. That requires a commit-history operation (`git commit --amend` / non-interactive rebase to reword), which is explicitly out of scope for this task ("Do NOT run git commit or any command that creates a commit — only make file changes"). There's no file-content change that can satisfy a `git log`-based commit-message gate.

**Recommended remediation (to be applied outside this constrained session):**
```
git rebase -i origin/main   # mark 2afd464 as "reword"
# change subject to:
# core: fix(security): escape song title/filename/error in retune modal HTML
```
No other files need modification — `static/app.js` and `CHANGELOG.md` are already correct for the PR's stated purpose.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

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
carochacs pushed a commit that referenced this pull request Aug 7, 2026
…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
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 93c5230e-83dc-4cae-b117-d451bb3af9d2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@pullfrog

pullfrog Bot commented Aug 7, 2026

Copy link
Copy Markdown

Fixed the failing core-commit-labeling check on PR #44.

Diagnosis — the check-suite failure (job core-commit-labeling, .github/workflows/fork-audit.yml) was a fork-commit-labeling violation, not a code failure. The workflow's regex only accepts subjects prefixed ^(core|hook|sync)(\([^)]*\))?:, and two commits this PR introduces touch core paths (everything outside plugins/ docs/ tests/ scripts/ specs/ .specify/ CHANGELOG.md VERSION) without a valid prefix:

  • 2afd464fix(security): escape song title/filename/error in retune modal HTML touches static/app.js; fix(security): is not in the allowed prefix set.
  • 071a797Fix remaining security-audit findings: XML DoS, zip bomb, missing headers touches lib/**, server.py, requirements.txt.

(33962cd only touches CHANGELOG.md, which is excluded — not flagged.) The failure is directly attributable to this PR's own commits; no code bug.

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):

  • 2afd464core(security): escape song title/filename/error in retune modal HTML
  • 071a797core: Fix remaining security-audit findings: XML DoS, zip bomb, missing headers

Verification — re-ran the exact CI step (rev-list e995a66..HEAD + prefix grep) locally: passes (exit=0). Confirmed content-identical to the pre-rewrite branch (tree hash bf38c194… unchanged) and clean working tree. Pushed to origin/claude/security-issues-jdpj26 (now ec713b4); the existing PR #44 picks up the rewrite on its next check run.

Task list (7/7 completed)
  • Checkout PR Fix stored XSS in retune modal #44 branch
  • Fetch check suite logs to diagnose failure
  • Verify failure was introduced by this PR
  • Diagnose and fix the issue
  • Verify fix by re-running exact CI command
  • Commit and push to existing PR
  • Report progress with diagnosis and fix summary

Pullfrog  | View workflow run | via Pullfrog | Using Big Pickle (free) | 𝕏

@pullfrog
pullfrog Bot force-pushed the claude/security-issues-jdpj26 branch from 071a797 to ec713b4 Compare August 7, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

2 participants