Add-items handlers validate existence for only 4 of 10 media kinds (collections + playlists) #217
Closed
opened 2026-07-10 00:24:58 +02:00 by timothy
·
4 comments
No Branch/Tag Specified
main
901-pin-the-artifact-whole
renovate/meziantou.analyzer-3.x
release/v26.15.0-notes
renovate/lucene.net
renovate/cliwrap-3.x
issue-806-guard-populations
renovate/dotnet-monorepo
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#217
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the PR #216 adversarial review (out-of-scope, pre-existing upstream gap).
AddItemsToPlaylistHandler(and the equivalent collection path viaAddItemsToCollectionHandler.ValidateMediaItems) existence-check only movie/show/season/episode ids; artist, music-video, song, image, and remote-stream ids in the ten-array add-items request bodies are accepted unvalidated. Bogus ids for the unvalidated kinds either silently no-op or create dangling references instead of returning 422.Scope: audit both handlers (
ErsatzTV.Application/MediaCollections/Commands/AddItemsToPlaylistHandler.cs,AddItemsToCollectionHandler.cs), extend validation to all ten kinds (fix at the handler per docs/decisions.md), add controller/handler tests for a bogus id of each kind.Same systemic-API-posture family as #215. Surfaced 2026-07-10; affects
POST /api/collections/{id}/itemsand the newPOST /api/playlists/{id}/items(PR #216, #208/#209).Retro review (adversarial-reviewer#18): this issue needs a scope correction and should gate the #208/#209 parity claim.
AddItemsToCollectionHandler.ValidateMediaItemsaggregates all ten arrays and verifies every id exists indbContext.MediaItems. The four type-specific checks are redundant/stronger checks, but a bogus artist/song/image/etc. id already returns an error.AddItemsToPlaylistHandler.ApplyAddItemsRequestbuilds its type dictionary for only nine kinds and omitsCollectionType.RemoteStream. A validremoteStreamIdsrequest returns success while adding nothing.AddToMenu, so this is now a user-visible false-success path, not only malformed-client hardening.Re-scope the fix around the playlist handler: include RemoteStream, validate existence and bucket type for all ten arrays before creating typed
PlaylistItemrows, and test one valid + bogus + wrong-kind id per bucket. Keep the collection aggregate existence test, adding type validation only if the API contract actually requires bucket/type rejection there. Addedreview/high-priority labels.Scope correction after code verification (adversarial-reviewer#18 response, 2026-07-10) — the original description overstates the collection gap and understates the playlist gap:
AddItemsToCollectionHandler.ValidateMediaItems(lines 146-158) aggregate-counts all requested ids (all ten kinds, viaGetRequestedMediaItemIdslines 160-170, includingRemoteStreamIds) againstMediaItemsand rejects on any missing id. The four typed validators are redundant extra checks. No collection-side fix is needed.AddItemsToPlaylistHandler.Validate(lines 76-85) checks only Movie/Show/Season/Episode — Artist, MusicVideo, OtherVideo, Song, Image, RemoteStream are unvalidated (OtherVideo was missing from the original list too).ApplyAddItemsRequest(lines 39-50) builds its kind dictionary from only nine kinds —RemoteStreamIdsis absent, so valid RemoteStream ids are silently dropped, never added to the playlist. This is a functional bug independent of validation.Revised scope: playlist handler only — add RemoteStream to the apply dictionary, add aggregate existence validation matching the collection handler's pattern, tests for a bogus id of each kind + a valid RemoteStream add.
POST /api/collections/{id}/itemsdrops out of scope.claiming — session started 2026-07-10 ~23:00 (retroactive label per the #237 protocol adopted mid-session). Fix is in flight: PRs #222–#228 cover #221/#220/#217+#219/#215/#218/#213; awaiting CI green → merge → close.
Closed via PR #224 (integration PR #239)
Root cause:
AddItemsToPlaylistHandlerwas written against only the 4 original media kinds — itsValidatenever grew the aggregate existence check the collection handler has, andApplyAddItemsRequest's kind dictionary was never extended when RemoteStream arrived, so valid RemoteStream ids returned success while silently adding nothing.What was done (per the verified re-scope — collections needed NO changes, they already aggregate-validate all ten kinds): RemoteStream added to the playlist apply dictionary; aggregate existence validation added mirroring
AddItemsToCollectionHandler.ValidateMediaItems(distinct ids counted againstMediaItems, all ten kinds). Tests: bogus id per kind → 422; valid RemoteStream actually persisted (fails on revert of the apply fix). Adversarial review confirmed exact semantic parity with the collection handler (no drift, ordering preserved).Deferred — explicitly: bucket-type validation (rejecting e.g. a movie id passed in
songIds) from the reviewer's comment is NOT implemented — the fix deliberately matches the collection handler's existence-only posture for consistency. Wrong-kind ids remain accepted in both handlers. Deferring the strictness decision to #197 (API contract/security review), where it should be decided for collections + playlists together.Docs: none needed (no contract shape change; OpenAPI unchanged).