diff --git a/AppInspector.Benchmarks/Program.cs b/AppInspector.Benchmarks/Program.cs index bb77cb6..bd421ce 100644 --- a/AppInspector.Benchmarks/Program.cs +++ b/AppInspector.Benchmarks/Program.cs @@ -1,5 +1,4 @@ -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; namespace ApplicationInspector.Benchmarks; diff --git a/AppInspector.Benchmarks/WriterBench.cs b/AppInspector.Benchmarks/WriterBench.cs index ac3d860..84e1022 100644 --- a/AppInspector.Benchmarks/WriterBench.cs +++ b/AppInspector.Benchmarks/WriterBench.cs @@ -3,7 +3,6 @@ using System.IO; using System.Reflection; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -using DotLiquid; using Microsoft.ApplicationInspector.CLI; using Microsoft.ApplicationInspector.Commands; using Microsoft.ApplicationInspector.RulesEngine; diff --git a/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs b/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs index 425b316..83d5e4f 100644 --- a/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs +++ b/AppInspector.CLI/Writers/AnalyzeSarifWriter.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.Json; using Microsoft.ApplicationInspector.Commands; using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.CodeAnalysis.Sarif; diff --git a/AppInspector.CLI/Writers/JsonWriter.cs b/AppInspector.CLI/Writers/JsonWriter.cs index 7cb8f19..7cf1582 100644 --- a/AppInspector.CLI/Writers/JsonWriter.cs +++ b/AppInspector.CLI/Writers/JsonWriter.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Net.Sockets; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.ApplicationInspector.Commands; diff --git a/AppInspector.CLI/html/index.html b/AppInspector.CLI/html/index.html index ebffce2..7f4cda0 100644 --- a/AppInspector.CLI/html/index.html +++ b/AppInspector.CLI/html/index.html @@ -4,7 +4,7 @@ - + diff --git a/AppInspector.CLI/preferences/tagreportgroups.json b/AppInspector.CLI/preferences/tagreportgroups.json index 27ec94c..bf1d19b 100644 --- a/AppInspector.CLI/preferences/tagreportgroups.json +++ b/AppInspector.CLI/preferences/tagreportgroups.json @@ -82,6 +82,21 @@ "searchPattern": "^CloudServices.AdvertisingNetwork.*", "displayName": "Advertising network", "detectedIcon": "fas fa-ad" + }, + { + "searchPattern": "^CloudServices.SalesForce$", + "displayName": "Salesforce", + "detectedIcon": "fa-solid fa-cloud" + }, + { + "searchPattern": "^CloudServices.ServiceNow$", + "displayName": "ServiceNow", + "detectedIcon": "fa-solid fa-bell-concierge" + }, + { + "searchPattern": "^CloudServices.WorkDay$", + "displayName": "WorkDay", + "detectedIcon": "fa-solid fa-calendar-day" } ] }, @@ -315,12 +330,12 @@ "detectedIcon": "far fa-file-archive" }, { - "searchPattern": "^Metric.Code.HTMLForm.Defined$", + "searchPattern": ".Code.HTMLForm.Defined$", "displayName": "HTML form", "detectedIcon": "far fa-window-maximize" }, { - "searchPattern": "^Metric.Code.Exception.Caught$", + "searchPattern": ".Code.Exception.Caught$", "displayName": "Exception caught", "detectedIcon": "fas fa-meteor" }, diff --git a/AppInspector.Common/Utils.cs b/AppInspector.Common/Utils.cs index 92d85d4..dfdf0d6 100644 --- a/AppInspector.Common/Utils.cs +++ b/AppInspector.Common/Utils.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Text.RegularExpressions; diff --git a/AppInspector.RulesEngine/TextContainer.cs b/AppInspector.RulesEngine/TextContainer.cs index 17e55b3..15c4b1c 100644 --- a/AppInspector.RulesEngine/TextContainer.cs +++ b/AppInspector.RulesEngine/TextContainer.cs @@ -243,15 +243,50 @@ public class TextContainer } } + private int GetPrefixLocation(int startOfLineIndex, int currentIndex, string prefix, bool multiline) + { + // Find the first potential index of the prefix + var prefixLoc = FullContent.LastIndexOf(prefix, currentIndex, StringComparison.Ordinal); + if (prefixLoc != -1) + { + // TODO: Possibly support quoted multiline comment markers + if (multiline) + { + return prefixLoc; + } + if (prefixLoc < startOfLineIndex) + { + return -1; + } + // Check how many quote marks occur on the line before the prefix location + // TODO: This doesn't account for multi-line strings + var numDoubleQuotes = FullContent[startOfLineIndex..prefixLoc].Count(x => x == '"'); + var numSingleQuotes = FullContent[startOfLineIndex..prefixLoc].Count(x => x == '\''); + + // If the number of quotes is odd, this is in a string, so not actually a comment prefix + // It might be like var address = "http://contoso.com"; + if (numDoubleQuotes % 2 == 1 || numSingleQuotes % 2 == 1) + { + return GetPrefixLocation(startOfLineIndex, prefixLoc, prefix, multiline); + } + } + + return prefixLoc; + } + /// /// Populates the CommentedStates Dictionary based on the index and the provided comment prefix and suffix /// /// The character index in FullContent /// The comment prefix /// The comment suffix - private void PopulateCommentedStatesInternal(int index, string prefix, string suffix) + private void PopulateCommentedStatesInternal(int index, string prefix, string suffix, bool multiline) { - var prefixLoc = FullContent.LastIndexOf(prefix, index, StringComparison.Ordinal); + // Get the line boundary for the prefix location + var startOfLine = GetLineBoundary(index); + // Get the index of the prefix + var prefixLoc = GetPrefixLocation(startOfLine.Index, index, prefix, multiline); + if (prefixLoc != -1) { if (!CommentedStates.ContainsKey(prefixLoc)) @@ -287,13 +322,13 @@ public class TextContainer // Populate true for the indexes of the most immediately preceding instance of the multiline comment type if found if (!string.IsNullOrEmpty(prefix) && !string.IsNullOrEmpty(suffix)) { - PopulateCommentedStatesInternal(index, prefix, suffix); + PopulateCommentedStatesInternal(index, prefix, suffix, true); } // Populate true for indexes of the most immediately preceding instance of the single-line comment type if found if (!CommentedStates.ContainsKey(index) && !string.IsNullOrEmpty(inline)) { - PopulateCommentedStatesInternal(index, inline, "\n"); + PopulateCommentedStatesInternal(index, inline, "\n", false); } var i = index; diff --git a/AppInspector.Tests/Commands/TestVerifyRulesCmd.cs b/AppInspector.Tests/Commands/TestVerifyRulesCmd.cs index 3aab372..42e33c9 100644 --- a/AppInspector.Tests/Commands/TestVerifyRulesCmd.cs +++ b/AppInspector.Tests/Commands/TestVerifyRulesCmd.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using CommandLine; using Microsoft.ApplicationInspector.Commands; using Microsoft.ApplicationInspector.Common; using Microsoft.ApplicationInspector.Logging; diff --git a/AppInspector.Tests/RuleProcessor/QuotedStringsTests.cs b/AppInspector.Tests/RuleProcessor/QuotedStringsTests.cs new file mode 100644 index 0000000..cda630b --- /dev/null +++ b/AppInspector.Tests/RuleProcessor/QuotedStringsTests.cs @@ -0,0 +1,80 @@ +using System.IO; +using System.Linq; +using Microsoft.ApplicationInspector.Logging; +using Microsoft.ApplicationInspector.RulesEngine; +using Microsoft.CST.RecursiveExtractor; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Serilog.Events; + +namespace AppInspector.Tests.RuleProcessor; + +[TestClass] +public class QuotedStringsTests +{ + + private const string testDoubleQuotesAreCode = "var url = \"https://contoso.com\"; // contoso.com"; + private const string testSingleQuotesAreCode = "var url = 'https://contoso.com'; // contoso.com"; + private const string testSingleLineWithQuotesInComment = "// var url = 'https://contoso.com';"; + private const string testSingleLineWithDoubleQuotesInComment = "// var url = 'https://contoso.com';"; + private const string testMultiLine = @"/* +https://contoso.com +*/"; + private const string testMultiLineWithoutProto = @" +/* +contoso.com +*/"; + private const string testMultiLineWithResultFollowingCommentEnd = @" +/* +contoso.com +*/ var url = ""https://contoso.com"""; + + private static string detectContosoRule = @" + [ + { + ""id"": ""RE000001"", + ""name"": ""Testing.Rules.Quotes"", + ""tags"": [ + ""Testing.Rules.Quotes"" + ], + ""severity"": ""Critical"", + ""description"": ""Find contoso.com"", + ""patterns"": [ + { + ""pattern"": ""contoso.com"", + ""type"": ""regex"", + ""confidence"": ""High"", + ""scopes"": [ + ""code"" + ] + } + ], + ""_comment"": """" + } +] +"; + + private readonly ILoggerFactory _loggerFactory = + new LogOptions { ConsoleVerbosityLevel = LogEventLevel.Verbose }.GetLoggerFactory(); + + private readonly Microsoft.ApplicationInspector.RulesEngine.Languages _languages = new(); + + [DataRow(testDoubleQuotesAreCode,1)] + [DataRow(testSingleQuotesAreCode,1)] + [DataRow(testMultiLine,0)] + [DataRow(testMultiLineWithoutProto,0)] + [DataRow(testMultiLineWithResultFollowingCommentEnd,1)] + [DataRow(testSingleLineWithQuotesInComment,0)] + [DataRow(testSingleLineWithDoubleQuotesInComment,0)] + [DataTestMethod] + public void QuotedStrings(string content, int numIssues) + { + RuleSet rules = new(_loggerFactory); + rules.AddString(detectContosoRule, "contosorule"); + Microsoft.ApplicationInspector.RulesEngine.RuleProcessor ruleProcessor = + new Microsoft.ApplicationInspector.RulesEngine.RuleProcessor(rules, new RuleProcessorOptions()); + _languages.FromFileNameOut("testfile.cs", out LanguageInfo info); + Assert.AreEqual(numIssues, + ruleProcessor.AnalyzeFile(content, new FileEntry("testfile.cs", new MemoryStream()), info).Count()); + } +} \ No newline at end of file diff --git a/AppInspector.Tests/RuleProcessor/XmlAndJsonTests.cs b/AppInspector.Tests/RuleProcessor/XmlAndJsonTests.cs index d98336c..2a4d11a 100644 --- a/AppInspector.Tests/RuleProcessor/XmlAndJsonTests.cs +++ b/AppInspector.Tests/RuleProcessor/XmlAndJsonTests.cs @@ -1,9 +1,6 @@ -using System; -using System.IO; -using System.Linq; +using System.IO; using Microsoft.ApplicationInspector.RulesEngine; using Microsoft.CST.RecursiveExtractor; -using Microsoft.VisualBasic.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace AppInspector.Tests.RuleProcessor; diff --git a/AppInspector/rules/default/cloud_services/saas.json b/AppInspector/rules/default/cloud_services/saas.json new file mode 100644 index 0000000..dbc72f2 --- /dev/null +++ b/AppInspector/rules/default/cloud_services/saas.json @@ -0,0 +1,69 @@ +[ + { + "name": "SaaS: Salesforce", + "id": "AI060001", + "description": "SaaS: Salesforce Rest API", + "tags": [ + "CloudServices.Salesforce" + ], + "severity": "moderate", + "patterns": [ + { + "confidence": "high", + "pattern": "my\\.salesforce\\.com", + "type": "regex", + "scopes": [ + "code" + ], + "modifiers": [ + "i" + ] + } + ] + }, + { + "name": "SaaS: ServiceNow", + "id": "AI060002", + "description": "SaaS: ServiceNow Rest API", + "tags": [ + "CloudServices.ServiceNow" + ], + "severity": "moderate", + "patterns": [ + { + "confidence": "high", + "pattern": "service-now\\.com/api", + "type": "regex", + "scopes": [ + "code" + ], + "modifiers": [ + "i" + ] + } + ] + } + , + { + "name": "SaaS: WorkDay", + "id": "AI060000", + "description": "SaaS: WorkDay Rest API", + "tags": [ + "CloudServices.WorkDay" + ], + "severity": "moderate", + "patterns": [ + { + "confidence": "high", + "pattern": "workday.com", + "type": "regex", + "scopes": [ + "code" + ], + "modifiers": [ + "i" + ] + } + ] + } +] \ No newline at end of file