diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/MtlsHttpClientFactoryTests.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/MtlsHttpClientFactoryTests.cs
new file mode 100644
index 00000000..b0bf867b
--- /dev/null
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Orchestration/MtlsHttpClientFactoryTests.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using AutopilotMonitor.Agent.V2.Core.Logging;
+using AutopilotMonitor.Agent.V2.Core.Orchestration;
+using AutopilotMonitor.Agent.V2.Core.Security;
+using AutopilotMonitor.Agent.V2.Core.Tests.Harness;
+using Xunit;
+
+namespace AutopilotMonitor.Agent.V2.Core.Tests.Orchestration
+{
+ public sealed class MtlsHttpClientFactoryTests
+ {
+ private sealed class FakeCertificateResolver : ICertificateResolver
+ {
+ private readonly X509Certificate2? _cert;
+ public int CallCount { get; private set; }
+
+ public FakeCertificateResolver(X509Certificate2? cert) => _cert = cert;
+
+ public X509Certificate2? FindClientCertificate(AgentLogger logger)
+ {
+ CallCount++;
+ return _cert;
+ }
+ }
+
+ ///
+ /// Creates an in-memory self-signed cert so the factory has something real to attach
+ /// without touching the Windows cert store. is
+ /// available on net48 (4.7.2+).
+ ///
+ private static X509Certificate2 CreateSelfSignedCert()
+ {
+ using (var rsa = RSA.Create(2048))
+ {
+ var req = new CertificateRequest(
+ "CN=apmon-v2-test",
+ rsa,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1);
+
+ return req.CreateSelfSigned(
+ DateTimeOffset.UtcNow.AddDays(-1),
+ DateTimeOffset.UtcNow.AddDays(1));
+ }
+ }
+
+ [Fact]
+ public void CreateHandler_attaches_cert_and_enables_decompression()
+ {
+ using var tmp = new TempDirectory();
+ var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info);
+ using var cert = CreateSelfSignedCert();
+ var resolver = new FakeCertificateResolver(cert);
+
+ using var handler = MtlsHttpClientFactory.CreateHandler(resolver, logger);
+
+ Assert.Equal(1, resolver.CallCount);
+ Assert.Equal(
+ DecompressionMethods.GZip | DecompressionMethods.Deflate,
+ handler.AutomaticDecompression);
+ Assert.Single(handler.ClientCertificates);
+ Assert.Equal(cert.Thumbprint, ((X509Certificate2)handler.ClientCertificates[0]).Thumbprint);
+ }
+
+ [Fact]
+ public void Create_returns_client_with_default_timeout()
+ {
+ using var tmp = new TempDirectory();
+ var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info);
+ using var cert = CreateSelfSignedCert();
+ var resolver = new FakeCertificateResolver(cert);
+
+ using var client = MtlsHttpClientFactory.Create(resolver, logger);
+
+ Assert.NotNull(client);
+ Assert.Equal(MtlsHttpClientFactory.DefaultTimeout, client.Timeout);
+ }
+
+ [Fact]
+ public void Create_respects_custom_timeout()
+ {
+ using var tmp = new TempDirectory();
+ var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info);
+ using var cert = CreateSelfSignedCert();
+ var resolver = new FakeCertificateResolver(cert);
+
+ using var client = MtlsHttpClientFactory.Create(resolver, logger, TimeSpan.FromSeconds(5));
+
+ Assert.Equal(TimeSpan.FromSeconds(5), client.Timeout);
+ }
+
+ [Fact]
+ public void CreateHandler_throws_when_resolver_returns_null()
+ {
+ using var tmp = new TempDirectory();
+ var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info);
+ var resolver = new FakeCertificateResolver(null);
+
+ var ex = Assert.Throws(
+ () => MtlsHttpClientFactory.CreateHandler(resolver, logger));
+
+ Assert.Contains("no MDM client certificate found", ex.Message);
+ Assert.Equal(1, resolver.CallCount);
+ }
+
+ [Fact]
+ public void Create_rejects_null_dependencies()
+ {
+ using var tmp = new TempDirectory();
+ var logger = new AgentLogger(tmp.Path, AgentLogLevel.Info);
+ using var cert = CreateSelfSignedCert();
+
+ Assert.Throws(
+ () => MtlsHttpClientFactory.Create(null!, logger));
+
+ Assert.Throws(
+ () => MtlsHttpClientFactory.Create(new FakeCertificateResolver(cert), null!));
+ }
+ }
+}
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/MtlsHttpClientFactory.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/MtlsHttpClientFactory.cs
new file mode 100644
index 00000000..504bdbd7
--- /dev/null
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/MtlsHttpClientFactory.cs
@@ -0,0 +1,87 @@
+#nullable enable
+using System;
+using System.Net;
+using System.Net.Http;
+using AutopilotMonitor.Agent.V2.Core.Logging;
+using AutopilotMonitor.Agent.V2.Core.Security;
+
+namespace AutopilotMonitor.Agent.V2.Core.Orchestration
+{
+ ///
+ /// Baut einen mit mTLS-Client-Cert für den
+ /// . Plan §4.x M4.4.5.d.
+ ///
+ /// Design: Static Factory + -Seam statt direkte
+ /// Abhängigkeit an . Tests injizieren einen
+ /// Fake-Resolver mit einem self-signed Cert; Prod nutzt
+ /// , der den Windows-Cert-Store durchsucht.
+ ///
+ ///
+ /// Konfiguration (analog Legacy BackendApiClient):
+ ///
+ /// - +
+ /// - Default-Timeout 30s (override via Parameter)
+ /// - Keine CookieContainer, keine Default-Credentials
+ ///
+ ///
+ ///
+ public static class MtlsHttpClientFactory
+ {
+ /// Plan §4.x M4.4.5.d — Default-Timeout (analog Legacy).
+ public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
+
+ ///
+ /// Baut einen mit attached Client-Cert. Wirft
+ /// wenn kein
+ /// Zertifikat liefert — der Agent darf ohne Cert nicht starten (mTLS ist Pflicht).
+ ///
+ /// Separat exponiert, damit Tests direkt auf dem Handler asserten können
+ /// ('s _handler-Feld ist internal und in net48
+ /// reflection-unzuverlässig).
+ ///
+ ///
+ public static HttpClientHandler CreateHandler(
+ ICertificateResolver resolver,
+ AgentLogger logger)
+ {
+ if (resolver == null) throw new ArgumentNullException(nameof(resolver));
+ if (logger == null) throw new ArgumentNullException(nameof(logger));
+
+ var cert = resolver.FindClientCertificate(logger);
+ if (cert == null)
+ {
+ throw new InvalidOperationException(
+ "MtlsHttpClientFactory: no MDM client certificate found. " +
+ "Agent cannot start without a client cert (mTLS is mandatory). " +
+ "Check cert enrollment status in HKLM\\SOFTWARE\\Microsoft\\Enrollments.");
+ }
+
+ var handler = new HttpClientHandler
+ {
+ AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
+ };
+ handler.ClientCertificates.Add(cert);
+
+ logger.Info(
+ $"MtlsHttpClientFactory: handler ready (cert thumbprint={cert.Thumbprint}).");
+
+ return handler;
+ }
+
+ ///
+ /// Baut einen fertig konfigurierten — Handler via
+ /// , Timeout via Parameter (default ).
+ ///
+ public static HttpClient Create(
+ ICertificateResolver resolver,
+ AgentLogger logger,
+ TimeSpan? timeout = null)
+ {
+ var handler = CreateHandler(resolver, logger);
+ return new HttpClient(handler)
+ {
+ Timeout = timeout ?? DefaultTimeout,
+ };
+ }
+ }
+}
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/ICertificateResolver.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/ICertificateResolver.cs
new file mode 100644
index 00000000..46f7b981
--- /dev/null
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/ICertificateResolver.cs
@@ -0,0 +1,38 @@
+#nullable enable
+using System.Security.Cryptography.X509Certificates;
+using AutopilotMonitor.Agent.V2.Core.Logging;
+
+namespace AutopilotMonitor.Agent.V2.Core.Security
+{
+ ///
+ /// Abstraktion über , damit Tests die
+ /// Zert-Auflösung ohne echten Windows-Cert-Store ersetzen können. Plan §4.x M4.4.5.d.
+ ///
+ public interface ICertificateResolver
+ {
+ ///
+ /// Sucht das MDM-Client-Zertifikat. null wenn nichts gefunden — der Caller
+ /// (typischerweise ) entscheidet dann
+ /// über Fehlerbehandlung (wirft in Prod, Test-Resolver liefert null für die negative
+ /// Test-Variante).
+ ///
+ X509Certificate2? FindClientCertificate(AgentLogger logger);
+ }
+
+ ///
+ /// Produktiver . Delegiert an
+ /// .
+ ///
+ public sealed class DefaultCertificateResolver : ICertificateResolver
+ {
+ private readonly string? _thumbprintOverride;
+
+ public DefaultCertificateResolver(string? thumbprintOverride = null)
+ {
+ _thumbprintOverride = thumbprintOverride;
+ }
+
+ public X509Certificate2? FindClientCertificate(AgentLogger logger) =>
+ CertificateHelper.FindMdmCertificate(_thumbprintOverride, logger);
+ }
+}