Skip to content

fix(play): serve the album (decode URLs, support byte ranges) - #74

Merged
ralyodio merged 1 commit into
mainfrom
fix-static-unicode
Sep 23, 2026
Merged

ralyodio merged 1 commit into
mainfrom
fix-static-unicode

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Live bug: none of the album tracks play on nightcell7.com. Each MP3 request returns index.html with a 200.

Cause: the built-in game file server in start-all.mjs never percent-decoded the URL. …/%C3%9Er%C3%B8ngva/After%20the%20Winter%20of%20Want/… was looked up on disk literally, so any file whose name has a space or a non-ASCII letter could never be served. That includes the old More Than Enough.mp3.

Fix:

  • Decode the URL path before looking up the file, and keep the path-traversal guard. It now also rejects a sibling folder such as dist-old, which the old prefix check let through.
  • Support HTTP Range requests (206 and 416) and stream files instead of reading them whole. Safari won't play <audio> from a server that ignores Range.
  • Move the server into tools/release/game-static.mjs with 8 tests.

Checked locally against the real production build: album track 019 returns 200 as audio/mpeg with all 3,164,787 bytes, and Range: bytes=0-1023 returns 206.

🤖 Generated with Claude Code

The production game file server (start-all.mjs) looked the raw URL up on
disk without percent-decoding it. Every track on Þrøngva's album is requested
as .../%C3%9Er%C3%B8ngva/After%20the%20Winter%20of%20Want/..., found nothing,
and got index.html with a 200 from the SPA fallback, so no music played on
nightcell7.com. The old "More Than Enough.mp3" had the same problem.

- Decode the path (400 on malformed encoding) before resolving it; the
  traversal guard now checks against dist + separator, so a decoded %2e%2e%2f
  or a sibling like dist-old cannot escape.
- Support single byte ranges (206/416) and stream files: Safari will not play
  <audio> from a server that ignores Range, and a whole-file read buffered
  every 9 MB track in memory per request.
- The server moves to game-static.mjs so it has tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

9 finding(s)

HIGH/CRITICAL: 4 | MEDIUM: 5

Severity Rule Location
HIGH manifest-typosquat apps/game/package.json:17
HIGH sh-remote-script-execution apps/site/public/install.sh:210
HIGH sh-remote-script-execution apps/site/public/install.sh:213
HIGH sh-remote-script-execution apps/site/public/install.sh:216
MEDIUM sh-remote-script-execution apps/site/public/install.sh:219
MEDIUM sh-remote-script-execution apps/site/public/install.sh:432
MEDIUM sh-remote-script-execution apps/site/public/install.sh:433
MEDIUM js-path-traversal tools/art/tactical-sample/validate.mjs:6
MEDIUM js-path-traversal tools/art/tactical-sample/validate.mjs:10

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 4966cef into main Sep 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant