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
Owner

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

  • System.Security.Cryptography.Pkcs 9.0.4 → 9.0.21 (GHSA-846f-f764-76mx High, GHSA-2fmv-rv2c-59x7): ErsatzTV.Infrastructure.csproj references it directly, following the Microsoft.OpenApi / SQLitePCLRaw pattern. All 11 projects that pull it in resolve 9.0.21. CentralPackageTransitivePinningEnabled was tried and rejected: restore fails with NU1109 in every project, because of the exact Microsoft.Extensions.DependencyModel [8.0.2] pin.
  • ImageSharp is held at 3.1.12 (decision record security.imagesharp-3x-tiff-decoder-disabled):
    • Every ImageSharp decode (Identify and Load, remote and local) now goes through ImageSharpDecoding's configuration, which has every default format except TIFF. That closes off the two TIFF decoder advisories.
    • The other four 3.1.12 advisories are in code paths ErsatzTV never calls: the TIFF encoders, the ICC profile entries and HistogramEqualization.
    • ErsatzTV.Architecture.Tests/ImageSharpSurfaceTests.cs enforces both points against the IL of every production assembly that can reach ImageSharp. That set of assemblies is worked out from ErsatzTV.sln and the csproj graph, not listed by hand. It checks four shapes: a decode without DecoderOptions; DecoderOptions constructed outside ImageSharpDecoding; outside ImageSharpDecoding, a DecoderOptions.Configuration assignment, a Configuration.Default read or an ImageFormatManager reference; and any TIFF, ICC or histogram-equalization reference. A mutation sweep reddens each shape.
    • The ICC advisory (gwg2) has a half the IL scan can't see: whether ImageSharp itself parses a profile while decoding or cloning. ImageSharpDecodingTests and WatermarkElementRemoteImageTests pin that by allocation. A 208-byte profile declaring a 255-grid-point mft2 CLUT is carried in every format whose decoder reads a profile: BMP, JPEG, PNG and WebP. It goes through the validator, CloneFrame/Resize/CloneAs and a real remote WatermarkElement. 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 reads Entries on 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 an Entries read into the validator reddens all 8 cases (4 validator-path, 4 element). Injecting one into ImageElementBase after the resize reddens the 4 element cases.
    • The carrier list is checked, not trusted. Every format the decode configuration registers is round-tripped with the profile through its default encoder, and the formats that come back carrying it must be exactly the list. Dropping bmp from 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 an IccProfile, so nothing is missed today.
    • renovate.json holds ImageSharp on 3.1.x for ordinary update PRs. Whether it also holds the OSV security PR can't be measured before merge.
  • Docs: 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 unlisted ErsatzTV.Infrastructure.Tests), and the ffmpeg.remote-image-fetcher-bounded record, whose MaxFrames measurement is now dated to 3.1.12. The record edits need the Decisions-Edit: yes trailer, which is on d1af9daf5, bc329bcfd, 233d53b0c and bd24d21e2.

Vulnerable scan (2026-10-27)

dotnet list ErsatzTV.sln package --vulnerable --include-transitive now 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:

Advisory Status
GHSA-v76p, GHSA-wmxv TIFF can't be decoded
GHSA-jjfr, GHSA-j9gm No TIFF encode
GHSA-j3p4 No Normalization processors
GHSA-gwg2 Nothing reads ICC entries (IL scan), and decode/clone/resize don't parse the profile (pinned by allocation, see above)

dotnet list ignores NuGetAuditSuppress, 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/main e919b925f. 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 ChannelLogo watermark 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:

    Logo Frame md5 Logo-region md5
    PNG 7b2c9241… a8d915f6…
    JPEG 277e232f… 453416fb…

    The cached logo bytes match the source files' md5 on both builds, and /iptv/logos/gen is 5f215b44… 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..bd24d21e2 lists exactly one):

  • bd24d21e2 test(929): the ICC carrier list gains BMP (whose 3.1.12 decoder also constructs an IccProfile); a new test round-trips the hostile profile through every format of ImageSharpDecoding.Configuration and requires the formats that come back carrying it to equal the list (witnessed red by dropping bmp); the record and the element test comment now say only the validator cases assert the profile stays attached. Touches ImageSharpDecodingTests.cs, WatermarkElementRemoteImageTests.cs and 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

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 - **System.Security.Cryptography.Pkcs 9.0.4 → 9.0.21** (GHSA-846f-f764-76mx High, GHSA-2fmv-rv2c-59x7): `ErsatzTV.Infrastructure.csproj` references it directly, following the Microsoft.OpenApi / SQLitePCLRaw pattern. All 11 projects that pull it in resolve 9.0.21. `CentralPackageTransitivePinningEnabled` was tried and rejected: restore fails with NU1109 in every project, because of the exact `Microsoft.Extensions.DependencyModel [8.0.2]` pin. - **ImageSharp is held at 3.1.12** (decision record `security.imagesharp-3x-tiff-decoder-disabled`): - Every ImageSharp decode (Identify and Load, remote and local) now goes through `ImageSharpDecoding`'s configuration, which has every default format except TIFF. That closes off the two TIFF decoder advisories. - The other four 3.1.12 advisories are in code paths ErsatzTV never calls: the TIFF encoders, the ICC profile entries and HistogramEqualization. - `ErsatzTV.Architecture.Tests/ImageSharpSurfaceTests.cs` enforces both points against the IL of every production assembly that can reach ImageSharp. That set of assemblies is worked out from `ErsatzTV.sln` and the csproj graph, not listed by hand. It checks four shapes: a decode without `DecoderOptions`; `DecoderOptions` constructed outside `ImageSharpDecoding`; outside `ImageSharpDecoding`, a `DecoderOptions.Configuration` assignment, a `Configuration.Default` read or an `ImageFormatManager` reference; and any TIFF, ICC or histogram-equalization reference. A mutation sweep reddens each shape. - The ICC advisory (gwg2) has a half the IL scan can't see: whether ImageSharp itself parses a profile while decoding or cloning. `ImageSharpDecodingTests` and `WatermarkElementRemoteImageTests` pin that by allocation. A 208-byte profile declaring a 255-grid-point `mft2` CLUT is carried in every format whose decoder reads a profile: BMP, JPEG, PNG and WebP. It goes through the validator, `CloneFrame`/`Resize`/`CloneAs` and a real remote `WatermarkElement`. 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 reads `Entries` on 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 an `Entries` read into the validator reddens all 8 cases (4 validator-path, 4 element). Injecting one into `ImageElementBase` after the resize reddens the 4 element cases. - The carrier list is checked, not trusted. Every format the decode configuration registers is round-tripped with the profile through its default encoder, and the formats that come back carrying it must be exactly the list. Dropping `bmp` from 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 an `IccProfile`, so nothing is missed today. - `renovate.json` holds ImageSharp on 3.1.x for ordinary update PRs. Whether it also holds the OSV security PR can't be measured before merge. - Docs: `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 unlisted `ErsatzTV.Infrastructure.Tests`), and the `ffmpeg.remote-image-fetcher-bounded` record, whose MaxFrames measurement is now dated to 3.1.12. The record edits need the `Decisions-Edit: yes` trailer, which is on d1af9daf5, bc329bcfd, 233d53b0c and bd24d21e2. ## Vulnerable scan (2026-10-27) `dotnet list ErsatzTV.sln package --vulnerable --include-transitive` now 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: | Advisory | Status | |---|---| | GHSA-v76p, GHSA-wmxv | TIFF can't be decoded | | GHSA-jjfr, GHSA-j9gm | No TIFF encode | | GHSA-j3p4 | No Normalization processors | | GHSA-gwg2 | Nothing reads ICC entries (IL scan), and decode/clone/resize don't parse the profile (pinned by allocation, see above) | `dotnet list` ignores `NuGetAuditSuppress`, 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/main `e919b925f`. 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 `ChannelLogo` watermark 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: | Logo | Frame md5 | Logo-region md5 | |---|---|---| | PNG | `7b2c9241…` | `a8d915f6…` | | JPEG | `277e232f…` | `453416fb…` | The cached logo bytes match the source files' md5 on both builds, and `/iptv/logos/gen` is `5f215b44…` 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..bd24d21e2` lists exactly one): - `bd24d21e2` test(929): the ICC carrier list gains BMP (whose 3.1.12 decoder also constructs an `IccProfile`); a new test round-trips the hostile profile through every format of `ImageSharpDecoding.Configuration` and requires the formats that come back carrying it to equal the list (witnessed red by dropping `bmp`); the record and the element test comment now say only the validator cases assert the profile stays attached. Touches `ImageSharpDecodingTests.cs`, `WatermarkElementRemoteImageTests.cs` and 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](https://claude.com/claude-code)
timothy added 7 commits 2026-10-30 00:03:55 +01:00
The Pkcs 9.0.4 that EFCore.BulkExtensions' Oracle/SqlServer providers pull
transitively carries GHSA-846f-f764-76mx (High) and GHSA-2fmv-rv2c-59x7.
It is overridden by a direct reference in ErsatzTV.Infrastructure, the
existing pattern (Microsoft.OpenApi, SQLitePCLRaw). The central transitive
pin the issue suggested was measured and rejected: enabling
CentralPackageTransitivePinningEnabled fails restore with NU1109 in every
project, because Microsoft.Extensions.DependencyModel is exact-pinned [8.0.2]
below the >= 9.0.x EF Core requires.

ImageSharp stays on 3.1.12. Every patched 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 (measured: dotnet build -c Release errors 'No Six Labors
license found'), and CI and the image build Release. Of the six 3.1.12
advisories now listed, the two DECODER ones are TIFF; every ImageSharp
decode now goes through a configuration that cannot detect TIFF. The rest
are an encoder, a processor and an accessor ErsatzTV never calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1
New record security.imagesharp-3x-tiff-decoder-disabled: the license-key
build gate in every patched release (measured on 3.2.0 and 4.1.3 Release
builds), the per-advisory reachability table, the masked Load-pass clause
(measured: reverting it alone leaves the suite green), that dotnet list
--vulnerable ignores NuGetAuditSuppress (measured), and the revisit trigger.
ci-cd.md Dependency management records the Pkcs direct pin, the rejected
central transitive pin (NU1109), and that the weekly scan is red by design
on ImageSharp until then. The #511 record's MaxFrames measurement is dated
to 3.1.12 and no longer lists TIFF as in play.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1
The bounds in security.imagesharp-3x-tiff-decoder-disabled were prose:
they held only while every decode passed ImageSharpDecoding's options
and nothing touched ICC entries, the TIFF codec or histogram
equalization. A new decode site or accessor would have reopened an
advisory with no test going red.

ImageSharpSurfaceTests reads the IL of every production assembly that
can reach ImageSharp (derived from ErsatzTV.sln and the csproj graph,
so a project gaining the reference joins the population) and fails on a
decode entry point without DecoderOptions, a DecoderOptions built
outside ImageSharpDecoding, or any reference into the TIFF, ICC or
Normalization namespaces or a Tiff/HistogramEqualization member. The
host joins Architecture.Tests' references so it is scanned too.

The ImageSharp pin now carries a comment naming the hold and its record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1
security.imagesharp-3x-tiff-decoder-disabled now says the bounds are
enforced by ImageSharpSurfaceTests rather than by its own prose. The
"Load pass is masked" claim is dropped from the record and from the two
code comments that repeated it: with the architecture pin, reverting the
Load pass's options alone now reddens
Only_ImageSharpDecoding_constructs_DecoderOptions (witnessed
2026-10-27), so the claim had become false. The residual it cannot see,
an extension-dispatched Save(path), is named.

Renovate: a packageRule holds SixLabors.ImageSharp on 3.1.x. The
existing "range pins are respected" sentence in ci-cd.md and the EF
group's description were wrong: Dependency Dashboard #22 offers EF Core
v10 and MediatR 14.2.0 over their range pins, so a range does not hold
Renovate. Whether the rule holds the OSV security PR (#923) cannot be
measured before merge, and the record says so.

guard-inventory.md's hand-written C# guard list gains the new fixture
and its two Architecture.Tests siblings, and stops quoting a count.

This branch also edits the rationale prose of
ffmpeg.remote-image-fetcher-bounded (commit 73045865f: the MaxFrames
measurement is dated to 3.1.12 and TIFF is no longer in play), which
the decisions lifecycle check requires this trailer for.

Decisions-Edit: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1
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-89c1f70dd3e1
Round-3 review: the record said every bound is enforced over compiled code,
but half of the gwg2 (ICC CLUT) bound is ImageSharp 3.1.12's own runtime
behaviour, which the IL scan cannot see.

A 208-byte ICC profile declaring an mft2 CLUT (3 in, 15 out, 255 grid
points), carried in PNG, JPEG and WebP, now goes through
RemoteImageValidator.DecodeAndValidate + CloneFrame/Resize/CloneAs and
through a real remote WatermarkElement. Each case asserts the profile is
still attached and fails above 16 MiB allocated. The positive control reads
Entries on the same profile and must allocate over 64 MiB.

Measured 2026-10-28 on 3.1.12: the product paths allocate 38-94 KiB and
the element 322-390 KiB; Entries allocates about 129,550 KiB. Mutation
witnesses: a try/catch read of IccProfile.Entries injected after the
validator's post-decode check reddens all 6 product-path cases on the
allocation assertion. The same read after ImageElementBase's per-frame
Resize reddens the 3 element cases. Both were restored from git.

The record's gwg2 row and 'What holds the bounds' now name the two halves
and which test holds each. docs/testing.md names the ICC cases.

Decisions-Edit: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1
test(929): the ICC carrier list includes BMP and is checked against a round trip of every configured format
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 20s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
PR Gates / Docs update reminder (pull_request) Successful in 11s
PR Gates / decisions lifecycle (pull_request) Successful in 14s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 10s
Review verdict / Set review-verdict status (pull_request_target) Successful in 16s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 12m42s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m37s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m56s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
review-verdict/h10 Review-verdict: MERGEABLE @ 71d660f (base: main)
71d660f213
Round 4 review found two gaps in the gwg2 runtime pin. The carrier list
(png, jpeg, webp) was hand-written and left out BMP, whose 3.1.12
decoder also constructs an IccProfile (BmpDecoderCore::ReadInfoHeader)
and which remote-URL and on-disk graphics can reach. And the record
said every hostile-ICC case asserts the profile is still attached,
while the WatermarkElement case asserts only allocation (its images are
private).

- IccCarriers is now bmp, jpeg, png, webp, with a BmpEncoder arm.
- Icc_Carriers_Are_Every_Configured_Format_That_Round_Trips_A_Profile
  round-trips the hostile profile through each format of
  ImageSharpDecoding.Configuration via its default encoder and requires
  the formats that come back carrying a profile to equal IccCarriers.
  Witnessed red by dropping "bmp" from the list.
- A Cecil scan of the 3.1.12 package (2026-10-28) lists the decoders that
  construct an IccProfile as BMP, JPEG, PNG, WebP and TIFF, so the
  round trip's blind spot (a decoder reading a profile its encoder never
  writes) is empty today; the record says so, dated.
- The record and the element test's comment now say only the validator
  cases assert attachment; the element case shares their fixture.

Measured: product path 38-94 KiB, element 368-834 KiB, Entries control
about 129,545 KiB for all four carriers.

Decisions-Edit: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: f9ce6bc6-5b36-491a-8d72-89c1f70dd3e1
timothy force-pushed 929-vuln-pkcs-imagesharp-v4 from bd24d21e23 to 71d660f213 2026-10-30 00:03:55 +01:00 Compare
Author
Owner

Review-verdict: MERGEABLE @ 71d660f

Orchestrator verdict at 71d660f21 (a pure rebase of bd24d21e2 onto 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).

Review-verdict: MERGEABLE @ 71d660f Orchestrator verdict at 71d660f21 (a pure rebase of bd24d21e2 onto 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).
All checks were successful
Build ErsatzTV Image / CI toolchain image resolves (pull_request) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (pull_request) Successful in 20s
PR Gates / CI image pin matches docker/ci (pull_request) Successful in 9s
PR Gates / Docs update reminder (pull_request) Successful in 11s
PR Gates / decisions lifecycle (pull_request) Successful in 14s
PR Gates / Fix proofs (Proves trailers) (pull_request) Successful in 10s
Review verdict / Set review-verdict status (pull_request_target) Successful in 16s
PR Gates / Script lint and tests (ruff + pytest) (pull_request) Successful in 12m42s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m37s
Required
Details
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 6m0s
Required
Details
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Skipped
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (pull_request) Successful in 5m56s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 8s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 7s
review-verdict/h10 Review-verdict: MERGEABLE @ 71d660f (base: main)
Required
Details
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 929-vuln-pkcs-imagesharp-v4:929-vuln-pkcs-imagesharp-v4
git checkout 929-vuln-pkcs-imagesharp-v4
Sign in to join this conversation.