diff --git a/AGENTS.md b/AGENTS.md index 9a5002b..ac0daa5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,6 +77,15 @@ The client uses `track.id` for: - Fetching audio (`/api/tracks/:trackId`) - Checking cache status (`M.cachedTracks.has(trackId)`) +## XSS Prevention + +All server-controlled strings (channel names, usernames, track titles, playlist names, toast messages) must be escaped before reaching `innerHTML`: + +- **`M.escapeHtml(str)`** (`public/utils.js`) โ the single shared escaping helper. Do not define local copies. +- **`M.trackComponent.getTitle(track)`** (`public/trackComponent.js`) โ the single source of truth for a track's display title. Use it instead of inline `track.title || track.filename` fallbacks. + +A Content-Security-Policy header is set on all static responses in `routes/static.ts` (`script-src 'self'`, no inline scripts) as defense-in-depth. + ## Client Caching System ### Segment-Based Buffering diff --git a/public/channelSync.js b/public/channelSync.js index 340eff5..18eb071 100644 --- a/public/channelSync.js +++ b/public/channelSync.js @@ -159,7 +159,7 @@ counts[name] = (counts[name] || 0) + 1; } const listenersHtml = Object.entries(counts).map(([name, count]) => - `