Repository navigation
fix(jellyfin): clamp negative StartIndex to zero - #6341 - #6366
Conversation
Signed-off-by: Santhi Prakash <[email protected]>
Strix Security Review✅ No open security findings. PR summary: Clamps a negative client-supplied StartIndex to zero in the Jellyfin pagination handlers (items, genres, studios, playlist items), adds a guard in the in-memory paginate helper, and makes the multi-type /Items search window overflow-safe, with regression specs for each endpoint. Review summaryThe PR clamps a client-supplied negative StartIndex to zero across the Jellyfin /Items, /Genres, /Studios and /Playlists/{id}/Items pagination reads and adds a defensive non-negative guard inside paginate. Verified that this closes the previously reported StartIndex-overflow panic on the multi-type search merge (offset is capped before the sum and search limits are clamped to [1, maxSearchLimit]), so that finding is resolved. One issue remains: the new shared pageParams helper still forwards Limit unvalidated, so on the non-search multi-type /Items merge a negative Limit or an overflowing StartIndex+Limit wraps the per-type window to a non-positive value, which persistence treats as "no LIMIT" — every row of every merged type is then drained and materialised in memory instead of taking the streaming path. This is reported as "Unvalidated Limit and StartIndex+Limit overflow force full-library materialisation on multi-type /Items". Library scoping for the edited /Studios handler and the SQL-offset paths (single-type /Items, playlists, genres) were traced and found sound. Ruled out (1)
Review history (4)
Review 4 on this PR · Reviewed by Strix |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Code Metrics Report
Details | | master (27d0414) | #6366 (ef563a8) | +/- |
|---------------------|------------------|-----------------|-------|
- | Coverage | 75.7% | 75.6% | -0.1% |
| Files | 464 | 464 | 0 |
| Lines | 30100 | 30105 | +5 |
+ | Covered | 22787 | 22788 | +1 |
+ | Test Execution Time | 3m16s | 3m8s | -8s |Code coverage of files in pull request scope (82.2% → 82.0%, patch 100.0%)
Reported by octocov |
… helper The negative-StartIndex clamp was repeated in getItems (listParams), getGenres, getStudios and getPlaylistItems. Move the StartIndex/Limit parsing into one pageParams helper so a new paginated endpoint gets the clamp by calling it, instead of having to copy max(..., 0). Behavior is unchanged: Limit is read as before, and the existing negative-StartIndex specs cover every caller.
…tartindex/6341 Signed-off-by: Santhi Prakash <[email protected]> # Conflicts: # server/jellyfin/items.go
|
Thanks for the fix and the thorough specs, @santhiprakash! I pushed a small follow-up commit that moves the |
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…ge window On a multi-type /Items search, mergeTypesPaged computed the per-type window as offset+limit and only then capped it at maxSearchLimit. A StartIndex near the int limit wrapped that sum negative, the cap kept the negative value, and searchPage sliced rows[:Max] with it inside an errgroup worker, where the HTTP Recoverer cannot catch the panic, so one request stopped the server. Cap the offset before adding the limit. The search limit is already bounded by clampLimit, so the sum stays small and the window is unchanged for every in-range request.
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Resolve the semantic conflicts with master. navidrome#6414 replaced artistId with artistIds, so hasFilters now checks len(q.artistIds). navidrome#6414 also pairs each SortOrder value with its SortBy key, so sortViews uses listParts and sortDirection the same way applySort does, and a new spec covers the pairing. navidrome#6366 clamps StartIndex in pageParams, so the user-root branch drops its own max(q.offset, 0).
Description
GET /Items?IncludeItemTypes=Audio,MusicAlbum&StartIndex=-1&Limit=5panics withruntime error: slice bounds out of range [-1:](recovered as an HTTP 500), even on an empty library.Root cause:
StartIndexis read with no lower bound and reachespaginate, whoseoffset >= len(items)check only guards the upper bound beforeitems[offset:]slices. A negativeStartIndexalso under-widens the per-type merge window (window := offset + q.limit) and is echoed back verbatim in the response'sStartIndexfield.The same unguarded read reaches
paginatefrom two other endpoints —/Genres(vialistGenres) and/Studios— soStartIndex=-1panics there too./Playlists/{id}/Itemsand single-type/Itemspush the offset down to SQL instead, so they cannot panic, but they still echoed the negative value.Fix: clamp
StartIndexto >= 0 at everystartindexread (listParams,getGenres,getStudios,getPlaylistItems) — the same clamp idiom the codebase already uses forlimit— and guardpaginateitself so no current or future caller can slice with a negative offset. A negativeStartIndexnow behaves exactly likeStartIndex=0on all of these endpoints.Related Issues
Closes #6341
Prior agreement is not needed here — this falls under the small-change exception: a small, unambiguous fix restoring established behavior (a panic recovered as HTTP 500), with reproduction evidence under How to Test.
Type of Change
Checklist
How to Test
New regression specs fail before the fix and pass after:
server/jellyfin/items_test.go: multi-type/Items?IncludeItemTypes=Audio,MusicAlbum&StartIndex=-1&Limit=2(the reported panic), plus a single-type/Items?...StartIndex=-1controlserver/jellyfin/browsing_test.go:/Genres?StartIndex=-1and/Studios?StartIndex=-1server/jellyfin/playlists_test.go:/Playlists/{id}/Items?StartIndex=-1Verified on current
master(df77c462):slice bounds out of range [-1:], the 5th showedOffset/StartIndexpropagating as -1.go test ./server/jellyfin/→ ok (510 specs);go test -tags netgo,sqlite_fts5 ./...→ all packages ok;go vet -tags netgo,sqlite_fts5 ./...→ clean.go test/go vetwithout build tags fail on cleanmastertoo — the project requires themake testtags formain.go/FTS5; unrelated to this change.)Screenshots / Demos (if applicable)
N/A — backend/API change, no UI.
Additional Notes
StartIndexonly inside its newuserRootbranch; it does not touchlistParams,paginate, or these handlers, so this change is disjoint — a rebase after it merges should be clean.Offsetwhen it is> 0; clamping there keeps semantics consistent and makes the echoedStartIndexhonest.chained's skip loop already behaved like offset 0 for a negativeskip(n < skipis never true), so the unbounded multi-type merge path was safe.Assistance
AI use (check one):
AI tools helped draft this change and this description; the verification steps were actually run.
Summary by CodeRabbit