Skip to content

tracker: allow unsafe-eval, which the SID engine cannot run without - #212

Merged
eetu merged 1 commit into
mainfrom
fix-sid-csp
Aug 7, 2026
Merged

tracker: allow unsafe-eval, which the SID engine cannot run without#212
eetu merged 1 commit into
mainfrom
fix-sid-csp

Conversation

@eetu

@eetu eetu commented Aug 7, 2026

Copy link
Copy Markdown
Owner

SIDs fail on tracker.anarkisti.com with Couldn't play this module and a CSP
violation, while working on the dev server. Modules play fine either way.

libsidplayfp is bound through Emscripten's Embind, which builds its invoker
functions with the Function constructor — string evaluation, which
'wasm-unsafe-eval' does not cover (that permits wasm compilation and
nothing else). libopenmpt reaches its C API through cwrap and needs none of
it, which is exactly why one format worked and the other didn't.

Neither shipped artifact avoids it — residfp and sidlite both use Embind — and a
worker can't relax an inherited policy, since a dedicated worker's CSP is the
union of its own and its owner's. So it's 'unsafe-eval' or no SID playback.
Narrowing it again means rebuilding the wasm with -sDYNAMIC_EXECUTION=0, which
is upstream work.

Everything else stays as strict as it was: no 'unsafe-inline' for scripts, the
SvelteKit bootstrap still hashed, connect-src / img-src / frame-ancestors
untouched.

Why nothing caught it

The CSP is set by the Rust backend, and it's the only thing that sets one. The
dev server sends none, and the e2e suite runs the built SPA through
vite preview — which also sends none. So the whole suite passes against a page
whose security policy bears no resemblance to production.

That's the third escape of this shape on this feature: a dev-only fs.allow
403, a worker no typecheck covered, and now this. A spec that runs the real
backend over the real build would have caught all three; I reproduced this one
that way and it's worth making permanent, but it's a new harness rather than
something to bolt onto a one-line fix.

The unit test pins 'unsafe-eval' with the reasoning attached, because it
reads exactly like something a future tightening pass should delete — and
deleting it breaks SID playback in a way CI cannot see.

Verification

Reproduced against the real backend serving the real build, then confirmed
fixed: transport 0:05, no error banner, no CSP violations. Before the fix,
same setup, same page: EvalError … 'unsafe-eval' is not an allowed source.

The stack was the only thing that identified Embind — the minified call is
Function(...), not new Function, so grepping for the obvious spelling found
nothing and sent me looking in the wrong place first.

79 backend unit · 35 integration · clippy 0 warnings · fmt clean.

(Split out of #211, which merged before this commit landed on the branch.)

SIDs failed on the deployed Pi with "Couldn't play this module" and a CSP
violation, while modules played fine. libsidplayfp is bound through
Emscripten's Embind, which builds its invoker functions with the `Function`
constructor — string evaluation, which `'wasm-unsafe-eval'` does not cover;
that permits wasm compilation and nothing else. libopenmpt reaches its C API
through `cwrap` and needs none of it, hence one format working and the other
not.

Neither shipped artifact avoids it — residfp and sidlite both use Embind —
and a worker cannot relax an inherited policy, since a dedicated worker's CSP
is the union of its own and its owner's. So it is this or no SID playback.
Narrowing it again means rebuilding the wasm with `-sDYNAMIC_EXECUTION=0`,
which is upstream work. Everything else stays as strict as it was: no
`'unsafe-inline'` for scripts, the SvelteKit bootstrap still hashed,
connect-src / img-src / frame-ancestors untouched.

Worth recording why no test caught this: the CSP is set by *this* backend,
and the e2e suite serves the built SPA through `vite preview`, which sends no
CSP at all. So the whole suite passes against a page whose security policy
bears no resemblance to production. Reproduced by running the real backend
over the real build and driving it with a browser — the violation named the
directive but not the cause, and the stack was the only thing that pointed at
Embind (the minified call is `Function(...)`, not `new Function`, so grepping
for the obvious spelling found nothing).

The unit test now pins `'unsafe-eval'` with that reasoning attached, because
it reads exactly like something a future tightening pass should remove — and
removing it breaks SID playback silently as far as CI is concerned.
@eetu
eetu merged commit 55e6fc6 into main Aug 7, 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