From fd67255482d897d59f0cf7ceb2c59dc705aa2835 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 13 Jan 2026 20:34:11 -0500
Subject: [PATCH 01/20] Add some integration tests for the Server project
---
bitwarden-server.sln | 8 +-
.../Server.IntegrationTest.csproj | 23 ++++
test/Server.IntegrationTest/Server.cs | 44 ++++++++
test/Server.IntegrationTest/ServerTests.cs | 102 ++++++++++++++++++
4 files changed, 176 insertions(+), 1 deletion(-)
create mode 100644 test/Server.IntegrationTest/Server.IntegrationTest.csproj
create mode 100644 test/Server.IntegrationTest/Server.cs
create mode 100644 test/Server.IntegrationTest/ServerTests.cs
diff --git a/bitwarden-server.sln b/bitwarden-server.sln
index ae9571a4a546..1a46d1e2c803 100644
--- a/bitwarden-server.sln
+++ b/bitwarden-server.sln
@@ -140,10 +140,11 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi", "util\SeederApi\SeederApi.csproj", "{9F08DFBB-482B-4C9D-A5F4-6BDA6EC2E68F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi.IntegrationTest", "test\SeederApi.IntegrationTest\SeederApi.IntegrationTest.csproj", "{A2E067EF-609C-4D13-895A-E054C61D48BB}"
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSO.Test", "bitwarden_license\test\SSO.Test\SSO.Test.csproj", "{7D98784C-C253-43FB-9873-25B65C6250D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sso.IntegrationTest", "bitwarden_license\test\Sso.IntegrationTest\Sso.IntegrationTest.csproj", "{FFB09376-595B-6F93-36F0-70CAE90AFECB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.IntegrationTest", "test\Server.IntegrationTest\Server.IntegrationTest.csproj", "{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -372,6 +373,10 @@ Global
{FFB09376-595B-6F93-36F0-70CAE90AFECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFB09376-595B-6F93-36F0-70CAE90AFECB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -432,6 +437,7 @@ Global
{A2E067EF-609C-4D13-895A-E054C61D48BB} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
{7D98784C-C253-43FB-9873-25B65C6250D6} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
{FFB09376-595B-6F93-36F0-70CAE90AFECB} = {287CFF34-BBDB-4BC4-AF88-1E19A5A4679B}
+ {E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
diff --git a/test/Server.IntegrationTest/Server.IntegrationTest.csproj b/test/Server.IntegrationTest/Server.IntegrationTest.csproj
new file mode 100644
index 000000000000..362ada84a01b
--- /dev/null
+++ b/test/Server.IntegrationTest/Server.IntegrationTest.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Server.IntegrationTest/Server.cs b/test/Server.IntegrationTest/Server.cs
new file mode 100644
index 000000000000..5a34ab8326b6
--- /dev/null
+++ b/test/Server.IntegrationTest/Server.cs
@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace Bit.Server.IntegrationTest;
+
+public class Server : WebApplicationFactory
+{
+ public string? ContentRoot { get; set; }
+ public string? WebRoot { get; set; }
+ public bool ServeUnknown { get; set; }
+ public bool? WebVault { get; set; }
+ public string? AppIdLocation { get; set; }
+
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ base.ConfigureWebHost(builder);
+
+ builder.ConfigureLogging(logging =>
+ {
+ logging.SetMinimumLevel(LogLevel.Debug);
+ });
+
+ var config = new Dictionary
+ {
+ {"contentRoot", ContentRoot},
+ {"webRoot", WebRoot},
+ {"serveUnknown", ServeUnknown.ToString().ToLowerInvariant()},
+ };
+
+ if (WebVault.HasValue)
+ {
+ config["webVault"] = WebVault.Value.ToString().ToLowerInvariant();
+ }
+
+ if (!string.IsNullOrEmpty(AppIdLocation))
+ {
+ config["appIdLocation"] = AppIdLocation;
+ }
+
+ builder.UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(config).Build());
+ }
+}
diff --git a/test/Server.IntegrationTest/ServerTests.cs b/test/Server.IntegrationTest/ServerTests.cs
new file mode 100644
index 000000000000..cf143ee2d27b
--- /dev/null
+++ b/test/Server.IntegrationTest/ServerTests.cs
@@ -0,0 +1,102 @@
+using System.Net;
+using System.Runtime.CompilerServices;
+
+namespace Bit.Server.IntegrationTest;
+
+public class ServerTests
+{
+ [Fact]
+ public async Task AttachmentsStyleUse()
+ {
+ using var tempDir = new TempDir();
+
+ await tempDir.WriteAsync("my-file.txt", "Hello!");
+
+ using var server = new Server
+ {
+ ContentRoot = tempDir.Info.FullName,
+ WebRoot = ".",
+ ServeUnknown = true,
+ };
+
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/my-file.txt", TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("Hello!", await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken));
+ }
+
+ [Fact]
+ public async Task WebVaultStyleUse()
+ {
+ using var tempDir = new TempDir();
+
+ await tempDir.WriteAsync("index.html", "");
+ await tempDir.WriteAsync(Path.Join("app", "file.js"), "AppStuff");
+ await tempDir.WriteAsync(Path.Join("locales", "file.json"), "LocalesStuff");
+ await tempDir.WriteAsync(Path.Join("fonts", "file.ttf"), "FontsStuff");
+ await tempDir.WriteAsync(Path.Join("connectors", "file.js"), "ConnectorsStuff");
+ await tempDir.WriteAsync(Path.Join("scripts", "file.js"), "ScriptsStuff");
+ await tempDir.WriteAsync(Path.Join("images", "file.avif"), "ImagesStuff");
+ await tempDir.WriteAsync(Path.Join("test", "file.json"), "{}");
+
+ using var server = new Server
+ {
+ ContentRoot = tempDir.Info.FullName,
+ WebRoot = ".",
+ ServeUnknown = false,
+ WebVault = true,
+ AppIdLocation = Path.Join(tempDir.Info.FullName, "test", "file.json"),
+ };
+
+ var client = server.CreateClient();
+
+ // Going to root should return the default file
+ var response = await client.GetAsync("", TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("", await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken));
+ // No caching on the default document
+ Assert.Null(response.Headers.CacheControl?.MaxAge);
+
+ await ExpectMaxAgeAsync("app/file.js", TimeSpan.FromDays(14));
+ await ExpectMaxAgeAsync("locales/file.json", TimeSpan.FromDays(14));
+ await ExpectMaxAgeAsync("fonts/file.ttf", TimeSpan.FromDays(14));
+ await ExpectMaxAgeAsync("connectors/file.js", TimeSpan.FromDays(14));
+ await ExpectMaxAgeAsync("scripts/file.js", TimeSpan.FromDays(14));
+ await ExpectMaxAgeAsync("images/file.avif", TimeSpan.FromDays(7));
+
+ response = await client.GetAsync("app-id.json", TestContext.Current.CancellationToken);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType);
+
+ async Task ExpectMaxAgeAsync(string path, TimeSpan maxAge)
+ {
+ response = await client.GetAsync(path);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.NotNull(response.Headers.CacheControl);
+ Assert.Equal(maxAge, response.Headers.CacheControl.MaxAge);
+ }
+ }
+
+ private class TempDir([CallerMemberName] string test = null!) : IDisposable
+ {
+ public DirectoryInfo Info { get; } = Directory.CreateTempSubdirectory(test);
+
+ public void Dispose()
+ {
+ Info.Delete(recursive: true);
+ }
+
+ public async Task WriteAsync(string fileName, string content)
+ {
+ var fullPath = Path.Join(Info.FullName, fileName);
+ var directory = Path.GetDirectoryName(fullPath);
+ if (directory != null)
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ await File.WriteAllTextAsync(fullPath, content, TestContext.Current.CancellationToken);
+ }
+ }
+}
From c7942d20ac23806a338ccaad8cc8677c363cdc28 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 13 Jan 2026 20:40:03 -0500
Subject: [PATCH 02/20] Not sure why this project got removed?
---
bitwarden-server.sln | 2 ++
1 file changed, 2 insertions(+)
diff --git a/bitwarden-server.sln b/bitwarden-server.sln
index 1a46d1e2c803..409906e2d0f0 100644
--- a/bitwarden-server.sln
+++ b/bitwarden-server.sln
@@ -141,6 +141,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi", "util\SeederApi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeederApi.IntegrationTest", "test\SeederApi.IntegrationTest\SeederApi.IntegrationTest.csproj", "{A2E067EF-609C-4D13-895A-E054C61D48BB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SSO.Test", "bitwarden_license\test\SSO.Test\SSO.Test.csproj", "{7D98784C-C253-43FB-9873-25B65C6250D6}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sso.IntegrationTest", "bitwarden_license\test\Sso.IntegrationTest\Sso.IntegrationTest.csproj", "{FFB09376-595B-6F93-36F0-70CAE90AFECB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.IntegrationTest", "test\Server.IntegrationTest\Server.IntegrationTest.csproj", "{E75E1F10-BC6F-4EB1-BA75-D897C45AEA0D}"
From 6c844ea581061bde830be0997eef201cd734a5fb Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 14 Jan 2026 09:14:21 -0500
Subject: [PATCH 03/20] Format
---
test/Server.IntegrationTest/Server.cs | 2 +-
test/Server.IntegrationTest/ServerTests.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/Server.IntegrationTest/Server.cs b/test/Server.IntegrationTest/Server.cs
index 5a34ab8326b6..403ccdb3d33e 100644
--- a/test/Server.IntegrationTest/Server.cs
+++ b/test/Server.IntegrationTest/Server.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
diff --git a/test/Server.IntegrationTest/ServerTests.cs b/test/Server.IntegrationTest/ServerTests.cs
index cf143ee2d27b..e432f5377573 100644
--- a/test/Server.IntegrationTest/ServerTests.cs
+++ b/test/Server.IntegrationTest/ServerTests.cs
@@ -1,4 +1,4 @@
-using System.Net;
+using System.Net;
using System.Runtime.CompilerServices;
namespace Bit.Server.IntegrationTest;
From 1c9ff89db174cbb904315b12db9c32392c3a6b65 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 14 Jan 2026 10:10:29 -0500
Subject: [PATCH 04/20] capture debug output
---
test/Server.IntegrationTest/Properties/AssemblyInfo.cs | 1 +
1 file changed, 1 insertion(+)
create mode 100644 test/Server.IntegrationTest/Properties/AssemblyInfo.cs
diff --git a/test/Server.IntegrationTest/Properties/AssemblyInfo.cs b/test/Server.IntegrationTest/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000000..d0ccb303b5e1
--- /dev/null
+++ b/test/Server.IntegrationTest/Properties/AssemblyInfo.cs
@@ -0,0 +1 @@
+[assembly: CaptureTrace]
From 2ecdc13f2d2532acc348fcca0d9ca4781ff2748f Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 14 Jan 2026 11:35:12 -0500
Subject: [PATCH 05/20] Update tests to work with the now legacy WebHostBuilder
- I accidentally had the updated Program locally and that was why tests were working for me locally
---
test/Server.IntegrationTest/Server.cs | 35 ++++++++++++++-------------
util/Server/Program.cs | 10 ++++++--
2 files changed, 26 insertions(+), 19 deletions(-)
diff --git a/test/Server.IntegrationTest/Server.cs b/test/Server.IntegrationTest/Server.cs
index 403ccdb3d33e..073dbffb5a6c 100644
--- a/test/Server.IntegrationTest/Server.cs
+++ b/test/Server.IntegrationTest/Server.cs
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.TestHost;
namespace Bit.Server.IntegrationTest;
@@ -13,32 +12,34 @@ public class Server : WebApplicationFactory
public bool? WebVault { get; set; }
public string? AppIdLocation { get; set; }
- protected override void ConfigureWebHost(IWebHostBuilder builder)
+ protected override IWebHostBuilder? CreateWebHostBuilder()
{
- base.ConfigureWebHost(builder);
-
- builder.ConfigureLogging(logging =>
- {
- logging.SetMinimumLevel(LogLevel.Debug);
- });
-
- var config = new Dictionary
+ var args = new List
{
- {"contentRoot", ContentRoot},
- {"webRoot", WebRoot},
- {"serveUnknown", ServeUnknown.ToString().ToLowerInvariant()},
+ "/contentRoot",
+ ContentRoot ?? "",
+ "/webRoot",
+ WebRoot ?? "",
+ "/serveUnknown",
+ ServeUnknown.ToString().ToLowerInvariant(),
};
if (WebVault.HasValue)
{
- config["webVault"] = WebVault.Value.ToString().ToLowerInvariant();
+ args.Add("/webVault");
+ args.Add(WebVault.Value.ToString().ToLowerInvariant());
}
if (!string.IsNullOrEmpty(AppIdLocation))
{
- config["appIdLocation"] = AppIdLocation;
+ args.Add("/appIdLocation");
+ args.Add(AppIdLocation);
}
- builder.UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(config).Build());
+ var builder = WebHostBuilderFactory.CreateFromTypesAssemblyEntryPoint([.. args])
+ ?? throw new InvalidProgramException("Could not create builder from assembly.");
+
+ builder.UseSetting("TEST_CONTENTROOT_SERVER", ContentRoot);
+ return builder;
}
}
diff --git a/util/Server/Program.cs b/util/Server/Program.cs
index a2d7e5f68765..3d563830ab20 100644
--- a/util/Server/Program.cs
+++ b/util/Server/Program.cs
@@ -6,6 +6,13 @@ namespace Bit.Server;
public class Program
{
public static void Main(string[] args)
+ {
+ var builder = CreateWebHostBuilder(args);
+ var host = builder.Build();
+ host.Run();
+ }
+
+ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
@@ -37,7 +44,6 @@ public static void Main(string[] args)
builder.UseWebRoot(webRoot);
}
- var host = builder.Build();
- host.Run();
+ return builder;
}
}
From a5f1b3f815878023c337a1c32c00202449cf56e2 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 14 Jan 2026 11:55:16 -0500
Subject: [PATCH 06/20] Formatting...again
---
test/Server.IntegrationTest/Properties/AssemblyInfo.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/Server.IntegrationTest/Properties/AssemblyInfo.cs b/test/Server.IntegrationTest/Properties/AssemblyInfo.cs
index d0ccb303b5e1..80afc76e2e38 100644
--- a/test/Server.IntegrationTest/Properties/AssemblyInfo.cs
+++ b/test/Server.IntegrationTest/Properties/AssemblyInfo.cs
@@ -1 +1 @@
-[assembly: CaptureTrace]
+[assembly: CaptureTrace]
From 753c702cccc2c5dc01c0bf91a4bb24e716fa887d Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 14 Jan 2026 13:32:52 -0500
Subject: [PATCH 07/20] Update to `IHostBuilder` style
---
test/Server.IntegrationTest/Server.cs | 29 ++++++++-----------
util/Server/Program.cs | 40 +++++++++++++--------------
2 files changed, 30 insertions(+), 39 deletions(-)
diff --git a/test/Server.IntegrationTest/Server.cs b/test/Server.IntegrationTest/Server.cs
index 073dbffb5a6c..963e6e0ecd7e 100644
--- a/test/Server.IntegrationTest/Server.cs
+++ b/test/Server.IntegrationTest/Server.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.Configuration;
namespace Bit.Server.IntegrationTest;
@@ -12,34 +12,27 @@ public class Server : WebApplicationFactory
public bool? WebVault { get; set; }
public string? AppIdLocation { get; set; }
- protected override IWebHostBuilder? CreateWebHostBuilder()
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
{
- var args = new List
+ base.ConfigureWebHost(builder);
+
+ var config = new Dictionary
{
- "/contentRoot",
- ContentRoot ?? "",
- "/webRoot",
- WebRoot ?? "",
- "/serveUnknown",
- ServeUnknown.ToString().ToLowerInvariant(),
+ {"contentRoot", ContentRoot},
+ {"webRoot", WebRoot},
+ {"serveUnknown", ServeUnknown.ToString().ToLowerInvariant()},
};
if (WebVault.HasValue)
{
- args.Add("/webVault");
- args.Add(WebVault.Value.ToString().ToLowerInvariant());
+ config["webVault"] = WebVault.Value.ToString().ToLowerInvariant();
}
if (!string.IsNullOrEmpty(AppIdLocation))
{
- args.Add("/appIdLocation");
- args.Add(AppIdLocation);
+ config["appIdLocation"] = AppIdLocation;
}
- var builder = WebHostBuilderFactory.CreateFromTypesAssemblyEntryPoint([.. args])
- ?? throw new InvalidProgramException("Could not create builder from assembly.");
-
- builder.UseSetting("TEST_CONTENTROOT_SERVER", ContentRoot);
- return builder;
+ builder.UseConfiguration(new ConfigurationBuilder().AddInMemoryCollection(config).Build());
}
}
diff --git a/util/Server/Program.cs b/util/Server/Program.cs
index 3d563830ab20..edebb7e0e3bc 100644
--- a/util/Server/Program.cs
+++ b/util/Server/Program.cs
@@ -6,27 +6,30 @@ namespace Bit.Server;
public class Program
{
public static void Main(string[] args)
- {
- var builder = CreateWebHostBuilder(args);
- var host = builder.Build();
- host.Run();
- }
-
- public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
- var builder = new WebHostBuilder()
- .UseConfiguration(config)
- .UseKestrel()
- .UseStartup()
- .ConfigureLogging((hostingContext, logging) =>
+ var builder = new HostBuilder()
+ .ConfigureWebHost(builder =>
{
- logging.AddConsole().AddDebug();
+ builder.UseConfiguration(config);
+ builder.UseKestrel();
+ builder.UseStartup();
+ builder.ConfigureKestrel((_,_) => {});
+
+ var webRoot = config.GetValue("webRoot");
+ if (string.IsNullOrWhiteSpace(webRoot))
+ {
+ builder.UseWebRoot(webRoot);
+ }
})
- .ConfigureKestrel((context, options) => { });
+ .ConfigureLogging(logging =>
+ {
+ logging.AddConsole()
+ .AddDebug();
+ });
var contentRoot = config.GetValue("contentRoot");
if (!string.IsNullOrWhiteSpace(contentRoot))
@@ -38,12 +41,7 @@ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
builder.UseContentRoot(Directory.GetCurrentDirectory());
}
- var webRoot = config.GetValue("webRoot");
- if (string.IsNullOrWhiteSpace(webRoot))
- {
- builder.UseWebRoot(webRoot);
- }
-
- return builder;
+ var host = builder.Build();
+ host.Run();
}
}
From 00cc684af02b5878c880c5b31e62c302740b8a44 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Wed, 14 Jan 2026 16:17:18 -0500
Subject: [PATCH 08/20] Formatting
---
util/Server/Program.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/util/Server/Program.cs b/util/Server/Program.cs
index edebb7e0e3bc..bc00b46ecbcd 100644
--- a/util/Server/Program.cs
+++ b/util/Server/Program.cs
@@ -17,7 +17,7 @@ public static void Main(string[] args)
builder.UseConfiguration(config);
builder.UseKestrel();
builder.UseStartup();
- builder.ConfigureKestrel((_,_) => {});
+ builder.ConfigureKestrel((_, _) => { });
var webRoot = config.GetValue("webRoot");
if (string.IsNullOrWhiteSpace(webRoot))
From cddcfd24602abe7e57d247a9f1b43fb17fbd378e Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Wed, 4 Mar 2026 09:01:30 -0600
Subject: [PATCH 09/20] make integration test work on existing databases
---
.../Tools/SendRepositoryTests.cs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/test/Infrastructure.IntegrationTest/Tools/SendRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Tools/SendRepositoryTests.cs
index bcc525e0d29a..e1df69b7b083 100644
--- a/test/Infrastructure.IntegrationTest/Tools/SendRepositoryTests.cs
+++ b/test/Infrastructure.IntegrationTest/Tools/SendRepositoryTests.cs
@@ -37,7 +37,6 @@ public async Task CreateAsync_Works(ISendRepository sendRepository)
}
[DatabaseTheory, DatabaseData]
- // This test runs best on a fresh database and may fail on subsequent runs with other tests.
public async Task GetByDeletionDateAsync_Works(ISendRepository sendRepository)
{
var deletionDate = DateTime.UtcNow.AddYears(-1);
@@ -61,7 +60,7 @@ public async Task GetByDeletionDateAsync_Works(ISendRepository sendRepository)
});
var toDeleteSends = await sendRepository.GetManyByDeletionDateAsync(deletionDate);
- var toDeleteSend = Assert.Single(toDeleteSends);
- Assert.Equal(shouldDeleteSend.Id, toDeleteSend.Id);
+ Assert.Contains(toDeleteSends, s => s.Id == shouldDeleteSend.Id);
+ Assert.DoesNotContain(toDeleteSends, s => s.Id == shouldKeepSend.Id);
}
}
From 39af40e80ad2f7b7f288364135abb76f2d7bf7de Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 11:45:25 -0600
Subject: [PATCH 10/20] mimekit .net10 support and security patch
---
src/Core/Core.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 54a8a0483fb2..c0c05a7d6445 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -38,7 +38,7 @@
-
+
From 932b81a967b98de4f839ae536a6ad5cd0c9e6eca Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 14:35:47 -0600
Subject: [PATCH 11/20] updated csproj files
---
Directory.Build.props | 7 ++++++-
bitwarden_license/src/Sso/Sso.csproj | 3 ---
.../test/SSO.Test/SSO.Test.csproj | 2 +-
.../Scim.IntegrationTest.csproj | 2 +-
.../Sso.IntegrationTest.csproj | 4 ++--
global.json | 4 ++--
src/Core/Core.csproj | 17 ++++++++--------
.../Infrastructure.EntityFramework.csproj | 20 ++++++++++++-------
src/Notifications/Notifications.csproj | 6 +++---
.../Core.IntegrationTest.csproj | 2 +-
.../Events.IntegrationTest.csproj | 2 +-
.../Identity.IntegrationTest.csproj | 2 +-
.../Infrastructure.Dapper.Test.csproj | 2 +-
.../Infrastructure.IntegrationTest.csproj | 6 +++---
.../IntegrationTestCommon.csproj | 2 +-
.../Server.IntegrationTest.csproj | 2 +-
util/Migrator/Migrator.csproj | 2 +-
.../MsSqlMigratorUtility.csproj | 4 ++--
util/MySqlMigrations/MySqlMigrations.csproj | 2 +-
.../PostgresMigrations.csproj | 2 +-
util/RustSdk/RustSdk.csproj | 2 +-
util/Seeder/Seeder.csproj | 2 +-
util/SeederApi/SeederApi.csproj | 2 +-
util/SeederUtility/SeederUtility.csproj | 2 +-
util/Setup/Setup.csproj | 2 +-
.../SqlServerEFScaffold.csproj | 2 +-
util/SqliteMigrations/SqliteMigrations.csproj | 2 +-
27 files changed, 57 insertions(+), 50 deletions(-)
diff --git a/Directory.Build.props b/Directory.Build.props
index adf984f7d195..7f3d88b869ec 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
2026.2.1
@@ -11,6 +11,11 @@
annotations
enable
true
+
+ $(NoWarn);NU1608
diff --git a/bitwarden_license/src/Sso/Sso.csproj b/bitwarden_license/src/Sso/Sso.csproj
index 709e8c2c4a52..d3adf65ffacd 100644
--- a/bitwarden_license/src/Sso/Sso.csproj
+++ b/bitwarden_license/src/Sso/Sso.csproj
@@ -8,9 +8,6 @@
-
-
-
diff --git a/bitwarden_license/test/SSO.Test/SSO.Test.csproj b/bitwarden_license/test/SSO.Test/SSO.Test.csproj
index 4b509c9a50a3..afa8c22104d3 100644
--- a/bitwarden_license/test/SSO.Test/SSO.Test.csproj
+++ b/bitwarden_license/test/SSO.Test/SSO.Test.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj b/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj
index d0d329397c91..919411df1c82 100644
--- a/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj
+++ b/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj
@@ -11,7 +11,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/bitwarden_license/test/Sso.IntegrationTest/Sso.IntegrationTest.csproj b/bitwarden_license/test/Sso.IntegrationTest/Sso.IntegrationTest.csproj
index 42d0743d514b..e4d9bed852c7 100644
--- a/bitwarden_license/test/Sso.IntegrationTest/Sso.IntegrationTest.csproj
+++ b/bitwarden_license/test/Sso.IntegrationTest/Sso.IntegrationTest.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
@@ -14,7 +14,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/global.json b/global.json
index 970250aec979..ee5bf6a6aa84 100644
--- a/global.json
+++ b/global.json
@@ -1,11 +1,11 @@
{
"sdk": {
- "version": "8.0.100",
+ "version": "10.0.103",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "4.1.0",
- "Microsoft.Build.Sql": "1.0.0",
+ "Microsoft.Build.Sql": "2.1.0",
"Bitwarden.Server.Sdk": "1.4.0"
}
}
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index c0c05a7d6445..3728e6417b57 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -29,7 +29,7 @@
-
+
@@ -39,7 +39,7 @@
-
+
@@ -47,10 +47,10 @@
-
-
-
-
+
+
+
+
@@ -61,7 +61,7 @@
-
+
@@ -73,8 +73,7 @@
-
-
+
diff --git a/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj b/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj
index 180bcd770575..75513e6b22c1 100644
--- a/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj
+++ b/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj
@@ -2,18 +2,24 @@
$(WarningsNotAsErrors);CA1304;CA1305
+
+ $(NoWarn);NU1608
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Notifications/Notifications.csproj b/src/Notifications/Notifications.csproj
index 76278fdea83c..f98d955d08b5 100644
--- a/src/Notifications/Notifications.csproj
+++ b/src/Notifications/Notifications.csproj
@@ -8,12 +8,12 @@
-
-
+
+
-
+
diff --git a/test/Core.IntegrationTest/Core.IntegrationTest.csproj b/test/Core.IntegrationTest/Core.IntegrationTest.csproj
index 133793d3d831..d645cdd24e41 100644
--- a/test/Core.IntegrationTest/Core.IntegrationTest.csproj
+++ b/test/Core.IntegrationTest/Core.IntegrationTest.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/test/Events.IntegrationTest/Events.IntegrationTest.csproj b/test/Events.IntegrationTest/Events.IntegrationTest.csproj
index dbfe14789214..1072149dc40b 100644
--- a/test/Events.IntegrationTest/Events.IntegrationTest.csproj
+++ b/test/Events.IntegrationTest/Events.IntegrationTest.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
false
true
diff --git a/test/Identity.IntegrationTest/Identity.IntegrationTest.csproj b/test/Identity.IntegrationTest/Identity.IntegrationTest.csproj
index 8a3c0d0fc221..b16c8efe10e5 100644
--- a/test/Identity.IntegrationTest/Identity.IntegrationTest.csproj
+++ b/test/Identity.IntegrationTest/Identity.IntegrationTest.csproj
@@ -12,7 +12,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj b/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj
index 7a6bd3ba2053..f3d28d60682a 100644
--- a/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj
+++ b/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
false
true
diff --git a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj
index 262a0fe85615..c175ffd461da 100644
--- a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj
+++ b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj
@@ -8,9 +8,9 @@
-
-
-
+
+
+
diff --git a/test/IntegrationTestCommon/IntegrationTestCommon.csproj b/test/IntegrationTestCommon/IntegrationTestCommon.csproj
index 8ed593d058d7..453e2dcd3e51 100644
--- a/test/IntegrationTestCommon/IntegrationTestCommon.csproj
+++ b/test/IntegrationTestCommon/IntegrationTestCommon.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/test/Server.IntegrationTest/Server.IntegrationTest.csproj b/test/Server.IntegrationTest/Server.IntegrationTest.csproj
index 362ada84a01b..7798985cb2d6 100644
--- a/test/Server.IntegrationTest/Server.IntegrationTest.csproj
+++ b/test/Server.IntegrationTest/Server.IntegrationTest.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/util/Migrator/Migrator.csproj b/util/Migrator/Migrator.csproj
index 29caf74f3941..a8a9859f3e7a 100644
--- a/util/Migrator/Migrator.csproj
+++ b/util/Migrator/Migrator.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/util/MsSqlMigratorUtility/MsSqlMigratorUtility.csproj b/util/MsSqlMigratorUtility/MsSqlMigratorUtility.csproj
index 7e68a91b65e1..3a3df2040b81 100644
--- a/util/MsSqlMigratorUtility/MsSqlMigratorUtility.csproj
+++ b/util/MsSqlMigratorUtility/MsSqlMigratorUtility.csproj
@@ -11,8 +11,8 @@
-
-
+
+
diff --git a/util/MySqlMigrations/MySqlMigrations.csproj b/util/MySqlMigrations/MySqlMigrations.csproj
index b297ea10416e..cb5de6c59525 100644
--- a/util/MySqlMigrations/MySqlMigrations.csproj
+++ b/util/MySqlMigrations/MySqlMigrations.csproj
@@ -12,7 +12,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/util/PostgresMigrations/PostgresMigrations.csproj b/util/PostgresMigrations/PostgresMigrations.csproj
index 66f3abe769e9..b0805f5b4476 100644
--- a/util/PostgresMigrations/PostgresMigrations.csproj
+++ b/util/PostgresMigrations/PostgresMigrations.csproj
@@ -11,7 +11,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/util/RustSdk/RustSdk.csproj b/util/RustSdk/RustSdk.csproj
index 14cc01736567..61dd2bcd9602 100644
--- a/util/RustSdk/RustSdk.csproj
+++ b/util/RustSdk/RustSdk.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
Bit.RustSDK
diff --git a/util/Seeder/Seeder.csproj b/util/Seeder/Seeder.csproj
index 1988c7d393cf..a2f07e3c18b4 100644
--- a/util/Seeder/Seeder.csproj
+++ b/util/Seeder/Seeder.csproj
@@ -2,7 +2,7 @@
- net8.0
+ net10.0
enable
enable
Bit.Seeder
diff --git a/util/SeederApi/SeederApi.csproj b/util/SeederApi/SeederApi.csproj
index 53e9941c1cb4..43a75eab69a9 100644
--- a/util/SeederApi/SeederApi.csproj
+++ b/util/SeederApi/SeederApi.csproj
@@ -2,7 +2,7 @@
bitwarden-seeder-api
- net8.0
+ net10.0
enable
enable
false
diff --git a/util/SeederUtility/SeederUtility.csproj b/util/SeederUtility/SeederUtility.csproj
index b0e9f7b5e40b..e55b41a85dd8 100644
--- a/util/SeederUtility/SeederUtility.csproj
+++ b/util/SeederUtility/SeederUtility.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net10.0
enable
enable
Bit.SeederUtility
diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj
index b4ab0bd806cc..1d45d32af11b 100644
--- a/util/Setup/Setup.csproj
+++ b/util/Setup/Setup.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj b/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj
index a2fb8173bf06..fd96f22655b5 100644
--- a/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj
+++ b/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj
@@ -5,7 +5,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/util/SqliteMigrations/SqliteMigrations.csproj b/util/SqliteMigrations/SqliteMigrations.csproj
index 26def0dad201..259f306c2924 100644
--- a/util/SqliteMigrations/SqliteMigrations.csproj
+++ b/util/SqliteMigrations/SqliteMigrations.csproj
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
From f53c099a639e06ccc82751802fa08bb93b49e11f Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 14:46:15 -0600
Subject: [PATCH 12/20] x509 api upgrade
---
.../Auth/Models/Request/OrganizationSsoRequestModel.cs | 2 +-
.../Billing/Services/Implementations/LicensingService.cs | 2 +-
src/Core/Utilities/CoreHelpers.cs | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Api/Auth/Models/Request/OrganizationSsoRequestModel.cs b/src/Api/Auth/Models/Request/OrganizationSsoRequestModel.cs
index 349bdebb8834..b71e809f5c32 100644
--- a/src/Api/Auth/Models/Request/OrganizationSsoRequestModel.cs
+++ b/src/Api/Auth/Models/Request/OrganizationSsoRequestModel.cs
@@ -147,7 +147,7 @@ public IEnumerable Validate(ValidationContext context)
try
{
var certData = CoreHelpers.Base64UrlDecode(StripPemCertificateElements(IdpX509PublicCert));
- new X509Certificate2(certData);
+ X509CertificateLoader.LoadCertificate(certData);
}
catch (FormatException)
{
diff --git a/src/Core/Billing/Services/Implementations/LicensingService.cs b/src/Core/Billing/Services/Implementations/LicensingService.cs
index 6f0cdec8f55b..f4a1dc0470a3 100644
--- a/src/Core/Billing/Services/Implementations/LicensingService.cs
+++ b/src/Core/Billing/Services/Implementations/LicensingService.cs
@@ -62,7 +62,7 @@ public LicensingService(
"B34876439FCDA2846505B2EFBBA6C4A951313EBE";
if (_globalSettings.SelfHosted)
{
- _certificate = CoreHelpers.GetEmbeddedCertificateAsync(environment.IsDevelopment() ? "licensing_dev.cer" : "licensing.cer", null)
+ _certificate = CoreHelpers.GetEmbeddedCertificateAsync(environment.IsDevelopment() ? "licensing_dev.cer" : "licensing.cer")
.GetAwaiter().GetResult();
}
else if (CoreHelpers.SettingHasValue(_globalSettings.Storage?.ConnectionString) &&
diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs
index 1461601d184a..eb68547b0a21 100644
--- a/src/Core/Utilities/CoreHelpers.cs
+++ b/src/Core/Utilities/CoreHelpers.cs
@@ -141,17 +141,17 @@ public static string CleanCertificateThumbprint(string thumbprint)
public static X509Certificate2 GetCertificate(string file, string password)
{
- return new X509Certificate2(file, password);
+ return X509CertificateLoader.LoadPkcs12FromFile(file, password);
}
- public async static Task GetEmbeddedCertificateAsync(string file, string password)
+ public async static Task GetEmbeddedCertificateAsync(string file)
{
var assembly = typeof(CoreHelpers).GetTypeInfo().Assembly;
using (var s = assembly.GetManifestResourceStream($"Bit.Core.{file}")!)
using (var ms = new MemoryStream())
{
await s.CopyToAsync(ms);
- return new X509Certificate2(ms.ToArray(), password);
+ return X509CertificateLoader.LoadCertificate(ms.ToArray());
}
}
@@ -176,7 +176,7 @@ public static string GetEmbeddedResourceContentsAsync(string file)
using var memStream = new MemoryStream();
await blobRef.DownloadToAsync(memStream).ConfigureAwait(false);
- return new X509Certificate2(memStream.ToArray(), password);
+ return X509CertificateLoader.LoadPkcs12(memStream.ToArray(), password);
}
catch (RequestFailedException ex)
when (ex.ErrorCode == BlobErrorCode.ContainerNotFound || ex.ErrorCode == BlobErrorCode.BlobNotFound)
From 43d2afab865c9fea576756fdc7edc5d13180ac92 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 15:15:27 -0600
Subject: [PATCH 13/20] missed an x509 upgrade
---
.../src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs
index db574e71c536..b05f91f0f3a4 100644
--- a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs
+++ b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs
@@ -406,7 +406,7 @@ private DynamicAuthenticationScheme GetSaml2AuthenticationScheme(string name, Ss
if (!string.IsNullOrWhiteSpace(config.IdpX509PublicCert))
{
var cert = CoreHelpers.Base64UrlDecode(config.IdpX509PublicCert);
- idp.SigningKeys.AddConfiguredKey(new X509Certificate2(cert));
+ idp.SigningKeys.AddConfiguredKey(X509CertificateLoader.LoadCertificate(cert));
}
idp.ArtifactResolutionServiceUrls.Clear();
// This must happen last since it calls Validate() internally.
From 943e15d2842f3e8c7bf75860f7f7f61095cd210e Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 15:16:52 -0600
Subject: [PATCH 14/20] nullable fixups
---
src/Core/Settings/GlobalSettings.cs | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs
index b9ba879b0b2f..0d38621bdf81 100644
--- a/src/Core/Settings/GlobalSettings.cs
+++ b/src/Core/Settings/GlobalSettings.cs
@@ -259,7 +259,7 @@ public string ConnectionString
_readOnlyConnectionString = null;
}
- _connectionString = value.Trim('"');
+ _connectionString = value?.Trim('"');
}
}
@@ -267,13 +267,13 @@ public string ReadOnlyConnectionString
{
get => string.IsNullOrWhiteSpace(_readOnlyConnectionString) ?
_connectionString : _readOnlyConnectionString;
- set => _readOnlyConnectionString = value.Trim('"');
+ set => _readOnlyConnectionString = value?.Trim('"');
}
public string JobSchedulerConnectionString
{
get => _jobSchedulerConnectionString;
- set => _jobSchedulerConnectionString = value.Trim('"');
+ set => _jobSchedulerConnectionString = value?.Trim('"');
}
}
@@ -325,19 +325,19 @@ public class AzureServiceBusSettings
public string ConnectionString
{
get => _connectionString;
- set => _connectionString = value.Trim('"');
+ set => _connectionString = value?.Trim('"');
}
public string EventTopicName
{
get => _eventTopicName;
- set => _eventTopicName = value.Trim('"');
+ set => _eventTopicName = value?.Trim('"');
}
public string IntegrationTopicName
{
get => _integrationTopicName;
- set => _integrationTopicName = value.Trim('"');
+ set => _integrationTopicName = value?.Trim('"');
}
}
@@ -372,27 +372,27 @@ public class RabbitMqSettings
public string HostName
{
get => _hostName;
- set => _hostName = value.Trim('"');
+ set => _hostName = value?.Trim('"');
}
public string Username
{
get => _username;
- set => _username = value.Trim('"');
+ set => _username = value?.Trim('"');
}
public string Password
{
get => _password;
- set => _password = value.Trim('"');
+ set => _password = value?.Trim('"');
}
public string EventExchangeName
{
get => _eventExchangeName;
- set => _eventExchangeName = value.Trim('"');
+ set => _eventExchangeName = value?.Trim('"');
}
public string IntegrationExchangeName
{
get => _integrationExchangeName;
- set => _integrationExchangeName = value.Trim('"');
+ set => _integrationExchangeName = value?.Trim('"');
}
}
}
@@ -422,7 +422,7 @@ public class ConnectionStringSettings : IConnectionStringSettings
public string ConnectionString
{
get => _connectionString;
- set => _connectionString = value.Trim('"');
+ set => _connectionString = value?.Trim('"');
}
}
@@ -445,7 +445,7 @@ public FileStorageSettings(GlobalSettings globalSettings, string urlName, string
public string ConnectionString
{
get => _connectionString;
- set => _connectionString = value.Trim('"');
+ set => _connectionString = value?.Trim('"');
}
public string BaseDirectory
From 820d5a06067da1b9fd98f2e028a416b5db07e671 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 15:31:58 -0600
Subject: [PATCH 15/20] fix bugged test
---
.../ServiceAccounts/ServiceAccountSecretsDetailsQueryTests.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/ServiceAccountSecretsDetailsQueryTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/ServiceAccountSecretsDetailsQueryTests.cs
index 0f926ceae9cb..55004a7a5d9c 100644
--- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/ServiceAccountSecretsDetailsQueryTests.cs
+++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/ServiceAccountSecretsDetailsQueryTests.cs
@@ -38,13 +38,13 @@ public async Task GetManyByOrganizationId_CallsDifferentRepoMethods(
if (includeAccessToSecrets)
{
await sutProvider.GetDependency().Received(1)
- .GetManyByOrganizationIdWithSecretsDetailsAsync(Arg.Is(AssertHelper.AssertPropertyEqual(mockSaDetails.ServiceAccount.OrganizationId)),
+ .GetManyByOrganizationIdWithSecretsDetailsAsync(Arg.Is(AssertHelper.AssertPropertyEqual(organizationId)),
Arg.Any(), Arg.Any());
}
else
{
await sutProvider.GetDependency().Received(1)
- .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(mockSa.OrganizationId)),
+ .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(organizationId)),
Arg.Any(), Arg.Any());
Assert.Equal(0, result.First().AccessToSecrets);
}
From 12dfdcbcb86071ab34a9947e7e9c0ad9a7da6f55 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 18:26:00 -0600
Subject: [PATCH 16/20] dockerfiles
---
bitwarden_license/src/Scim/Dockerfile | 4 ++--
bitwarden_license/src/Sso/Dockerfile | 4 ++--
src/Admin/Dockerfile | 4 ++--
src/Api/Dockerfile | 4 ++--
src/Billing/Dockerfile | 4 ++--
src/Events/Dockerfile | 4 ++--
src/EventsProcessor/Dockerfile | 4 ++--
src/Icons/Dockerfile | 4 ++--
src/Identity/Dockerfile | 4 ++--
src/Notifications/Dockerfile | 4 ++--
util/Attachments/Dockerfile | 4 ++--
util/MsSqlMigratorUtility/Dockerfile | 4 ++--
util/Setup/Dockerfile | 4 ++--
13 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/bitwarden_license/src/Scim/Dockerfile b/bitwarden_license/src/Scim/Dockerfile
index fca3d83572dc..084b3618219a 100644
--- a/bitwarden_license/src/Scim/Dockerfile
+++ b/bitwarden_license/src/Scim/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/bitwarden_license/src/Sso/Dockerfile b/bitwarden_license/src/Sso/Dockerfile
index cbd049b9bd95..33d859d798e3 100644
--- a/bitwarden_license/src/Sso/Dockerfile
+++ b/bitwarden_license/src/Sso/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Admin/Dockerfile b/src/Admin/Dockerfile
index 84248639cf7b..e0dae6ffafa5 100644
--- a/src/Admin/Dockerfile
+++ b/src/Admin/Dockerfile
@@ -12,7 +12,7 @@ RUN npm run build
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -47,7 +47,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Api/Dockerfile b/src/Api/Dockerfile
index beacee89aeb4..5e561361fe3f 100644
--- a/src/Api/Dockerfile
+++ b/src/Api/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Billing/Dockerfile b/src/Billing/Dockerfile
index 1e182dedffdb..2455b6858c5d 100644
--- a/src/Billing/Dockerfile
+++ b/src/Billing/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Events/Dockerfile b/src/Events/Dockerfile
index 913e94da45d8..a9efeda671f0 100644
--- a/src/Events/Dockerfile
+++ b/src/Events/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/EventsProcessor/Dockerfile b/src/EventsProcessor/Dockerfile
index 433552d321ee..b71c68ec06e2 100644
--- a/src/EventsProcessor/Dockerfile
+++ b/src/EventsProcessor/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Icons/Dockerfile b/src/Icons/Dockerfile
index 5cd2b405d411..63fb6caca5bd 100644
--- a/src/Icons/Dockerfile
+++ b/src/Icons/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -36,7 +36,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Identity/Dockerfile b/src/Identity/Dockerfile
index e79439f275f4..457e46401f66 100644
--- a/src/Identity/Dockerfile
+++ b/src/Identity/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/src/Notifications/Dockerfile b/src/Notifications/Dockerfile
index 031df0b1b67c..fcaedf8ab777 100644
--- a/src/Notifications/Dockerfile
+++ b/src/Notifications/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/util/Attachments/Dockerfile b/util/Attachments/Dockerfile
index 5ba22918c497..586e87c448f8 100644
--- a/util/Attachments/Dockerfile
+++ b/util/Attachments/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/util/MsSqlMigratorUtility/Dockerfile b/util/MsSqlMigratorUtility/Dockerfile
index b8bd7ff4a116..468b45a33f42 100644
--- a/util/MsSqlMigratorUtility/Dockerfile
+++ b/util/MsSqlMigratorUtility/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -38,7 +38,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21 AS app
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS app
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden"
diff --git a/util/Setup/Dockerfile b/util/Setup/Dockerfile
index 2ab86c69ed86..db19f59661b1 100644
--- a/util/Setup/Dockerfile
+++ b/util/Setup/Dockerfile
@@ -1,7 +1,7 @@
###############################################
# Build stage #
###############################################
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine3.21 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
# Docker buildx supplies the value for this arg
ARG TARGETPLATFORM
@@ -37,7 +37,7 @@ RUN . /tmp/rid.txt && dotnet publish \
###############################################
# App stage #
###############################################
-FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.21
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine
ARG TARGETPLATFORM
LABEL com.bitwarden.product="bitwarden" com.bitwarden.project="setup"
From 14711187e0aeb3151d514eed661f8b3f23b02db1 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 18:27:50 -0600
Subject: [PATCH 17/20] pre-net9 b64 decoding support
---
.../Utilities/EncryptedStringAttribute.cs | 31 ++++++++++++++++---
test/Core.Test/Utilities/CoreHelpersTests.cs | 4 ++-
2 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/src/Core/Utilities/EncryptedStringAttribute.cs b/src/Core/Utilities/EncryptedStringAttribute.cs
index 9c59287df6a2..70af8c74c856 100644
--- a/src/Core/Utilities/EncryptedStringAttribute.cs
+++ b/src/Core/Utilities/EncryptedStringAttribute.cs
@@ -1,5 +1,4 @@
-using System.Buffers.Text;
-using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations;
using Bit.Core.Enums;
#nullable enable
@@ -111,7 +110,7 @@ private static bool ValidatePieces(ReadOnlySpan encryptionPart, int requir
if (requiredPieces == 1)
{
// Only one more part is needed so don't split and check the chunk
- if (rest.IsEmpty || !Base64.IsValid(rest))
+ if (rest.IsEmpty || !IsValidBase64(rest))
{
return false;
}
@@ -128,7 +127,7 @@ private static bool ValidatePieces(ReadOnlySpan encryptionPart, int requir
}
// Is the required chunk valid base 64?
- if (chunk.IsEmpty || !Base64.IsValid(chunk))
+ if (chunk.IsEmpty || !IsValidBase64(chunk))
{
return false;
}
@@ -141,4 +140,28 @@ private static bool ValidatePieces(ReadOnlySpan encryptionPart, int requir
// No more parts are required, so check there are no extra parts
return rest.IndexOf('|') == -1;
}
+
+ // System.Buffers.Text.Base64.IsValid enforces canonical padding (non-zero unused bits are rejected)
+ // starting with .NET 9. Bitwarden clients may produce non-canonical base64 for random encrypted bytes,
+ // so we validate only character set, length, and padding structure.
+ private static bool IsValidBase64(ReadOnlySpan value)
+ {
+ if (value.IsEmpty || value.Length % 4 != 0) return false;
+
+ var paddingCount = 0;
+ if (value[^1] == '=') paddingCount++;
+ if (value[^2] == '=') paddingCount++;
+
+ for (int i = 0; i < value.Length - paddingCount; i++)
+ {
+ char c = value[i];
+ if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || c == '+' || c == '/'))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/test/Core.Test/Utilities/CoreHelpersTests.cs b/test/Core.Test/Utilities/CoreHelpersTests.cs
index d006df536ba2..c9894ca74a80 100644
--- a/test/Core.Test/Utilities/CoreHelpersTests.cs
+++ b/test/Core.Test/Utilities/CoreHelpersTests.cs
@@ -410,7 +410,9 @@ public void TokenIsValid_Success(string unprotectedTokenTemplate, string firstPa
{
var protector = new TestDataProtector(string.Format(unprotectedTokenTemplate, CoreHelpers.ToEpocMilliseconds(creationTime)));
- Assert.Equal(isValid, CoreHelpers.TokenIsValid(firstPart, protector, "protected_token", userEmail, id, expirationInHours));
+ // TestDataProtector ignores the decoded bytes; value just needs to be valid base64url
+ var token = CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes("protected_token"));
+ Assert.Equal(isValid, CoreHelpers.TokenIsValid(firstPart, protector, token, userEmail, id, expirationInHours));
}
private class TestDataProtector : IDataProtector
From e9b108d5e3003418ef72d46a8d2c7a6b1c125a70 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 18:47:23 -0600
Subject: [PATCH 18/20] known networks update
---
src/SharedWeb/Utilities/ServiceCollectionExtensions.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
index 8d65547f7621..426926cc0bb4 100644
--- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
+++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
@@ -597,14 +597,14 @@ public static void UseForwardedHeaders(this IApplicationBuilder app, IGlobalSett
var proxyNetworks = globalSettings.KnownNetworks.Split(',');
foreach (var proxyNetwork in proxyNetworks)
{
- if (Microsoft.AspNetCore.HttpOverrides.IPNetwork.TryParse(proxyNetwork.Trim(), out var ipn))
+ if (System.Net.IPNetwork.TryParse(proxyNetwork.Trim(), out var ipn))
{
- options.KnownNetworks.Add(ipn);
+ options.KnownIPNetworks.Add(ipn);
}
}
}
- if (options.KnownProxies.Count > 1 || options.KnownNetworks.Count > 1)
+ if (options.KnownProxies.Count > 1 || options.KnownIPNetworks.Count > 1)
{
options.ForwardLimit = null;
}
From 5e444802a9f0e50b6180e2f9df13a527dcc9db22 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 18:50:38 -0600
Subject: [PATCH 19/20] linq2db and ef upgrade
---
.../AdminConsole/Repositories/OrganizationRepository.cs | 2 +-
.../Billing/Repositories/ProviderInvoiceItemRepository.cs | 2 +-
.../Dirt/Repositories/OrganizationApplicationRepository.cs | 2 +-
.../Dirt/Repositories/OrganizationReportRepository.cs | 1 +
.../Repositories/PasswordHealthReportApplicationRepository.cs | 2 +-
.../Endpoints/IdentityServerTwoFactorTests.cs | 2 +-
.../Vault/Repositories/CipherRepositoryTests.cs | 2 +-
.../Factories/IdentityApplicationFactory.cs | 2 +-
8 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs
index efead6e24aae..af2f11e355b1 100644
--- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs
+++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs
@@ -10,7 +10,7 @@
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
-using LinqToDB.Tools;
+using LinqToDB;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
diff --git a/src/Infrastructure.EntityFramework/Billing/Repositories/ProviderInvoiceItemRepository.cs b/src/Infrastructure.EntityFramework/Billing/Repositories/ProviderInvoiceItemRepository.cs
index ed729070ae99..6c143ecd7153 100644
--- a/src/Infrastructure.EntityFramework/Billing/Repositories/ProviderInvoiceItemRepository.cs
+++ b/src/Infrastructure.EntityFramework/Billing/Repositories/ProviderInvoiceItemRepository.cs
@@ -2,7 +2,7 @@
using Bit.Core.Billing.Providers.Entities;
using Bit.Core.Billing.Providers.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
-using LinqToDB;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using EFProviderInvoiceItem = Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem;
diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs
index f408ed85ba4a..b5504906b49f 100644
--- a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs
+++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs
@@ -2,7 +2,7 @@
using Bit.Core.Dirt.Repositories;
using Bit.Infrastructure.EntityFramework.Dirt.Models;
using Bit.Infrastructure.EntityFramework.Repositories;
-using LinqToDB;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories;
diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs
index d08e70c35325..98829c8ea17c 100644
--- a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs
+++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs
@@ -8,6 +8,7 @@
using Bit.Core.Dirt.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using LinqToDB;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs
index 296a542a8bb7..d49dd93cb035 100644
--- a/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs
+++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs
@@ -2,7 +2,7 @@
using Bit.Core.Dirt.Repositories;
using Bit.Infrastructure.EntityFramework.Dirt.Models;
using Bit.Infrastructure.EntityFramework.Repositories;
-using LinqToDB;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories;
diff --git a/test/Identity.IntegrationTest/Endpoints/IdentityServerTwoFactorTests.cs b/test/Identity.IntegrationTest/Endpoints/IdentityServerTwoFactorTests.cs
index d0d35f5d7bfe..74ee098b82f0 100644
--- a/test/Identity.IntegrationTest/Endpoints/IdentityServerTwoFactorTests.cs
+++ b/test/Identity.IntegrationTest/Endpoints/IdentityServerTwoFactorTests.cs
@@ -20,7 +20,7 @@
using Duende.IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Stores;
-using LinqToDB;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using NSubstitute;
using Xunit;
diff --git a/test/Infrastructure.EFIntegration.Test/Vault/Repositories/CipherRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Vault/Repositories/CipherRepositoryTests.cs
index a314d15ddab9..cc673e3a2487 100644
--- a/test/Infrastructure.EFIntegration.Test/Vault/Repositories/CipherRepositoryTests.cs
+++ b/test/Infrastructure.EFIntegration.Test/Vault/Repositories/CipherRepositoryTests.cs
@@ -8,7 +8,7 @@
using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers;
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
using Bit.Test.Common.AutoFixture.Attributes;
-using LinqToDB;
+using Microsoft.EntityFrameworkCore;
using Xunit;
using EfAdminConsoleRepo = Bit.Infrastructure.EntityFramework.AdminConsole.Repositories;
using EfRepo = Bit.Infrastructure.EntityFramework.Repositories;
diff --git a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs
index af60cadce464..12f28c586cbe 100644
--- a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs
+++ b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs
@@ -15,9 +15,9 @@
using Bit.Identity.IdentityServer;
using Bit.Identity.IdentityServer.RequestValidators;
using Bit.Test.Common.Helpers;
-using LinqToDB;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
+using Microsoft.EntityFrameworkCore;
using NSubstitute;
using Xunit;
From f3973f0850d60c18ba226c7434e272b374837001 Mon Sep 17 00:00:00 2001
From: Derek Nance
Date: Fri, 6 Mar 2026 19:01:35 -0600
Subject: [PATCH 20/20] update json deserialization err msg
---
test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs
index e3728995999c..fd12963aac81 100644
--- a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs
+++ b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs
@@ -108,7 +108,7 @@ public void Validate_RequiredFieldNotProvided_Invalid(string requiredField)
var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull);
var jsonException =
Assert.Throws(() => JsonSerializer.Deserialize>(serialized));
- Assert.Contains($"missing required properties, including the following: {requiredField}",
+ Assert.Contains($"was missing required properties including: '{requiredField}'",
jsonException.Message);
}