From fc21e1c601553f64e7d51d94f76fbaa9d7ddacd3 Mon Sep 17 00:00:00 2001 From: arunmish Date: Sat, 23 May 2026 02:15:27 +0530 Subject: [PATCH 1/7] vulnerable logs removed --- AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs b/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs index 858475a..513ea95 100644 --- a/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs +++ b/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs @@ -28,12 +28,11 @@ public static ANetApiResponse PostData(AuthorizeNet.Environment env, TQ { ANetApiResponse response = null; if (null == request) - { - throw new ArgumentNullException("request"); - } - Logger.LogDebug("MerchantInfo->LoginId/TransactionKey: '{0}':'{1}'->{2}", request.merchantAuthentication.name, request.merchantAuthentication.ItemElementName, request.merchantAuthentication.Item); + { + throw new ArgumentNullException("request"); + } - var postUrl = GetPostUrl(env); + var postUrl = GetPostUrl(env); string responseAsString = null; using (var clientHandler = new HttpClientHandler()) From 9c2f749abd3f9e2d74a35790740f687e0afdd754 Mon Sep 17 00:00:00 2001 From: arunmish Date: Sat, 23 May 2026 02:30:07 +0530 Subject: [PATCH 2/7] change to ensure no Shared mutable singleton CUSTOM causes credential/endpoint bleed --- AuthorizeNET/AuthorizeNET/Environment.cs | 41 +++++++++++------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Environment.cs b/AuthorizeNET/AuthorizeNET/Environment.cs index 07fee21..d0fac63 100644 --- a/AuthorizeNET/AuthorizeNET/Environment.cs +++ b/AuthorizeNET/AuthorizeNET/Environment.cs @@ -26,12 +26,12 @@ public class Environment public int HttpProxyPort { get; set; } - private Environment(string baseUrl, string xmlBaseUrl, string cardPresentUrl) - { - BaseUrl = baseUrl; - XmlBaseUrl = xmlBaseUrl; - CardPresentUrl = cardPresentUrl; - } + public Environment(string baseUrl, string xmlBaseUrl, string cardPresentUrl) + { + BaseUrl = baseUrl; + XmlBaseUrl = xmlBaseUrl; + CardPresentUrl = cardPresentUrl; + } /// /// Gets the base url @@ -61,22 +61,17 @@ public static Environment createEnvironment(string baseUrl, string xmlBaseUrl) } - /// - /// Create a custom environment with the specified base url - /// - /// Base url - /// Xml base url - /// Card present url - /// The custom environment - public static Environment createEnvironment(string baseUrl, string xmlBaseUrl, string cardPresentUrl) - { - var environment = CUSTOM; - environment.BaseUrl = baseUrl; - environment.XmlBaseUrl = xmlBaseUrl; - environment.CardPresentUrl = cardPresentUrl; - - return environment; - } + /// + /// Create a custom environment with the specified base url + /// + /// Base url + /// Xml base url + /// Card present url + /// The custom environment + public static Environment createEnvironment(string baseUrl, string xmlBaseUrl, string cardPresentUrl) + { + return new Environment(baseUrl, xmlBaseUrl, cardPresentUrl); + } /// /// Reads an integer value from the environment @@ -122,4 +117,4 @@ public static string GetProperty(string propertyName) return System.Environment.GetEnvironmentVariable(propertyName); } } -} \ No newline at end of file +} From 42e8d79daa9e226745e047cefcd3c97406ef8a3b Mon Sep 17 00:00:00 2001 From: arunmish Date: Sat, 23 May 2026 03:10:18 +0530 Subject: [PATCH 3/7] warning added regarding multi-tenant --- .../Api/Controllers/Bases/ApiOperationBase.cs | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs index 26c2c67..f75b79c 100644 --- a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs +++ b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs @@ -11,10 +11,64 @@ public abstract class ApiOperationBase : IApiOperation where TQ : ANetApiRequest where TS : ANetApiResponse { - protected static ILogger Logger = LogFactory.getLog(typeof(ApiOperationBase)); - - public static AuthorizeNet.Environment RunEnvironment { get; set; } - public static merchantAuthenticationType MerchantAuthentication { get; set; } + protected static ILogger Logger = LogFactory.getLog(typeof(ApiOperationBase)); + + /// + /// WARNING: This static property is NOT THREAD-SAFE and should NOT be used in + /// multi-tenant or concurrent scenarios. + /// + /// In ASP.NET applications serving multiple merchants concurrently, one merchant's + /// environment setting can be overwritten by another merchant's request, causing + /// transactions to be sent to the wrong endpoint. + /// + /// RECOMMENDED: Pass environment parameter to Execute() method instead. + /// + /// + /// UNSAFE (multi-tenant): + /// + /// ApiOperationBase.RunEnvironment = Environment.PRODUCTION; // DON'T DO THIS + /// controller.Execute(); + /// + /// + /// SAFE (per-request): + /// + /// controller.Execute(Environment.PRODUCTION); // DO THIS INSTEAD + /// + /// + [Obsolete("Static RunEnvironment is not thread-safe in multi-tenant applications. " + + "Pass environment parameter to Execute() method instead.", false)] + public static AuthorizeNet.Environment RunEnvironment { get; set; } + + /// + /// WARNING: This static property is NOT THREAD-SAFE and should NOT be used in + /// multi-tenant or concurrent scenarios. + /// + /// In ASP.NET applications serving multiple merchants concurrently, one merchant's + /// credentials can be used for another merchant's transaction, leading to + /// unauthorized charges or credential disclosure. + /// + /// RECOMMENDED: Set merchantAuthentication on the request object instead. + /// + /// + /// UNSAFE (multi-tenant): + /// + /// ApiOperationBase.MerchantAuthentication = merchantAuth; // DON'T DO THIS + /// var request = new createTransactionRequest(); + /// var controller = new createTransactionController(request); + /// controller.Execute(); + /// + /// + /// SAFE (per-request): + /// + /// var request = new createTransactionRequest(); + /// request.merchantAuthentication = merchantAuth; // DO THIS INSTEAD + /// var controller = new createTransactionController(request); + /// controller.Execute(); + /// + /// + [Obsolete("Static MerchantAuthentication is not thread-safe in multi-tenant applications. " + + "Set merchantAuthentication on the request object instead.", false)] + public static merchantAuthenticationType MerchantAuthentication { get; set; } private TQ _apiRequest; private TS _apiResponse; From fb95af3853ed777c2e4c5abbb6887e34c693996a Mon Sep 17 00:00:00 2001 From: arunmish Date: Sat, 23 May 2026 17:38:33 +0530 Subject: [PATCH 4/7] fixed added regarding multi-tenant --- .../Api/Controllers/Bases/ApiOperationBase.cs | 64 ++++++++-------- README.md | 75 ++++++++++++++----- 2 files changed, 92 insertions(+), 47 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs index f75b79c..0addd6b 100644 --- a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs +++ b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs @@ -3,6 +3,7 @@ namespace AuthorizeNet.Api.Controllers.Bases using System.Collections.Generic; using System.Globalization; using System; + using System.Threading; using Contracts.V1; using Utilities; using Microsoft.Extensions.Logging; @@ -13,62 +14,65 @@ public abstract class ApiOperationBase : IApiOperation { protected static ILogger Logger = LogFactory.getLog(typeof(ApiOperationBase)); + // AsyncLocal backing storage for thread-safe access + private static AsyncLocal _runEnvironment = new AsyncLocal(); + private static AsyncLocal _merchantAuthentication = new AsyncLocal(); + /// - /// WARNING: This static property is NOT THREAD-SAFE and should NOT be used in - /// multi-tenant or concurrent scenarios. - /// - /// In ASP.NET applications serving multiple merchants concurrently, one merchant's - /// environment setting can be overwritten by another merchant's request, causing - /// transactions to be sent to the wrong endpoint. + /// Gets or sets the runtime environment for API requests. + /// This property is now thread-safe using AsyncLocal storage, preventing cross-tenant credential bleed. /// - /// RECOMMENDED: Pass environment parameter to Execute() method instead. + /// RECOMMENDED: Pass environment parameter to Execute() method instead of using this static property. /// /// - /// UNSAFE (multi-tenant): + /// RECOMMENDED (per-request): /// - /// ApiOperationBase.RunEnvironment = Environment.PRODUCTION; // DON'T DO THIS - /// controller.Execute(); + /// controller.Execute(Environment.PRODUCTION); // DO THIS /// /// - /// SAFE (per-request): + /// LEGACY (now thread-safe but discouraged): /// - /// controller.Execute(Environment.PRODUCTION); // DO THIS INSTEAD + /// ApiOperationBase.RunEnvironment = Environment.PRODUCTION; + /// controller.Execute(); /// /// - [Obsolete("Static RunEnvironment is not thread-safe in multi-tenant applications. " + - "Pass environment parameter to Execute() method instead.", false)] - public static AuthorizeNet.Environment RunEnvironment { get; set; } + [Obsolete("Static RunEnvironment is discouraged in multi-tenant applications. " + + "Pass environment parameter to Execute() method instead.", true)] + public static AuthorizeNet.Environment RunEnvironment + { + get => _runEnvironment.Value; + set => _runEnvironment.Value = value; + } /// - /// WARNING: This static property is NOT THREAD-SAFE and should NOT be used in - /// multi-tenant or concurrent scenarios. - /// - /// In ASP.NET applications serving multiple merchants concurrently, one merchant's - /// credentials can be used for another merchant's transaction, leading to - /// unauthorized charges or credential disclosure. + /// Gets or sets the merchant authentication credentials for API requests. + /// This property is now thread-safe using AsyncLocal storage, preventing cross-tenant credential bleed. /// - /// RECOMMENDED: Set merchantAuthentication on the request object instead. + /// RECOMMENDED: Set merchantAuthentication on the request object instead of using this static property. /// /// - /// UNSAFE (multi-tenant): + /// RECOMMENDED (per-request): /// - /// ApiOperationBase.MerchantAuthentication = merchantAuth; // DON'T DO THIS /// var request = new createTransactionRequest(); + /// request.merchantAuthentication = merchantAuth; // DO THIS /// var controller = new createTransactionController(request); /// controller.Execute(); /// /// - /// SAFE (per-request): + /// LEGACY (now thread-safe but discouraged): /// - /// var request = new createTransactionRequest(); - /// request.merchantAuthentication = merchantAuth; // DO THIS INSTEAD + /// ApiOperationBase.MerchantAuthentication = merchantAuth; /// var controller = new createTransactionController(request); /// controller.Execute(); /// /// - [Obsolete("Static MerchantAuthentication is not thread-safe in multi-tenant applications. " + - "Set merchantAuthentication on the request object instead.", false)] - public static merchantAuthenticationType MerchantAuthentication { get; set; } + [Obsolete("Static MerchantAuthentication is discouraged in multi-tenant applications. " + + "Set merchantAuthentication on the request object instead.", true)] + public static merchantAuthenticationType MerchantAuthentication + { + get => _merchantAuthentication.Value; + set => _merchantAuthentication.Value = value; + } private TQ _apiRequest; private TS _apiResponse; diff --git a/README.md b/README.md index 1ed2459..8eb042f 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,62 @@ -# Authorize.Net .NET Core SDK - Beta +# Authorize.Net .NET Core SDK - Beta [//]: # "[![Travis CI Status](https://travis-ci.org/AuthorizeNet/sdk-dotnet.svg?branch=master)](https://travis-ci.org/AuthorizeNet/sdk-dotnet)" [//]: # "[![Code Climate](https://codeclimate.com/github/AuthorizeNet/sdk-dotnet/badges/gpa.svg)](https://codeclimate.com/github/AuthorizeNet/sdk-dotnet)" - - ## Requirements -* .NET Core 1.1.0 or later -* Microsoft® Visual Studio 2017 or later -* An Authorize.Net account (see _Registration & Configuration_ section below) + +- .NET Core 1.1.0 or later +- Microsoft® Visual Studio 2017 or later +- An Authorize.Net account (see _Registration & Configuration_ section below) ### TLS 1.2 -The Authorize.Net APIs only support connections using the TLS 1.2 security protocol. It's important to make sure you have new enough versions of all required components to support TLS 1.2. Additionally, it's very important to keep these components up to date going forward to mitigate the risk of any security flaws that may be discovered in your system or any libraries it uses. +The Authorize.Net APIs only support connections using the TLS 1.2 security protocol. It's important to make sure you have new enough versions of all required components to support TLS 1.2. Additionally, it's very important to keep these components up to date going forward to mitigate the risk of any security flaws that may be discovered in your system or any libraries it uses. ## Installation -[//]: # "To install the AuthorizeNet .NET Core SDK, run the following command in the Package Manager Console:" +[//]: # "To install the AuthorizeNet .NET Core SDK, run the following command in the Package Manager Console:" [//]: # "`PM> Install-Package AuthorizeNet`" Since this is a beta release, the SDK will not be available in NuGet gallery at the moment. To facilitate testing by the developer community, we have pre-compiled the SDK and placed it in `ReleaseArtifact` folder. - ## Registration & Configuration + Use of this SDK and the Authorize.Net APIs requires having an account on our system. You can find these details in the Settings section. If you don't currently have a production Authorize.Net account and need a sandbox account for testing, you can easily sign up for one [here](https://developer.authorize.net/sandbox/). ### Authentication + To authenticate with the Authorize.Net API you will need to use your account's API Login ID and Transaction Key. If you don't have these values, you can obtain them from our Merchant Interface site. Access the Merchant Interface for production accounts at (https://account.authorize.net/) or sandbox accounts at (https://sandbox.authorize.net). -Once you have your keys simply load them into the appropriate variables in your code, as per the below sample code dealing with the authentication part of the API request. +Once you have your keys simply load them into the appropriate variables in your code, as per the below sample code dealing with the authentication part of the API request. #### To set your API credentials for an API request: + +**⚠️ IMPORTANT: Thread Safety in Multi-Tenant Applications** + +The static properties `MerchantAuthentication` and `RunEnvironment` are now thread-safe using AsyncLocal storage, but setting credentials per-request is still the recommended approach for multi-tenant applications. + +**Recommended approach (per-request):** + +```csharp +var request = new createTransactionRequest() +{ + merchantAuthentication = new merchantAuthenticationType() + { + name = "YOUR_API_LOGIN_ID", + ItemElementName = ItemChoiceType.transactionKey, + Item = "YOUR_TRANSACTION_KEY", + } +}; +var controller = new createTransactionController(request); +controller.Execute(AuthorizeNet.Environment.PRODUCTION); +``` + +**Legacy approach (now thread-safe but discouraged):** + ```csharp +// This pattern is now thread-safe but generates a compiler warning ApiOperationBase.MerchantAuthentication = new merchantAuthenticationType() { name = "YOUR_API_LOGIN_ID", @@ -44,30 +68,46 @@ ApiOperationBase.MerchantAuthentication = new m You should never include your Login ID and Transaction Key directly in a file that's in a publically accessible portion of your website. A better practice would be to define these in a constants file, and then reference those constants in the appropriate place in your code. ### Switching between the sandbox environment and the production environment -Authorize.Net maintains a complete sandbox environment for testing and development purposes. This sandbox environment is an exact duplicate of our production environment with the transaction authorization and settlement process simulated. By default, this SDK is configured to communicate with the sandbox environment. To switch to the production environment, set the appropriate environment constant using ApiOperationBase `RunEnvironment` method. For example: + +Authorize.Net maintains a complete sandbox environment for testing and development purposes. This sandbox environment is an exact duplicate of our production environment with the transaction authorization and settlement process simulated. By default, this SDK is configured to communicate with the sandbox environment. + +**Recommended approach (per-request):** + +```csharp +// Pass environment directly to Execute() method +var controller = new createTransactionController(request); +controller.Execute(AuthorizeNet.Environment.PRODUCTION); // For PRODUCTION +// or +controller.Execute(AuthorizeNet.Environment.SANDBOX); // For SANDBOX +``` + +**Legacy approach (now thread-safe but discouraged):** + ```csharp -// For PRODUCTION use +// This pattern is now thread-safe but generates a compiler warning ApiOperationBase.RunEnvironment = AuthorizeNet.Environment.PRODUCTION; ``` API credentials are different for each environment, so be sure to switch to the appropriate credentials when switching environments. - ## SDK Usage Examples and Sample Code + To get started using this SDK, it's highly recommended to download our sample code repository: -* [Authorize.Net C# Sample Code Repository (on GitHub)](https://github.com/AuthorizeNet/sample-code-csharp) + +- [Authorize.Net C# Sample Code Repository (on GitHub)](https://github.com/AuthorizeNet/sample-code-csharp) In that respository, we have comprehensive sample code for all common uses of our API: Additionally, you can find details and examples of how our API is structured in our API Reference Guide: -* [Developer Center API Reference](http://developer.authorize.net/api/reference/index.html) -The API Reference Guide provides examples of what information is needed for a particular request and how that information would be formatted. Using those examples, you can easily determine what methods would be necessary to include that information in a request using this SDK. +- [Developer Center API Reference](http://developer.authorize.net/api/reference/index.html) +The API Reference Guide provides examples of what information is needed for a particular request and how that information would be formatted. Using those examples, you can easily determine what methods would be necessary to include that information in a request using this SDK. ## Building & Testing the SDK ### Running the SDK Tests + All the tests can be run against a stub backend using the USELOCAL run configuration. Get a sandbox account at https://developer.authorize.net/sandbox/ @@ -76,8 +116,9 @@ Update app.config in the AuthorizeNetTest folder to run all the tests against yo For reporting tests, go to https://sandbox.authorize.net/ under Account tab->Transaction Details API and enable it. ### Testing Guide -For additional help in testing your own code, Authorize.Net maintains a [comprehensive testing guide](http://developer.authorize.net/hello_world/testing_guide/) that includes test credit card numbers to use and special triggers to generate certain responses from the sandbox environment. +For additional help in testing your own code, Authorize.Net maintains a [comprehensive testing guide](http://developer.authorize.net/hello_world/testing_guide/) that includes test credit card numbers to use and special triggers to generate certain responses from the sandbox environment. ## License + This repository is distributed under a proprietary license. See the provided [`LICENSE.txt`](/LICENSE.txt) file. From 57acce29dfbba28e6f3ee2538ce7562c00385ca2 Mon Sep 17 00:00:00 2001 From: arunmish Date: Sat, 23 May 2026 18:07:36 +0530 Subject: [PATCH 5/7] security fixed --- .../Api/Controllers/Bases/ApiOperationBase.cs | 6 +-- AuthorizeNET/AuthorizeNET/Environment.cs | 52 +++++++++++++++++-- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs index 0addd6b..2815109 100644 --- a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs +++ b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs @@ -145,7 +145,7 @@ public void Execute(AuthorizeNet.Environment environment = null) { BeforeExecute(); - if (null == environment) { environment = ApiOperationBase.RunEnvironment; } + if (null == environment) { environment = _runEnvironment.Value; } if (null == environment) throw new ArgumentException(NullEnvironmentErrorMessage); var httpApiResponse = HttpUtility.PostData(environment, GetApiRequest()); @@ -249,9 +249,9 @@ private void ValidateAndSetMerchantAuthentication() if (null == request.merchantAuthentication) { - if (null != ApiOperationBase.MerchantAuthentication) + if (null != _merchantAuthentication.Value) { - request.merchantAuthentication = ApiOperationBase.MerchantAuthentication; + request.merchantAuthentication = _merchantAuthentication.Value; } else { diff --git a/AuthorizeNET/AuthorizeNET/Environment.cs b/AuthorizeNET/AuthorizeNET/Environment.cs index d0fac63..0f51457 100644 --- a/AuthorizeNET/AuthorizeNET/Environment.cs +++ b/AuthorizeNET/AuthorizeNET/Environment.cs @@ -19,18 +19,60 @@ public class Environment public static readonly Environment HOSTED_VM = new Environment(null, null, null); public static Environment CUSTOM = new Environment(null, null, null); - public bool HttpUseProxy { get; set; } - public string HttpsProxyUsername { get; set; } - public string HttpsProxyPassword { get; set; } - public string HttpProxyHost { get; set; } - public int HttpProxyPort { get; set; } + /// + /// Gets whether to use HTTP proxy. Immutable - set via constructor for thread safety. + /// + public bool HttpUseProxy { get; } + + /// + /// Gets the HTTPS proxy username. Immutable - set via constructor for thread safety. + /// + public string HttpsProxyUsername { get; } + + /// + /// Gets the HTTPS proxy password. Immutable - set via constructor for thread safety. + /// + public string HttpsProxyPassword { get; } + + /// + /// Gets the HTTP proxy host. Immutable - set via constructor for thread safety. + /// + public string HttpProxyHost { get; } + + /// + /// Gets the HTTP proxy port. Immutable - set via constructor for thread safety. + /// + public int HttpProxyPort { get; } public Environment(string baseUrl, string xmlBaseUrl, string cardPresentUrl) + : this(baseUrl, xmlBaseUrl, cardPresentUrl, false, null, 0, null, null) + { + } + + /// + /// Creates a new Environment with the specified URLs and optional proxy settings. + /// + /// Base URL + /// XML base URL + /// Card present URL + /// Whether to use HTTP proxy + /// Proxy host address + /// Proxy port number + /// Proxy username for authentication + /// Proxy password for authentication + public Environment(string baseUrl, string xmlBaseUrl, string cardPresentUrl, + bool httpUseProxy = false, string proxyHost = null, int proxyPort = 0, + string proxyUsername = null, string proxyPassword = null) { BaseUrl = baseUrl; XmlBaseUrl = xmlBaseUrl; CardPresentUrl = cardPresentUrl; + HttpUseProxy = httpUseProxy; + HttpProxyHost = proxyHost; + HttpProxyPort = proxyPort; + HttpsProxyUsername = proxyUsername; + HttpsProxyPassword = proxyPassword; } /// From d8965ee2ac2d2b382fa53074af722cbc99411aa7 Mon Sep 17 00:00:00 2001 From: arunmish Date: Sat, 23 May 2026 18:07:36 +0530 Subject: [PATCH 6/7] fix: log sanitization and redaction --- .../Api/Controllers/Bases/ApiOperationBase.cs | 14 ++++---- .../AuthorizeNET/Utilities/LogFactory.cs | 34 +++++++++++++++++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs index 2815109..440cda8 100644 --- a/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs +++ b/AuthorizeNET/AuthorizeNET/Api/Controllers/Bases/ApiOperationBase.cs @@ -152,30 +152,32 @@ public void Execute(AuthorizeNet.Environment environment = null) if (null != httpApiResponse) { - Logger.LogDebug("Received Response:'{0}' for request:'{1}'", httpApiResponse, GetApiRequest()); + // SECURITY: Log only type names, not full DTOs which may contain + // merchantAuthentication credentials, card numbers, or session tokens. + Logger.LogDebug("Received Response type:'{0}' for request type:'{1}'", httpApiResponse.GetType().Name, _requestClass.Name); if (httpApiResponse.GetType() == _responseClass) { var response = (TS)httpApiResponse; SetApiResponse(response); - Logger.LogDebug("Setting response: '{0}'", response); + Logger.LogDebug("Setting response type: '{0}'", _responseClass.Name); } else if (httpApiResponse.GetType() == typeof(ErrorResponse)) { SetErrorResponse(httpApiResponse); - Logger.LogDebug("Received ErrorResponse:'{0}'", httpApiResponse); + Logger.LogDebug("Received ErrorResponse for request type:'{0}'", _requestClass.Name); } else { SetErrorResponse(httpApiResponse); - Logger.LogError("Invalid response:'{0}'", httpApiResponse); + Logger.LogError("Invalid response type:'{0}' for request type:'{1}'", httpApiResponse.GetType().Name, _requestClass.Name); } - Logger.LogDebug("Response obtained: {0}", GetApiResponse()); + Logger.LogDebug("Response obtained for request type: {0}", _requestClass.Name); SetResultStatus(); } else { - Logger.LogDebug("Got a 'null' Response for request:'{0}'\n", GetApiRequest()); + Logger.LogDebug("Got a 'null' Response for request type:'{0}'", _requestClass.Name); } AfterExecute(); } diff --git a/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs b/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs index 8f0ad06..f332bcd 100644 --- a/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs +++ b/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs @@ -3,13 +3,41 @@ using System; using Microsoft.Extensions.Logging; + /// + /// Factory for creating SDK loggers. Consumers can inject their own ILoggerFactory + /// via SetLoggerFactory() to control log routing, sinks, and levels. + /// + /// SECURITY NOTE: The default log level is Warning (not Debug) to prevent + /// accidental exposure of sensitive data (merchantAuthentication credentials, + /// card numbers, session tokens) in request/response DTOs. If you need Debug-level + /// SDK logging during development, call LogFactory.SetLoggerFactory(...) with your + /// own factory configured at the desired level, and ensure sensitive fields are + /// redacted before they reach persistent log sinks. + /// public static class LogFactory { - private static ILoggerFactory LoggerFactory => new LoggerFactory().AddDebug(LogLevel.Debug); + private static ILoggerFactory _loggerFactory; + + /// + /// Allows consumers to inject their own ILoggerFactory for full control + /// over log routing, sinks, and filtering levels. + /// + /// The ILoggerFactory to use for all SDK logging. + public static void SetLoggerFactory(ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory; + } + + private static ILoggerFactory GetLoggerFactory() + { + // Default: Warning level via Debug output (only captured when debugger is attached). + // Consumers should call SetLoggerFactory() to wire up their own sinks/levels. + return _loggerFactory ?? new LoggerFactory().AddDebug(LogLevel.Warning); + } public static ILogger getLog(Type classType) { - return LoggerFactory.CreateLogger(classType.FullName); + return GetLoggerFactory().CreateLogger(classType.FullName); } } -} \ No newline at end of file +} From bde6afe5d80843e30176a8a5ca7a8d583fe4683a Mon Sep 17 00:00:00 2001 From: arunmish Date: Wed, 3 Jun 2026 13:37:53 +0530 Subject: [PATCH 7/7] fix(security): eliminate sensitive data logging in HttpUtility and XmlUtility - HttpUtility.cs: Replace raw response body/object logging with HTTP status code, reason phrase, content length, and content type metadata only - XmlUtility.cs: Remove raw XML from error log on deserialization failure; log only exception message, response type name, and XML length - LogFactory.cs: Make thread-safe with volatile + lock (double-checked pattern) to prevent shared-config-bleed in multi-tenant hosts Addresses PCI A3.2.6, KC 7.10.9, KC 7.13.1 DLP requirements: sensitive data (PAN, transactionKey, session tokens) must never reach log sinks at any level. --- .../AuthorizeNET/Utilities/HttpUtility.cs | 11 +++--- .../AuthorizeNET/Utilities/LogFactory.cs | 34 ++++++++++++++----- .../AuthorizeNET/Utilities/XmlUtility.cs | 4 ++- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs b/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs index 513ea95..780c75f 100644 --- a/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs +++ b/AuthorizeNET/AuthorizeNET/Utilities/HttpUtility.cs @@ -45,12 +45,13 @@ public static ANetApiResponse PostData(AuthorizeNet.Environment env, TQ client.Timeout = TimeSpan.FromMilliseconds(httpConnectionTimeout != 0 ? httpConnectionTimeout : Constants.HttpConnectionDefaultTimeout); var content = new StringContent(XmlUtility.Serialize(request), Encoding.UTF8, "text/xml"); var webResponse = client.PostAsync(postUrl, content).Result; - Logger.LogDebug("Retrieving Response from Url: '{0}'", postUrl); + Logger.LogDebug("Retrieving Response from Url: '{0}'", postUrl); - // Get the response - Logger.LogDebug("Received Response: '{0}'", webResponse); - responseAsString = webResponse.Content.ReadAsStringAsync().Result; - Logger.LogDebug("Response from Stream: '{0}'", responseAsString); + // Get the response — SECURITY: Log only HTTP status, never raw body + // (response may contain PAN, transactionKey, session tokens) + Logger.LogDebug("Received Response: StatusCode='{0}', ReasonPhrase='{1}'", webResponse.StatusCode, webResponse.ReasonPhrase); + responseAsString = webResponse.Content.ReadAsStringAsync().Result; + Logger.LogDebug("Response received, ContentLength='{0}', ContentType='{1}'", responseAsString?.Length, webResponse.Content?.Headers?.ContentType); } } diff --git a/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs b/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs index f332bcd..e0bd9a2 100644 --- a/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs +++ b/AuthorizeNET/AuthorizeNET/Utilities/LogFactory.cs @@ -9,30 +9,46 @@ /// /// SECURITY NOTE: The default log level is Warning (not Debug) to prevent /// accidental exposure of sensitive data (merchantAuthentication credentials, - /// card numbers, session tokens) in request/response DTOs. If you need Debug-level - /// SDK logging during development, call LogFactory.SetLoggerFactory(...) with your - /// own factory configured at the desired level, and ensure sensitive fields are - /// redacted before they reach persistent log sinks. + /// card numbers, session tokens) in request/response DTOs. No raw XML or + /// response body content is ever logged at any level — only metadata such as + /// HTTP status codes, content lengths, type names, and error messages. /// public static class LogFactory { - private static ILoggerFactory _loggerFactory; + private static readonly object _lock = new object(); + private static volatile ILoggerFactory _loggerFactory; /// /// Allows consumers to inject their own ILoggerFactory for full control /// over log routing, sinks, and filtering levels. + /// Thread-safe: uses volatile read + lock on write. /// /// The ILoggerFactory to use for all SDK logging. public static void SetLoggerFactory(ILoggerFactory loggerFactory) { - _loggerFactory = loggerFactory; + lock (_lock) + { + _loggerFactory = loggerFactory; + } } private static ILoggerFactory GetLoggerFactory() { - // Default: Warning level via Debug output (only captured when debugger is attached). - // Consumers should call SetLoggerFactory() to wire up their own sinks/levels. - return _loggerFactory ?? new LoggerFactory().AddDebug(LogLevel.Warning); + // Volatile read — no lock needed for read path (double-checked pattern) + var factory = _loggerFactory; + if (factory != null) + return factory; + + lock (_lock) + { + if (_loggerFactory == null) + { + // Default: Warning level via Debug output (only captured when debugger is attached). + // Consumers should call SetLoggerFactory() to wire up their own sinks/levels. + _loggerFactory = new LoggerFactory().AddDebug(LogLevel.Warning); + } + return _loggerFactory; + } } public static ILogger getLog(Type classType) diff --git a/AuthorizeNET/AuthorizeNET/Utilities/XmlUtility.cs b/AuthorizeNET/AuthorizeNET/Utilities/XmlUtility.cs index bafbc47..1360a9d 100644 --- a/AuthorizeNET/AuthorizeNET/Utilities/XmlUtility.cs +++ b/AuthorizeNET/AuthorizeNET/Utilities/XmlUtility.cs @@ -57,7 +57,9 @@ public static T Deserialize(string xml) } catch (Exception e) { - Logger.LogError("Error:'{0}' when deserializing the into object:'{1}' from xml:'{2}'", e.Message, responseType, xml); + // SECURITY: Never log raw XML — it may contain PAN, transactionKey, + // session tokens, or other sensitive payment data (PCI A3.2.6, KC 7.10.9). + Logger.LogError("Error:'{0}' when deserializing into object:'{1}' (xmlLength={2})", e.Message, responseType, xml?.Length); throw; } }