Skip to content

v0.0.2 — internal error detail no longer reaches the browser

Choose a tag to compare

@sredevopsorgagents sredevopsorgagents released this 08 Oct 03:56
· 57 commits to main since this release

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 the error frame. An unexpected failure
    now reports a code and 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_text is that function, and the single definition of what a
    client may be told. The distinction it draws is provenance, not severity: a
    MetaeditError message 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
    httpx failures 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.
  • SelectionError now subclasses MetaeditError instead of ValueError. It was an
    expected failure with a person-readable message all along, and being a bare ValueError
    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/checkout v7,
    actions/upload-artifact v7, actions/setup-node v7) and astral-sh/setup-uv to
    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.json is the only interface difference.

Full changelog: CHANGELOG.md
Compare: v0.0.1...v0.0.2