Files
Tha.Les a27762b518 fix: studio audio rendering — CSP data:/blob: + engine waveform visibility (#187)
* fix: allow data:/blob: in CSP connect-src so stems load (#186)

The CSP from #171 omitted data:/blob: from connect-src. multitrack.js fetches
a data: URI during track init, the browser blocked it, Multitrack.create threw,
and no audio elements were created — blank lane waveforms, 0:00, no playback in
every browser, for both YouTube and local jobs.

Add data: blob: to connect-src. Both are inline/same-origin schemes (not network
endpoints), already trusted in this policy for media-src/img-src/font-src, so no
exfiltration channel opens. script-src 'self' (no unsafe-inline/eval — the actual
XSS-execution defense from #171) is untouched. Adds tests/test_csp.py guarding
both the data:/blob: allowance and that script-src stays locked.

Diagnosis, patch, and regression test by @drewmerc302 (fork PRs are restricted,
so applied on their behalf).

Co-Authored-By: drewmerc302 <drewmerc302@users.noreply.github.com>

* fix: show SVG overview waveform when the engine owns playback

#185 mounts the multitrack with null URLs when the Web Audio engine is active,
so WaveSurfer never decodes audio and its canvas — the normal visible waveform
source — is empty. The SVG overview layer (rendered from peaks.json) is
CSS-hidden by `.daw .stem-waveform-layer { display: none }`, so the studio
showed no waveform at all whenever the engine was on (every platform; most
visible in Safari/WebKit).

Add an `engine-waveforms` class on `.app` while the engine owns playback and
unhide the SVG layer in that state, so it becomes the visible waveform source.
Verified end-to-end in Playwright WebKit (Safari engine) with a real track:
waveforms render in every lane, playback advances, zero CSP violations.

---------

Co-authored-by: drewmerc302 <drewmerc302@users.noreply.github.com>
2026-06-04 15:15:11 +01:00

31 lines
1.0 KiB
Python

from __future__ import annotations
from fastapi.testclient import TestClient
from app.main import app
def _csp_directive(name: str) -> str:
"""Return the named directive from the served Content-Security-Policy header."""
with TestClient(app) as c:
resp = c.get("/")
csp = resp.headers["content-security-policy"]
return next(d.strip() for d in csp.split(";") if d.strip().startswith(name))
def test_connect_src_permits_data_and_blob():
# Regression for #186: multitrack.js fetches a data: URI while initializing
# each track's audio. Without data:/blob: in connect-src the browser blocks
# it, Multitrack.create throws, and no audio/waveform/playback loads.
connect = _csp_directive("connect-src")
assert "data:" in connect
assert "blob:" in connect
def test_script_src_stays_locked():
# Lock #171's intent: loosening connect-src must not weaken the XSS defense.
script = _csp_directive("script-src")
assert "'self'" in script
assert "unsafe-inline" not in script
assert "unsafe-eval" not in script