fix(929): pin Pkcs 9.0.21; hold ImageSharp 3.1.12 with TIFF decoding disabled #934
Open
timothy
wants to merge 7 commits from
929-vuln-pkcs-imagesharp-v4 into main
pull from: 929-vuln-pkcs-imagesharp-v4
merge into: :main
:main
:renovate/meziantou.analyzer-3.x
:renovate/nuget-sixlabors.imagesharp-vulnerability
:913-scripted-playout-401
:929-vuln-pkcs-imagesharp-v4
:renovate/dotnet-monorepo
:release/v26.15.0-notes
:renovate/lucene.net
:renovate/cliwrap-3.x
:issue-806-guard-populations
: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
No Reviewers
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
No labels
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#934
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.
fixes #929
Supersedes #923 (Renovate: ImageSharp → v4). That PR cannot merge as it stands: every patched ImageSharp release (3.2.0, 4.1.2, 4.1.3) ships a build-time Six Labors license check that fails a Release build without a key, and CI and the Docker image both build Release. Close #923 after this lands.
What changed
ErsatzTV.Infrastructure.csprojreferences it directly, following the Microsoft.OpenApi / SQLitePCLRaw pattern. All 11 projects that pull it in resolve 9.0.21.CentralPackageTransitivePinningEnabledwas tried and rejected: restore fails with NU1109 in every project, because of the exactMicrosoft.Extensions.DependencyModel [8.0.2]pin.security.imagesharp-3x-tiff-decoder-disabled):ImageSharpDecoding's configuration, which has every default format except TIFF. That closes off the two TIFF decoder advisories.ErsatzTV.Architecture.Tests/ImageSharpSurfaceTests.csenforces both points against the IL of every production assembly that can reach ImageSharp. That set of assemblies is worked out fromErsatzTV.slnand the csproj graph, not listed by hand. It checks four shapes: a decode withoutDecoderOptions;DecoderOptionsconstructed outsideImageSharpDecoding; outsideImageSharpDecoding, aDecoderOptions.Configurationassignment, aConfiguration.Defaultread or anImageFormatManagerreference; and any TIFF, ICC or histogram-equalization reference. A mutation sweep reddens each shape.ImageSharpDecodingTestsandWatermarkElementRemoteImageTestspin that by allocation. A 208-byte profile declaring a 255-grid-pointmft2CLUT is carried in every format whose decoder reads a profile: BMP, JPEG, PNG and WebP. It goes through the validator,CloneFrame/Resize/CloneAsand a real remoteWatermarkElement. Every case must stay under 16 MiB. The validator cases also assert the profile is still attached after each step. The element case cannot see its private images, so it checks only allocation, using the same fixture. A control readsEntrieson the same profile and must allocate over 64 MiB. Measured on 3.1.12: 38-94 KiB on the product path, under 1 MiB for the element (368-844 KiB across runs), and about 127 MiB for the control. Injecting anEntriesread into the validator reddens all 8 cases (4 validator-path, 4 element). Injecting one intoImageElementBaseafter the resize reddens the 4 element cases.bmpfrom the list reddens that test. The round trip can't see a decoder that reads a profile its own encoder never writes. A Cecil scan of the 3.1.12 package (2026-10-28) finds that only BMP, JPEG, PNG, WebP and TIFF construct anIccProfile, so nothing is missed today.renovate.jsonholds ImageSharp on 3.1.x for ordinary update PRs. Whether it also holds the OSV security PR can't be measured before merge.docs/ci-cd.md(Dependency management, Renovate),docs/guard-inventory.md,docs/testing.md(the Architecture.Tests row covers the IL scan; a row for the previously unlistedErsatzTV.Infrastructure.Tests), and theffmpeg.remote-image-fetcher-boundedrecord, whose MaxFrames measurement is now dated to 3.1.12. The record edits need theDecisions-Edit: yestrailer, which is ond1af9daf5,bc329bcfd,233d53b0candbd24d21e2.Vulnerable scan (2026-10-27)
dotnet list ErsatzTV.sln package --vulnerable --include-transitivenow lists only SixLabors.ImageSharp 3.1.12: six advisories across 11 projects. Pkcs is gone. Each remaining advisory is covered row by row in the record's table:dotnet listignoresNuGetAuditSuppress, so the weekly scan stays red on ImageSharp until a license key is provisioned. That is an owner decision; the Community licence is free under USD 1M revenue.Live E2E (local, port 8500, 2026-10-27, same assets on both builds)
The branch keeps 3.1.12, so there is no 4.x output to compare. The base build is this branch with the two decode call sites (
RemoteImageValidator.cs,ImageElementBase.cs) checked out from origin/maine919b925f. A TIFF logo URL returns 201 on that build, which confirms it really is the old decode path.Channel logo render. I saved a PNG logo (96x64 RGBA with half alpha) and a JPEG logo from a URL. Each was rendered on stream as a
ChannelLogowatermark through the graphics engine; the ffmpeg command line shows-f rawvideo -pix_fmt bgra … -i -plus overlay on both builds. Results were identical on base and branch:7b2c9241…a8d915f6…277e232f…453416fb…The cached logo bytes match the source files' md5 on both builds, and
/iptv/logos/genis5f215b44…on both.Watermark render. A custom PNG watermark gives an identical frame md5 (
fb1a68cb…) on both builds.TIFF logo URL. Returns 201 on main and 422 on the branch. The 422 names the decoders, and TIFF is not among them.
Gate (head
bd24d21e2)Full output:
/private/tmp/claude-501/-Users-timothy-ersatztv/f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1/scratchpad/e2e929/gate-bd24d21e23f363da3fc5ea5262b78671172d495a.txt. Release build 0 errors; dotnet test: Mcp 61, FFmpeg 83, Scanner 1504, Core 720 (1 skipped), Architecture 12, Infrastructure 136, ErsatzTV.Tests 2142 (6 skipped), 0 failed. web: vitest 1371 passed. scripts: pytest 1599 passed, 3 skipped. doc-narrative: 0 warnings. dotnet format whitespace: clean.Review history
<<REVIEW HISTORY
round 1: 3 lens(es); 0 blocking, 3 should-fix, 4 nit; answered by the fix commit(s) in git log --oneline 233d53b0c7148b79616a2cfe40f48a5e84336ca3..bd24d21e23f363da3fc5ea5262b78671172d495a
round 2: 3 lens(es); 0 blocking, 0 should-fix, 4 nit; clean — loop ended
REVIEW HISTORY>>
Fix commit in that range (
git log --oneline 233d53b0c..bd24d21e2lists exactly one):bd24d21e2test(929): the ICC carrier list gains BMP (whose 3.1.12 decoder also constructs anIccProfile); a new test round-trips the hostile profile through every format ofImageSharpDecoding.Configurationand requires the formats that come back carrying it to equal the list (witnessed red by droppingbmp); the record and the element test comment now say only the validator cases assert the profile stays attached. TouchesImageSharpDecodingTests.cs,WatermarkElementRemoteImageTests.csand the decision record.Cross-family review status: codex
Deliberately left: none. The ImageSharp license-key provisioning (the route to 4.x and a clean weekly scan) is an owner decision raised by the orchestrator, not deferred work in this PR.
🤖 Generated with Claude Code
Review found that the surface guard checked that every decode takes DecoderOptions and that only ImageSharpDecoding constructs them, but not that the TIFF-blind configuration is the one that picks the decoder. A new rule rejects, outside ImageSharpDecoding, a DecoderOptions Configuration assignment, a read of Configuration.Default, and any ImageFormatManager reference (a decoder fetched from a registry decodes its own format whatever the options carry). Mutation sweep against ImageElementBase.LoadImage, restored from git after each: - Configuration.Default.ImageFormatsManager.GetDecoder(...).DecodeAsync( ImageSharpDecoding.Options(), ...): only the new rule reds (get_Default, get_ImageFormatsManager, GetDecoder sites). - ImageSharpDecoding.Configuration.ImageFormatsManager.GetDecoder(...): only the new rule reds (ImageFormatManager clause alone). - new DecoderOptions { Configuration = ImageSharpDecoding.Configuration }: the new rule reds on set_Configuration and the construction rule on .ctor. - options.Configuration = ... on a returned instance does not compile (CS8852: init-only), so the reviewer's reassignment shape is not expressible in C#; the IL rule still covers the setter. docs/testing.md: the Architecture.Tests row now names all three rule families (layering, provider-statics wiring, the Mono.Cecil ImageSharp scan) and the host reference, and drops the stale exact count; the missing ErsatzTV.Infrastructure.Tests row is added, since this branch adds ImageSharpDecodingTests there. The record and guard inventory name the fourth shape and what an IL scan cannot see. Decisions-Edit: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1bd24d21e23to71d660f213Review-verdict: MERGEABLE @
71d660fOrchestrator verdict at
71d660f21(a pure rebase ofbd24d21e2onto main after #938; the patch is unchanged). Round 2 reviews all merge, Codex cross-family included, with nits only. CI green. Pkcs is pinned to 9.0.21. ImageSharp is held at 3.1.12 with TIFF decoding off and each advisory bounded in security.imagesharp-3x-tiff-decoder-disabled; the owner approved this hold, and the license-key follow-up is #936. Live E2E is at #929 comment 34502 (product code unchanged since).View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.