From 061477d4ecc322e33e458b18e9264f8d54387454 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 11 Oct 2023 17:29:15 +0200 Subject: [PATCH 01/14] Make regex static fields --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 6ecfc405427..45efdbe30cd 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Collections.Concurrent; using System.IO; using System.Security; using System.Text.RegularExpressions; @@ -38,13 +39,13 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; + private static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex UriUserInfoPattern; + private static readonly ConcurrentDictionary PasswordValuePattern = new(); + private readonly IAnalyzerConfiguration configuration; private readonly DiagnosticDescriptor rule; - private readonly Regex validCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase); - private readonly Regex uriUserInfoPattern; private string credentialWords; - private IEnumerable splitCredentialWords; - private Regex passwordValuePattern; protected abstract ILanguageFacade Language { get; } protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); @@ -59,17 +60,20 @@ public string CredentialWords set { credentialWords = value; - splitCredentialWords = value.ToUpperInvariant() - .Split(',') - .Select(x => x.Trim()) - .Where(x => x.Length != 0) - .ToList(); - passwordValuePattern = new Regex(string.Format(@"\b(?{0})\s*[:=]\s*(?.+)$", - string.Join("|", splitCredentialWords.Select(Regex.Escape))), RegexOptions.Compiled | RegexOptions.IgnoreCase); + SplitCredentialWords = GetSplitCredentialWords(credentialWords); } } + protected ImmutableList SplitCredentialWords { get; private set; } + protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) + { + this.configuration = configuration; + rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); + CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables + } + + static DoNotHardcodeCredentialsBase() { const string Rfc3986_Unreserved = "-._~"; // Numbers and letters are embedded in regex itself without escaping const string Rfc3986_Pct = "%"; @@ -78,10 +82,7 @@ protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) // See https://tools.ietf.org/html/rfc3986 Userinfo can contain groups: unreserved | pct-encoded | sub-delims var loginGroup = CreateUserInfoGroup("Login"); var passwordGroup = CreateUserInfoGroup("Password", ":"); // Additional ":" to capture passwords containing it - uriUserInfoPattern = new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled); - this.configuration = configuration; - rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); - CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables + UriUserInfoPattern = new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled); static string CreateUserInfoGroup(string name, string additionalCharacters = null) => $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; @@ -185,11 +186,18 @@ private string IssueMessage(string variableName, string variableValue) } } + private Regex PasswordValueRegex => + PasswordValuePattern.GetOrAdd(CredentialWords, static credentialWords => + { + var splitCredentialWords = string.Join("|", GetSplitCredentialWords(credentialWords).Select(Regex.Escape)); + return new Regex($@"\b(?{splitCredentialWords})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + }); + private IEnumerable FindCredentialWords(string variableName, string variableValue) { var credentialWordsFound = variableName .SplitCamelCaseToWords() - .Intersect(splitCredentialWords) + .Intersect(SplitCredentialWords) .ToHashSet(StringComparer.OrdinalIgnoreCase); if (credentialWordsFound.Any(x => variableValue.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) >= 0)) @@ -198,7 +206,7 @@ private IEnumerable FindCredentialWords(string variableName, string vari return Enumerable.Empty(); } - var match = passwordValuePattern.Match(variableValue); + var match = PasswordValueRegex.Match(variableValue); if (match.Success && !IsValidCredential(match.Groups["suffix"].Value)) { credentialWordsFound.Add(match.Groups["credential"].Value); @@ -211,19 +219,26 @@ private IEnumerable FindCredentialWords(string variableName, string vari private bool IsValidCredential(string suffix) { var candidateCredential = suffix.Split(CredentialSeparator).First().Trim(); - return string.IsNullOrWhiteSpace(candidateCredential) || validCredentialPattern.IsMatch(candidateCredential); + return string.IsNullOrWhiteSpace(candidateCredential) || ValidCredentialPattern.IsMatch(candidateCredential); } private bool ContainsUriUserInfo(string variableValue) { - var match = uriUserInfoPattern.Match(variableValue); + var match = UriUserInfoPattern.Match(variableValue); return match.Success && match.Groups["Password"].Value is { } password && !string.Equals(match.Groups["Login"].Value, password, StringComparison.OrdinalIgnoreCase) && password != CredentialSeparator.ToString() - && !validCredentialPattern.IsMatch(password); + && !ValidCredentialPattern.IsMatch(password); } + private static ImmutableList GetSplitCredentialWords(string credentialWords) => + credentialWords.ToUpperInvariant() + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()) + .Where(x => x.Length != 0) + .ToImmutableList(); + protected abstract class CredentialWordsFinderBase where TSyntaxNode : SyntaxNode { From cc98d7b24cf2e2d78a4e6de113dc0eca403c8f61 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 11 Oct 2023 20:25:09 +0200 Subject: [PATCH 02/14] Code smells --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 45efdbe30cd..ac5e59d26a6 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -28,7 +28,43 @@ namespace SonarAnalyzer.Rules { - public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer + public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer + { + protected static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + protected static readonly Regex UriUserInfoPattern; + protected static readonly ConcurrentDictionary PasswordValuePattern = new(); + + static DoNotHardcodeCredentialsBase() + { + const string Rfc3986_Unreserved = "-._~"; // Numbers and letters are embedded in regex itself without escaping + const string Rfc3986_Pct = "%"; + const string Rfc3986_SubDelims = "!$&'()*+,;="; + const string UriPasswordSpecialCharacters = Rfc3986_Unreserved + Rfc3986_Pct + Rfc3986_SubDelims; + // See https://tools.ietf.org/html/rfc3986 Userinfo can contain groups: unreserved | pct-encoded | sub-delims + var loginGroup = CreateUserInfoGroup("Login"); + var passwordGroup = CreateUserInfoGroup("Password", ":"); // Additional ":" to capture passwords containing it + UriUserInfoPattern = new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled); + + static string CreateUserInfoGroup(string name, string additionalCharacters = null) => + $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; + } + + protected static Regex PasswordValueRegex(string credentialWords) => + PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => + { + var splitCredentialWords = string.Join("|", GetSplitCredentialWords(credentialWords).Select(Regex.Escape)); + return new Regex($@"\b(?{splitCredentialWords})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + }); + + protected static ImmutableList GetSplitCredentialWords(string credentialWords) => + credentialWords.ToUpperInvariant() + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()) + .Where(x => x.Length != 0) + .ToImmutableList(); + } + + public abstract class DoNotHardcodeCredentialsBase : DoNotHardcodeCredentialsBase where TSyntaxKind : struct { protected const char CredentialSeparator = ';'; @@ -39,10 +75,6 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; - private static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex UriUserInfoPattern; - private static readonly ConcurrentDictionary PasswordValuePattern = new(); - private readonly IAnalyzerConfiguration configuration; private readonly DiagnosticDescriptor rule; private string credentialWords; @@ -73,21 +105,6 @@ protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } - static DoNotHardcodeCredentialsBase() - { - const string Rfc3986_Unreserved = "-._~"; // Numbers and letters are embedded in regex itself without escaping - const string Rfc3986_Pct = "%"; - const string Rfc3986_SubDelims = "!$&'()*+,;="; - const string UriPasswordSpecialCharacters = Rfc3986_Unreserved + Rfc3986_Pct + Rfc3986_SubDelims; - // See https://tools.ietf.org/html/rfc3986 Userinfo can contain groups: unreserved | pct-encoded | sub-delims - var loginGroup = CreateUserInfoGroup("Login"); - var passwordGroup = CreateUserInfoGroup("Password", ":"); // Additional ":" to capture passwords containing it - UriUserInfoPattern = new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled); - - static string CreateUserInfoGroup(string name, string additionalCharacters = null) => - $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; - } - protected sealed override void Initialize(SonarParametrizedAnalysisContext context) { var input = new TrackerInput(context, configuration, rule); @@ -186,13 +203,6 @@ private string IssueMessage(string variableName, string variableValue) } } - private Regex PasswordValueRegex => - PasswordValuePattern.GetOrAdd(CredentialWords, static credentialWords => - { - var splitCredentialWords = string.Join("|", GetSplitCredentialWords(credentialWords).Select(Regex.Escape)); - return new Regex($@"\b(?{splitCredentialWords})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - }); - private IEnumerable FindCredentialWords(string variableName, string variableValue) { var credentialWordsFound = variableName @@ -206,7 +216,7 @@ private IEnumerable FindCredentialWords(string variableName, string vari return Enumerable.Empty(); } - var match = PasswordValueRegex.Match(variableValue); + var match = PasswordValueRegex(CredentialWords).Match(variableValue); if (match.Success && !IsValidCredential(match.Groups["suffix"].Value)) { credentialWordsFound.Add(match.Groups["credential"].Value); @@ -216,13 +226,13 @@ private IEnumerable FindCredentialWords(string variableName, string vari return credentialWordsFound.Select(x => x.ToLowerInvariant()); } - private bool IsValidCredential(string suffix) + private static bool IsValidCredential(string suffix) { var candidateCredential = suffix.Split(CredentialSeparator).First().Trim(); return string.IsNullOrWhiteSpace(candidateCredential) || ValidCredentialPattern.IsMatch(candidateCredential); } - private bool ContainsUriUserInfo(string variableValue) + private static bool ContainsUriUserInfo(string variableValue) { var match = UriUserInfoPattern.Match(variableValue); return match.Success @@ -232,13 +242,6 @@ private bool ContainsUriUserInfo(string variableValue) && !ValidCredentialPattern.IsMatch(password); } - private static ImmutableList GetSplitCredentialWords(string credentialWords) => - credentialWords.ToUpperInvariant() - .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .Where(x => x.Length != 0) - .ToImmutableList(); - protected abstract class CredentialWordsFinderBase where TSyntaxNode : SyntaxNode { From effc800aacc219d7c3e622580dc70b581a852b66 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 11 Oct 2023 21:06:56 +0200 Subject: [PATCH 03/14] Remove static ctor --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index ac5e59d26a6..f1d71dce7a7 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -31,24 +31,9 @@ namespace SonarAnalyzer.Rules public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer { protected static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - protected static readonly Regex UriUserInfoPattern; + protected static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); protected static readonly ConcurrentDictionary PasswordValuePattern = new(); - static DoNotHardcodeCredentialsBase() - { - const string Rfc3986_Unreserved = "-._~"; // Numbers and letters are embedded in regex itself without escaping - const string Rfc3986_Pct = "%"; - const string Rfc3986_SubDelims = "!$&'()*+,;="; - const string UriPasswordSpecialCharacters = Rfc3986_Unreserved + Rfc3986_Pct + Rfc3986_SubDelims; - // See https://tools.ietf.org/html/rfc3986 Userinfo can contain groups: unreserved | pct-encoded | sub-delims - var loginGroup = CreateUserInfoGroup("Login"); - var passwordGroup = CreateUserInfoGroup("Password", ":"); // Additional ":" to capture passwords containing it - UriUserInfoPattern = new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled); - - static string CreateUserInfoGroup(string name, string additionalCharacters = null) => - $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; - } - protected static Regex PasswordValueRegex(string credentialWords) => PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => { @@ -62,6 +47,21 @@ protected static ImmutableList GetSplitCredentialWords(string credential .Select(x => x.Trim()) .Where(x => x.Length != 0) .ToImmutableList(); + + private static Regex CreateUriUserInfoPattern() + { + const string Rfc3986_Unreserved = "-._~"; // Numbers and letters are embedded in regex itself without escaping + const string Rfc3986_Pct = "%"; + const string Rfc3986_SubDelims = "!$&'()*+,;="; + const string UriPasswordSpecialCharacters = Rfc3986_Unreserved + Rfc3986_Pct + Rfc3986_SubDelims; + // See https://tools.ietf.org/html/rfc3986 Userinfo can contain groups: unreserved | pct-encoded | sub-delims + var loginGroup = CreateUserInfoGroup("Login"); + var passwordGroup = CreateUserInfoGroup("Password", ":"); // Additional ":" to capture passwords containing it + return new Regex(@$"\w+:\/\/{loginGroup}:{passwordGroup}@", RegexOptions.Compiled); + + static string CreateUserInfoGroup(string name, string additionalCharacters = null) => + $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; + } } public abstract class DoNotHardcodeCredentialsBase : DoNotHardcodeCredentialsBase From a0e6d26d548e0266e39ade937b29358d894f5546 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 12 Oct 2023 12:53:13 +0200 Subject: [PATCH 04/14] Move CredentialWords prop to base class --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index f1d71dce7a7..3f4d7cfc35b 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -30,9 +30,30 @@ namespace SonarAnalyzer.Rules { public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer { + private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; + private static readonly ConcurrentDictionary PasswordValuePattern = new(); protected static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); protected static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); - protected static readonly ConcurrentDictionary PasswordValuePattern = new(); + + private string credentialWords; + + [RuleParameter("credentialWords", PropertyType.String, "Comma separated list of words identifying potential credentials", DefaultCredentialWords)] + public string CredentialWords + { + get => credentialWords; + set + { + credentialWords = value; + SplitCredentialWords = GetSplitCredentialWords(credentialWords); + } + } + + protected ImmutableList SplitCredentialWords { get; private set; } + + public DoNotHardcodeCredentialsBase() + { + CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables + } protected static Regex PasswordValueRegex(string credentialWords) => PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => @@ -73,11 +94,9 @@ public abstract class DoNotHardcodeCredentialsBase : DoNotHardcodeC private const string MessageHardcodedPassword = "Please review this hard-coded password."; private const string MessageFormatCredential = @"""{0}"" detected here, make sure this is not a hard-coded credential."; private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; - private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; private readonly IAnalyzerConfiguration configuration; private readonly DiagnosticDescriptor rule; - private string credentialWords; protected abstract ILanguageFacade Language { get; } protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); @@ -85,24 +104,10 @@ public abstract class DoNotHardcodeCredentialsBase : DoNotHardcodeC public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); - [RuleParameter("credentialWords", PropertyType.String, "Comma separated list of words identifying potential credentials", DefaultCredentialWords)] - public string CredentialWords - { - get => credentialWords; - set - { - credentialWords = value; - SplitCredentialWords = GetSplitCredentialWords(credentialWords); - } - } - - protected ImmutableList SplitCredentialWords { get; private set; } - protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) { this.configuration = configuration; rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); - CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } protected sealed override void Initialize(SonarParametrizedAnalysisContext context) From e19d88730e39d140f754636663ced610ea1a7328 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 12 Oct 2023 13:48:05 +0200 Subject: [PATCH 05/14] Code smells --- .../Rules/Hotspots/DoNotHardcodeCredentialsBase.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 3f4d7cfc35b..009111ada87 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -44,13 +44,13 @@ public string CredentialWords set { credentialWords = value; - SplitCredentialWords = GetSplitCredentialWords(credentialWords); + SplitCredentialWords = SplitCredentialWordsByComma(credentialWords); } } protected ImmutableList SplitCredentialWords { get; private set; } - public DoNotHardcodeCredentialsBase() + protected DoNotHardcodeCredentialsBase() { CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } @@ -58,11 +58,11 @@ public DoNotHardcodeCredentialsBase() protected static Regex PasswordValueRegex(string credentialWords) => PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => { - var splitCredentialWords = string.Join("|", GetSplitCredentialWords(credentialWords).Select(Regex.Escape)); + var splitCredentialWords = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); return new Regex($@"\b(?{splitCredentialWords})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); }); - protected static ImmutableList GetSplitCredentialWords(string credentialWords) => + protected static ImmutableList SplitCredentialWordsByComma(string credentialWords) => credentialWords.ToUpperInvariant() .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) From 790d13c51b55f285e2536603f5522552d8968a93 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Fri, 17 Nov 2023 14:50:23 +0100 Subject: [PATCH 06/14] Use single class. --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 009111ada87..cd1e0db05b1 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -28,13 +28,24 @@ namespace SonarAnalyzer.Rules { - public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer + public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer + where TSyntaxKind : struct { private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; + private const string DiagnosticId = "S2068"; + private const string MessageFormat = "{0}"; + private const string MessageHardcodedPassword = "Please review this hard-coded password."; + private const string MessageFormatCredential = @"""{0}"" detected here, make sure this is not a hard-coded credential."; + private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; + protected const char CredentialSeparator = ';'; + private static readonly ConcurrentDictionary PasswordValuePattern = new(); protected static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); protected static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); + private readonly IAnalyzerConfiguration configuration; + private readonly DiagnosticDescriptor rule; + private string credentialWords; [RuleParameter("credentialWords", PropertyType.String, "Comma separated list of words identifying potential credentials", DefaultCredentialWords)] @@ -83,20 +94,6 @@ private static Regex CreateUriUserInfoPattern() static string CreateUserInfoGroup(string name, string additionalCharacters = null) => $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; } - } - - public abstract class DoNotHardcodeCredentialsBase : DoNotHardcodeCredentialsBase - where TSyntaxKind : struct - { - protected const char CredentialSeparator = ';'; - private const string DiagnosticId = "S2068"; - private const string MessageFormat = "{0}"; - private const string MessageHardcodedPassword = "Please review this hard-coded password."; - private const string MessageFormatCredential = @"""{0}"" detected here, make sure this is not a hard-coded credential."; - private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; - - private readonly IAnalyzerConfiguration configuration; - private readonly DiagnosticDescriptor rule; protected abstract ILanguageFacade Language { get; } protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); From 071dacc42116c71d04cc3d172fa7c4d88f8bc586 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Fri, 17 Nov 2023 14:57:46 +0100 Subject: [PATCH 07/14] Move member --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index cd1e0db05b1..7636ff78301 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -31,13 +31,13 @@ namespace SonarAnalyzer.Rules public abstract class DoNotHardcodeCredentialsBase : ParametrizedDiagnosticAnalyzer where TSyntaxKind : struct { - private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; + protected const char CredentialSeparator = ';'; private const string DiagnosticId = "S2068"; private const string MessageFormat = "{0}"; private const string MessageHardcodedPassword = "Please review this hard-coded password."; private const string MessageFormatCredential = @"""{0}"" detected here, make sure this is not a hard-coded credential."; private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; - protected const char CredentialSeparator = ';'; + private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; private static readonly ConcurrentDictionary PasswordValuePattern = new(); protected static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -47,6 +47,13 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private readonly DiagnosticDescriptor rule; private string credentialWords; + private ImmutableList splitCredentialWords; + + protected abstract ILanguageFacade Language { get; } + protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); + protected abstract bool IsSecureStringAppendCharFromConstant(SyntaxNode argumentNode, SemanticModel model); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); [RuleParameter("credentialWords", PropertyType.String, "Comma separated list of words identifying potential credentials", DefaultCredentialWords)] public string CredentialWords @@ -55,25 +62,23 @@ public string CredentialWords set { credentialWords = value; - SplitCredentialWords = SplitCredentialWordsByComma(credentialWords); + splitCredentialWords = SplitCredentialWordsByComma(credentialWords); } } - protected ImmutableList SplitCredentialWords { get; private set; } - protected DoNotHardcodeCredentialsBase() { CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } - protected static Regex PasswordValueRegex(string credentialWords) => + private static Regex PasswordValueRegex(string credentialWords) => PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => { var splitCredentialWords = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); return new Regex($@"\b(?{splitCredentialWords})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); }); - protected static ImmutableList SplitCredentialWordsByComma(string credentialWords) => + private static ImmutableList SplitCredentialWordsByComma(string credentialWords) => credentialWords.ToUpperInvariant() .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) @@ -95,12 +100,6 @@ static string CreateUserInfoGroup(string name, string additionalCharacters = nul $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; } - protected abstract ILanguageFacade Language { get; } - protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); - protected abstract bool IsSecureStringAppendCharFromConstant(SyntaxNode argumentNode, SemanticModel model); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(rule); - protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) { this.configuration = configuration; @@ -209,7 +208,7 @@ private IEnumerable FindCredentialWords(string variableName, string vari { var credentialWordsFound = variableName .SplitCamelCaseToWords() - .Intersect(SplitCredentialWords) + .Intersect(splitCredentialWords) .ToHashSet(StringComparer.OrdinalIgnoreCase); if (credentialWordsFound.Any(x => variableValue.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) >= 0)) From e9db09afa1e3a8f63b34d5feee7152d6c6f88fd3 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Fri, 17 Nov 2023 15:04:36 +0100 Subject: [PATCH 08/14] Move member --- .../Rules/Hotspots/DoNotHardcodeCredentialsBase.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 7636ff78301..3ff26caeefe 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -66,16 +66,18 @@ public string CredentialWords } } - protected DoNotHardcodeCredentialsBase() + protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) { + this.configuration = configuration; + rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } private static Regex PasswordValueRegex(string credentialWords) => PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => { - var splitCredentialWords = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); - return new Regex($@"\b(?{splitCredentialWords})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + var credentialWordsPattern = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); + return new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); }); private static ImmutableList SplitCredentialWordsByComma(string credentialWords) => @@ -100,12 +102,6 @@ static string CreateUserInfoGroup(string name, string additionalCharacters = nul $@"(?<{name}>[\w\d{Regex.Escape(UriPasswordSpecialCharacters)}{additionalCharacters}]+)"; } - protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) - { - this.configuration = configuration; - rule = Language.CreateDescriptor(DiagnosticId, MessageFormat); - } - protected sealed override void Initialize(SonarParametrizedAnalysisContext context) { var input = new TrackerInput(context, configuration, rule); From 92a8d0a454606e5e0b6237c4c89974f492ff5511 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Fri, 17 Nov 2023 15:35:10 +0100 Subject: [PATCH 09/14] Make fields private --- .../Rules/Hotspots/DoNotHardcodeCredentialsBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 3ff26caeefe..f18cb9b203d 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -40,8 +40,8 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; private static readonly ConcurrentDictionary PasswordValuePattern = new(); - protected static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); - protected static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); + private static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); private readonly IAnalyzerConfiguration configuration; private readonly DiagnosticDescriptor rule; From cd100bfb23b9e8e08a63bc3e4c3688da0664247d Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 23 Nov 2023 12:36:55 +0100 Subject: [PATCH 10/14] Use Tuple class instead of ConcurrentDictionary. --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index f18cb9b203d..f904f186e51 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Concurrent; using System.IO; using System.Security; using System.Text.RegularExpressions; @@ -39,7 +38,9 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; - private static readonly ConcurrentDictionary PasswordValuePattern = new(); + // Assumption: There will only be one "CredentialWords" parameter value per AppDomain. If this changes, + // use a concurrent dictionary instead (Don't use ValueTuple as the write is not atomic). + private static Tuple PasswordValuePattern; private static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); @@ -73,12 +74,20 @@ protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } - private static Regex PasswordValueRegex(string credentialWords) => - PasswordValuePattern.GetOrAdd(credentialWords, static credentialWords => + private static Regex PasswordValueRegex(string credentialWords) + { + // Make a local copy of the reference to the tuple class to avoid concurrency issues between the access of Item1 and Item2 + var local = PasswordValuePattern; + if (local.Item1 == credentialWords) { - var credentialWordsPattern = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); - return new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - }); + return local.Item2; + } + var credentialWordsPattern = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); + var regex = new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + // There is no need for locking on the write. The read is atomic and any tuple read is valid (but may fail the condition). + PasswordValuePattern = new(credentialWords, regex); + return regex; + } private static ImmutableList SplitCredentialWordsByComma(string credentialWords) => credentialWords.ToUpperInvariant() From ebb2b8be35a02ad8a42ca7fc99f70e459ea9a6f4 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 23 Nov 2023 12:56:30 +0100 Subject: [PATCH 11/14] Fix NRE --- .../Rules/Hotspots/DoNotHardcodeCredentialsBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index f904f186e51..3879ba623b8 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -78,7 +78,7 @@ private static Regex PasswordValueRegex(string credentialWords) { // Make a local copy of the reference to the tuple class to avoid concurrency issues between the access of Item1 and Item2 var local = PasswordValuePattern; - if (local.Item1 == credentialWords) + if (local?.Item1 == credentialWords) { return local.Item2; } From 08feef6dd0ff0ecc4d989c573444a8c5b6df89ed Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 23 Nov 2023 15:14:54 +0100 Subject: [PATCH 12/14] Make PasswordValuePattern an instance property. --- .../Hotspots/DoNotHardcodeCredentialsBase.cs | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 3879ba623b8..1ac5b95840c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -38,9 +38,6 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private const string MessageUriUserInfo = "Review this hard-coded URI, which may contain a credential."; private const string DefaultCredentialWords = "password, passwd, pwd, passphrase"; - // Assumption: There will only be one "CredentialWords" parameter value per AppDomain. If this changes, - // use a concurrent dictionary instead (Don't use ValueTuple as the write is not atomic). - private static Tuple PasswordValuePattern; private static readonly Regex ValidCredentialPattern = new(@"^(\?|:\w+|\{\d+[^}]*\}|""|')$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex UriUserInfoPattern = CreateUriUserInfoPattern(); @@ -48,6 +45,7 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private readonly DiagnosticDescriptor rule; private string credentialWords; + private Regex PasswordValuePattern; private ImmutableList splitCredentialWords; protected abstract ILanguageFacade Language { get; } @@ -63,7 +61,10 @@ public string CredentialWords set { credentialWords = value; - splitCredentialWords = SplitCredentialWordsByComma(credentialWords); + var split = SplitCredentialWordsByComma(credentialWords); + splitCredentialWords = split; + var credentialWordsPattern = string.Join("|", split.Select(Regex.Escape)); + PasswordValuePattern = new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.IgnoreCase); } } @@ -74,21 +75,6 @@ protected DoNotHardcodeCredentialsBase(IAnalyzerConfiguration configuration) CredentialWords = DefaultCredentialWords; // Property will initialize multiple state variables } - private static Regex PasswordValueRegex(string credentialWords) - { - // Make a local copy of the reference to the tuple class to avoid concurrency issues between the access of Item1 and Item2 - var local = PasswordValuePattern; - if (local?.Item1 == credentialWords) - { - return local.Item2; - } - var credentialWordsPattern = string.Join("|", SplitCredentialWordsByComma(credentialWords).Select(Regex.Escape)); - var regex = new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - // There is no need for locking on the write. The read is atomic and any tuple read is valid (but may fail the condition). - PasswordValuePattern = new(credentialWords, regex); - return regex; - } - private static ImmutableList SplitCredentialWordsByComma(string credentialWords) => credentialWords.ToUpperInvariant() .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) @@ -222,7 +208,7 @@ private IEnumerable FindCredentialWords(string variableName, string vari return Enumerable.Empty(); } - var match = PasswordValueRegex(CredentialWords).Match(variableValue); + var match = PasswordValuePattern.Match(variableValue); if (match.Success && !IsValidCredential(match.Groups["suffix"].Value)) { credentialWordsFound.Add(match.Groups["credential"].Value); From 42adbdd3a4531fb9fe4fe2bb27f40568f241e514 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Thu, 23 Nov 2023 15:17:41 +0100 Subject: [PATCH 13/14] lower case --- .../Rules/Hotspots/DoNotHardcodeCredentialsBase.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 1ac5b95840c..7638951614c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -45,8 +45,8 @@ public abstract class DoNotHardcodeCredentialsBase : ParametrizedDi private readonly DiagnosticDescriptor rule; private string credentialWords; - private Regex PasswordValuePattern; private ImmutableList splitCredentialWords; + private Regex passwordValuePattern; protected abstract ILanguageFacade Language { get; } protected abstract void InitializeActions(SonarParametrizedAnalysisContext context); @@ -64,7 +64,7 @@ public string CredentialWords var split = SplitCredentialWordsByComma(credentialWords); splitCredentialWords = split; var credentialWordsPattern = string.Join("|", split.Select(Regex.Escape)); - PasswordValuePattern = new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.IgnoreCase); + passwordValuePattern = new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.IgnoreCase); } } @@ -208,7 +208,7 @@ private IEnumerable FindCredentialWords(string variableName, string vari return Enumerable.Empty(); } - var match = PasswordValuePattern.Match(variableValue); + var match = passwordValuePattern.Match(variableValue); if (match.Success && !IsValidCredential(match.Groups["suffix"].Value)) { credentialWordsFound.Add(match.Groups["credential"].Value); From 021fccfdc43059d3b63b63ab89e31b1d4083bbff Mon Sep 17 00:00:00 2001 From: Martin Strecker <103252490+martin-strecker-sonarsource@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:19:27 +0100 Subject: [PATCH 14/14] Update analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs Co-authored-by: Pavel Mikula <57188685+pavel-mikula-sonarsource@users.noreply.github.com> --- .../Rules/Hotspots/DoNotHardcodeCredentialsBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs index 7638951614c..d6f247bd6ff 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/Hotspots/DoNotHardcodeCredentialsBase.cs @@ -63,7 +63,7 @@ public string CredentialWords credentialWords = value; var split = SplitCredentialWordsByComma(credentialWords); splitCredentialWords = split; - var credentialWordsPattern = string.Join("|", split.Select(Regex.Escape)); + var credentialWordsPattern = split.Select(Regex.Escape).JoinStr("|"); passwordValuePattern = new Regex($@"\b(?{credentialWordsPattern})\s*[:=]\s*(?.+)$", RegexOptions.IgnoreCase); } }