docs(929): record why ImageSharp stays on 3.1.12 and what bounds each advisory
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
This commit is contained in:
@@ -60,7 +60,9 @@ public class ImageSharpDecodingTests
|
||||
image.Height.ShouldBe(32);
|
||||
}
|
||||
|
||||
// pins the DECODE pass of RemoteImageValidator (render path, save path and upload path)
|
||||
// the validator as a whole (render path, save path and upload path). The Identify pass rejects
|
||||
// first, so the Load pass's own configuration is MASKED here: reverting only that one leaves
|
||||
// this fixture green. The header test below is what pins the Identify pass.
|
||||
[Test]
|
||||
public async Task DecodeAndValidate_Rejects_A_Tiff_As_An_Unknown_Format()
|
||||
{
|
||||
|
||||
@@ -42,6 +42,8 @@ public class RemoteImageValidator : IRemoteImageValidator
|
||||
// Without this, adding the header pre-pass would silently disable every animated-PNG logo
|
||||
// that worked before this change. Only Width/Height are read below, and those stay correct.
|
||||
// Both passes use ImageSharpDecoding's configuration, which cannot detect TIFF (ersatztv#929).
|
||||
// This pass is the one that rejects a TIFF, so the decode pass's identical configuration is
|
||||
// masked by it in tests; it is kept so the two passes always agree on which formats exist.
|
||||
ImageInfo info = await Image.IdentifyAsync(
|
||||
ImageSharpDecoding.Options(maxFrames: 1),
|
||||
stream,
|
||||
|
||||
+15
-4
@@ -2594,7 +2594,16 @@ The advisories that prompted this were resolved in ersatztv#8 (NCalcSync→6.x;
|
||||
bundle 3.x) and ersatztv#314 (**Microsoft.OpenApi 2.0.0→2.7.5**, GHSA-v5pm-xwqc-g5wc High —
|
||||
direct-pinned in `ErsatzTV.csproj` over the 2.0.0 that `Microsoft.AspNetCore.OpenApi` +
|
||||
`Scalar.AspNetCore` pull transitively; the SQLitePCLRaw override pattern; regenerates the
|
||||
OpenAPI doc byte-identically). The NU1901-1903 demotion is **kept by design**: criticals (NU1904)
|
||||
OpenAPI doc byte-identically) and ersatztv#929 (**System.Security.Cryptography.Pkcs 9.0.4→9.0.21**,
|
||||
GHSA-846f-f764-76mx High — direct-referenced in `ErsatzTV.Infrastructure.csproj`, the only project
|
||||
that references `EFCore.BulkExtensions`, whose Oracle/SqlServer providers pull it in). **Override a
|
||||
transitive with a direct reference, not `CentralPackageTransitivePinningEnabled`**: the central
|
||||
transitive pin was measured 2026-10-27 to fail restore with NU1109 in every project, because it pins
|
||||
every transitive that has a `<PackageVersion>`, and the exact `Microsoft.Extensions.DependencyModel`
|
||||
`[8.0.2]` sits below the `>= 9.0.x` that EF Core requires. **SixLabors.ImageSharp is held at 3.1.12**:
|
||||
every patched release fails a Release build without a Six Labors license key — rationale, the
|
||||
per-advisory bounds and the TIFF decoder exclusion are in `security.imagesharp-3x-tiff-decoder-disabled`.
|
||||
The NU1901-1903 demotion is **kept by design**: criticals (NU1904)
|
||||
still hard-block, while low/moderate/high advisories surface as warnings + via the weekly scan and
|
||||
Renovate security PRs, rather than breaking unrelated PRs the moment a new transitive
|
||||
advisory drops.
|
||||
@@ -2607,9 +2616,11 @@ greps for the "has the following vulnerable packages" marker and fails the run i
|
||||
Detection only — it surfaces advisories on a schedule, a Gitea-native stand-in for
|
||||
Dependabot; it does **not** open update PRs (that's Renovate — server-management#484).
|
||||
Gitea registers `schedule` triggers only from the default branch, so the cron starts
|
||||
after merge to `main`; use `workflow_dispatch` to run on demand. It went **green** once
|
||||
ersatztv#8 cleared the NCalcSync/SQLitePCLRaw advisories — a red run now means a **new**
|
||||
advisory has appeared. (ersatztv#14, ersatztv#8)
|
||||
after merge to `main`; use `workflow_dispatch` to run on demand. Since 2026-10-27 it is **red by
|
||||
design on `SixLabors.ImageSharp` 3.1.12** until a Six Labors license key allows the patched line
|
||||
(`security.imagesharp-3x-tiff-decoder-disabled`); `dotnet list` ignores `NuGetAuditSuppress`, so
|
||||
the run cannot be greened by suppression. Read a red run's log for a package OTHER than ImageSharp:
|
||||
that is a new advisory. (ersatztv#14, ersatztv#8, ersatztv#929)
|
||||
|
||||
**Renovate (automated update PRs)** — `.gitea/workflows/renovate.yml` runs self-hosted
|
||||
[Renovate](https://docs.renovatebot.com) weekly (cron `0 3 * * 1`) + on `workflow_dispatch`,
|
||||
|
||||
@@ -184,6 +184,7 @@ the link for rationale. Superseded/retired history lives in `archive/`. Regenera
|
||||
| `security.corp-same-origin` | `SecurityHeadersMiddleware` sends `Cross-Origin-Resource-Policy: same-origin` on every response including `/docs`/`/openapi`, blocking cross-origin `no-cors` embedding without affecting allowed CORS-mode fetches or server-side Jellyfin `/iptv/*` requests. | 2026-07-13 | [link](records/security/corp-same-origin.md) |
|
||||
| `security.csp-permissions-policy` | `SecurityHeadersMiddleware` sends an enforcing (not report-only) `Content-Security-Policy` (no `unsafe-inline`/`unsafe-eval`; the one inline theme-bootstrap script allow-listed by hash) and a deny-all `Permissions-Policy` on the SPA/`/api`/`/artwork`/`/iptv`; `/docs` and `/openapi` keep only the baseline headers, excluded from CSP because Scalar needs inline bootstrap. | 2026-07-12 | [link](records/security/csp-permissions-policy.md) |
|
||||
| `security.fail-closed-api-auth` | Every mutating `/api` request requires `X-Api-Key` (no open mode); reads are gated by `Api:RequireKeyForReads` (default true) OR `[RequiresApiKey]` on sensitive controllers; CORS is an exact-origin allowlist (`ApiCors`); `ForwardedHeaders` trust stays configurable but defaults to trust-all-with-warning. | 2026-07-12 | [link](records/security/fail-closed-api-auth.md) |
|
||||
| `security.imagesharp-3x-tiff-decoder-disabled` | SixLabors.ImageSharp stays on 3.1.12 until a Six Labors license key is provisioned for CI and the image build. Every ImageSharp decode (Identify and Load, remote and local) passes `ImageSharpDecoding.Options(...)`, whose configuration registers every format `Configuration.Default` does EXCEPT TIFF; no code calls `IccProfile.Entries`, a TIFF encoder or `HistogramEqualization`. Revisit trigger: a provisioned key — then move to the patched line and re-measure the #511 decoder behaviours. | 2026-10-27 | [link](records/security/imagesharp-3x-tiff-decoder-disabled.md) |
|
||||
| `security.iptv-access-token-transport` | The `/iptv` `?access_token=` value is percent-encoded (`Uri.EscapeDataString`) everywhere it is interpolated into an M3U/HLS/XMLTV URL (XMLTV additionally XML-escapes the encoded value), so a structural character can't malform the manifest or guide; Serilog logs a scrubbed request path (`access_token` → `***` via `IncludeQueryInRequestPath = false` + a `RequestPathScrubbed` enricher), so a 5xx/Debug `/iptv` request never writes the token; and every dynamic token-bearing `/iptv` manifest (`channels.m3u`, `xmltv.xml`, the HLS multi-variant/media playlists) returns `Cache-Control: private, no-store`. | 2026-07-23 | [link](records/security/iptv-access-token-transport.md) |
|
||||
| `security.iptv-browser-token` | Under a JWT-enabled deployment (`JWT:IssuerSigningKey` set), the browser SPA obtains a short-lived, globally-scoped `/iptv/*` access token from an authenticated `GET /api/v1/auth/iptv-token` and appends it as `?access_token=`; the endpoint answers 204 when JWT is disabled (nothing to mint). Lifetime defaults to 60 min, configurable via `JWT:BrowserTokenLifetimeMinutes`. | 2026-07-22 | [link](records/security/iptv-browser-token.md) |
|
||||
| `security.session-auth-dual-credential` | `ApiAuthorizationFilter` accepts a request when a valid `X-Api-Key` matches OR the principal is an authenticated session (cookie `ctv-session`, `HttpOnly`/`SameSite=Lax`); session-authenticated mutations require the presence-only `X-CSRF` header or are rejected 403. This narrows the OIDC-inert sub-claim of `security.blazor-removal-auth-posture` (#206) — the rest of that record's auth-surface enumeration still holds. | 2026-07-12 | [link](records/security/session-auth-dual-credential.md) |
|
||||
|
||||
@@ -57,7 +57,8 @@ image descriptor exceeds its logical screen is clamped by the decoder, verified)
|
||||
frames of that size the budget affords; passes that to **`DecoderOptions.MaxFrames`**, which the
|
||||
decoder enforces regardless of what the header claimed; and then **re-verifies the real
|
||||
`image.Frames.Count`** after decoding, disposing and rejecting if it is over. `MaxFrames` was
|
||||
measured as honored by every animated decoder in play (APNG, GIF, WebP, TIFF), which is what makes
|
||||
measured on 3.1.12 as honored by every animated decoder then in play (APNG, GIF, WebP, TIFF; TIFF is
|
||||
no longer decodable at all — `security.imagesharp-3x-tiff-decoder-disabled`), which is what makes
|
||||
it a real bound rather than another advisory one. The code asks for `affordable + 2` so an
|
||||
animation exactly at the limit still decodes in full while anything over it is visible to the
|
||||
post-decode check; the slop is at most two frames, since `MaxFrames = N` yields `N` frames for
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
key: security.imagesharp-3x-tiff-decoder-disabled
|
||||
title: 2026-10-27 — ImageSharp stays on 3.1.12 because every patched release needs a license key to build in Release; every ImageSharp decode goes through a TIFF-blind configuration (#929)
|
||||
status: active
|
||||
since: '2026-10-27'
|
||||
supersedes: none
|
||||
superseded-by: none
|
||||
rule: 'SixLabors.ImageSharp stays on 3.1.12 until a Six Labors license key is provisioned for CI and the image build. Every ImageSharp decode (Identify and Load, remote and local) passes `ImageSharpDecoding.Options(...)`, whose configuration registers every format `Configuration.Default` does EXCEPT TIFF; no code calls `IccProfile.Entries`, a TIFF encoder or `HistogramEqualization`. Revisit trigger: a provisioned key — then move to the patched line and re-measure the #511 decoder behaviours.'
|
||||
signals: 'ImageSharp upgrade, ImageSharp 4, Six Labors license key, SixLaborsLicenseKey, No Six Labors license found, Release build fails, Renovate ImageSharp PR, vulnerable ImageSharp, TIFF decoder disabled, UnknownImageFormatException, ICC profile, dotnet list package --vulnerable, NuGetAuditSuppress · paths: `ErsatzTV.Infrastructure/Images/ImageSharpDecoding.cs`, `RemoteImageValidator.DecodeAndValidate`, `ImageElementBase.LoadImage`, `ImageSharpDecodingTests`, `Directory.Packages.props` · issues: #929, #923, #511, #525, #924'
|
||||
mechanics: '`ImageSharpDecoding.Configuration` is a fresh `Configuration` built from the format modules, NOT `Configuration.Default.Clone()` — Clone is shallow, so removing a detector from it would remove it from Default for the whole process'
|
||||
---
|
||||
|
||||
**Why 3.x is kept.** The ImageSharp releases that patch the October 2026 advisories — 3.2.0 on the
|
||||
3.x line, 4.1.2 and 4.1.3 on 4.x — ship an MSBuild target
|
||||
(`SixLabors_ValidateLicense`, before `CoreCompile`) that fails a non-Debug build unless
|
||||
`SixLaborsLicenseKey`, `SixLaborsLicenseFile` or a `sixlabors.lic` is present. Measured 2026-10-27: `dotnet build ErsatzTV.Infrastructure -c Release` errors with
|
||||
`No Six Labors license found` on 3.2.0 and on 4.1.3; a Debug build only warns. The 4.0.0 and 4.1.2
|
||||
packages carry the same `SixLabors.Licensing` task; 3.1.12 is the newest release without it. CI's `test`/`build`
|
||||
jobs and the Docker image all build Release, so a bare version bump (Renovate #923) turns every PR
|
||||
red. A key is application-based (Six Labors offers a free Community licence below USD 1M revenue),
|
||||
so obtaining one is an owner decision, not a code change. The licence TEXT is unchanged across
|
||||
3.1.12/3.2.0/4.1.3 (Six Labors Split License 1.0, byte-identical) — the build gate is what is new.
|
||||
|
||||
Disabling the target, or setting a placeholder key, is **rejected**: it defeats a licence check
|
||||
rather than satisfying it.
|
||||
|
||||
**What bounds each 3.1.12 advisory** (`dotnet list package --vulnerable --include-transitive`,
|
||||
2026-10-27; all fixed in 3.2.0):
|
||||
|
||||
| Advisory | Where the defect is | Why ErsatzTV does not reach it |
|
||||
|---|---|---|
|
||||
| GHSA-v76p-62qx-wwq2 (High) | TIFF decoder, tiled fax heap OOB write | TIFF is not detectable by `ImageSharpDecoding.Configuration`; a TIFF throws `UnknownImageFormatException` before any TIFF code runs |
|
||||
| GHSA-wmxv-xphr-5c9g (Moderate) | TIFF decoder, BigTIFF IFD loop | same — and the Identify pass, which walks the IFD, is the pass the tests pin |
|
||||
| GHSA-jjfr-hcj7-qf5w, GHSA-j9gm-c75j-xc9q (High) | TIFF CCITT T6/T4 **encoder** | production code never encodes TIFF (only test fixtures do) |
|
||||
| GHSA-j3p4-wp97-rph4 (High) | `HistogramEqualization` processor | never called; the only processor is `Resize` |
|
||||
| GHSA-gwg2-r3hj-4w44 (Moderate) | ICC CLUT parser | on 3.x reached only through `ImageMetadata.IccProfile.Entries`, which nothing reads; decode alone does not parse the CLUT |
|
||||
|
||||
The ImageSharp decode surface is exactly `RemoteImageValidator.DecodeAndValidate` (remote logos at
|
||||
render time, logo URLs saved via #525, uploaded artwork) and the local-path load in
|
||||
`ImageElementBase.LoadImage`. Artwork elsewhere goes through SkiaSharp, and uploads are already
|
||||
clamped to PNG/JPEG/GIF/WebP by `ImageContentTypes`, so the only input this rejects is a remote-URL
|
||||
or on-disk TIFF graphic, which now disables its element the same way an unreachable URL does.
|
||||
|
||||
**What the tests prove, and what they do not.** `ImageSharpDecodingTests` derives the expected
|
||||
format list from `Configuration.Default` minus TIFF, so a package bump that adds a format reddens
|
||||
rather than silently narrowing the decoder; a header-only TIFF declaring 30000x30000 pins the
|
||||
Identify pass (a TIFF-aware Identify would report the size and fail the pixel budget instead). The
|
||||
watermark fixture pins the local and remote element paths with PNG twins. The Load pass's own
|
||||
configuration is MASKED: Identify rejects first, so reverting only the Load options leaves the
|
||||
suite green (measured 2026-10-27). It is kept so both passes agree on the format set.
|
||||
|
||||
**The scheduled scan stays red on ImageSharp until the revisit.** `NuGetAuditSuppress` silences the
|
||||
NU1902/NU1903 restore warnings but `dotnet list package --vulnerable` ignores it (measured
|
||||
2026-10-27: a suppressed GHSA still listed), so suppression would hide the advisory from build logs
|
||||
without making `dependency-scan.yml` green. None is added; the advisories stay visible.
|
||||
|
||||
**On revisit.** With a key provisioned (a CI secret passed as `SixLaborsLicenseKey`, and the same
|
||||
for the Docker build), move to the patched line. The #511 measurements in
|
||||
`ffmpeg.remote-image-fetcher-bounded` (APNG `FrameMetadataCollection.Count == 0`, the default
|
||||
Identify throwing on APNGs, `MaxFrames` honoured per format) were taken on 3.1.12 and must be
|
||||
re-measured on the new version before the workaround comments are trusted. Whether TIFF comes back
|
||||
is a separate call: nothing in the product needs it.
|
||||
Reference in New Issue
Block a user