What changed, and why it matters
This commit upgrades BTCPay Server from .NET 8 to .NET 10 and bumps many third-party libraries. It also makes small code changes to adapt to new framework APIs. The update itself is a routine maintenance change, but it removes some older dependencies and includes a few minor hardening tweaks, such as adding a null check before processing payment requests and filtering swagger files by extension. There is no direct evidence in the commit that this fixes a known security vulnerability, and the commit message does not mention security.
Treat this as a routine major framework upgrade. Review the .NET 10 and dependency release notes for any security fixes included in the bumped versions, validate that the nullable SSH ExitStatus change does not break error handling, and run the full test suite before deploying. No immediate security patch action is indicated by the commit alone.
Security signals we found
Major framework and dependency version bump (.NET 8 -> .NET 10)
Removal of System.IO.Pipelines and System.Text.RegularExpressions explicit package references
Nullable ExitStatus handling in SSH.NET integration
Null guard added for PaymentRequestData in PaymentRequestService
Swagger file enumeration filtered to .json files only
No commit-level security disclosure or advisory reference present
Evidence from the diff
The commit migrates the project to .NET 10 (TargetFramework net10.0), updates Docker base images to .NET 10.0.101/10.0.1-noble, and bumps numerous NuGet packages including EF Core, ASP.NET Core, NBitcoin, Newtonsoft.Json, SSH.NET, MailKit, YamlDotNet, Serilog, and others. Code changes adapt to .NET 10 API differences: IWebHost is replaced by IHost/Host.CreateDefaultBuilder, WebHostExtensions is renamed to HostExtensions and adds GetServerFeatures
Changed components
BTCPayServer.AbstractionsBTCPayServer.ClientBTCPayServer.DataBTCPayServer.PluginPackerBTCPayServer.RatingBTCPayServer.TestsBTCPayServer (main app)BTCPayServer/Extensions/HostExtensions.cs (new)BTCPayServer/Extensions/WebHostExtensions.cs (deleted)BTCPayServer/Hosting/Startup.csBTCPayServer/Hosting/BTCPayServerServices.csBTCPayServer/Program.csBTCPayServer/PaymentRequest/PaymentRequestService.csBTCPayServer/Services/DefaultSwaggerProvider.csBTCPayServer/Services/Invoices/PosAppData.csDockerfileBTCPayServer.Tests/DockerfileInspect captured patch +251 / −177
diff --git a/BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj b/BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
index 2412e14..3858e4c 100644
--- a/BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
+++ b/BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
@@ -32,8 +32,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
- <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.11" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
diff --git a/BTCPayServer.Client/BTCPayServer.Client.csproj b/BTCPayServer.Client/BTCPayServer.Client.csproj
index b5485f4..b15906d 100644
--- a/BTCPayServer.Client/BTCPayServer.Client.csproj
+++ b/BTCPayServer.Client/BTCPayServer.Client.csproj
@@ -31,8 +31,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.2" />
- <PackageReference Include="NBitcoin" Version="9.0.0" />
- <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+ <PackageReference Include="NBitcoin" Version="9.0.4" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>
<ItemGroup>
<None Include="icon.png" Pack="true" PackagePath="\" />
diff --git a/BTCPayServer.Data/BTCPayServer.Data.csproj b/BTCPayServer.Data/BTCPayServer.Data.csproj
index 466b323..4c298c1 100644
--- a/BTCPayServer.Data/BTCPayServer.Data.csproj
+++ b/BTCPayServer.Data/BTCPayServer.Data.csproj
@@ -3,13 +3,13 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
- <PackageReference Include="NBitcoin.Altcoins" Version="5.0.0" />
- <PackageReference Include="Dapper" Version="2.1.35" />
+ <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.1" />
+ <PackageReference Include="NBitcoin.Altcoins" Version="5.0.1" />
+ <PackageReference Include="Dapper" Version="2.1.66" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
diff --git a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs
index 48a5cbb..f4a0134 100644
--- a/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/BTCPayServer.Data/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -18,7 +18,7 @@ namespace BTCPayServer.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "8.0.11")
+ .HasAnnotation("ProductVersion", "10.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@@ -280,7 +280,7 @@ namespace BTCPayServer.Migrations
.HasColumnName("additional_data")
.HasDefaultValueSql("'{}'::jsonb");
- b.Property<string[]>("BCC")
+ b.PrimitiveCollection<string[]>("BCC")
.IsRequired()
.HasColumnType("text[]")
.HasColumnName("bcc");
@@ -290,7 +290,7 @@ namespace BTCPayServer.Migrations
.HasColumnType("text")
.HasColumnName("body");
- b.Property<string[]>("CC")
+ b.PrimitiveCollection<string[]>("CC")
.IsRequired()
.HasColumnType("text[]")
.HasColumnName("cc");
@@ -325,7 +325,7 @@ namespace BTCPayServer.Migrations
.HasColumnType("text")
.HasColumnName("subject");
- b.Property<string[]>("To")
+ b.PrimitiveCollection<string[]>("To")
.IsRequired()
.HasColumnType("text[]")
.HasColumnName("to");
@@ -817,7 +817,7 @@ namespace BTCPayServer.Migrations
b.Property<DateTimeOffset?>("Expiry")
.HasColumnType("timestamp with time zone");
- b.Property<string[]>("OutpointsUsed")
+ b.PrimitiveCollection<string[]>("OutpointsUsed")
.HasColumnType("text[]");
b.Property<int>("State")
@@ -1023,7 +1023,7 @@ namespace BTCPayServer.Migrations
b.Property<string>("Id")
.HasColumnType("text");
- b.Property<List<string>>("Permissions")
+ b.PrimitiveCollection<List<string>>("Permissions")
.HasColumnType("text[]");
b.Property<string>("Role")
diff --git a/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj b/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj
index bf355f3..0021cc8 100644
--- a/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj
+++ b/BTCPayServer.PluginPacker/BTCPayServer.PluginPacker.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<Version>1.0.0.0</Version>
<PackAsTool>true</PackAsTool>
<ToolCommandName>btcpay-plugin</ToolCommandName>
diff --git a/BTCPayServer.Rating/BTCPayServer.Rating.csproj b/BTCPayServer.Rating/BTCPayServer.Rating.csproj
index 63cafba..454a487 100644
--- a/BTCPayServer.Rating/BTCPayServer.Rating.csproj
+++ b/BTCPayServer.Rating/BTCPayServer.Rating.csproj
@@ -4,12 +4,11 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
- <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
- <PackageReference Include="NBitcoin" Version="9.0.0" />
- <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
- <PackageReference Include="System.Text.Json" Version="8.0.5" />
- <PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.0" />
+ <PackageReference Include="NBitcoin" Version="9.0.4" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
+ <PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/BTCPayServer.Tests/BTCPayServer.Tests.csproj b/BTCPayServer.Tests/BTCPayServer.Tests.csproj
index 2d08fb9..12125a4 100644
--- a/BTCPayServer.Tests/BTCPayServer.Tests.csproj
+++ b/BTCPayServer.Tests/BTCPayServer.Tests.csproj
@@ -40,12 +40,12 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Microsoft.Playwright" Version="1.57.0" />
- <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.16" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
- <PackageReference Include="xunit" Version="2.9.2" />
- <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
+ <PackageReference Include="Newtonsoft.Json.Schema" Version="4.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.1" />
+ <PackageReference Include="xunit" Version="2.9.3" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs
index 25b3004..615bdf4 100644
--- a/BTCPayServer.Tests/BTCPayServerTester.cs
+++ b/BTCPayServer.Tests/BTCPayServerTester.cs
@@ -76,7 +76,7 @@ namespace BTCPayServer.Tests
get; set;
}
- IWebHost _Host;
+ IHost _Host;
public int Port
{
get; set;
@@ -180,37 +180,55 @@ namespace BTCPayServer.Tests
if (LoadPluginsInDefaultAssemblyContext)
confBuilder.AddInMemoryCollection([new("TEST_RUNNER_ENABLED", "true")]);
var conf = confBuilder.Build();
- _Host = new WebHostBuilder()
- .UseDefaultServiceProvider(options =>
- {
- options.ValidateScopes = true;
- })
- .UseEnvironment(HostEnvironment)
- .UseConfiguration(conf)
- .UseContentRoot(FindBTCPayServerDirectory())
- .UseWebRoot(Path.Combine(FindBTCPayServerDirectory(), "wwwroot"))
- .ConfigureServices(s =>
- {
- s.AddLogging(l =>
+ _Host = Host.CreateDefaultBuilder()
+ .UseDefaultServiceProvider(options =>
+ {
+ options.ValidateScopes = true;
+ })
+ .ConfigureAppConfiguration((hostingContext, configBuilder) =>
+ {
+ configBuilder.AddConfiguration(conf);
+ })
+ .ConfigureLogging(logging =>
+ {
+ // This matches your logging setup more directly than services.AddLogging(...)
+ logging.ClearProviders(); // optional: keep if you want full control
+
+ logging.SetMinimumLevel(LogLevel.Information);
+ logging.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
+ logging.AddFilter("Microsoft", LogLevel.Error);
+ logging.AddFilter("Microsoft.EntityFrameworkCore.Migrations", LogLevel.Information);
+ logging.AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error);
+
+ // If LoggerProvider is an ILoggerProvider instance:
+ logging.ClearProviders();
+ logging.AddProvider(LoggerProvider);
+
+ // If you removed ClearProviders(), also add back defaults you want (Console/EventSource/etc.)
+ // logging.AddConsole();
+ // logging.AddDebug();
+ })
+ .ConfigureSerilog(conf)
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ var root = FindBTCPayServerDirectory();
+
+ webBuilder
+ .UseContentRoot(root)
+ .UseWebRoot(Path.Combine(root, "wwwroot"))
+ .UseKestrel()
+ .UseStartup<Startup>()
+ .ConfigureServices(services =>
{
- l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
- l.SetMinimumLevel(LogLevel.Information)
- .AddFilter("Microsoft", LogLevel.Error)
- .AddFilter("Microsoft.EntityFrameworkCore.Migrations", LogLevel.Information)
- .AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error)
- .AddProvider(LoggerProvider);
+ services.TryAddSingleton<IFeeProviderFactory>(
+ new BTCPayServer.Services.Fees.FixedFeeProvider(new FeeRate(100L, 1)));
});
- })
- .ConfigureServices(services =>
- {
- services.TryAddSingleton<IFeeProviderFactory>(new BTCPayServer.Services.Fees.FixedFeeProvider(new FeeRate(100L, 1)));
- })
- .UseKestrel()
- .UseStartup<Startup>()
- .Build();
+ })
+ .UseEnvironment(HostEnvironment)
+ .Build();
await _Host.StartWithTasksAsync();
- var urls = _Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
+ var urls = _Host.GetServerFeatures<IServerAddressesFeature>().Addresses;
foreach (var url in urls)
{
TestLogs.LogInformation("Listening on " + url);
@@ -386,7 +404,12 @@ namespace BTCPayServer.Tests
public void Dispose()
{
if (_Host != null)
+ {
+ var app = _Host.Services.GetService<IHostApplicationLifetime>();
+ app.StopApplication();
+ _Host.WaitForShutdown();
_Host.Dispose();
+ }
}
public void ChangeRate(string pair, BidAsk bidAsk)
diff --git a/BTCPayServer.Tests/CustomerHttpServer.cs b/BTCPayServer.Tests/CustomerHttpServer.cs
index c70a5e6..ed97d7f 100644
--- a/BTCPayServer.Tests/CustomerHttpServer.cs
+++ b/BTCPayServer.Tests/CustomerHttpServer.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -14,40 +15,50 @@ namespace BTCPayServer.Tests
{
public class CustomServer : IDisposable
{
- readonly IWebHost _Host = null;
- readonly CancellationTokenSource _Closed = new CancellationTokenSource();
- readonly Channel<JObject> _Requests = Channel.CreateUnbounded<JObject>();
+ readonly IHost _host;
+ readonly CancellationTokenSource _closed = new CancellationTokenSource();
+ readonly Channel<JObject> _requests = Channel.CreateUnbounded<JObject>();
public CustomServer()
{
var port = Utils.FreeTcpPort();
- _Host = new WebHostBuilder()
- .Configure(app =>
+ _host = Host.CreateDefaultBuilder()
+ .ConfigureWebHostDefaults(webBuilder =>
{
- app.Run(async req =>
- {
- await _Requests.Writer.WriteAsync(JsonConvert.DeserializeObject<JObject>(await new StreamReader(req.Request.Body).ReadToEndAsync()), _Closed.Token);
- req.Response.StatusCode = 200;
- });
+ webBuilder
+ .UseKestrel()
+ .UseUrls($"http://127.0.0.1:{port}")
+ .Configure(app =>
+ {
+ app.Run(async req =>
+ {
+ using var reader = new StreamReader(req.Request.Body);
+ var body = await reader.ReadToEndAsync();
+
+ await _requests.Writer.WriteAsync(
+ JsonConvert.DeserializeObject<JObject>(body),
+ _closed.Token);
+
+ req.Response.StatusCode = 200;
+ });
+ });
})
- .UseKestrel()
- .UseUrls("http://127.0.0.1:" + port)
.Build();
- _Host.Start();
+ _host.Start();
}
public Uri GetUri()
{
- return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
+ return new Uri(_host.GetServerFeatures<IServerAddressesFeature>().Addresses.First());
}
public async Task<JObject> GetNextRequest()
{
- using CancellationTokenSource cancellation = new CancellationTokenSource(2000000);
+ using var cancellation = new CancellationTokenSource(2000000);
try
{
- JObject req = null;
- while (!await _Requests.Reader.WaitToReadAsync(cancellation.Token) ||
- !_Requests.Reader.TryRead(out req))
+ JObject req;
+ while (!await _requests.Reader.WaitToReadAsync(cancellation.Token) ||
+ !_requests.Reader.TryRead(out req))
{
}
@@ -61,8 +72,8 @@ namespace BTCPayServer.Tests
public void Dispose()
{
- _Closed.Cancel();
- _Host.Dispose();
+ _closed.Cancel();
+ _host.Dispose();
}
}
}
diff --git a/BTCPayServer.Tests/Dockerfile b/BTCPayServer.Tests/Dockerfile
index 0a6d210..f139182 100644
--- a/BTCPayServer.Tests/Dockerfile
+++ b/BTCPayServer.Tests/Dockerfile
@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0.404-bookworm-slim AS builder
+FROM mcr.microsoft.com/dotnet/sdk:10.0.101-noble AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*
diff --git a/BTCPayServer.Tests/FakeServer.cs b/BTCPayServer.Tests/FakeServer.cs
index a9692a2..93d5485 100644
--- a/BTCPayServer.Tests/FakeServer.cs
+++ b/BTCPayServer.Tests/FakeServer.cs
@@ -3,19 +3,17 @@ using System.Linq;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
-using ExchangeSharp;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BTCPayServer.Tests
{
public class FakeServer : IDisposable
{
- IWebHost webHost;
+ IHost host;
readonly SemaphoreSlim semaphore;
readonly CancellationTokenSource cts = new CancellationTokenSource();
public FakeServer()
@@ -27,20 +25,24 @@ namespace BTCPayServer.Tests
readonly Channel<HttpContext> _channel;
public async Task Start()
{
- webHost = new WebHostBuilder()
- .UseKestrel()
- .UseUrls("http://127.0.0.1:0")
- .Configure(appBuilder =>
- {
- appBuilder.Run(async ctx =>
+ host = Host.CreateDefaultBuilder()
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder
+ .UseKestrel()
+ .UseUrls("http://127.0.0.1:0")
+ .Configure(app =>
{
- await _channel.Writer.WriteAsync(ctx);
- await semaphore.WaitAsync(cts.Token);
+ app.Run(async ctx =>
+ {
+ await _channel.Writer.WriteAsync(ctx);
+ await semaphore.WaitAsync(cts.Token);
+ });
});
- })
- .Build();
- await webHost.StartAsync();
- var port = new Uri(webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First(), UriKind.Absolute)
+ })
+ .Build();
+ await host.StartAsync();
+ var port = new Uri(host.GetServerFeatures<IServerAddressesFeature>().Addresses.First(), UriKind.Absolute)
.Port;
ServerUri = new Uri($"http://127.0.0.1:{port}/");
}
@@ -54,12 +56,12 @@ namespace BTCPayServer.Tests
public async Task Stop()
{
- await webHost.StopAsync();
+ await host.StopAsync();
}
public void Dispose()
{
cts.Dispose();
- webHost?.Dispose();
+ host?.Dispose();
semaphore.Dispose();
}
diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj
index e3662bd..9cd8607 100644
--- a/BTCPayServer/BTCPayServer.csproj
+++ b/BTCPayServer/BTCPayServer.csproj
@@ -51,40 +51,38 @@
<ItemGroup>
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.25" />
- <PackageReference Include="JetBrains.Annotations.Sources" Version="2025.2.2">
+ <PackageReference Include="JetBrains.Annotations.Sources" Version="2025.2.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="NBitcoin" Version="9.0.0" />
- <PackageReference Include="YamlDotNet" Version="8.0.0" />
+ <PackageReference Include="NBitcoin" Version="9.0.4" />
+ <PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="BIP78.Sender" Version="0.2.5" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.6" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.6.13" />
- <PackageReference Include="CsvHelper" Version="32.0.3" />
+ <PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="Fido2" Version="4.0.0" />
<PackageReference Include="Fido2.AspNet" Version="4.0.0" />
<PackageReference Include="LNURL" Version="0.0.36" />
- <PackageReference Include="MailKit" Version="4.8.0" />
- <PackageReference Include="QRCoder" Version="1.6.0" />
- <PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
+ <PackageReference Include="MailKit" Version="4.14.1" />
+ <PackageReference Include="QRCoder" Version="1.7.0" />
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
- <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NicolasDorier.CommandLine" Version="2.0.0" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="2.0.0" />
<PackageReference Include="NicolasDorier.RateLimits" Version="1.2.3" />
- <PackageReference Include="Serilog" Version="3.1.1" />
- <PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
- <PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00968" />
- <PackageReference Include="SSH.NET" Version="2023.0.0" />
- <PackageReference Include="TwentyTwenty.Storage" Version="2.24.2" />
- <PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.24.2" />
- <PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.24.2" />
- <PackageReference Include="TwentyTwenty.Storage.Google" Version="2.24.2" />
- <PackageReference Include="TwentyTwenty.Storage.Local" Version="2.24.2" />
- <PackageReference Condition="'$(RazorCompileOnBuild)' == 'false'" Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.11" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
- <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
- <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.11" />
+ <PackageReference Include="Serilog" Version="4.3.0" />
+ <PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
+ <PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
+ <PackageReference Include="SSH.NET" Version="2025.1.0" />
+ <PackageReference Include="TwentyTwenty.Storage" Version="2.26.1" />
+ <PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.26.1" />
+ <PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.26.1" />
+ <PackageReference Include="TwentyTwenty.Storage.Google" Version="2.26.1" />
+ <PackageReference Include="TwentyTwenty.Storage.Local" Version="2.26.1" />
+ <PackageReference Condition="'$(RazorCompileOnBuild)' == 'false'" Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.1" />
+ <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
diff --git a/BTCPayServer/Extensions/HostExtensions.cs b/BTCPayServer/Extensions/HostExtensions.cs
new file mode 100644
index 0000000..a2c4d99
--- /dev/null
+++ b/BTCPayServer/Extensions/HostExtensions.cs
@@ -0,0 +1,58 @@
+using System.Threading;
+using System.Threading.Tasks;
+using BTCPayServer.Abstractions.Contracts;
+using BTCPayServer.Configuration;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Serilog;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+ public static class WebHostExtensions
+ {
+ public static T GetServerFeatures<T>(this IHost host) where T : class
+ {
+ var server = host.Services.GetRequiredService<IServer>();
+ var features = server.Features;
+ return features.Get<T>();
+ }
+
+ public static async Task StartWithTasksAsync(this IHost host, CancellationToken cancellationToken = default)
+ {
+ // Load all tasks from DI
+ var startupTasks = host.Services.GetServices<IStartupTask>();
+
+ // Execute all the tasks
+ foreach (var startupTask in startupTasks)
+ {
+ await startupTask.ExecuteAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ // Start the tasks as normal
+ await host.StartAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ private const long MAX_DEBUG_LOG_FILE_SIZE = 2000000; // If debug log is in use roll it every N MB.
+ public static IHostBuilder ConfigureSerilog(this IHostBuilder builder, IConfiguration configuration)
+ {
+ builder.ConfigureLogging(logBuilder =>
+ {
+ var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration);
+ if (!string.IsNullOrEmpty(debugLogFile))
+ {
+ Serilog.Log.Logger = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .MinimumLevel.Is(BTCPayServerOptions.GetDebugLogLevel(configuration))
+ .WriteTo.File(debugLogFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: MAX_DEBUG_LOG_FILE_SIZE,
+ rollOnFileSizeLimit: true, retainedFileCountLimit: 1)
+ .CreateLogger();
+ logBuilder.AddProvider(new Serilog.Extensions.Logging.SerilogLoggerProvider(Log.Logger));
+ }
+ });
+ return builder;
+ }
+ }
+}
diff --git a/BTCPayServer/Extensions/SSHClientExtensions.cs b/BTCPayServer/Extensions/SSHClientExtensions.cs
index 22d6c31..6f794ea 100644
--- a/BTCPayServer/Extensions/SSHClientExtensions.cs
+++ b/BTCPayServer/Extensions/SSHClientExtensions.cs
@@ -93,11 +93,13 @@ namespace BTCPayServer
private static SSHCommandResult CreateSSHCommandResult(SshCommand sshCommand)
{
+ if (sshCommand.ExitStatus is null)
+ throw new InvalidOperationException("ExitStatus is null");
return new SSHCommandResult()
{
Output = sshCommand.Result,
Error = sshCommand.Error,
- ExitStatus = sshCommand.ExitStatus
+ ExitStatus = sshCommand.ExitStatus.Value
};
}
diff --git a/BTCPayServer/Extensions/WebHostExtensions.cs b/BTCPayServer/Extensions/WebHostExtensions.cs
deleted file mode 100644
index 6703be3..0000000
--- a/BTCPayServer/Extensions/WebHostExtensions.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-using BTCPayServer.Abstractions.Contracts;
-using BTCPayServer.Hosting;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Microsoft.AspNetCore.Hosting
-{
- public static class WebHostExtensions
- {
- public static async Task StartWithTasksAsync(this IWebHost webHost, CancellationToken cancellationToken = default)
- {
- // Load all tasks from DI
- var startupTasks = webHost.Services.GetServices<IStartupTask>();
-
- // Execute all the tasks
- foreach (var startupTask in startupTasks)
- {
- await startupTask.ExecuteAsync(cancellationToken).ConfigureAwait(false);
- }
-
- // Start the tasks as normal
- await webHost.StartAsync(cancellationToken).ConfigureAwait(false);
- }
- }
-}
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index 6d53833..b03cebd 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -517,20 +517,6 @@ namespace BTCPayServer.Hosting
options.AddPolicy(CorsPolicies.All, p => p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
});
services.AddRateLimits();
- services.AddLogging(logBuilder =>
- {
- var debugLogFile = BTCPayServerOptions.GetDebugLog(configuration);
- if (!string.IsNullOrEmpty(debugLogFile))
- {
- Serilog.Log.Logger = new LoggerConfiguration()
- .Enrich.FromLogContext()
- .MinimumLevel.Is(BTCPayServerOptions.GetDebugLogLevel(configuration))
- .WriteTo.File(debugLogFile, rollingInterval: RollingInterval.Day, fileSizeLimitBytes: MAX_DEBUG_LOG_FILE_SIZE,
- rollOnFileSizeLimit: true, retainedFileCountLimit: 1)
- .CreateLogger();
- logBuilder.AddProvider(new Serilog.Extensions.Logging.SerilogLoggerProvider(Log.Logger));
- }
- });
services.AddSingleton<IObjectModelValidator, SkippableObjectValidatorProvider>();
services.SkipModelValidation<RootedKeyPath>();
@@ -768,8 +754,6 @@ namespace BTCPayServer.Hosting
services.AddSingleton<SkippableObjectValidatorProvider.ISkipValidation, SkippableObjectValidatorProvider.SkipValidationType<T>>();
}
- private const long MAX_DEBUG_LOG_FILE_SIZE = 2000000; // If debug log is in use roll it every N MB.
-
private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services)
{
services.PostConfigure<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme, opt =>
diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs
index ff3125d..8ddc0e7 100644
--- a/BTCPayServer/Hosting/Startup.cs
+++ b/BTCPayServer/Hosting/Startup.cs
@@ -45,20 +45,35 @@ namespace BTCPayServer.Hosting
{
public class Startup
{
- public Startup(IConfiguration conf, ILoggerFactory loggerFactory)
+ public Startup(IConfiguration conf)
{
Configuration = conf;
- LoggerFactory = loggerFactory;
+ SetLoggerFactory(new NullLoggerFactory());
+ }
+
+ private void SetLoggerFactory(ILoggerFactory factory)
+ {
+ LoggerFactory = factory ?? new NullLoggerFactory();
Logs = new Logs();
- Logs.Configure(loggerFactory);
+ Logs.Configure(LoggerFactory);
}
public IConfiguration Configuration
{
get; set;
}
- public ILoggerFactory LoggerFactory { get; }
- public Logs Logs { get; }
+
+ private void EnsureLogging(IServiceCollection services)
+ {
+ if (LoggerFactory is NullLoggerFactory)
+ {
+ var service = services.BuildServiceProvider().GetService<ILoggerFactory>();
+ SetLoggerFactory(service);
+ }
+ }
+
+ public ILoggerFactory LoggerFactory { get; set; }
+ public Logs Logs { get; set; }
public static ServiceProvider CreateBootstrap(IConfiguration conf)
{
@@ -78,6 +93,7 @@ namespace BTCPayServer.Hosting
public void ConfigureServices(IServiceCollection services)
{
+ EnsureLogging(services);
var bootstrapServiceProvider = CreateBootstrap(Configuration, Logs, LoggerFactory);
services.AddSingleton(bootstrapServiceProvider.GetRequiredService<SelectedChains>());
services.AddSingleton(bootstrapServiceProvider.GetRequiredService<NBXplorerNetworkProvider>());
@@ -258,7 +274,8 @@ namespace BTCPayServer.Hosting
ILoggerFactory loggerFactory,
IRateLimitService rateLimits)
{
- Logs.Configure(loggerFactory);
+ SetLoggerFactory(loggerFactory);
+
Logs.Configuration.LogInformation($"Root Path: {options.RootPath}");
if (options.RootPath.Equals("/", StringComparison.OrdinalIgnoreCase))
{
@@ -310,7 +327,7 @@ namespace BTCPayServer.Hosting
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
- forwardingOptions.KnownNetworks.Clear();
+ forwardingOptions.KnownIPNetworks.Clear();
forwardingOptions.KnownProxies.Clear();
forwardingOptions.ForwardedHeaders = ForwardedHeaders.All;
app.UseForwardedHeaders(forwardingOptions);
diff --git a/BTCPayServer/PaymentRequest/PaymentRequestService.cs b/BTCPayServer/PaymentRequest/PaymentRequestService.cs
index af75fcb..fa95f90 100644
--- a/BTCPayServer/PaymentRequest/PaymentRequestService.cs
+++ b/BTCPayServer/PaymentRequest/PaymentRequestService.cs
@@ -51,8 +51,9 @@ namespace BTCPayServer.PaymentRequest
public async Task UpdatePaymentRequestStateIfNeeded(PaymentRequestData pr)
{
- var newStatus = pr.Status;
- newStatus = pr switch
+ if (pr is null)
+ return;
+ var newStatus = pr switch
{
{ Expirable: true, Expiry: { } e }
when e <= DateTimeOffset.UtcNow => PaymentRequestStatus.Expired,
diff --git a/BTCPayServer/Program.cs b/BTCPayServer/Program.cs
index 533a83a..0cb6de7 100644
--- a/BTCPayServer/Program.cs
+++ b/BTCPayServer/Program.cs
@@ -12,6 +12,7 @@ using BTCPayServer.Plugins;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
[assembly: InternalsVisibleTo("BTCPayServer.Tests")]
@@ -28,8 +29,7 @@ namespace BTCPayServer
if (args.Length > 0 && args[0] == "run")
args = args.Skip(1).ToArray(); // Hack to make dotnet watch work
- ServicePointManager.DefaultConnectionLimit = 100;
- IWebHost host = null;
+ IHost host = null;
var processor = new ConsoleLoggerProcessor();
var loggerProvider = new CustomConsoleLogProvider(processor);
using var loggerFactory = new LoggerFactory();
@@ -47,9 +47,9 @@ namespace BTCPayServer
confBuilder.AddJsonFile("appsettings.dev.json", true, false);
#endif
conf = confBuilder.Build();
- var builder = new WebHostBuilder()
- .UseKestrel()
- .UseConfiguration(conf)
+
+
+ var builder = Host.CreateDefaultBuilder(args)
.ConfigureLogging(l =>
{
l.AddFilter("Microsoft", LogLevel.Error);
@@ -66,9 +66,14 @@ namespace BTCPayServer
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
l.AddFilter("Microsoft.AspNetCore.Antiforgery.Internal", LogLevel.Critical);
l.AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error);
+ l.ClearProviders();
l.AddProvider(new CustomConsoleLogProvider(processor));
})
- .UseStartup<Startup>();
+ .ConfigureSerilog(conf)
+ .ConfigureWebHostDefaults(webBuilder =>
+ webBuilder.UseKestrel()
+ .UseConfiguration(conf)
+ .UseStartup<Startup>());
// When we run the app with dotnet run (typically in dev env), the wwwroot isn't in the same directory
// than this assembly.
@@ -84,7 +89,7 @@ namespace BTCPayServer
}
host = builder.Build();
await host.StartWithTasksAsync();
- var urls = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
+ var urls = host.GetServerFeatures<IServerAddressesFeature>().Addresses;
foreach (var url in urls)
{
// Some tools such as dotnet watch parse this exact log to open the browser
diff --git a/BTCPayServer/Services/DefaultSwaggerProvider.cs b/BTCPayServer/Services/DefaultSwaggerProvider.cs
index 34a1e14..e6a3533 100644
--- a/BTCPayServer/Services/DefaultSwaggerProvider.cs
+++ b/BTCPayServer/Services/DefaultSwaggerProvider.cs
@@ -1,4 +1,5 @@
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using Microsoft.AspNetCore.Hosting;
@@ -26,7 +27,7 @@ public class DefaultSwaggerProvider : ISwaggerProvider
JObject json = new JObject();
var directoryContents = _fileProvider.GetDirectoryContents("swagger/v1");
- foreach (IFileInfo fi in directoryContents)
+ foreach (IFileInfo fi in directoryContents.Where(fi => fi.Name.EndsWith(".json")))
{
await using var stream = fi.CreateReadStream();
using var reader = new StreamReader(fi.CreateReadStream());
diff --git a/BTCPayServer/Services/Invoices/PosAppData.cs b/BTCPayServer/Services/Invoices/PosAppData.cs
index e1a7022..62fc6d0 100644
--- a/BTCPayServer/Services/Invoices/PosAppData.cs
+++ b/BTCPayServer/Services/Invoices/PosAppData.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
using BTCPayServer.Plugins.PointOfSale;
using Newtonsoft.Json;
@@ -118,7 +117,7 @@ public class PosAppCartItemPriceJsonConverter : JsonConverter
case JTokenType.Null:
return null;
case JTokenType.Object:
- return token.ToObject<JObject>()?["value"]?.Value<decimal?>();
+ return token.ToObject<JObject>()?["value"]?.Value<decimal?>() ?? (objectType == typeof(decimal) ? 0m : null);
default:
throw new JsonSerializationException($"Unexpected token type: {token.Type}");
}
diff --git a/Build/Common.csproj b/Build/Common.csproj
index 1fd72ab..62e0e99 100644
--- a/Build/Common.csproj
+++ b/Build/Common.csproj
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">$(TargetFrameworkOverride)</TargetFramework>
<NoWarn>$(NoWarn),NU1701,CA1816,CA1308,CA1810,CA2208,CA1303,CA2000,CA2016,CA1835,CA2249,CA9998,CA1704;CS8981</NoWarn>
<LangVersion>12.0</LangVersion>
diff --git a/Dockerfile b/Dockerfile
index 4d97b2a..6acc19e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0.404-bookworm-slim AS builder
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.101-noble AS builder
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
WORKDIR /source
COPY nuget.config nuget.config
@@ -21,7 +21,7 @@ ARG CONFIGURATION_NAME=Release
ARG GIT_COMMIT
RUN cd BTCPayServer && dotnet publish -p:GitCommit=${GIT_COMMIT} --output /app/ --configuration ${CONFIGURATION_NAME}
-FROM mcr.microsoft.com/dotnet/aspnet:8.0.18-bookworm-slim
+FROM mcr.microsoft.com/dotnet/aspnet:10.0.1-noble
RUN apt-get update && apt-get install -y --no-install-recommends iproute2 openssh-client ca-certificates \
&& rm -rf /var/lib/apt/lists/*
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.