docs(929): the record names what holds each bound, and Renovate is held
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
This commit is contained in:
@@ -61,8 +61,8 @@ public class ImageSharpDecodingTests
|
||||
}
|
||||
|
||||
// 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.
|
||||
// a TIFF before the Load pass runs, so this exercises Identify; that the Load pass takes the
|
||||
// same options is held by ErsatzTV.Architecture.Tests' ImageSharpSurfaceTests.
|
||||
[Test]
|
||||
public async Task DecodeAndValidate_Rejects_A_Tiff_As_An_Unknown_Format()
|
||||
{
|
||||
|
||||
@@ -23,7 +23,9 @@ namespace ErsatzTV.Infrastructure.Images;
|
||||
/// TIFF graphic. It is a fresh instance rather than a <c>Configuration.Default.Clone()</c> because
|
||||
/// Clone is shallow: removing a detector from the clone would remove it from Default too.
|
||||
/// <c>ImageSharpDecodingTests</c> pins the module list against <c>Configuration.Default</c>, so a
|
||||
/// package bump that adds a format fails a test instead of silently dropping it.
|
||||
/// package bump that adds a format fails a test instead of silently dropping it, and
|
||||
/// <c>ImageSharpSurfaceTests</c> (ErsatzTV.Architecture.Tests) fails any production decode that does not
|
||||
/// take these options.
|
||||
/// Rationale and revisit trigger: <c>security.imagesharp-3x-tiff-decoder-disabled</c>. (ersatztv#929)
|
||||
/// </remarks>
|
||||
public static class ImageSharpDecoding
|
||||
|
||||
@@ -42,8 +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.
|
||||
// This pass is the one that rejects a TIFF; the decode pass takes the same configuration so
|
||||
// the two always agree on which formats exist (held by ImageSharpSurfaceTests).
|
||||
ImageInfo info = await Image.IdentifyAsync(
|
||||
ImageSharpDecoding.Options(maxFrames: 1),
|
||||
stream,
|
||||
|
||||
+5
-2
@@ -2638,8 +2638,11 @@ github.com PAT for changelogs — named `GH_`, **not** `GITHUB_`, a prefix Gitea
|
||||
**Patch** bumps to test/dev-only packages (NUnit\*, NSubstitute, Shouldly, coverlet,
|
||||
`Microsoft.NET.Test.Sdk`, `Testably.Abstractions*`, threading analyzer) **auto-merge** once
|
||||
the `Build & test (.NET)` check passes — branch protection on `main` requires that context;
|
||||
everything else is manual review (ersatztv is prod-bearing). Range-pinned packages (e.g. EF
|
||||
Core `[9.0.x,10)`) are respected — no v10 jump. PR volume is throttled (`prConcurrentLimit`
|
||||
everything else is manual review (ersatztv is prod-bearing). A version range in
|
||||
`Directory.Packages.props` does **not** hold Renovate back: the Dependency Dashboard (#22,
|
||||
2026-10-27) offers EF Core `[9.0.12,10)` → v10 and `MediatR [12.5.0]` → `[14.2.0]`. A package that
|
||||
must not move gets an `allowedVersions` packageRule in `renovate.json` (today: `SixLabors.ImageSharp`,
|
||||
`security.imagesharp-3x-tiff-decoder-disabled`). PR volume is throttled (`prConcurrentLimit`
|
||||
5 + `config:recommended`'s `prHourlyLimit` 2); tick a dashboard checkbox or raise the limits
|
||||
to drain faster. `workflow_dispatch` defaults to a safe **dry run**. Cross-repo rollout
|
||||
tracked in server-management#484. (server-management#484)
|
||||
|
||||
@@ -184,7 +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.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 references the TIFF codec, ICC profile types or the histogram-equalization processors. `ImageSharpSurfaceTests` enforces both over the IL of every production assembly that can reach ImageSharp. Revisit trigger: a provisioned key — then move to the patched line, drop the Renovate hold 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) |
|
||||
|
||||
@@ -5,8 +5,8 @@ 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'
|
||||
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 references the TIFF codec, ICC profile types or the histogram-equalization processors. `ImageSharpSurfaceTests` enforces both over the IL of every production assembly that can reach ImageSharp. Revisit trigger: a provisioned key — then move to the patched line, drop the Renovate hold 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`, `ErsatzTV.Architecture.Tests/ImageSharpSurfaceTests.cs`, `Directory.Packages.props`, `renovate.json` · 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'
|
||||
---
|
||||
|
||||
@@ -41,21 +41,43 @@ render time, logo URLs saved via #525, uploaded artwork) and the local-path load
|
||||
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.
|
||||
**What holds the bounds.** Every bound in the table is a property of how ErsatzTV calls
|
||||
ImageSharp, so each is enforced over the compiled code rather than left to review.
|
||||
`ImageSharpSurfaceTests` (`ErsatzTV.Architecture.Tests`) reads the IL of every non-test project in
|
||||
`ErsatzTV.sln` whose project-reference closure contains the ImageSharp package — the population is
|
||||
derived from the solution and csproj graph, and a project that joins it without being referenced
|
||||
by the test project fails the fixture. It rejects three shapes: an ImageSharp decode entry point
|
||||
(`Load*`, `Identify*`, `DetectFormat*`, `Decode*`) called without `DecoderOptions`, a
|
||||
`DecoderOptions` constructed outside `ImageSharpDecoding`, and any reference into the
|
||||
`Formats.Tiff`, `Metadata.Profiles.Icc` or `Processing.Processors.Normalization` namespaces, the
|
||||
`HistogramEqualizationExtensions` type, or an ImageSharp member whose name contains `Tiff` or
|
||||
`HistogramEqualization` (`ImageExtensions.SaveAsTiff` carries no TIFF type in its signature). Each
|
||||
check also asserts it sees something known to exist — the infrastructure and host assemblies in
|
||||
the population, the two decode sites, the one `DecoderOptions` construction, the `Resize` call
|
||||
inside a lambda — so an empty scan cannot pass.
|
||||
|
||||
`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 exercises the Identify pass (a TIFF-aware Identify would
|
||||
report the size and fail the pixel budget instead). The watermark fixture exercises the local and
|
||||
remote element paths with PNG twins. Identify rejects a TIFF before Load runs, so these
|
||||
behavioural fixtures exercise the Identify pass; that the Load pass uses the same options is held
|
||||
by `ImageSharpSurfaceTests`. One shape is outside every check: a `Save(path)` that picks its
|
||||
encoder from the file extension. There is no ImageSharp encode in production code (2026-10-27).
|
||||
|
||||
**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.
|
||||
|
||||
**Renovate is held too.** A `renovate.json` packageRule limits `SixLabors.ImageSharp` to `3.1.x`
|
||||
for ordinary update PRs. A version range in `Directory.Packages.props` would not: the Dependency
|
||||
Dashboard (#22, 2026-10-27) offers `MediatR [12.5.0]` → `[14.2.0]` and EF Core `[9.0.12,10)` → v10
|
||||
for the existing range pins. Whether the rule also holds Renovate's OSV security PR (#923) is
|
||||
unmeasured — Renovate reads its config from `main`, so the rule cannot be probed before merge.
|
||||
|
||||
**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
|
||||
for the Docker build), delete the Renovate rule and 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
|
||||
|
||||
@@ -67,7 +67,12 @@ with have sat in these gaps. Treat anything not named here as unassessed, not as
|
||||
the check proves each job was classified, never that the classification is CORRECT; and an
|
||||
`inline` assertion cannot be proven to work without running the job, so those rows carry
|
||||
`Proof: NONE` honestly instead of borrowing a neighbouring file's proof.
|
||||
2. **C# and TypeScript guards** — seven files, none with a row:
|
||||
2. **C# and TypeScript guards** — none with a row. The list is hand-written, so it is a floor and
|
||||
not the population:
|
||||
`ErsatzTV.Architecture.Tests/ImageSharpSurfaceTests.cs` (ersatztv#929 — derives the assemblies
|
||||
that can reach ImageSharp from `ErsatzTV.sln` and the csproj graph and rejects decode, TIFF, ICC
|
||||
and histogram-equalization call shapes in their IL), its siblings
|
||||
`ErsatzTV.Architecture.Tests/LayeringTests.cs` and `…/ProviderStaticsWiringTests.cs`,
|
||||
`ErsatzTV.Tests/Integration/SearchIndexMutationCoverageTests.cs` (ersatztv#824 — derives the
|
||||
`ISearchIndex` population from the declaring assembly and asserts set equality against a
|
||||
hand-written covered set),
|
||||
|
||||
+6
-1
@@ -41,7 +41,7 @@
|
||||
"groupName": "Microsoft.AspNetCore"
|
||||
},
|
||||
{
|
||||
"description": "Group Entity Framework Core + providers (held to v9 by the range pins in Directory.Packages.props)",
|
||||
"description": "Group Entity Framework Core + providers. The range pins in Directory.Packages.props hold the build to v9 but do not stop Renovate proposing v10 (docs/ci-cd.md, Renovate)",
|
||||
"matchPackageNames": [
|
||||
"/^Microsoft\\.EntityFrameworkCore/",
|
||||
"/^EFCore\\.BulkExtensions/",
|
||||
@@ -49,6 +49,11 @@
|
||||
],
|
||||
"groupName": "Entity Framework Core"
|
||||
},
|
||||
{
|
||||
"description": "Hold SixLabors.ImageSharp on 3.1.x: every patched release fails a Release build without a Six Labors license key. Delete this rule when the key is provisioned (security.imagesharp-3x-tiff-decoder-disabled, ersatztv#929)",
|
||||
"matchPackageNames": ["SixLabors.ImageSharp"],
|
||||
"allowedVersions": "/^3\\.1\\./"
|
||||
},
|
||||
{
|
||||
"description": "Group the Serilog family into one PR",
|
||||
"matchPackageNames": ["/^Serilog/"],
|
||||
|
||||
Reference in New Issue
Block a user