From 01647b6e500f4d3e11adfc23cd0fb69648024e12 Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 27 Jun 2026 02:03:11 +0200 Subject: [PATCH] fix(deps): clear NCalcSync and SQLitePCLRaw vulnerability advisories NCalcSync 5.11.0 -> 6.3.2 clears CVE-2026-55254 / GHSA-3w5p-95mh-gq75 (the factorial-DoS advisory on NCalc.Core/NCalcSync). NCalc 6 split its assemblies and renamed the custom-function API, so port OpacityExpressionHelper: FunctionArgs -> FunctionEventArgs, and args.Parameters[i].Evaluate() -> args.Parameters.Evaluate(i) (FunctionData.Count / Evaluate(index)). Add a regression test covering the migrated opacity wiring (the feature had no tests). NCalc 6 transitively requires Microsoft.Extensions.Logging.Abstractions >= 10.0.7, so bump the centrally-pinned Microsoft.Extensions.* family 10.0.2 -> 10.0.7 to avoid the NU1605 downgrade error (a .NET 10 servicing patch bump). SQLitePCLRaw: EF Core 9's Sqlite provider pulls the vulnerable bundle 2.1.10 (GHSA-2m69-gcr7-jv3q, outdated bundled SQLite). Directly pin SQLitePCLRaw.bundle_e_sqlite3 3.0.3 in Infrastructure.Sqlite to override the transitive version with the patched native (lib.e_sqlite3 3.50.3); core 3.0.3 satisfies Microsoft.Data.Sqlite's >= 2.1.10 requirement under EF Core 9. Verified: `dotnet list package --vulnerable --include-transitive` reports 0 vulnerable projects; restore + Release build clean; full test suite green under UTC. (2 pre-existing PlayoutModeSchedulerBase filler tests fail only under non-UTC local timezones, unrelated to these deps; they pass in CI.) Refs #8 Co-Authored-By: Claude Opus 4.8 --- Directory.Packages.props | 28 +++++----- .../ErsatzTV.Infrastructure.Sqlite.csproj | 3 ++ .../Graphics/OpacityExpressionHelperTests.cs | 52 +++++++++++++++++++ .../Graphics/OpacityExpressionHelper.cs | 24 ++++----- 4 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index a3750c7fd..560487eb0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -44,24 +44,24 @@ - - - - - - + + + + + + - - - - - + + + + + - + @@ -90,6 +90,10 @@ + + diff --git a/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj b/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj index d1cabae48..c42756d17 100644 --- a/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj +++ b/ErsatzTV.Infrastructure.Sqlite/ErsatzTV.Infrastructure.Sqlite.csproj @@ -16,6 +16,9 @@ + + diff --git a/ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs b/ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs new file mode 100644 index 000000000..f98357fba --- /dev/null +++ b/ErsatzTV.Infrastructure.Tests/Streaming/Graphics/OpacityExpressionHelperTests.cs @@ -0,0 +1,52 @@ +using ErsatzTV.Infrastructure.Streaming.Graphics; +using NCalc; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Infrastructure.Tests.Streaming.Graphics; + +[TestFixture] +public class OpacityExpressionHelperTests +{ + // Exercises the NCalc custom-function wiring (FunctionEventArgs / FunctionData under NCalc 6.x) + // exactly the way the graphics elements do: build an Expression, hook EvaluateFunction, evaluate. + private static float Evaluate(string expressionString, TimeSpan timeOfDay) + { + var expression = new Expression(expressionString); + expression.EvaluateFunction += OpacityExpressionHelper.EvaluateFunction; + return OpacityExpressionHelper.GetOpacity( + expression, + timeOfDay, + TimeSpan.Zero, + TimeSpan.Zero, + TimeSpan.Zero); + } + + // LinearFadePoints(time, start=0, peakStart=10, peakEnd=20, end=30) + [TestCase(5, 0.5)] // fade in: (5-0)/(10-0) + [TestCase(15, 1.0)] // solid + [TestCase(25, 0.5)] // fade out: (30-25)/(30-20) + [TestCase(35, 0.0)] // past end + public void LinearFadePoints_ReturnsExpectedOpacity(int timeOfDaySeconds, double expected) + { + float opacity = Evaluate( + "LinearFadePoints(time_of_day_seconds, 0, 10, 20, 30)", + TimeSpan.FromSeconds(timeOfDaySeconds)); + + opacity.ShouldBe((float)expected, 0.0001); + } + + // LinearFadeDuration(time, start=0, fadeSeconds=10, peakSeconds=10) + // -> peakStart=10, peakEnd=20, end=30 (same curve as the LinearFadePoints case above) + [TestCase(5, 0.5)] // mid fade in + [TestCase(15, 1.0)] // solid + [TestCase(25, 0.5)] // mid fade out + public void LinearFadeDuration_ReturnsExpectedOpacity(int timeOfDaySeconds, double expected) + { + float opacity = Evaluate( + "LinearFadeDuration(time_of_day_seconds, 0, 10, 10)", + TimeSpan.FromSeconds(timeOfDaySeconds)); + + opacity.ShouldBe((float)expected, 0.0001); + } +} diff --git a/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs b/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs index 93a82ea3f..3b9f07c53 100644 --- a/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs +++ b/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs @@ -6,37 +6,37 @@ namespace ErsatzTV.Infrastructure.Streaming.Graphics; public static class OpacityExpressionHelper { - public static void EvaluateFunction(string name, FunctionArgs args) + public static void EvaluateFunction(string name, FunctionEventArgs args) { switch (name) { case "LinearFadePoints": { - if (args.Parameters.Length != 5) + if (args.Parameters.Count != 5) { throw new ArgumentException("LinearFadePoints() requires 5 arguments."); } - var time = Convert.ToDouble(args.Parameters[0].Evaluate(), CultureInfo.CurrentCulture); - var start = Convert.ToDouble(args.Parameters[1].Evaluate(), CultureInfo.CurrentCulture); - var peakStart = Convert.ToDouble(args.Parameters[2].Evaluate(), CultureInfo.CurrentCulture); - var peakEnd = Convert.ToDouble(args.Parameters[3].Evaluate(), CultureInfo.CurrentCulture); - var end = Convert.ToDouble(args.Parameters[4].Evaluate(), CultureInfo.CurrentCulture); + var time = Convert.ToDouble(args.Parameters.Evaluate(0), CultureInfo.CurrentCulture); + var start = Convert.ToDouble(args.Parameters.Evaluate(1), CultureInfo.CurrentCulture); + var peakStart = Convert.ToDouble(args.Parameters.Evaluate(2), CultureInfo.CurrentCulture); + var peakEnd = Convert.ToDouble(args.Parameters.Evaluate(3), CultureInfo.CurrentCulture); + var end = Convert.ToDouble(args.Parameters.Evaluate(4), CultureInfo.CurrentCulture); args.Result = LinearFadePoints(time, start, peakStart, peakEnd, end); break; } case "LinearFadeDuration": { - if (args.Parameters.Length != 4) + if (args.Parameters.Count != 4) { throw new ArgumentException("LinearFadeDuration() requires 4 arguments."); } - var time = Convert.ToDouble(args.Parameters[0].Evaluate(), CultureInfo.CurrentCulture); - var start = Convert.ToDouble(args.Parameters[1].Evaluate(), CultureInfo.CurrentCulture); - var fadeSeconds = Convert.ToDouble(args.Parameters[2].Evaluate(), CultureInfo.CurrentCulture); - var peakSeconds = Convert.ToDouble(args.Parameters[3].Evaluate(), CultureInfo.CurrentCulture); + var time = Convert.ToDouble(args.Parameters.Evaluate(0), CultureInfo.CurrentCulture); + var start = Convert.ToDouble(args.Parameters.Evaluate(1), CultureInfo.CurrentCulture); + var fadeSeconds = Convert.ToDouble(args.Parameters.Evaluate(2), CultureInfo.CurrentCulture); + var peakSeconds = Convert.ToDouble(args.Parameters.Evaluate(3), CultureInfo.CurrentCulture); args.Result = LinearFadeDuration(time, start, fadeSeconds, peakSeconds); break; -- 2.47.3