Repository navigation
v0.0.2 — internal error detail no longer reaches the browser
What changed
A fix release for two information-disclosure defects in the streaming endpoints, found
by CodeQL after it was enabled on this repository.
If you run this app, this release is worth taking. No credential was ever reachable,
but internal error detail (driver names, host and port) could reach a browser. Details,
including what was not affected and why, are in the changelog.
Nothing in the metadata contract changed: what gets written to your Jellyfin library is
identical to v0.0.1.
Fixed
Internal error text no longer reaches the browser. CodeQL found this
(py/stack-trace-exposure), and it found it twice — which is the interesting part,
because the second instance was still there after the first fix. Each place that reported
a failure was answering "is this exception safe to quote?" for itself, and one of them
answered wrong.
- The SSE catch-all put
str(exc)straight into theerrorframe. An unexpected failure
now reports acodeand a short reference id; the traceback goes to the server log
under that same reference, so a report from a user still leads to the cause. - The same expression appeared a second time in the per-item failure payloads for bulk
apply and revert. Both now go through one function. domain.errors.public_error_textis that function, and the single definition of what a
client may be told. The distinction it draws is provenance, not severity: a
MetaeditErrormessage was written by us for a reader and passes through; anything
else gets a reference.- No credential was ever reachable, which was checked rather than assumed: Last.fm's
httpxfailures collapse to a type name before escaping, so the key in the query string
never reached a message; the Jellyfin key travels in a header and its bodies already
pass through_sanitise_body; and a failing Postgres connection renders with the
password masked. What leaked was internal topology — driver names, host and port. SelectionErrornow subclassesMetaeditErrorinstead ofValueError. It was an
expected failure with a person-readable message all along, and being a bareValueError
meant the write path had two hierarchies to catch and its HTTP status lived in a handler
in another layer. The status (422) and code (invalid_request) are unchanged, but they
now sit next to the failure that causes them, and the per-item failure payload no longer
needs to special-case it.
domain/errors.py had promised this from the start — "no upstream payload ever reaches a
client verbatim" — so both leaks were omissions, not decisions.
Found by CodeQL code scanning,
which was enabled on this repository between the two releases.
Notes
- GitHub Actions were brought to current majors (
actions/checkoutv7,
actions/upload-artifactv7,actions/setup-nodev7) andastral-sh/setup-uvto
v10.1.0, pinned to a commit SHA so a moved tag cannot change what CI runs. - This is a fix release, so nothing in the metadata contract changed. The API version
reported by/openapi.jsonis the only interface difference.
Full changelog: CHANGELOG.md
Compare: v0.0.1...v0.0.2