From 0a682316236ab346b7262780471de7aec2223b55 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Wed, 3 Aug 2022 12:11:38 -0500 Subject: [PATCH 01/61] Feature: Added GUID list and query suppression support --- .../SuppressCommand.cs | 65 ++++++++++++++++++- .../SuppressOptions.cs | 19 +++++- src/Sarif/Visitors/SuppressVisitor.cs | 27 ++++++-- .../SuppressCommandTests.cs | 56 ++++++++++++++-- .../Baseline/DefaultBaselineUnitTests.cs | 2 +- .../Baseline/StrictBaselineUnitTests.cs | 2 +- .../RandomSarifLogGenerator.cs | 7 +- .../Visitors/SuppressVisitorTests.cs | 59 +++++++++++------ 8 files changed, 200 insertions(+), 37 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 5cf1813fa..8be831778 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -2,9 +2,16 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; + +using Kusto.Cloud.Platform.Utils; using Microsoft.CodeAnalysis.Sarif.Driver; +using Microsoft.CodeAnalysis.Sarif.Query; +using Microsoft.CodeAnalysis.Sarif.Query.Evaluators; using Microsoft.CodeAnalysis.Sarif.Readers; using Microsoft.CodeAnalysis.Sarif.Visitors; using Microsoft.CodeAnalysis.Sarif.Writers; @@ -34,12 +41,19 @@ public int Run(SuppressOptions options) options.Formatting, out string _); + if (!string.IsNullOrWhiteSpace(options.Expression)) + { + // Merge list of guids if exit?? + options.Guids = ReturnQueryExpressionGuids(options); + } + SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, - options.Guids, + options.UniqueIdentifiers, options.Timestamps, options.ExpiryInDays, - options.Status).VisitSarifLog(currentSarifLog); + options.Status, + options.Guids).VisitSarifLog(currentSarifLog); string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options); if (options.SarifOutputVersion == SarifVersion.OneZeroZero) @@ -66,6 +80,53 @@ public int Run(SuppressOptions options) return SUCCESS; } + private IEnumerable ReturnQueryExpressionGuids(SuppressOptions options) + { + int originalTotal = 0; + int matchCount = 0; + // Parse the Query and create a Result evaluator for it + IExpression expression = ExpressionParser.ParseExpression(options.Expression); + IExpressionEvaluator evaluator = expression.ToEvaluator(SarifEvaluators.ResultEvaluator); + + // Read the log + SarifLog log = ReadSarifFile(this.FileSystem, options.InputFilePath); + + foreach (Run run in log.Runs) + { + if (run.Results == null) { continue; } + run.SetRunOnResults(); + + originalTotal += run.Results.Count; + + // Find matches for Results in the Run + BitArray matches = new BitArray(run.Results.Count); + evaluator.Evaluate(run.Results, matches); + + // Count the new matches + matchCount += matches.TrueCount(); + + // Filter the Run.Results to the matches + run.Results = matches.MatchingSubset(run.Results); + + //// Write to console, if caller requested + //if (options.WriteToConsole) + //{ + // foreach (Result result in run.Results) + // { + // Console.WriteLine(result.FormatForVisualStudio()); + // } + //} + } + + // Remove any Runs with no remaining matches + log.Runs = log.Runs.Where(r => (r?.Results?.Count ?? 0) > 0).ToList(); + + var guids = log.Runs.SelectMany(x => x.Results.Select(y => y.Guid)).ToList(); + + return guids; + + } + private bool ValidateOptions(SuppressOptions options) { bool valid = true; diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index ef2162763..a10a9a52e 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; + using CommandLine; using Microsoft.CodeAnalysis.Sarif.Driver; @@ -22,9 +24,22 @@ public class SuppressOptions : SingleFileOptionsBase public string Alias { get; set; } [Option( - "guids", + "uuids", HelpText = "A UUID that will be associated with a suppression.")] - public bool Guids { get; set; } + public bool UniqueIdentifiers { get; set; } + + [Value(0, + MetaName = "", + HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", + Required = false)] + public IEnumerable Guids { get; set; } + + [Option( + 'e', + "expression", + HelpText = "Result Expression to Evaluate (ex: (BaselineState != 'Unchanged'))", + Required = false)] + public string Expression { get; set; } [Option( "timestamps", diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index a421a5398..56c727268 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.CodeAnalysis.Sarif.Visitors { public class SuppressVisitor : SarifRewritingVisitor { - private readonly bool guids; + private readonly bool uuids; + private readonly IEnumerable guids; private readonly string alias; private readonly bool timestamps; private readonly DateTime timeUtc; @@ -19,19 +21,21 @@ public class SuppressVisitor : SarifRewritingVisitor public SuppressVisitor(string justification, string alias, - bool guids, + bool uuids, bool timestamps, int expiryInDays, - SuppressionStatus suppressionStatus) + SuppressionStatus suppressionStatus, + IEnumerable guids) { this.alias = alias; - this.guids = guids; + this.uuids = uuids; this.timestamps = timestamps; this.timeUtc = DateTime.UtcNow; this.expiryInDays = expiryInDays; this.justification = justification; this.suppressionStatus = suppressionStatus; this.expiryUtc = this.timeUtc.AddDays(expiryInDays); + this.guids = guids; } public override Result VisitResult(Result node) @@ -53,7 +57,7 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(alias), alias); } - if (guids) + if (this.uuids) { suppression.Guid = Guid.NewGuid().ToString(SarifConstants.GuidFormat); } @@ -68,7 +72,18 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(expiryUtc), expiryUtc); } - node.Suppressions.Add(suppression); + if (this.guids != null && this.guids.Any()) + { + if (this.guids.Contains(node.Guid)) + { + node.Suppressions.Add(suppression); + } + } + else + { + node.Suppressions.Add(suppression); + } + return base.VisitResult(node); } } diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index f95856d47..2da48fe11 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text; using FluentAssertions; @@ -56,6 +57,14 @@ public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied() OutputFilePath = outputPath, Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + ExpiryInDays = 1, + Justification = "some justification", + OutputFilePath = outputPath, + Expression = "fail", + Status = SuppressionStatus.Accepted + }, }; var mock = new Mock(); @@ -91,20 +100,43 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - Guids = true, + UniqueIdentifiers = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + Status = SuppressionStatus.Accepted + }, + new SuppressOptions + { + UniqueIdentifiers = true, + ExpiryInDays = 5, + Timestamps = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + Status = SuppressionStatus.Accepted + }, + new SuppressOptions + { + UniqueIdentifiers = true, + ExpiryInDays = 5, + Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", + Expression = "BaseLineState = \"New\"", Status = SuppressionStatus.Accepted }, new SuppressOptions { - Guids = true, + UniqueIdentifiers = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", + Guids = new List() { "NEWGUID"}, + Expression = string.Empty, Status = SuppressionStatus.Accepted }, }; @@ -127,7 +159,9 @@ private static void VerifySuppressCommand(SuppressOptions options) { new Result { - RuleId = "Test0001" + RuleId = "Test0001", + Guid = "NEWGUID", + BaselineState = BaselineState.New } } } @@ -135,11 +169,20 @@ private static void VerifySuppressCommand(SuppressOptions options) }; var transformedContents = new StringBuilder(); + var currentStream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(current))); + + var currentContents = new StringBuilder(JsonConvert.SerializeObject(current)); var mockFileSystem = new Mock(); + mockFileSystem .Setup(x => x.FileReadAllText(options.InputFilePath)) .Returns(JsonConvert.SerializeObject(current)); + mockFileSystem + .Setup(x => x.FileOpenRead(options.InputFilePath)) + .Returns(() => + currentStream); + mockFileSystem .Setup(x => x.FileCreate(options.OutputFilePath)) .Returns(() => new MemoryStreamToStringBuilder(transformedContents)); @@ -160,11 +203,16 @@ private static void VerifySuppressCommand(SuppressOptions options) suppression.GetProperty("alias").Should().Be(options.Alias); } - if (options.Guids) + if (options.UniqueIdentifiers) { suppression.Guid.Should().NotBeNullOrEmpty(); } + if (!string.IsNullOrWhiteSpace(options.Expression)) + { + suppressed.Runs[0].Results[0].BaselineState.Should().Be(BaselineState.New); + } + if (options.Timestamps && suppression.TryGetProperty("timeUtc", out DateTime timeUtc)) { timeUtc.Should().BeCloseTo(DateTime.UtcNow, DateTimeAssertPrecision); diff --git a/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs b/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs index d3e491782..1f612a8d8 100644 --- a/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs +++ b/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs @@ -42,7 +42,7 @@ public void DefaultBaseline_NewResultAdded_New() Random random = RandomSarifLogGenerator.GenerateRandomAndLog(this.output); Run baseline = RandomSarifLogGenerator.GenerateRandomRunWithoutDuplicateIssues(random, DefaultBaseline.ResultBaselineEquals.DefaultInstance, random.Next(100) + 5); Run next = baseline.DeepClone(); - next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); + next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { "NEWGUID"}, new List() { new Uri(@"c:\test\testfile") }, 1).First()); Run result = defaultBaseliner.CreateBaselinedRun(baseline, next); diff --git a/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs b/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs index fa778d22e..fb58cfff8 100644 --- a/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs +++ b/src/Test.UnitTests.Sarif/Baseline/StrictBaselineUnitTests.cs @@ -42,7 +42,7 @@ public void StrictBaseline_NewResultAdded_New() Random random = RandomSarifLogGenerator.GenerateRandomAndLog(this.output); Run baseline = RandomSarifLogGenerator.GenerateRandomRunWithoutDuplicateIssues(random, Result.ValueComparer, random.Next(100) + 5); Run next = baseline.DeepClone(); - next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); + next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { "NEWGUID" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); Run result = strictBaseliner.CreateBaselinedRun(baseline, next); diff --git a/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs b/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs index 8ec52b4b9..1f81c3a93 100644 --- a/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs +++ b/src/Test.UnitTests.Sarif/RandomSarifLogGenerator.cs @@ -49,6 +49,7 @@ public static SarifLog GenerateSarifLogWithRuns(Random randomGen, int runCount, public static Run GenerateRandomRun(Random random, int? resultCount = null, RandomDataFields dataFields = RandomDataFields.None) { List ruleIds = new List() { "TEST001", "TEST002", "TEST003", "TEST004", "TEST005" }; + List guids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }; List filePaths = GenerateFakeFiles(GeneratorBaseUri, random.Next(20) + 1).Select(a => new Uri(a)).ToList(); int results = resultCount == null ? random.Next(100) : (int)resultCount; @@ -64,7 +65,7 @@ public static Run GenerateRandomRun(Random random, int? resultCount = null, Rand } }, Artifacts = GenerateFiles(filePaths), - Results = GenerateFakeResults(random, ruleIds, filePaths, results, dataFields) + Results = GenerateFakeResults(random, ruleIds, guids, filePaths, results, dataFields) }; } @@ -96,17 +97,19 @@ public static IEnumerable GenerateFakeFiles(string baseAddress, int coun return results; } - public static IList GenerateFakeResults(Random random, List ruleIds, List filePaths, int resultCount, RandomDataFields dataFields = RandomDataFields.None) + public static IList GenerateFakeResults(Random random, List ruleIds, List guids, List filePaths, int resultCount, RandomDataFields dataFields = RandomDataFields.None) { List results = new List(); for (int i = 0; i < resultCount; i++) { int fileIndex = random.Next(filePaths.Count); int ruleIndex = random.Next(ruleIds.Count); + int guidIndex = random.Next(guids.Count); results.Add(new Result() { RuleId = ruleIds[ruleIndex], RuleIndex = ruleIndex, + Guid = guids[guidIndex], Locations = new Location[] { new Location diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index 742fcb6f2..8019d3454 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentAssertions; @@ -19,61 +20,78 @@ public class SuppressVisitorTests [Fact] public void SuppressVisitor_ShouldFlowPropertiesCorrectly() { + var guids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }; var testCases = new[] { new { Alias = string.Empty, Justification = "some suppress justification", - Guids = false, + Uuids = false, Timestamps = false, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = false, + Uuids = false, Timestamps = false, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = false, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = true, ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = true, ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.Accepted + SuppressionStatus = SuppressionStatus.Accepted, + Guids = new List() }, new { Alias = "some alias", Justification = "some suppress justification", - Guids = true, + Uuids = true, Timestamps = true, ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.UnderReview + SuppressionStatus = SuppressionStatus.UnderReview, + Guids = new List() + }, + new + { + Alias = "some alias", + Justification = "some suppress justification", + Uuids = true, + Timestamps = true, + ExpiryInDays = 1, + SuppressionStatus = SuppressionStatus.Accepted, + Guids = guids.ToList() }, }; @@ -81,26 +99,29 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly() { VerifySuppressVisitor(testCase.Alias, testCase.Justification, - testCase.Guids, + testCase.Uuids, testCase.Timestamps, testCase.ExpiryInDays, - testCase.SuppressionStatus); + testCase.SuppressionStatus, + testCase.Guids); } } private static void VerifySuppressVisitor(string alias, string justification, - bool guids, + bool uuids, bool timestamps, int expiryInDays, - SuppressionStatus suppressionStatus) + SuppressionStatus suppressionStatus, + IEnumerable guids) { var visitor = new SuppressVisitor(justification, alias, - guids, + uuids, timestamps, expiryInDays, - suppressionStatus); + suppressionStatus, + guids); var random = new Random(); SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); @@ -120,7 +141,7 @@ private static void VerifySuppressVisitor(string alias, suppression.GetProperty("alias").Should().Be(alias); } - if (guids) + if (uuids) { suppression.Guid.Should().NotBeNullOrEmpty(); } From a08d8da3ced9f8342d8382c0de7cb3214624a5bf Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Wed, 3 Aug 2022 15:58:11 -0500 Subject: [PATCH 02/61] Fixed bug: suppress options with guids type --- src/Sarif.Multitool.Library/SuppressOptions.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index a10a9a52e..ca0b745ec 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -28,17 +28,16 @@ public class SuppressOptions : SingleFileOptionsBase HelpText = "A UUID that will be associated with a suppression.")] public bool UniqueIdentifiers { get; set; } - [Value(0, - MetaName = "", + [Option( + "", HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", - Required = false)] + Default = null)] public IEnumerable Guids { get; set; } [Option( 'e', "expression", - HelpText = "Result Expression to Evaluate (ex: (BaselineState != 'Unchanged'))", - Required = false)] + HelpText = "Result Expression to Evaluate (ex: (BaselineState != 'Unchanged'))")] public string Expression { get; set; } [Option( From 4c62456df3edea3115e31d643f265d52eafedb27 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Wed, 3 Aug 2022 16:47:07 -0500 Subject: [PATCH 03/61] fixed bug in SuppressOptions with guids options --- src/Sarif.Multitool.Library/SuppressOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index ca0b745ec..1e98933f9 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -29,7 +29,7 @@ public class SuppressOptions : SingleFileOptionsBase public bool UniqueIdentifiers { get; set; } [Option( - "", + "guids", HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", Default = null)] public IEnumerable Guids { get; set; } From 6f0174e58996ad46feb79f178106944b7bc201c8 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Thu, 4 Aug 2022 15:03:36 -0500 Subject: [PATCH 04/61] Bug: Allowed using results-guids and expression arguments together. --- .../SuppressCommand.cs | 28 ++++++++----------- .../SuppressOptions.cs | 10 +++---- src/Sarif/Visitors/SuppressVisitor.cs | 10 +++---- .../SuppressCommandTests.cs | 26 ++++++++++++----- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 8be831778..c7ec612ee 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -43,17 +43,24 @@ public int Run(SuppressOptions options) if (!string.IsNullOrWhiteSpace(options.Expression)) { - // Merge list of guids if exit?? - options.Guids = ReturnQueryExpressionGuids(options); + var expressionGuids = ReturnQueryExpressionGuids(options); + if (options.ResultsGuids != null && options.ResultsGuids.Any()) + { + options.ResultsGuids.Union(expressionGuids); + } + else + { + options.ResultsGuids = expressionGuids; + } } SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, - options.UniqueIdentifiers, + options.Guids, options.Timestamps, options.ExpiryInDays, options.Status, - options.Guids).VisitSarifLog(currentSarifLog); + options.ResultsGuids).VisitSarifLog(currentSarifLog); string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options); if (options.SarifOutputVersion == SarifVersion.OneZeroZero) @@ -107,24 +114,13 @@ private IEnumerable ReturnQueryExpressionGuids(SuppressOptions options) // Filter the Run.Results to the matches run.Results = matches.MatchingSubset(run.Results); - - //// Write to console, if caller requested - //if (options.WriteToConsole) - //{ - // foreach (Result result in run.Results) - // { - // Console.WriteLine(result.FormatForVisualStudio()); - // } - //} } // Remove any Runs with no remaining matches log.Runs = log.Runs.Where(r => (r?.Results?.Count ?? 0) > 0).ToList(); - var guids = log.Runs.SelectMany(x => x.Results.Select(y => y.Guid)).ToList(); - return guids; - + return guids; } private bool ValidateOptions(SuppressOptions options) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index 1e98933f9..e2b8a2aca 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -24,15 +24,15 @@ public class SuppressOptions : SingleFileOptionsBase public string Alias { get; set; } [Option( - "uuids", + "guids", HelpText = "A UUID that will be associated with a suppression.")] - public bool UniqueIdentifiers { get; set; } + public bool Guids { get; set; } [Option( - "guids", - HelpText = "Guid(s) to SARIF log(s) comprising the current set of results, without result matching information", + "results-guids", + HelpText = "Guid(s) to SARIF log result(s) comprising the current result guid, without result matching information", Default = null)] - public IEnumerable Guids { get; set; } + public IEnumerable ResultsGuids { get; set; } [Option( 'e', diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index 56c727268..6b1694ac1 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Visitors public class SuppressVisitor : SarifRewritingVisitor { private readonly bool uuids; - private readonly IEnumerable guids; + private readonly IEnumerable resultsGuids; private readonly string alias; private readonly bool timestamps; private readonly DateTime timeUtc; @@ -25,7 +25,7 @@ public SuppressVisitor(string justification, bool timestamps, int expiryInDays, SuppressionStatus suppressionStatus, - IEnumerable guids) + IEnumerable resultsGuids) { this.alias = alias; this.uuids = uuids; @@ -35,7 +35,7 @@ public SuppressVisitor(string justification, this.justification = justification; this.suppressionStatus = suppressionStatus; this.expiryUtc = this.timeUtc.AddDays(expiryInDays); - this.guids = guids; + this.resultsGuids = resultsGuids; } public override Result VisitResult(Result node) @@ -72,9 +72,9 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(expiryUtc), expiryUtc); } - if (this.guids != null && this.guids.Any()) + if (this.resultsGuids != null && this.resultsGuids.Any()) { - if (this.guids.Contains(node.Guid)) + if (this.resultsGuids.Contains(node.Guid)) { node.Suppressions.Add(suppression); } diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 2da48fe11..999b70432 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -100,7 +100,7 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", @@ -108,7 +108,7 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", @@ -118,7 +118,7 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", @@ -129,16 +129,28 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( }, new SuppressOptions { - UniqueIdentifiers = true, + Guids = true, ExpiryInDays = 5, Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", Justification = "some justification", - Guids = new List() { "NEWGUID"}, + ResultsGuids = new List() { "GUID"}, Expression = string.Empty, Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + Guids = true, + ExpiryInDays = 5, + Timestamps = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + ResultsGuids = new List() { "GUID", "GUID2"}, + Expression = "BaseLineState = \"New\"", + Status = SuppressionStatus.Accepted + }, }; foreach (SuppressOptions options in optionsTestCases) @@ -160,7 +172,7 @@ private static void VerifySuppressCommand(SuppressOptions options) new Result { RuleId = "Test0001", - Guid = "NEWGUID", + Guid = "GUID", BaselineState = BaselineState.New } } @@ -203,7 +215,7 @@ private static void VerifySuppressCommand(SuppressOptions options) suppression.GetProperty("alias").Should().Be(options.Alias); } - if (options.UniqueIdentifiers) + if (options.Guids) { suppression.Guid.Should().NotBeNullOrEmpty(); } From 8c80a874608f04dbb667708f907a91c54193eeb5 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 5 Aug 2022 08:57:38 -0500 Subject: [PATCH 05/61] chore: typo in log message --- src/Sarif.Multitool.Library/SuppressCommand.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index c7ec612ee..de61885f1 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -51,7 +51,7 @@ public int Run(SuppressOptions options) else { options.ResultsGuids = expressionGuids; - } + } } SarifLog reformattedLog = new SuppressVisitor(options.Justification, @@ -76,7 +76,7 @@ public int Run(SuppressOptions options) } w.Stop(); - Console.WriteLine($"Supress completed in {w.Elapsed}."); + Console.WriteLine($"Suppress completed in {w.Elapsed}."); } catch (Exception ex) { @@ -120,7 +120,7 @@ private IEnumerable ReturnQueryExpressionGuids(SuppressOptions options) log.Runs = log.Runs.Where(r => (r?.Results?.Count ?? 0) > 0).ToList(); var guids = log.Runs.SelectMany(x => x.Results.Select(y => y.Guid)).ToList(); - return guids; + return guids; } private bool ValidateOptions(SuppressOptions options) From 4b91d6b110c443be04c87d5a2c661aee171c25c3 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 5 Aug 2022 09:12:45 -0500 Subject: [PATCH 06/61] feat: console log number of suppressions applied --- src/Sarif.Multitool.Library/SuppressCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index de61885f1..419af0a69 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -53,6 +53,7 @@ public int Run(SuppressOptions options) options.ResultsGuids = expressionGuids; } } + Console.WriteLine($"Suppressing {options.ResultsGuids.Count()} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, From 46e65de2168460a1e5931cf9200bace6cdc972d0 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 5 Aug 2022 09:13:00 -0500 Subject: [PATCH 07/61] bug: comma delimit results arg --- src/Sarif.Multitool.Library/SuppressOptions.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index e2b8a2aca..bf156b062 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -28,10 +28,11 @@ public class SuppressOptions : SingleFileOptionsBase HelpText = "A UUID that will be associated with a suppression.")] public bool Guids { get; set; } - [Option( + [Option( "results-guids", - HelpText = "Guid(s) to SARIF log result(s) comprising the current result guid, without result matching information", - Default = null)] + HelpText = "A comma delimited list of SARIF log result guid(s) to suppress.", + Default = null, + Separator = ',')] public IEnumerable ResultsGuids { get; set; } [Option( From 15ec4134151224ade0dbd6e0bfdcbd2b0cfa69fc Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 8 Aug 2022 16:07:35 -0500 Subject: [PATCH 08/61] feat: cis cat pro json converter --- docs/multitool-usage.md | 2 + .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/CisCatConverter.cs | 168 ++++++++++++++++++ .../CisCatObjectModel/CisCatReport.cs | 33 ++++ .../CisCatObjectModel/CisCatReportReader.cs | 25 +++ .../CisCatObjectModel/CisCatRule.cs | 19 ++ src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- src/Sarif/HashUtilities.cs | 11 ++ 9 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/CisCatConverter.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 151f880c2..53d129a23 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -69,9 +69,11 @@ Sarif.Multitool validate Other.sarif ``` ## Supported Converters + Run ```Sarif.Multitool convert --help``` for the current list. - AndroidStudio +- CisCat - ClangAnalyzer - ClangTidy - CppCheck diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 8b1b1838d..b4804f890 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -25,6 +25,7 @@ private static Dictionary> CreateBuiltInConv { var result = new Dictionary>(); CreateConverterRecord(result, ToolFormat.AndroidStudio); + CreateConverterRecord(result, ToolFormat.CisCat); CreateConverterRecord(result, ToolFormat.CppCheck); CreateConverterRecord(result, ToolFormat.ClangAnalyzer); CreateConverterRecord(result, ToolFormat.ClangTidy); diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs new file mode 100644 index 000000000..2744dbeda --- /dev/null +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class CisCatConverter : ToolFileConverterBase + { + private readonly LogReader logReader; + + public CisCatConverter() + { + logReader = new CisCatReportReader(); + } + + public override string ToolName => ToolFormat.CisCat; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //Read CIS CAT data + CisCatReport log = logReader.ReadLog(input); + + //Top level run object for the scan data + var run = new Run(); + + //Set the tool details + run.Tool = new Tool(); + run.Tool.Driver = CreateDriver(log); + + //Set the list of tool rules + run.Tool.Driver.Rules = new List(); + foreach (CisCatRule rule in log.Rules) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(rule)); + } + + var results = new List(); + foreach (CisCatRule rule in log.Rules) + { + results.Add(CreateResult(rule)); + } + + PersistResults(output, results, run); + } + + internal ToolComponent CreateDriver(CisCatReport report) + { + + var driver = new ToolComponent(); + + driver.Name = this.ToolName; + driver.FullName = report.BenchmarkTitle; + driver.Version = report.BenchmarkVersion; + driver.SemanticVersion = report.BenchmarkVersion; + driver.InformationUri = new Uri("https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre"); + + driver.SetProperty("benchmarkId", report.BenchmarkId); + driver.SetProperty("profileId", report.ProfileId); + driver.SetProperty("profileTitle", report.ProfileTitle); + driver.SetProperty("score", report.Score); + + return driver; + } + + internal ReportingDescriptor CreateReportDescriptor(CisCatRule rule) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = rule.RuleId; + descriptor.Name = rule.RuleTitle; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + descriptor.Help = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + + return descriptor; + } + + internal Result CreateResult(CisCatRule rule) + { + //set the result metadata + Result result = new Result + { + RuleId = rule.RuleId, + Message = new Message { Text = rule.RuleTitle }, + }; + + //Kind & Level determine the status + //Result: "fail": Level = Error, Kind = Fail + //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review + switch (rule.Result) + { + case "fail": + result.Level = FailureLevel.Error; + result.Kind = ResultKind.Fail; + break; + case "pass": + result.Level = FailureLevel.None; + result.Kind = ResultKind.Pass; + break; + case "notchecked": + result.Level = FailureLevel.None; + result.Kind = ResultKind.NotApplicable; + break; + case "informational": + result.Level = FailureLevel.None; + result.Kind = ResultKind.Informational; + break; + case "unknown": + default: + result.Level = FailureLevel.None; + result.Kind = ResultKind.Review; + break; + }; + + //Set the unique fingerprint + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); + + // var region = new Region + // { + // StartColumn = int.Parse(defect.Column), + // StartLine = int.Parse(defect.Line) + // }; + + // var fileUri = new Uri($"{defect.FilePath}", UriKind.RelativeOrAbsolute); + // var physicalLocation = new PhysicalLocation + // { + // ArtifactLocation = new ArtifactLocation + // { + // Uri = fileUri + // }, + // Region = region + // }; + + // var location = new Location + // { + // PhysicalLocation = physicalLocation + // }; + + // result.Locations = new List + // { + // location + // }; + + return result; + } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs new file mode 100644 index 000000000..ea6710f95 --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatReport + { + [JsonProperty("benchmark-id")] + public string BenchmarkId { get; set; } + + [JsonProperty("benchmark-title")] + public string BenchmarkTitle { get; set; } + + [JsonProperty("benchmark-version")] + public string BenchmarkVersion { get; set; } + + [JsonProperty("profile-id")] + public string ProfileId { get; set; } + + [JsonProperty("profile-title")] + public string ProfileTitle { get; set; } + + [JsonProperty("score")] + public string Score { get; set; } + + [JsonProperty("rules")] + public IEnumerable Rules { get; set; } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs new file mode 100644 index 000000000..8f2b107e6 --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatReportReader : LogReader + { + public override CisCatReport ReadLog(Stream input) + { + string reportData; + + using (TextReader streamReader = new StreamReader(input)) + { + reportData = streamReader.ReadToEnd(); + } + + return JsonConvert.DeserializeObject(reportData); + } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs new file mode 100644 index 000000000..b4d7f1bfc --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatRule + { + [JsonProperty("rule-id")] + public string RuleId { get; set; } + + [JsonProperty("rule-title")] + public string RuleTitle { get; set; } + + [JsonProperty("result")] + public string Result { get; set; } + } +} diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index 562e1cd9b..a44694fa8 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -12,6 +12,9 @@ public static class ToolFormat /// Android Studio's file format. public const string AndroidStudio = nameof(AndroidStudio); + /// CIS CAT file format. + public const string CisCat = nameof(CisCat); + /// Clang analyzer's file format. public const string ClangAnalyzer = nameof(ClangAnalyzer); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index cbc869926..ce9450df5 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 3e8ca9434..00946de47 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif @@ -185,5 +186,15 @@ public static string ComputeMD5Hash(string fileName) catch (UnauthorizedAccessException) { } return md5; } + + + public static string ComputeSha256HashValue(string value) + { + using (var sha = SHA256.Create()) + { + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } } } From e366b48a8b309766fe0a2d1e5ad69d0bfc0d942e Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 9 Aug 2022 10:08:50 -0500 Subject: [PATCH 09/61] bug: exclude passing tests from results for match forward baseline status --- src/Sarif.Converters/CisCatConverter.cs | 41 +++++-------------- .../CisCatObjectModel/CisCatRule.cs | 5 +++ 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index 2744dbeda..a0fccab5f 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -45,7 +45,10 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm var results = new List(); foreach (CisCatRule rule in log.Rules) { - results.Add(CreateResult(rule)); + if (!rule.IsPass()) + { + results.Add(CreateResult(rule)); + } } PersistResults(output, results, run); @@ -109,14 +112,16 @@ internal Result CreateResult(CisCatRule rule) //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review switch (rule.Result) { + //PASS CASES ARE NOT INCLUDED IN THE RESULTS, AS MATCH FORWARD DOES NOT PRODUCE + //THE CORRECT ABSENT / NEW STATES WHEN THEY EXIST + // case "pass": + // result.Level = FailureLevel.None; + // result.Kind = ResultKind.Pass; + // break; case "fail": result.Level = FailureLevel.Error; result.Kind = ResultKind.Fail; break; - case "pass": - result.Level = FailureLevel.None; - result.Kind = ResultKind.Pass; - break; case "notchecked": result.Level = FailureLevel.None; result.Kind = ResultKind.NotApplicable; @@ -136,32 +141,6 @@ internal Result CreateResult(CisCatRule rule) result.Fingerprints = new Dictionary(); result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); - // var region = new Region - // { - // StartColumn = int.Parse(defect.Column), - // StartLine = int.Parse(defect.Line) - // }; - - // var fileUri = new Uri($"{defect.FilePath}", UriKind.RelativeOrAbsolute); - // var physicalLocation = new PhysicalLocation - // { - // ArtifactLocation = new ArtifactLocation - // { - // Uri = fileUri - // }, - // Region = region - // }; - - // var location = new Location - // { - // PhysicalLocation = physicalLocation - // }; - - // result.Locations = new List - // { - // location - // }; - return result; } } diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs index b4d7f1bfc..43e340708 100644 --- a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs @@ -15,5 +15,10 @@ public class CisCatRule [JsonProperty("result")] public string Result { get; set; } + + public bool IsPass() + { + return this.Result == "pass"; + } } } From aefdd5e2284842556bf34f6e02bdaf4ad8f85f53 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Tue, 9 Aug 2022 11:55:36 -0500 Subject: [PATCH 10/61] Fixed bug with ResultsGuids and added debug output --- src/Sarif.Multitool.Library/SuppressCommand.cs | 14 +++++++++++++- .../SuppressCommandTests.cs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 419af0a69..30e4df0f1 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -53,7 +53,19 @@ public int Run(SuppressOptions options) options.ResultsGuids = expressionGuids; } } - Console.WriteLine($"Suppressing {options.ResultsGuids.Count()} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); + if (options.ResultsGuids != null) + { + Console.WriteLine($"Suppressing {options.ResultsGuids.Count()} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); +#if DEBUG + foreach (var result in options.ResultsGuids) + { + Console.WriteLine($"{result}"); + } +#endif + } else + { + Console.WriteLine($"Suppressing {currentSarifLog.Runs.Sum(i => i.Results.Count)} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); + } SarifLog reformattedLog = new SuppressVisitor(options.Justification, options.Alias, diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 999b70432..4e53ee467 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -151,6 +151,18 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( Expression = "BaseLineState = \"New\"", Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + Guids = true, + ExpiryInDays = 5, + Timestamps = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + ResultsGuids = new List() {}, + Expression = "BaseLineState = \"New\"", + Status = SuppressionStatus.Accepted + }, }; foreach (SuppressOptions options in optionsTestCases) From 407183cc7b7d9bed94815f6bc17b752cc161da0c Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Tue, 9 Aug 2022 13:37:58 -0500 Subject: [PATCH 11/61] fixed bug with ResultsGuids union to Query guids --- src/Sarif.Multitool.Library/SuppressCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 30e4df0f1..b9849a70e 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -46,7 +46,7 @@ public int Run(SuppressOptions options) var expressionGuids = ReturnQueryExpressionGuids(options); if (options.ResultsGuids != null && options.ResultsGuids.Any()) { - options.ResultsGuids.Union(expressionGuids); + options.ResultsGuids = expressionGuids.Union(options.ResultsGuids); } else { From 723140b186e7754ceb1cf7849cef16069830f891 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Thu, 11 Aug 2022 11:32:26 -0500 Subject: [PATCH 12/61] WIP - Added IsSuppressed evaluator and updated unit tests --- src/Sarif/Core/Result.cs | 11 +++++++++-- src/Sarif/Query/Evaluators/SarifEvaluators.cs | 2 ++ .../QueryCommandTests.cs | 4 ++++ .../SuppressCommandTests.cs | 14 +++++++++++++- .../TestData/QueryCommand/elfie-arriba.sarif | 15 +++++++++++++-- src/Test.UnitTests.Sarif/Core/ResultTests.cs | 13 +++++++++++-- 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/Sarif/Core/Result.cs b/src/Sarif/Core/Result.cs index c9b3011db..9abd313fb 100644 --- a/src/Sarif/Core/Result.cs +++ b/src/Sarif/Core/Result.cs @@ -99,7 +99,7 @@ public ReportingDescriptor GetRule(Run run = null) return new ReportingDescriptor() { Id = this.RuleId ?? this.Rule?.Id }; } - public bool TryIsSuppressed(out bool isSuppressed) + public bool TryIsSuppressed(out bool isSuppressed, bool checkExpired = false) { isSuppressed = false; if (this == null) @@ -120,8 +120,15 @@ public bool TryIsSuppressed(out bool isSuppressed) // If the status of any of the suppressions is "underReview" or "rejected", // then the result should not be considered suppressed. Otherwise, the result should be considered suppressed. - // https://github.com/microsoft/sarif-tutorials/blob/main/docs/Displaying-results-in-a-viewer.md#determining-suppression-status + // https://github.com/microsoft/sarif-tutorials/blob/main/docs/Displaying-results-in-a-viewer.md#determining-suppression-status isSuppressed = !suppressions.Any(s => s.Status == SuppressionStatus.UnderReview || s.Status == SuppressionStatus.Rejected); + + // if it is suppressed and are expired + if (isSuppressed && checkExpired) + { + isSuppressed = suppressions.Any(s => (s.TryGetProperty("expiryUtc", out DateTime expiryUtc) && expiryUtc > DateTime.UtcNow) && s.Status == SuppressionStatus.Accepted); + } + return true; } diff --git a/src/Sarif/Query/Evaluators/SarifEvaluators.cs b/src/Sarif/Query/Evaluators/SarifEvaluators.cs index fa1e74c85..93b3bb796 100644 --- a/src/Sarif/Query/Evaluators/SarifEvaluators.cs +++ b/src/Sarif/Query/Evaluators/SarifEvaluators.cs @@ -34,6 +34,8 @@ public static IExpressionEvaluator ResultEvaluator(TermExpression term) return new DoubleEvaluator(r => r.Rank, term); case "ruleid": return new StringEvaluator(r => r.GetRule(r.Run).Id, term, StringComparison.OrdinalIgnoreCase); + case "issuppressed": + return new BoolEvaluator(r => r.TryIsSuppressed(out bool suppressed) && suppressed, term); case "uri": // Ensure the Run is provided, to look up Uri from Run.Artifacts when needed. diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs index 2dfce1018..6c90cc696 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs @@ -30,6 +30,10 @@ public void QueryCommand_Basics() RunAndVerifyCount(1, new QueryOptions() { Expression = "Level != Error", InputFilePath = filePath }); RunAndVerifyCount(1, new QueryOptions() { Expression = "Level != Error && RuleId = CSCAN0060/0", InputFilePath = filePath }); + // Suppression filtering + RunAndVerifyCount(1, new QueryOptions() { Expression = "IsSuppressed == True", InputFilePath = filePath }); + RunAndVerifyCount(1, new QueryOptions() { Expression = "IsSuppressed == True && RuleId = CSCAN0060/0", InputFilePath = filePath }); + // Intersection w/no matches RunAndVerifyCount(0, new QueryOptions() { Expression = "Level != Error && RuleId != CSCAN0060/0", InputFilePath = filePath }); diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 4e53ee467..3bf9522a7 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -163,6 +163,18 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( Expression = "BaseLineState = \"New\"", Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + Guids = true, + ExpiryInDays = 5, + Timestamps = true, + InputFilePath = @"C:\input.sarif", + OutputFilePath = @"C:\output.sarif", + Justification = "some justification", + ResultsGuids = new List() {}, + Expression = "IsSuppressed == False", + Status = SuppressionStatus.Accepted + }, }; foreach (SuppressOptions options in optionsTestCases) @@ -185,7 +197,7 @@ private static void VerifySuppressCommand(SuppressOptions options) { RuleId = "Test0001", Guid = "GUID", - BaselineState = BaselineState.New + BaselineState = BaselineState.New } } } diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/elfie-arriba.sarif b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/elfie-arriba.sarif index f56f82c05..46f52fe82 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/elfie-arriba.sarif +++ b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/elfie-arriba.sarif @@ -74,7 +74,18 @@ }, "properties": { "matchState": "NotSuppressed" - } + }, + "suppressions": [ + { + "kind": "external", + "status": "accepted", + "justification": "justification", + "properties": { + "alias": "alias", + "expiryUtc": "2022-09-08T19:22:23.438465Z" + } + } + ] }, { "ruleId": "CSCAN0060/0", @@ -198,7 +209,7 @@ }, "properties": { "matchState": "NotSuppressed" - } + } } ], "automationDetails": {}, diff --git a/src/Test.UnitTests.Sarif/Core/ResultTests.cs b/src/Test.UnitTests.Sarif/Core/ResultTests.cs index 094e84e06..a2b40b12c 100644 --- a/src/Test.UnitTests.Sarif/Core/ResultTests.cs +++ b/src/Test.UnitTests.Sarif/Core/ResultTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using FluentAssertions; @@ -55,8 +56,9 @@ public void Result_TryIsSuppressed_ShouldReturnTrueWhenSuppressionsAreAvailable( { var result = new Result { - Suppressions = new List() - }; + Suppressions = new List() {} + }; + result.TryIsSuppressed(out bool isSuppressed).Should().BeTrue(); isSuppressed.Should().BeFalse(); @@ -86,6 +88,13 @@ public void Result_TryIsSuppressed_ShouldReturnTrueWhenSuppressionsAreAvailable( result.Suppressions.Add(new Suppression { Status = SuppressionStatus.Accepted }); result.TryIsSuppressed(out isSuppressed).Should().BeTrue(); isSuppressed.Should().BeTrue(); + + // Suppression with 'Accepted' only and expired. + result.Suppressions.Clear(); + result.Suppressions.Add(new Suppression { Status = SuppressionStatus.Accepted }); + result.Suppressions[0].SetProperty("expiryUtc", DateTime.UtcNow.AddDays(1)); + result.TryIsSuppressed(out isSuppressed, true).Should().BeTrue(); + isSuppressed.Should().BeTrue(); } } } From 3f84ae41f3a7df13403adfa680048299c72a9a73 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 11 Aug 2022 15:57:58 -0500 Subject: [PATCH 13/61] feat: nessus converter v0 --- docs/multitool-usage.md | 1 + .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/NessusConverter.cs | 203 ++++++++++++++++++ .../NessusObjectModel/FamilyItem.cs | 13 ++ .../NessusObjectModel/FamilySelection.cs | 15 ++ .../NessusObjectModel/HostProperties.cs | 15 ++ .../NessusObjectModel/HostTag.cs | 16 ++ .../IndividualPluginSelection.cs | 14 ++ .../NessusObjectModel/Item.cs | 31 +++ .../NessusObjectModel/NessusClientData.cs | 17 ++ .../NessusObjectModel/PluginItem.cs | 22 ++ .../NessusObjectModel/PluginsPreferences.cs | 14 ++ .../NessusObjectModel/Policy.cs | 26 +++ .../NessusObjectModel/Preference.cs | 16 ++ .../NessusObjectModel/Preferences.cs | 17 ++ .../NessusObjectModel/Report.cs | 17 ++ .../NessusObjectModel/ReportHost.cs | 20 ++ .../NessusObjectModel/ReportItem.cs | 101 +++++++++ .../NessusObjectModel/ServerPreference.cs | 14 ++ src/Sarif.Converters/ToolFileConverterBase.cs | 6 + src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- src/Sarif/HashUtilities.cs | 10 + src/Sarif/IResultLogWriter.cs | 8 + src/Sarif/Writers/ResultLogJsonWriter.cs | 6 + 25 files changed, 607 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/NessusConverter.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/FamilyItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/FamilySelection.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/HostProperties.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/HostTag.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Item.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/NessusClientData.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/PluginItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Policy.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Preference.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Preferences.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Report.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ReportHost.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ReportItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ServerPreference.cs diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 151f880c2..fbc0d7e7a 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -79,6 +79,7 @@ Run ```Sarif.Multitool convert --help``` for the current list. - Fortify - FortifyFpr - FxCop +- Nessus - PREfast - Pylint - SemmleQL diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 8b1b1838d..86d19dcf0 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -34,6 +34,7 @@ private static Dictionary> CreateBuiltInConv CreateConverterRecord(result, ToolFormat.FxCop); CreateConverterRecord(result, ToolFormat.FlawFinder); CreateConverterRecord(result, ToolFormat.Hdf); + CreateConverterRecord(result, ToolFormat.Nessus); CreateConverterRecord(result, ToolFormat.PREfast); CreateConverterRecord(result, ToolFormat.Pylint); CreateConverterRecord(result, ToolFormat.SemmleQL); diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs new file mode 100644 index 000000000..3995daf34 --- /dev/null +++ b/src/Sarif.Converters/NessusConverter.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Serialization; + +using Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class NessusConverter : ToolFileConverterBase + { + public override string ToolName => ToolFormat.Nessus; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //LogicalLocations.Clear(); + + XmlReaderSettings settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Ignore, + XmlResolver = null + }; + + //Parse nessus data + var serializer = new XmlSerializer(typeof(NessusClientData)); + + //List of runs (one for each nessus host scanned) + var log = new SarifLog(); + log.Runs = new List(); + + using (var reader = XmlReader.Create(input, settings)) + { + var nessusClientData = (NessusClientData)serializer.Deserialize(reader); + + foreach (ReportHost host in nessusClientData.Report.ReportHosts) + { + var run = new Run(); + + //Init objects + run.Tool = new Tool(); + run.Results = new List(); + + //Set driver details + run.Tool.Driver = CreateDriver(nessusClientData, host.Name); + + //Set the list of tool rules + foreach (ReportItem item in host.ReportItems) + { + //Add rule (plugin id) if not exits + if (!run.Tool.Driver.Rules.Any(i => i.Id == item.PluginId)) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(item)); + } + + run.Results.Add(CreateResult(item, host.Name)); + } + + log.Runs.Add(run); + } + + PersistResults(output, log); + } + } + + private ToolComponent CreateDriver(NessusClientData data, string targetId) + { + var driver = new ToolComponent(); + driver.Rules = new List(); + + driver.Name = this.ToolName; + driver.FullName = data.Report.Name; + driver.Version = data.Policy.Preferences.ServerPreference.Preferences.FirstOrDefault(i => i.Name.Equals("sc_version"))?.Value; + driver.InformationUri = new Uri("https://static.tenable.com/documentation/nessus_v2_file_format.pdf"); + driver.SetProperty("targetId", targetId); + return driver; + } + + private ReportingDescriptor CreateReportDescriptor(ReportItem item) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = item.PluginId; + descriptor.Name = item.PluginName; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = item.Synopsis, + Markdown = item.Synopsis, + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = item.Description, + Markdown = item.Description, + }; + + if (!string.IsNullOrWhiteSpace(item.SeeAlso)) + { + descriptor.Help = new MultiformatMessageString() + { + Text = item.SeeAlso, + Markdown = item.SeeAlso, + }; + } + + descriptor.SetProperty("pluginFamily", item.PluginFamily); + descriptor.SetProperty("pluginModificationDate", item.PluginModificationDate); + descriptor.SetProperty("pluginPublicationDate", item.PluginPublicationDate); + descriptor.SetProperty("pluginType", item.PluginType); + + return descriptor; + } + + internal Result CreateResult(ReportItem item, string hostName) + { + //set the result metadata + Result result = new Result + { + RuleId = item.PluginId, + Message = new Message { Text = item.PluginOutput }, + }; + + //set misc properties + result.SetProperty("port", item.Port); + result.SetProperty("protocol", item.Protocol); + result.SetProperty("service", item.ServiceName); + + //set solution if present + if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) + result.SetProperty("solution", item.Solution); + + //set severity (rank) + //ignore risk factor (H/M/L) as it conflicts with rank + result.Kind = ResultKind.Fail; + result.Rank = double.Parse(item.Severity); + + //vulnerable packages contain cve / cvss data + if (!string.IsNullOrEmpty(item.Cvss3BaseScore)) + { + result.SetProperty("cvss3BaseScore", item.Cvss3BaseScore); + } + + if (!string.IsNullOrEmpty(item.Cvss3TemporalScore)) + { + result.SetProperty("cvss3TemporalScore", item.Cvss3TemporalScore); + } + + if (!string.IsNullOrEmpty(item.Cvss3Vector)) + { + result.SetProperty("cvss3Vector", item.Cvss3Vector); + } + + if (!string.IsNullOrEmpty(item.Cvss3TemporalVector)) + { + result.SetProperty("cvss3TemporalVector", item.Cvss3TemporalVector); + } + + if (!string.IsNullOrEmpty(item.VulnPublicationDate)) + { + result.SetProperty("vulnPublicationDate", item.VulnPublicationDate); + } + + if (!string.IsNullOrEmpty(item.PatchPublicationDate)) + { + result.SetProperty("patchPublicationDate", item.PatchPublicationDate); + } + + if (item.Cves.Any()) + { + result.SetProperty("cve", item.Cves); + } + + if (item.Xrefs.Any()) + { + result.SetProperty("xref", item.Xrefs); + } + + if (!string.IsNullOrEmpty(item.ExploitAvailable)) + { + result.SetProperty("exploitAvailable", bool.Parse(item.ExploitAvailable)); + } + + //Set the unique fingerprint per item + var fingerprints = new List() { + item.PluginId, + item.Port, + item.Protocol, + item.ServiceName, + }; + + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(string.Join(".", fingerprints)).ToLower()); + + return result; + } + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs b/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs new file mode 100644 index 000000000..79db92f53 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class FamilyItem + { + [XmlElement("FamilyName")] + public string Name { get; set; } = string.Empty; + + [XmlElement("Status")] + public string Status { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs b/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs new file mode 100644 index 000000000..0cfb18074 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class FamilySelection + { + [XmlElement("FamilyItem")] + public List FamilyItems { get; set; } = new List(); + + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/HostProperties.cs b/src/Sarif.Converters/NessusObjectModel/HostProperties.cs new file mode 100644 index 000000000..ba3efcb16 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/HostProperties.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class HostProperties + { + [XmlElement("tag")] + public List Tags { get; set; } = new List(); + + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/HostTag.cs b/src/Sarif.Converters/NessusObjectModel/HostTag.cs new file mode 100644 index 000000000..d78d41923 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/HostTag.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class HostTag + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlText] + public string Value { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs b/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs new file mode 100644 index 000000000..b1ff6558d --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class IndividualPluginSelection + { + [XmlElement("PluginItem")] + public List PluginItems { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Item.cs b/src/Sarif.Converters/NessusObjectModel/Item.cs new file mode 100644 index 000000000..16f18555e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Item.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Item + { + [XmlElement("fullName")] + public string FullName { get; set; } = string.Empty; + + [XmlElement("pluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlElement("pluginId")] + public string PluginId { get; set; } = string.Empty; + + [XmlElement("preferenceName")] + public string PreferenceName { get; set; } = string.Empty; + + [XmlElement("preferenceType")] + public string PreferenceType { get; set; } = string.Empty; + + [XmlElement("preferenceValues")] + public string PreferenceValues { get; set; } = string.Empty; + + [XmlElement("selectedValue")] + public string SelectedValue { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs b/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs new file mode 100644 index 000000000..7f0b0a96e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + [XmlRoot("NessusClientData_v2")] + public class NessusClientData + { + [XmlElement("Policy")] + public Policy Policy { get; set; } = new Policy(); + + [XmlElement("Report")] + public Report Report { get; set; } = new Report(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/PluginItem.cs b/src/Sarif.Converters/NessusObjectModel/PluginItem.cs new file mode 100644 index 000000000..1a0df86fa --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/PluginItem.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class PluginItem + { + [XmlElement("PluginId")] + public string PluginId { get; set; } = string.Empty; + + [XmlElement("PluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlElement("Family")] + public string Family { get; set; } = string.Empty; + + [XmlElement("Status")] + public string Status { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs b/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs new file mode 100644 index 000000000..ba4fdccc4 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class PluginsPreferences + { + [XmlElement("item")] + public List Items { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Policy.cs b/src/Sarif.Converters/NessusObjectModel/Policy.cs new file mode 100644 index 000000000..acf6bffd9 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Policy.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Policy + { + [XmlElement("policyName")] + public string PolicyName { get; set; } = string.Empty; + + [XmlElement("policyComments")] + public string PolicyComments { get; set; } = string.Empty; + + [XmlElement("Preferences")] + public Preferences Preferences { get; set; } = new Preferences(); + + [XmlElement("FamilySelection")] + public FamilySelection FamilySelection { get; set; } = new FamilySelection(); + + [XmlElement("IndividualPluginSelection")] + public IndividualPluginSelection IndividualPluginSelection { get; set; } = new IndividualPluginSelection(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Preference.cs b/src/Sarif.Converters/NessusObjectModel/Preference.cs new file mode 100644 index 000000000..0694c9553 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Preference.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Preference + { + [XmlElement("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("value")] + public string Value { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Preferences.cs b/src/Sarif.Converters/NessusObjectModel/Preferences.cs new file mode 100644 index 000000000..4efead73c --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Preferences.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Preferences + { + [XmlElement("ServerPreferences")] + public ServerPreference ServerPreference { get; set; } = new ServerPreference(); + + [XmlElement("PluginsPreferences")] + public PluginsPreferences PluginsPreferences { get; set; } = new PluginsPreferences(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Report.cs b/src/Sarif.Converters/NessusObjectModel/Report.cs new file mode 100644 index 000000000..923ba605e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Report.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Report + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("ReportHost")] + public List ReportHosts { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Sarif.Converters/NessusObjectModel/ReportHost.cs b/src/Sarif.Converters/NessusObjectModel/ReportHost.cs new file mode 100644 index 000000000..b93653323 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ReportHost.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ReportHost + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("HostProperties")] + public HostProperties HostProperties { get; set; } = new HostProperties(); + + [XmlElement("ReportItem")] + public List ReportItems { get; set; } + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/ReportItem.cs b/src/Sarif.Converters/NessusObjectModel/ReportItem.cs new file mode 100644 index 000000000..017267fc2 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ReportItem.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ReportItem + { + [XmlAttribute("severity")] + public string Severity { get; set; } = string.Empty; + + [XmlAttribute("port")] + public string Port { get; set; } = string.Empty; + + [XmlAttribute("pluginFamily")] + public string PluginFamily { get; set; } = string.Empty; + + [XmlAttribute("pluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlAttribute("pluginID")] + public string PluginId { get; set; } = string.Empty; + + [XmlAttribute("protocol")] + public string Protocol { get; set; } = string.Empty; + + [XmlAttribute("svc_name")] + public string ServiceName { get; set; } = string.Empty; + + [XmlElement("plugin_modification_date")] + public string PluginModificationDate { get; set; } = string.Empty; + + [XmlElement("plugin_publication_date")] + public string PluginPublicationDate { get; set; } = string.Empty; + + [XmlElement("plugin_type")] + public string PluginType { get; set; } = string.Empty; + + [XmlElement("solution")] + public string Solution { get; set; } = string.Empty; + + [XmlElement("description")] + public string Description { get; set; } = string.Empty; + + [XmlElement("synopsis")] + public string Synopsis { get; set; } = string.Empty; + + [XmlElement("risk_factor")] + public string RiskFactor { get; set; } = string.Empty; + + [XmlElement("script_version")] + public string ScriptVersion { get; set; } = string.Empty; + + [XmlElement("plugin_output")] + public string PluginOutput { get; set; } = string.Empty; + + [XmlElement("see_also")] + public string SeeAlso { get; set; } = string.Empty; + + [XmlElement("cvss_base_score")] + public string CvssBaseScore { get; set; } = string.Empty; + + [XmlElement("cvss_temporal_score")] + public string CvssTemporalScore { get; set; } = string.Empty; + + [XmlElement("cvss3_base_score")] + public string Cvss3BaseScore { get; set; } = string.Empty; + + [XmlElement("cvss3_temporal_score")] + public string Cvss3TemporalScore { get; set; } = string.Empty; + + [XmlElement("exploit_available")] + public string ExploitAvailable { get; set; } = string.Empty; + + [XmlElement("patch_publication_date")] + public string PatchPublicationDate { get; set; } = string.Empty; + + [XmlElement("vuln_publication_date")] + public string VulnPublicationDate { get; set; } = string.Empty; + + [XmlElement("cvss3_temporal_vector")] + public string Cvss3TemporalVector { get; set; } = string.Empty; + + [XmlElement("cvss3_vector")] + public string Cvss3Vector { get; set; } = string.Empty; + + [XmlElement("cvss_temporal_vector")] + public string CvssTemporalVector { get; set; } = string.Empty; + + [XmlElement("cvss_vector")] + public string CvssVector { get; set; } = string.Empty; + + [XmlElement("cve")] + public List Cves { get; set; } = new List(); + + [XmlElement("xref")] + public List Xrefs { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs b/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs new file mode 100644 index 000000000..455c2b1b1 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ServerPreference + { + [XmlElement("preference")] + public List Preferences { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/ToolFileConverterBase.cs b/src/Sarif.Converters/ToolFileConverterBase.cs index e83cabfb4..4fc2020c6 100644 --- a/src/Sarif.Converters/ToolFileConverterBase.cs +++ b/src/Sarif.Converters/ToolFileConverterBase.cs @@ -105,5 +105,11 @@ protected static Run PersistResults(IResultLogWriter output, IList resul return run; } + + protected static void PersistResults(IResultLogWriter output, SarifLog log) + { + output.WriteLog(log); + return; + } } } diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index 562e1cd9b..c8259c7f4 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -39,6 +39,9 @@ public static class ToolFormat /// Heimdall Tools's file format. public const string Hdf = nameof(Hdf); + /// Tenable Nessus XML file format. + public const string Nessus = nameof(Nessus); + /// PREfast's file format. public const string PREfast = nameof(PREfast); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index cbc869926..05cc6521e 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, Nessus, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 3e8ca9434..a46458943 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif @@ -185,5 +186,14 @@ public static string ComputeMD5Hash(string fileName) catch (UnauthorizedAccessException) { } return md5; } + + public static string ComputeSha256HashValue(string value) + { + using (var sha = SHA256.Create()) + { + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } } } diff --git a/src/Sarif/IResultLogWriter.cs b/src/Sarif/IResultLogWriter.cs index b6b8736b7..00f1c460a 100644 --- a/src/Sarif/IResultLogWriter.cs +++ b/src/Sarif/IResultLogWriter.cs @@ -116,5 +116,13 @@ public interface IResultLogWriter /// The invocations to write. /// void WriteInvocations(IEnumerable invocations); + + /// + /// Write a full SARIF log object to the output stream. + /// + /// + /// The SarifLog to write. + /// + void WriteLog(SarifLog log); } } diff --git a/src/Sarif/Writers/ResultLogJsonWriter.cs b/src/Sarif/Writers/ResultLogJsonWriter.cs index 9e26fdac9..cbcefcab0 100644 --- a/src/Sarif/Writers/ResultLogJsonWriter.cs +++ b/src/Sarif/Writers/ResultLogJsonWriter.cs @@ -427,5 +427,11 @@ private void EnsureResultsArrayIsNotOpen() throw new InvalidOperationException(SdkResources.ResultsSerializationNotComplete); } } + + public void WriteLog(SarifLog log) + { + _serializer.Serialize(_jsonWriter, log); + _writeConditions |= Conditions.RunCompleted; + } } } From bec3834bda5266ec6377417d6ec0c726ca242310 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 12 Aug 2022 09:23:37 -0500 Subject: [PATCH 14/61] bug: fingerprint targetid + empty plugin output --- src/Sarif.Converters/NessusConverter.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index 3995daf34..3f77e793f 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -123,13 +123,17 @@ internal Result CreateResult(ReportItem item, string hostName) Result result = new Result { RuleId = item.PluginId, - Message = new Message { Text = item.PluginOutput }, + Message = new Message + { + Text = string.IsNullOrWhiteSpace(item.PluginOutput.Trim()) ? item.Synopsis.Trim() : item.PluginOutput.Trim(), + }, }; //set misc properties result.SetProperty("port", item.Port); result.SetProperty("protocol", item.Protocol); result.SetProperty("service", item.ServiceName); + result.SetProperty("targetId", hostName); //set solution if present if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) @@ -188,6 +192,7 @@ internal Result CreateResult(ReportItem item, string hostName) //Set the unique fingerprint per item var fingerprints = new List() { + hostName, item.PluginId, item.Port, item.Protocol, From 2a250aaecb460f2d7e5928b2e15c54cccc33e6ba Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 15 Aug 2022 09:14:48 -0500 Subject: [PATCH 15/61] feat: cis cat unit tests --- src/Sarif/HashUtilities.cs | 1 - .../CisCatConverterTests.cs | 62 +++++++++++++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 12 +++- .../ExpectedOutputs/NoResults.sarif | 25 ++++++++ .../ExpectedOutputs/ValidResults.sarif | 62 +++++++++++++++++++ .../Inputs/InvalidResults.json | 8 +++ .../CisCatConverter/Inputs/NoResults.json | 9 +++ .../CisCatConverter/Inputs/ValidResults.json | 20 ++++++ .../ResultLogObjectWriter.cs | 5 ++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index f0f7ed200..a46458943 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -8,7 +8,6 @@ using System.IO; using System.Security.Cryptography; using System.Text; -using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif diff --git a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs new file mode 100644 index 000000000..3b33b9687 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class CisCatConverterTests : ConverterTestsBase + { + private static readonly string NoOutputExpected = string.Empty; + + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new CisCatConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new CisCatConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ThrowsExpectedException() + { + string input = Extractor.GetResourceInputText("InvalidResults.json"); + Action action = () => RunTestCase(input, NoOutputExpected); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(CisCatConverterTests)); + private const string ResourceNamePrefix = ToolFormat.CisCat; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index 4a994089d..e078fad95 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -46,6 +46,11 @@ + + + + + @@ -69,6 +74,11 @@ + + + + + @@ -94,4 +104,4 @@ - + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..10a400280 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [], + "tool": { + "driver": { + "name": "CisCat", + "fullName": "CIS Benchmark Title", + "version": "2.1.0", + "semanticVersion": "2.1.0", + "informationUri": "https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre", + "properties": { + "benchmarkId": "cis.benchmark.id", + "profileId": "profile.id", + "profileTitle": "profile.title", + "score": "0.0" + } + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..175458bcb --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,62 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "rule.id.2", + "level": "error", + "message": { + "text": "Rule id 2 title" + }, + "fingerprints": { + "0": "d23eea429d06cfe703ab69b8d0d0c1abd41fc9e26b4167575e3356733c97bf61" + } + } + ], + "tool": { + "driver": { + "name": "CisCat", + "fullName": "CIS Benchmark Title", + "version": "2.1.0", + "semanticVersion": "2.1.0", + "informationUri": "https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre", + "rules": [ + { + "id": "rule.id.1", + "name": "Rule id 1 title", + "fullDescription": { + "text": "Rule id 1 title", + "markdown": "Rule id 1 title" + }, + "help": { + "text": "Rule id 1 title", + "markdown": "Rule id 1 title" + } + }, + { + "id": "rule.id.2", + "name": "Rule id 2 title", + "fullDescription": { + "text": "Rule id 2 title", + "markdown": "Rule id 2 title" + }, + "help": { + "text": "Rule id 2 title", + "markdown": "Rule id 2 title" + } + } + ], + "properties": { + "benchmarkId": "cis.benchmark.id", + "profileId": "profile.id", + "profileTitle": "profile.title", + "score": "50.00" + } + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json new file mode 100644 index 000000000..645453a3f --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json @@ -0,0 +1,8 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "ProfileId", + "profile-title": "Title", + "score": "0.0" +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json new file mode 100644 index 000000000..e15b964ec --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json @@ -0,0 +1,9 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "profile.id", + "profile-title": "profile.title", + "score": "0.0", + "rules": [] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json new file mode 100644 index 000000000..c4a3bc64d --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json @@ -0,0 +1,20 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "profile.id", + "profile-title": "profile.title", + "score": "50.00", + "rules": [ + { + "rule-id": "rule.id.1", + "rule-title": "Rule id 1 title", + "result": "pass" + }, + { + "rule-id": "rule.id.2", + "rule-title": "Rule id 2 title", + "result": "fail" + } + ] +} \ No newline at end of file diff --git a/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs b/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs index 27bb3a542..21cc4e808 100644 --- a/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs +++ b/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs @@ -65,5 +65,10 @@ public void WriteRules(IList rules) { throw new NotImplementedException(); } + + public void WriteLog(SarifLog log) + { + + } } } From 2304ccd6ac8808c70a0b1ca2227178b5a8dccbca Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 15 Aug 2022 14:31:59 -0500 Subject: [PATCH 16/61] feat: nessus unit tests --- .../NessusConverterTests.cs | 60 +++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 12 ++ .../ExpectedOutputs/InvalidResults.sarif | 5 + .../ExpectedOutputs/NoResults.sarif | 34 +++++ .../ExpectedOutputs/ValidResults.sarif | 119 ++++++++++++++++++ .../Inputs/InvalidResults.nessus.xml | 9 ++ .../Inputs/NoResults.nessus.xml | 58 +++++++++ .../Inputs/ValidResults.nessus.xml | 86 +++++++++++++ 8 files changed, 383 insertions(+) create mode 100644 src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml diff --git a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs new file mode 100644 index 000000000..c7eb093f2 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class NessusConverterTests : ConverterTestsBase + { + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new NessusConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new NessusConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ReturnsEmptyResults() + { + string input = Extractor.GetResourceInputText("InvalidResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("InvalidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(NessusConverterTests)); + private const string ResourceNamePrefix = ToolFormat.Nessus; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index e078fad95..cc9389ec9 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -51,6 +51,12 @@ + + + + + + @@ -79,6 +85,12 @@ + + + + + + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif new file mode 100644 index 000000000..787ce0d80 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif @@ -0,0 +1,5 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..9d9dfaa1e --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,34 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "properties": { + "targetId": "10.0.0.1" + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "properties": { + "targetId": "10.0.0.2" + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..86987d502 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,119 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "rules": [ + { + "id": "12345", + "name": "PluginName1", + "fullDescription": { + "text": "Description text", + "markdown": "Description text" + }, + "help": { + "text": "https://github.com/microsoft/sarif-sdk", + "markdown": "https://github.com/microsoft/sarif-sdk" + }, + "shortDescription": { + "text": "Synopsis test", + "markdown": "Synopsis test" + }, + "properties": { + "pluginFamily": "Family Name", + "pluginModificationDate": "2022/08/15", + "pluginPublicationDate": "2021/08/15", + "pluginType": "remote" + } + } + ], + "properties": { + "targetId": "10.0.0.1" + } + } + }, + "results": [ + { + "ruleId": "12345", + "message": { + "text": "plugin output text" + }, + "fingerprints": { + "0": "1e6bb36ff29ed6c40f06c82791515391820f7b546e700886adbd88b2c897279c" + }, + "rank": 1.0, + "properties": { + "port": "99999", + "protocol": "tcp", + "service": "unknown", + "targetId": "10.0.0.1" + } + } + ], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "rules": [ + { + "id": "12345", + "name": "CVE Scan", + "fullDescription": { + "text": "description text", + "markdown": "description text" + }, + "help": { + "text": "https://github.com/microsoft/sarif-sdk", + "markdown": "https://github.com/microsoft/sarif-sdk" + }, + "shortDescription": { + "text": "Synopis text", + "markdown": "Synopis text" + }, + "properties": { + "pluginFamily": "Family Name", + "pluginModificationDate": "2022/08/15", + "pluginPublicationDate": "2021/08/15", + "pluginType": "remote" + } + } + ], + "properties": { + "targetId": "10.0.0.2" + } + } + }, + "results": [ + { + "ruleId": "12345", + "message": { + "text": "plugin output" + }, + "fingerprints": { + "0": "c630cd9cdc8784d7ecdbbbe2ae5b6e0e30609841d2df1cef336b5e646391d876" + }, + "rank": 2.0, + "properties": { + "port": "0", + "protocol": "tcp", + "service": "unknown", + "targetId": "10.0.0.2", + "solution": "Solution text or generate a proper SSL certificate for this service.", + "cvss3BaseScore": "9.0", + "cvss3Vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml new file mode 100644 index 000000000..ba9be8e83 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml @@ -0,0 +1,9 @@ + + + + InvalidNessusConverterTest + + + + + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml new file mode 100644 index 000000000..2d18ca8ef --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml @@ -0,0 +1,58 @@ + + + + NessusConverterTest + policy comments text + + + + TARGET + 10.0.0.1,10.0.0.2 + + + preference1 + value1 + + + + + PluginName1 + 12345 + Plugin Full Name 12345 + Plugin Name 12345 Short Hame + radio + None + None + + + + + + Family Name + enabled + + + + + 12345 + Selected Plugin 1 + Family Name + enabled + + + + + + + value1 + 10.0.0.1 + + + + + value1 + 10.0.0.1 + + + + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml new file mode 100644 index 000000000..0f67ecbac --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml @@ -0,0 +1,86 @@ + + + + NessusConverterTest + policy comments text + + + + TARGET + 10.0.0.1,10.0.0.2 + + + preference1 + value1 + + + + + PluginName1 + 12345 + Plugin Full Name 12345 + Plugin Name 12345 Short Hame + radio + None + None + + + + + + Family Name + enabled + + + + + 12345 + Selected Plugin 1 + Family Name + enabled + + + + + + + value1 + 10.0.0.1 + + + 2022/08/15 + 2021/08/15 + remote + n/a + Description text + Synopsis test + https://github.com/microsoft/sarif-sdk + None + 1.0 + plugin output text + + + + + value1 + 10.0.0.1 + + + 9.0 + 9.0 + 2022/08/15 + 2021/08/15 + remote + Solution text or generate a proper SSL certificate for this service. + AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N + AV:N/AC:L/AU:N/C:P/I:P/A:N + description text + Synopis text + https://github.com/microsoft/sarif-sdk + High + 1.0 + plugin output + + + + \ No newline at end of file From 56d084936cbb7539ef55a1c9d094ed3dd995082a Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 8 Aug 2022 16:07:35 -0500 Subject: [PATCH 17/61] feat: cis cat pro json converter --- docs/multitool-usage.md | 2 + .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/CisCatConverter.cs | 168 ++++++++++++++++++ .../CisCatObjectModel/CisCatReport.cs | 33 ++++ .../CisCatObjectModel/CisCatReportReader.cs | 25 +++ .../CisCatObjectModel/CisCatRule.cs | 19 ++ src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- src/Sarif/HashUtilities.cs | 11 ++ 9 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/CisCatConverter.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs create mode 100644 src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 151f880c2..53d129a23 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -69,9 +69,11 @@ Sarif.Multitool validate Other.sarif ``` ## Supported Converters + Run ```Sarif.Multitool convert --help``` for the current list. - AndroidStudio +- CisCat - ClangAnalyzer - ClangTidy - CppCheck diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 8b1b1838d..b4804f890 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -25,6 +25,7 @@ private static Dictionary> CreateBuiltInConv { var result = new Dictionary>(); CreateConverterRecord(result, ToolFormat.AndroidStudio); + CreateConverterRecord(result, ToolFormat.CisCat); CreateConverterRecord(result, ToolFormat.CppCheck); CreateConverterRecord(result, ToolFormat.ClangAnalyzer); CreateConverterRecord(result, ToolFormat.ClangTidy); diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs new file mode 100644 index 000000000..2744dbeda --- /dev/null +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class CisCatConverter : ToolFileConverterBase + { + private readonly LogReader logReader; + + public CisCatConverter() + { + logReader = new CisCatReportReader(); + } + + public override string ToolName => ToolFormat.CisCat; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //Read CIS CAT data + CisCatReport log = logReader.ReadLog(input); + + //Top level run object for the scan data + var run = new Run(); + + //Set the tool details + run.Tool = new Tool(); + run.Tool.Driver = CreateDriver(log); + + //Set the list of tool rules + run.Tool.Driver.Rules = new List(); + foreach (CisCatRule rule in log.Rules) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(rule)); + } + + var results = new List(); + foreach (CisCatRule rule in log.Rules) + { + results.Add(CreateResult(rule)); + } + + PersistResults(output, results, run); + } + + internal ToolComponent CreateDriver(CisCatReport report) + { + + var driver = new ToolComponent(); + + driver.Name = this.ToolName; + driver.FullName = report.BenchmarkTitle; + driver.Version = report.BenchmarkVersion; + driver.SemanticVersion = report.BenchmarkVersion; + driver.InformationUri = new Uri("https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre"); + + driver.SetProperty("benchmarkId", report.BenchmarkId); + driver.SetProperty("profileId", report.ProfileId); + driver.SetProperty("profileTitle", report.ProfileTitle); + driver.SetProperty("score", report.Score); + + return driver; + } + + internal ReportingDescriptor CreateReportDescriptor(CisCatRule rule) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = rule.RuleId; + descriptor.Name = rule.RuleTitle; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + descriptor.Help = new MultiformatMessageString() + { + Text = rule.RuleTitle, + Markdown = rule.RuleTitle, + }; + + return descriptor; + } + + internal Result CreateResult(CisCatRule rule) + { + //set the result metadata + Result result = new Result + { + RuleId = rule.RuleId, + Message = new Message { Text = rule.RuleTitle }, + }; + + //Kind & Level determine the status + //Result: "fail": Level = Error, Kind = Fail + //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review + switch (rule.Result) + { + case "fail": + result.Level = FailureLevel.Error; + result.Kind = ResultKind.Fail; + break; + case "pass": + result.Level = FailureLevel.None; + result.Kind = ResultKind.Pass; + break; + case "notchecked": + result.Level = FailureLevel.None; + result.Kind = ResultKind.NotApplicable; + break; + case "informational": + result.Level = FailureLevel.None; + result.Kind = ResultKind.Informational; + break; + case "unknown": + default: + result.Level = FailureLevel.None; + result.Kind = ResultKind.Review; + break; + }; + + //Set the unique fingerprint + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); + + // var region = new Region + // { + // StartColumn = int.Parse(defect.Column), + // StartLine = int.Parse(defect.Line) + // }; + + // var fileUri = new Uri($"{defect.FilePath}", UriKind.RelativeOrAbsolute); + // var physicalLocation = new PhysicalLocation + // { + // ArtifactLocation = new ArtifactLocation + // { + // Uri = fileUri + // }, + // Region = region + // }; + + // var location = new Location + // { + // PhysicalLocation = physicalLocation + // }; + + // result.Locations = new List + // { + // location + // }; + + return result; + } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs new file mode 100644 index 000000000..ea6710f95 --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatReport.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatReport + { + [JsonProperty("benchmark-id")] + public string BenchmarkId { get; set; } + + [JsonProperty("benchmark-title")] + public string BenchmarkTitle { get; set; } + + [JsonProperty("benchmark-version")] + public string BenchmarkVersion { get; set; } + + [JsonProperty("profile-id")] + public string ProfileId { get; set; } + + [JsonProperty("profile-title")] + public string ProfileTitle { get; set; } + + [JsonProperty("score")] + public string Score { get; set; } + + [JsonProperty("rules")] + public IEnumerable Rules { get; set; } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs new file mode 100644 index 000000000..8f2b107e6 --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatReportReader.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatReportReader : LogReader + { + public override CisCatReport ReadLog(Stream input) + { + string reportData; + + using (TextReader streamReader = new StreamReader(input)) + { + reportData = streamReader.ReadToEnd(); + } + + return JsonConvert.DeserializeObject(reportData); + } + } +} diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs new file mode 100644 index 000000000..b4d7f1bfc --- /dev/null +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel +{ + public class CisCatRule + { + [JsonProperty("rule-id")] + public string RuleId { get; set; } + + [JsonProperty("rule-title")] + public string RuleTitle { get; set; } + + [JsonProperty("result")] + public string Result { get; set; } + } +} diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index 562e1cd9b..a44694fa8 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -12,6 +12,9 @@ public static class ToolFormat /// Android Studio's file format. public const string AndroidStudio = nameof(AndroidStudio); + /// CIS CAT file format. + public const string CisCat = nameof(CisCat); + /// Clang analyzer's file format. public const string ClangAnalyzer = nameof(ClangAnalyzer); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index cbc869926..ce9450df5 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 3e8ca9434..00946de47 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif @@ -185,5 +186,15 @@ public static string ComputeMD5Hash(string fileName) catch (UnauthorizedAccessException) { } return md5; } + + + public static string ComputeSha256HashValue(string value) + { + using (var sha = SHA256.Create()) + { + byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty); + } + } } } From f94940f41d4ef11a331250f3ec9f404a01323b9c Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 9 Aug 2022 10:08:50 -0500 Subject: [PATCH 18/61] bug: exclude passing tests from results for match forward baseline status --- src/Sarif.Converters/CisCatConverter.cs | 41 +++++-------------- .../CisCatObjectModel/CisCatRule.cs | 5 +++ 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index 2744dbeda..a0fccab5f 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -45,7 +45,10 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm var results = new List(); foreach (CisCatRule rule in log.Rules) { - results.Add(CreateResult(rule)); + if (!rule.IsPass()) + { + results.Add(CreateResult(rule)); + } } PersistResults(output, results, run); @@ -109,14 +112,16 @@ internal Result CreateResult(CisCatRule rule) //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review switch (rule.Result) { + //PASS CASES ARE NOT INCLUDED IN THE RESULTS, AS MATCH FORWARD DOES NOT PRODUCE + //THE CORRECT ABSENT / NEW STATES WHEN THEY EXIST + // case "pass": + // result.Level = FailureLevel.None; + // result.Kind = ResultKind.Pass; + // break; case "fail": result.Level = FailureLevel.Error; result.Kind = ResultKind.Fail; break; - case "pass": - result.Level = FailureLevel.None; - result.Kind = ResultKind.Pass; - break; case "notchecked": result.Level = FailureLevel.None; result.Kind = ResultKind.NotApplicable; @@ -136,32 +141,6 @@ internal Result CreateResult(CisCatRule rule) result.Fingerprints = new Dictionary(); result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); - // var region = new Region - // { - // StartColumn = int.Parse(defect.Column), - // StartLine = int.Parse(defect.Line) - // }; - - // var fileUri = new Uri($"{defect.FilePath}", UriKind.RelativeOrAbsolute); - // var physicalLocation = new PhysicalLocation - // { - // ArtifactLocation = new ArtifactLocation - // { - // Uri = fileUri - // }, - // Region = region - // }; - - // var location = new Location - // { - // PhysicalLocation = physicalLocation - // }; - - // result.Locations = new List - // { - // location - // }; - return result; } } diff --git a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs index b4d7f1bfc..43e340708 100644 --- a/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs +++ b/src/Sarif.Converters/CisCatObjectModel/CisCatRule.cs @@ -15,5 +15,10 @@ public class CisCatRule [JsonProperty("result")] public string Result { get; set; } + + public bool IsPass() + { + return this.Result == "pass"; + } } } From 92b8d443ff898b00c4d16ed4ab669b8c701af31e Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 11 Aug 2022 15:57:58 -0500 Subject: [PATCH 19/61] feat: nessus converter v0 --- docs/multitool-usage.md | 1 + .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/NessusConverter.cs | 203 ++++++++++++++++++ .../NessusObjectModel/FamilyItem.cs | 13 ++ .../NessusObjectModel/FamilySelection.cs | 15 ++ .../NessusObjectModel/HostProperties.cs | 15 ++ .../NessusObjectModel/HostTag.cs | 16 ++ .../IndividualPluginSelection.cs | 14 ++ .../NessusObjectModel/Item.cs | 31 +++ .../NessusObjectModel/NessusClientData.cs | 17 ++ .../NessusObjectModel/PluginItem.cs | 22 ++ .../NessusObjectModel/PluginsPreferences.cs | 14 ++ .../NessusObjectModel/Policy.cs | 26 +++ .../NessusObjectModel/Preference.cs | 16 ++ .../NessusObjectModel/Preferences.cs | 17 ++ .../NessusObjectModel/Report.cs | 17 ++ .../NessusObjectModel/ReportHost.cs | 20 ++ .../NessusObjectModel/ReportItem.cs | 101 +++++++++ .../NessusObjectModel/ServerPreference.cs | 14 ++ src/Sarif.Converters/ToolFileConverterBase.cs | 6 + src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- src/Sarif/HashUtilities.cs | 1 + src/Sarif/IResultLogWriter.cs | 8 + src/Sarif/Writers/ResultLogJsonWriter.cs | 6 + 25 files changed, 598 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/NessusConverter.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/FamilyItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/FamilySelection.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/HostProperties.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/HostTag.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Item.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/NessusClientData.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/PluginItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Policy.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Preference.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Preferences.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/Report.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ReportHost.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ReportItem.cs create mode 100644 src/Sarif.Converters/NessusObjectModel/ServerPreference.cs diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index 53d129a23..e2326ebf1 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -81,6 +81,7 @@ Run ```Sarif.Multitool convert --help``` for the current list. - Fortify - FortifyFpr - FxCop +- Nessus - PREfast - Pylint - SemmleQL diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index b4804f890..1c1bc09b7 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -35,6 +35,7 @@ private static Dictionary> CreateBuiltInConv CreateConverterRecord(result, ToolFormat.FxCop); CreateConverterRecord(result, ToolFormat.FlawFinder); CreateConverterRecord(result, ToolFormat.Hdf); + CreateConverterRecord(result, ToolFormat.Nessus); CreateConverterRecord(result, ToolFormat.PREfast); CreateConverterRecord(result, ToolFormat.Pylint); CreateConverterRecord(result, ToolFormat.SemmleQL); diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs new file mode 100644 index 000000000..3995daf34 --- /dev/null +++ b/src/Sarif.Converters/NessusConverter.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Serialization; + +using Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class NessusConverter : ToolFileConverterBase + { + public override string ToolName => ToolFormat.Nessus; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //LogicalLocations.Clear(); + + XmlReaderSettings settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Ignore, + XmlResolver = null + }; + + //Parse nessus data + var serializer = new XmlSerializer(typeof(NessusClientData)); + + //List of runs (one for each nessus host scanned) + var log = new SarifLog(); + log.Runs = new List(); + + using (var reader = XmlReader.Create(input, settings)) + { + var nessusClientData = (NessusClientData)serializer.Deserialize(reader); + + foreach (ReportHost host in nessusClientData.Report.ReportHosts) + { + var run = new Run(); + + //Init objects + run.Tool = new Tool(); + run.Results = new List(); + + //Set driver details + run.Tool.Driver = CreateDriver(nessusClientData, host.Name); + + //Set the list of tool rules + foreach (ReportItem item in host.ReportItems) + { + //Add rule (plugin id) if not exits + if (!run.Tool.Driver.Rules.Any(i => i.Id == item.PluginId)) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(item)); + } + + run.Results.Add(CreateResult(item, host.Name)); + } + + log.Runs.Add(run); + } + + PersistResults(output, log); + } + } + + private ToolComponent CreateDriver(NessusClientData data, string targetId) + { + var driver = new ToolComponent(); + driver.Rules = new List(); + + driver.Name = this.ToolName; + driver.FullName = data.Report.Name; + driver.Version = data.Policy.Preferences.ServerPreference.Preferences.FirstOrDefault(i => i.Name.Equals("sc_version"))?.Value; + driver.InformationUri = new Uri("https://static.tenable.com/documentation/nessus_v2_file_format.pdf"); + driver.SetProperty("targetId", targetId); + return driver; + } + + private ReportingDescriptor CreateReportDescriptor(ReportItem item) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = item.PluginId; + descriptor.Name = item.PluginName; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = item.Synopsis, + Markdown = item.Synopsis, + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = item.Description, + Markdown = item.Description, + }; + + if (!string.IsNullOrWhiteSpace(item.SeeAlso)) + { + descriptor.Help = new MultiformatMessageString() + { + Text = item.SeeAlso, + Markdown = item.SeeAlso, + }; + } + + descriptor.SetProperty("pluginFamily", item.PluginFamily); + descriptor.SetProperty("pluginModificationDate", item.PluginModificationDate); + descriptor.SetProperty("pluginPublicationDate", item.PluginPublicationDate); + descriptor.SetProperty("pluginType", item.PluginType); + + return descriptor; + } + + internal Result CreateResult(ReportItem item, string hostName) + { + //set the result metadata + Result result = new Result + { + RuleId = item.PluginId, + Message = new Message { Text = item.PluginOutput }, + }; + + //set misc properties + result.SetProperty("port", item.Port); + result.SetProperty("protocol", item.Protocol); + result.SetProperty("service", item.ServiceName); + + //set solution if present + if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) + result.SetProperty("solution", item.Solution); + + //set severity (rank) + //ignore risk factor (H/M/L) as it conflicts with rank + result.Kind = ResultKind.Fail; + result.Rank = double.Parse(item.Severity); + + //vulnerable packages contain cve / cvss data + if (!string.IsNullOrEmpty(item.Cvss3BaseScore)) + { + result.SetProperty("cvss3BaseScore", item.Cvss3BaseScore); + } + + if (!string.IsNullOrEmpty(item.Cvss3TemporalScore)) + { + result.SetProperty("cvss3TemporalScore", item.Cvss3TemporalScore); + } + + if (!string.IsNullOrEmpty(item.Cvss3Vector)) + { + result.SetProperty("cvss3Vector", item.Cvss3Vector); + } + + if (!string.IsNullOrEmpty(item.Cvss3TemporalVector)) + { + result.SetProperty("cvss3TemporalVector", item.Cvss3TemporalVector); + } + + if (!string.IsNullOrEmpty(item.VulnPublicationDate)) + { + result.SetProperty("vulnPublicationDate", item.VulnPublicationDate); + } + + if (!string.IsNullOrEmpty(item.PatchPublicationDate)) + { + result.SetProperty("patchPublicationDate", item.PatchPublicationDate); + } + + if (item.Cves.Any()) + { + result.SetProperty("cve", item.Cves); + } + + if (item.Xrefs.Any()) + { + result.SetProperty("xref", item.Xrefs); + } + + if (!string.IsNullOrEmpty(item.ExploitAvailable)) + { + result.SetProperty("exploitAvailable", bool.Parse(item.ExploitAvailable)); + } + + //Set the unique fingerprint per item + var fingerprints = new List() { + item.PluginId, + item.Port, + item.Protocol, + item.ServiceName, + }; + + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(string.Join(".", fingerprints)).ToLower()); + + return result; + } + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs b/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs new file mode 100644 index 000000000..79db92f53 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/FamilyItem.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class FamilyItem + { + [XmlElement("FamilyName")] + public string Name { get; set; } = string.Empty; + + [XmlElement("Status")] + public string Status { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs b/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs new file mode 100644 index 000000000..0cfb18074 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/FamilySelection.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class FamilySelection + { + [XmlElement("FamilyItem")] + public List FamilyItems { get; set; } = new List(); + + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/HostProperties.cs b/src/Sarif.Converters/NessusObjectModel/HostProperties.cs new file mode 100644 index 000000000..ba3efcb16 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/HostProperties.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class HostProperties + { + [XmlElement("tag")] + public List Tags { get; set; } = new List(); + + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/HostTag.cs b/src/Sarif.Converters/NessusObjectModel/HostTag.cs new file mode 100644 index 000000000..d78d41923 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/HostTag.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class HostTag + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlText] + public string Value { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs b/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs new file mode 100644 index 000000000..b1ff6558d --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/IndividualPluginSelection.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class IndividualPluginSelection + { + [XmlElement("PluginItem")] + public List PluginItems { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Item.cs b/src/Sarif.Converters/NessusObjectModel/Item.cs new file mode 100644 index 000000000..16f18555e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Item.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Item + { + [XmlElement("fullName")] + public string FullName { get; set; } = string.Empty; + + [XmlElement("pluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlElement("pluginId")] + public string PluginId { get; set; } = string.Empty; + + [XmlElement("preferenceName")] + public string PreferenceName { get; set; } = string.Empty; + + [XmlElement("preferenceType")] + public string PreferenceType { get; set; } = string.Empty; + + [XmlElement("preferenceValues")] + public string PreferenceValues { get; set; } = string.Empty; + + [XmlElement("selectedValue")] + public string SelectedValue { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs b/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs new file mode 100644 index 000000000..7f0b0a96e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/NessusClientData.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + [XmlRoot("NessusClientData_v2")] + public class NessusClientData + { + [XmlElement("Policy")] + public Policy Policy { get; set; } = new Policy(); + + [XmlElement("Report")] + public Report Report { get; set; } = new Report(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/PluginItem.cs b/src/Sarif.Converters/NessusObjectModel/PluginItem.cs new file mode 100644 index 000000000..1a0df86fa --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/PluginItem.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class PluginItem + { + [XmlElement("PluginId")] + public string PluginId { get; set; } = string.Empty; + + [XmlElement("PluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlElement("Family")] + public string Family { get; set; } = string.Empty; + + [XmlElement("Status")] + public string Status { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs b/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs new file mode 100644 index 000000000..ba4fdccc4 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/PluginsPreferences.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class PluginsPreferences + { + [XmlElement("item")] + public List Items { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Policy.cs b/src/Sarif.Converters/NessusObjectModel/Policy.cs new file mode 100644 index 000000000..acf6bffd9 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Policy.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Policy + { + [XmlElement("policyName")] + public string PolicyName { get; set; } = string.Empty; + + [XmlElement("policyComments")] + public string PolicyComments { get; set; } = string.Empty; + + [XmlElement("Preferences")] + public Preferences Preferences { get; set; } = new Preferences(); + + [XmlElement("FamilySelection")] + public FamilySelection FamilySelection { get; set; } = new FamilySelection(); + + [XmlElement("IndividualPluginSelection")] + public IndividualPluginSelection IndividualPluginSelection { get; set; } = new IndividualPluginSelection(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Preference.cs b/src/Sarif.Converters/NessusObjectModel/Preference.cs new file mode 100644 index 000000000..0694c9553 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Preference.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Preference + { + [XmlElement("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("value")] + public string Value { get; set; } = string.Empty; + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Preferences.cs b/src/Sarif.Converters/NessusObjectModel/Preferences.cs new file mode 100644 index 000000000..4efead73c --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Preferences.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Preferences + { + [XmlElement("ServerPreferences")] + public ServerPreference ServerPreference { get; set; } = new ServerPreference(); + + [XmlElement("PluginsPreferences")] + public PluginsPreferences PluginsPreferences { get; set; } = new PluginsPreferences(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/Report.cs b/src/Sarif.Converters/NessusObjectModel/Report.cs new file mode 100644 index 000000000..923ba605e --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/Report.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class Report + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("ReportHost")] + public List ReportHosts { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/Sarif.Converters/NessusObjectModel/ReportHost.cs b/src/Sarif.Converters/NessusObjectModel/ReportHost.cs new file mode 100644 index 000000000..b93653323 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ReportHost.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ReportHost + { + [XmlAttribute("name")] + public string Name { get; set; } = string.Empty; + + [XmlElement("HostProperties")] + public HostProperties HostProperties { get; set; } = new HostProperties(); + + [XmlElement("ReportItem")] + public List ReportItems { get; set; } + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/ReportItem.cs b/src/Sarif.Converters/NessusObjectModel/ReportItem.cs new file mode 100644 index 000000000..017267fc2 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ReportItem.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ReportItem + { + [XmlAttribute("severity")] + public string Severity { get; set; } = string.Empty; + + [XmlAttribute("port")] + public string Port { get; set; } = string.Empty; + + [XmlAttribute("pluginFamily")] + public string PluginFamily { get; set; } = string.Empty; + + [XmlAttribute("pluginName")] + public string PluginName { get; set; } = string.Empty; + + [XmlAttribute("pluginID")] + public string PluginId { get; set; } = string.Empty; + + [XmlAttribute("protocol")] + public string Protocol { get; set; } = string.Empty; + + [XmlAttribute("svc_name")] + public string ServiceName { get; set; } = string.Empty; + + [XmlElement("plugin_modification_date")] + public string PluginModificationDate { get; set; } = string.Empty; + + [XmlElement("plugin_publication_date")] + public string PluginPublicationDate { get; set; } = string.Empty; + + [XmlElement("plugin_type")] + public string PluginType { get; set; } = string.Empty; + + [XmlElement("solution")] + public string Solution { get; set; } = string.Empty; + + [XmlElement("description")] + public string Description { get; set; } = string.Empty; + + [XmlElement("synopsis")] + public string Synopsis { get; set; } = string.Empty; + + [XmlElement("risk_factor")] + public string RiskFactor { get; set; } = string.Empty; + + [XmlElement("script_version")] + public string ScriptVersion { get; set; } = string.Empty; + + [XmlElement("plugin_output")] + public string PluginOutput { get; set; } = string.Empty; + + [XmlElement("see_also")] + public string SeeAlso { get; set; } = string.Empty; + + [XmlElement("cvss_base_score")] + public string CvssBaseScore { get; set; } = string.Empty; + + [XmlElement("cvss_temporal_score")] + public string CvssTemporalScore { get; set; } = string.Empty; + + [XmlElement("cvss3_base_score")] + public string Cvss3BaseScore { get; set; } = string.Empty; + + [XmlElement("cvss3_temporal_score")] + public string Cvss3TemporalScore { get; set; } = string.Empty; + + [XmlElement("exploit_available")] + public string ExploitAvailable { get; set; } = string.Empty; + + [XmlElement("patch_publication_date")] + public string PatchPublicationDate { get; set; } = string.Empty; + + [XmlElement("vuln_publication_date")] + public string VulnPublicationDate { get; set; } = string.Empty; + + [XmlElement("cvss3_temporal_vector")] + public string Cvss3TemporalVector { get; set; } = string.Empty; + + [XmlElement("cvss3_vector")] + public string Cvss3Vector { get; set; } = string.Empty; + + [XmlElement("cvss_temporal_vector")] + public string CvssTemporalVector { get; set; } = string.Empty; + + [XmlElement("cvss_vector")] + public string CvssVector { get; set; } = string.Empty; + + [XmlElement("cve")] + public List Cves { get; set; } = new List(); + + [XmlElement("xref")] + public List Xrefs { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs b/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs new file mode 100644 index 000000000..455c2b1b1 --- /dev/null +++ b/src/Sarif.Converters/NessusObjectModel/ServerPreference.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel +{ + public class ServerPreference + { + [XmlElement("preference")] + public List Preferences { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/ToolFileConverterBase.cs b/src/Sarif.Converters/ToolFileConverterBase.cs index e83cabfb4..4fc2020c6 100644 --- a/src/Sarif.Converters/ToolFileConverterBase.cs +++ b/src/Sarif.Converters/ToolFileConverterBase.cs @@ -105,5 +105,11 @@ protected static Run PersistResults(IResultLogWriter output, IList resul return run; } + + protected static void PersistResults(IResultLogWriter output, SarifLog log) + { + output.WriteLog(log); + return; + } } } diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index a44694fa8..fa32b00d4 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -42,6 +42,9 @@ public static class ToolFormat /// Heimdall Tools's file format. public const string Hdf = nameof(Hdf); + /// Tenable Nessus XML file format. + public const string Nessus = nameof(Nessus); + /// PREfast's file format. public const string PREfast = nameof(PREfast); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index ce9450df5..1fb406548 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, Nessus, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 00946de47..898fbfe23 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -8,6 +8,7 @@ using System.IO; using System.Security.Cryptography; using System.Text; +using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif diff --git a/src/Sarif/IResultLogWriter.cs b/src/Sarif/IResultLogWriter.cs index b6b8736b7..00f1c460a 100644 --- a/src/Sarif/IResultLogWriter.cs +++ b/src/Sarif/IResultLogWriter.cs @@ -116,5 +116,13 @@ public interface IResultLogWriter /// The invocations to write. /// void WriteInvocations(IEnumerable invocations); + + /// + /// Write a full SARIF log object to the output stream. + /// + /// + /// The SarifLog to write. + /// + void WriteLog(SarifLog log); } } diff --git a/src/Sarif/Writers/ResultLogJsonWriter.cs b/src/Sarif/Writers/ResultLogJsonWriter.cs index 9e26fdac9..cbcefcab0 100644 --- a/src/Sarif/Writers/ResultLogJsonWriter.cs +++ b/src/Sarif/Writers/ResultLogJsonWriter.cs @@ -427,5 +427,11 @@ private void EnsureResultsArrayIsNotOpen() throw new InvalidOperationException(SdkResources.ResultsSerializationNotComplete); } } + + public void WriteLog(SarifLog log) + { + _serializer.Serialize(_jsonWriter, log); + _writeConditions |= Conditions.RunCompleted; + } } } From ce1485f3fc6a6c67cee5114ed24740ee5d10a4c4 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 12 Aug 2022 09:23:37 -0500 Subject: [PATCH 20/61] bug: fingerprint targetid + empty plugin output --- src/Sarif.Converters/NessusConverter.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index 3995daf34..3f77e793f 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -123,13 +123,17 @@ internal Result CreateResult(ReportItem item, string hostName) Result result = new Result { RuleId = item.PluginId, - Message = new Message { Text = item.PluginOutput }, + Message = new Message + { + Text = string.IsNullOrWhiteSpace(item.PluginOutput.Trim()) ? item.Synopsis.Trim() : item.PluginOutput.Trim(), + }, }; //set misc properties result.SetProperty("port", item.Port); result.SetProperty("protocol", item.Protocol); result.SetProperty("service", item.ServiceName); + result.SetProperty("targetId", hostName); //set solution if present if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) @@ -188,6 +192,7 @@ internal Result CreateResult(ReportItem item, string hostName) //Set the unique fingerprint per item var fingerprints = new List() { + hostName, item.PluginId, item.Port, item.Protocol, From adcc11b296742ef26be12110911dd94eb14b2d46 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 15 Aug 2022 09:14:48 -0500 Subject: [PATCH 21/61] feat: cis cat unit tests --- src/Sarif/HashUtilities.cs | 1 - .../CisCatConverterTests.cs | 62 +++++++++++++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 12 +++- .../ExpectedOutputs/NoResults.sarif | 25 ++++++++ .../ExpectedOutputs/ValidResults.sarif | 62 +++++++++++++++++++ .../Inputs/InvalidResults.json | 8 +++ .../CisCatConverter/Inputs/NoResults.json | 9 +++ .../CisCatConverter/Inputs/ValidResults.json | 20 ++++++ .../ResultLogObjectWriter.cs | 5 ++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json diff --git a/src/Sarif/HashUtilities.cs b/src/Sarif/HashUtilities.cs index 898fbfe23..00946de47 100644 --- a/src/Sarif/HashUtilities.cs +++ b/src/Sarif/HashUtilities.cs @@ -8,7 +8,6 @@ using System.IO; using System.Security.Cryptography; using System.Text; -using System.Text; using System.Threading.Tasks; namespace Microsoft.CodeAnalysis.Sarif diff --git a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs new file mode 100644 index 000000000..3b33b9687 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class CisCatConverterTests : ConverterTestsBase + { + private static readonly string NoOutputExpected = string.Empty; + + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new CisCatConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new CisCatConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ThrowsExpectedException() + { + string input = Extractor.GetResourceInputText("InvalidResults.json"); + Action action = () => RunTestCase(input, NoOutputExpected); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(CisCatConverterTests)); + private const string ResourceNamePrefix = ToolFormat.CisCat; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index 4a994089d..e078fad95 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -46,6 +46,11 @@ + + + + + @@ -69,6 +74,11 @@ + + + + + @@ -94,4 +104,4 @@ - + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..10a400280 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [], + "tool": { + "driver": { + "name": "CisCat", + "fullName": "CIS Benchmark Title", + "version": "2.1.0", + "semanticVersion": "2.1.0", + "informationUri": "https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre", + "properties": { + "benchmarkId": "cis.benchmark.id", + "profileId": "profile.id", + "profileTitle": "profile.title", + "score": "0.0" + } + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..175458bcb --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,62 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "rule.id.2", + "level": "error", + "message": { + "text": "Rule id 2 title" + }, + "fingerprints": { + "0": "d23eea429d06cfe703ab69b8d0d0c1abd41fc9e26b4167575e3356733c97bf61" + } + } + ], + "tool": { + "driver": { + "name": "CisCat", + "fullName": "CIS Benchmark Title", + "version": "2.1.0", + "semanticVersion": "2.1.0", + "informationUri": "https://www.cisecurity.org/cybersecurity-tools/cis-cat-pro_pre", + "rules": [ + { + "id": "rule.id.1", + "name": "Rule id 1 title", + "fullDescription": { + "text": "Rule id 1 title", + "markdown": "Rule id 1 title" + }, + "help": { + "text": "Rule id 1 title", + "markdown": "Rule id 1 title" + } + }, + { + "id": "rule.id.2", + "name": "Rule id 2 title", + "fullDescription": { + "text": "Rule id 2 title", + "markdown": "Rule id 2 title" + }, + "help": { + "text": "Rule id 2 title", + "markdown": "Rule id 2 title" + } + } + ], + "properties": { + "benchmarkId": "cis.benchmark.id", + "profileId": "profile.id", + "profileTitle": "profile.title", + "score": "50.00" + } + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json new file mode 100644 index 000000000..645453a3f --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/InvalidResults.json @@ -0,0 +1,8 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "ProfileId", + "profile-title": "Title", + "score": "0.0" +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json new file mode 100644 index 000000000..e15b964ec --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/NoResults.json @@ -0,0 +1,9 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "profile.id", + "profile-title": "profile.title", + "score": "0.0", + "rules": [] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json new file mode 100644 index 000000000..c4a3bc64d --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/Inputs/ValidResults.json @@ -0,0 +1,20 @@ +{ + "benchmark-id": "cis.benchmark.id", + "benchmark-title": "CIS Benchmark Title", + "benchmark-version": "2.1.0", + "profile-id": "profile.id", + "profile-title": "profile.title", + "score": "50.00", + "rules": [ + { + "rule-id": "rule.id.1", + "rule-title": "Rule id 1 title", + "result": "pass" + }, + { + "rule-id": "rule.id.2", + "rule-title": "Rule id 2 title", + "result": "fail" + } + ] +} \ No newline at end of file diff --git a/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs b/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs index 27bb3a542..21cc4e808 100644 --- a/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs +++ b/src/Test.Utilities.Sarif/ResultLogObjectWriter.cs @@ -65,5 +65,10 @@ public void WriteRules(IList rules) { throw new NotImplementedException(); } + + public void WriteLog(SarifLog log) + { + + } } } From e327ed5ec9f6e805bc636dcb2f8a473cba06038d Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 15 Aug 2022 14:31:59 -0500 Subject: [PATCH 22/61] feat: nessus unit tests --- .../NessusConverterTests.cs | 60 +++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 12 ++ .../ExpectedOutputs/InvalidResults.sarif | 5 + .../ExpectedOutputs/NoResults.sarif | 34 +++++ .../ExpectedOutputs/ValidResults.sarif | 119 ++++++++++++++++++ .../Inputs/InvalidResults.nessus.xml | 9 ++ .../Inputs/NoResults.nessus.xml | 58 +++++++++ .../Inputs/ValidResults.nessus.xml | 86 +++++++++++++ 8 files changed, 383 insertions(+) create mode 100644 src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml diff --git a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs new file mode 100644 index 000000000..c7eb093f2 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class NessusConverterTests : ConverterTestsBase + { + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new NessusConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new NessusConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ReturnsEmptyResults() + { + string input = Extractor.GetResourceInputText("InvalidResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("InvalidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.nessus.xml"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(NessusConverterTests)); + private const string ResourceNamePrefix = ToolFormat.Nessus; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index e078fad95..cc9389ec9 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -51,6 +51,12 @@ + + + + + + @@ -79,6 +85,12 @@ + + + + + + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif new file mode 100644 index 000000000..787ce0d80 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/InvalidResults.sarif @@ -0,0 +1,5 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..9d9dfaa1e --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,34 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "properties": { + "targetId": "10.0.0.1" + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "properties": { + "targetId": "10.0.0.2" + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..86987d502 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,119 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "rules": [ + { + "id": "12345", + "name": "PluginName1", + "fullDescription": { + "text": "Description text", + "markdown": "Description text" + }, + "help": { + "text": "https://github.com/microsoft/sarif-sdk", + "markdown": "https://github.com/microsoft/sarif-sdk" + }, + "shortDescription": { + "text": "Synopsis test", + "markdown": "Synopsis test" + }, + "properties": { + "pluginFamily": "Family Name", + "pluginModificationDate": "2022/08/15", + "pluginPublicationDate": "2021/08/15", + "pluginType": "remote" + } + } + ], + "properties": { + "targetId": "10.0.0.1" + } + } + }, + "results": [ + { + "ruleId": "12345", + "message": { + "text": "plugin output text" + }, + "fingerprints": { + "0": "1e6bb36ff29ed6c40f06c82791515391820f7b546e700886adbd88b2c897279c" + }, + "rank": 1.0, + "properties": { + "port": "99999", + "protocol": "tcp", + "service": "unknown", + "targetId": "10.0.0.1" + } + } + ], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "Nessus", + "fullName": "NessusReport", + "informationUri": "https://static.tenable.com/documentation/nessus_v2_file_format.pdf", + "rules": [ + { + "id": "12345", + "name": "CVE Scan", + "fullDescription": { + "text": "description text", + "markdown": "description text" + }, + "help": { + "text": "https://github.com/microsoft/sarif-sdk", + "markdown": "https://github.com/microsoft/sarif-sdk" + }, + "shortDescription": { + "text": "Synopis text", + "markdown": "Synopis text" + }, + "properties": { + "pluginFamily": "Family Name", + "pluginModificationDate": "2022/08/15", + "pluginPublicationDate": "2021/08/15", + "pluginType": "remote" + } + } + ], + "properties": { + "targetId": "10.0.0.2" + } + } + }, + "results": [ + { + "ruleId": "12345", + "message": { + "text": "plugin output" + }, + "fingerprints": { + "0": "c630cd9cdc8784d7ecdbbbe2ae5b6e0e30609841d2df1cef336b5e646391d876" + }, + "rank": 2.0, + "properties": { + "port": "0", + "protocol": "tcp", + "service": "unknown", + "targetId": "10.0.0.2", + "solution": "Solution text or generate a proper SSL certificate for this service.", + "cvss3BaseScore": "9.0", + "cvss3Vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml new file mode 100644 index 000000000..ba9be8e83 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/InvalidResults.nessus.xml @@ -0,0 +1,9 @@ + + + + InvalidNessusConverterTest + + + + + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml new file mode 100644 index 000000000..2d18ca8ef --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/NoResults.nessus.xml @@ -0,0 +1,58 @@ + + + + NessusConverterTest + policy comments text + + + + TARGET + 10.0.0.1,10.0.0.2 + + + preference1 + value1 + + + + + PluginName1 + 12345 + Plugin Full Name 12345 + Plugin Name 12345 Short Hame + radio + None + None + + + + + + Family Name + enabled + + + + + 12345 + Selected Plugin 1 + Family Name + enabled + + + + + + + value1 + 10.0.0.1 + + + + + value1 + 10.0.0.1 + + + + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml new file mode 100644 index 000000000..0f67ecbac --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml @@ -0,0 +1,86 @@ + + + + NessusConverterTest + policy comments text + + + + TARGET + 10.0.0.1,10.0.0.2 + + + preference1 + value1 + + + + + PluginName1 + 12345 + Plugin Full Name 12345 + Plugin Name 12345 Short Hame + radio + None + None + + + + + + Family Name + enabled + + + + + 12345 + Selected Plugin 1 + Family Name + enabled + + + + + + + value1 + 10.0.0.1 + + + 2022/08/15 + 2021/08/15 + remote + n/a + Description text + Synopsis test + https://github.com/microsoft/sarif-sdk + None + 1.0 + plugin output text + + + + + value1 + 10.0.0.1 + + + 9.0 + 9.0 + 2022/08/15 + 2021/08/15 + remote + Solution text or generate a proper SSL certificate for this service. + AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N + AV:N/AC:L/AU:N/C:P/I:P/A:N + description text + Synopis text + https://github.com/microsoft/sarif-sdk + High + 1.0 + plugin output + + + + \ No newline at end of file From f1bf2681e5937f744578a22b5a2a9fd8894a0a1b Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 16 Aug 2022 18:45:53 -0500 Subject: [PATCH 23/61] bug: omit 0 severity results + cvss override --- src/Sarif.Converters/NessusConverter.cs | 46 +++++++++++++++++-- .../ExpectedOutputs/ValidResults.sarif | 8 ++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index 3f77e793f..354f9cdae 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -60,7 +60,10 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm run.Tool.Driver.Rules.Add(CreateReportDescriptor(item)); } - run.Results.Add(CreateResult(item, host.Name)); + if (item.Severity != "0") + { + run.Results.Add(CreateResult(item, host.Name)); + } } log.Runs.Add(run); @@ -139,10 +142,14 @@ internal Result CreateResult(ReportItem item, string hostName) if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) result.SetProperty("solution", item.Solution); - //set severity (rank) - //ignore risk factor (H/M/L) as it conflicts with rank + //set result level (risk rating) + //ignoring risk factor (H/M/L) as it conflicts with severity + //cvss3 base score overrides severity result.Kind = ResultKind.Fail; - result.Rank = double.Parse(item.Severity); + result.Level = getResultLevel(item.Cvss3BaseScore, item.Severity); + + //set severity value + result.SetProperty("severity", item.Severity); //vulnerable packages contain cve / cvss data if (!string.IsNullOrEmpty(item.Cvss3BaseScore)) @@ -204,5 +211,36 @@ internal Result CreateResult(ReportItem item, string hostName) return result; } + + private FailureLevel getResultLevel(string cvss3BaseScore, string severity) + { + //Failure level by cvss score + if (!string.IsNullOrWhiteSpace(cvss3BaseScore)) + { + double cvss3score = double.Parse(cvss3BaseScore); + + if (cvss3score >= 7.0) + return FailureLevel.Error; + else if (cvss3score >= 4.0 && cvss3score < 7.0) + return FailureLevel.Warning; + else if (cvss3score > 0 && cvss3score < 4.0) + return FailureLevel.Note; + else + return FailureLevel.None; + } + else + { + if (severity == "4") + return FailureLevel.Error; + else if (severity == "3") + return FailureLevel.Error; + else if (severity == "2") + return FailureLevel.Warning; + else if (severity == "1") + return FailureLevel.Note; + else + return FailureLevel.None; + } + } } } diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif index 86987d502..f9e0ce052 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -40,18 +40,19 @@ "results": [ { "ruleId": "12345", + "level": "note", "message": { "text": "plugin output text" }, "fingerprints": { "0": "1e6bb36ff29ed6c40f06c82791515391820f7b546e700886adbd88b2c897279c" }, - "rank": 1.0, "properties": { "port": "99999", "protocol": "tcp", "service": "unknown", - "targetId": "10.0.0.1" + "targetId": "10.0.0.1", + "severity": "1" } } ], @@ -95,19 +96,20 @@ "results": [ { "ruleId": "12345", + "level": "error", "message": { "text": "plugin output" }, "fingerprints": { "0": "c630cd9cdc8784d7ecdbbbe2ae5b6e0e30609841d2df1cef336b5e646391d876" }, - "rank": 2.0, "properties": { "port": "0", "protocol": "tcp", "service": "unknown", "targetId": "10.0.0.2", "solution": "Solution text or generate a proper SSL certificate for this service.", + "severity": "2", "cvss3BaseScore": "9.0", "cvss3Vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N" } From 39dd8ef34a1312160cc1c7dc33809b15397f63e3 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 18 Aug 2022 20:59:22 -0500 Subject: [PATCH 24/61] chore: ignore ds store --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a7db6359c..ba28ce38b 100644 --- a/.gitignore +++ b/.gitignore @@ -224,4 +224,5 @@ FakesAssemblies/ # LightSwitch generated files GeneratedArtifacts/ _Pvt_Extensions/ -ModelManifest.xml \ No newline at end of file +ModelManifest.xml +*.DS_Store From 9978c2f1ca0432327f5e208be19eb99166a687b8 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Tue, 23 Aug 2022 13:06:08 -0500 Subject: [PATCH 25/61] Fixed: Changes requested for #2530 --- src/Sarif/Visitors/SuppressVisitor.cs | 2 +- .../Visitors/SuppressVisitorTests.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index 6b1694ac1..156acba92 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -74,7 +74,7 @@ public override Result VisitResult(Result node) if (this.resultsGuids != null && this.resultsGuids.Any()) { - if (this.resultsGuids.Contains(node.Guid)) + if (this.resultsGuids.Contains(node.Guid, StringComparer.OrdinalIgnoreCase)) { node.Suppressions.Add(suppression); } diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index 8019d3454..e9240dafc 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -31,7 +31,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly() Timestamps = false, ExpiryInDays = 0, SuppressionStatus = SuppressionStatus.Accepted, - Guids = new List() + Guids = default(List) }, new { @@ -81,7 +81,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly() Timestamps = true, ExpiryInDays = 1, SuppressionStatus = SuppressionStatus.UnderReview, - Guids = new List() + Guids = default(List) }, new { @@ -155,6 +155,11 @@ private static void VerifySuppressVisitor(string alias, { expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(expiryInDays), DateTimeAssertPrecision); } + + if (guids != null && guids.Any()) + { + suppression.Should().Match(b => (guids.Contains(result.Guid, StringComparer.OrdinalIgnoreCase))); + } } } } From 7b9633ffcb40f0e627b131301ff6c7d03b09a720 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 23 Aug 2022 16:51:48 -0500 Subject: [PATCH 26/61] bug: validate result guid values are not whitespace --- src/Sarif.Multitool.Library/SuppressCommand.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index b9849a70e..2b9c20e63 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -44,9 +44,9 @@ public int Run(SuppressOptions options) if (!string.IsNullOrWhiteSpace(options.Expression)) { var expressionGuids = ReturnQueryExpressionGuids(options); - if (options.ResultsGuids != null && options.ResultsGuids.Any()) + if (options.ResultsGuids != null && options.ResultsGuids.Where(i => !string.IsNullOrWhiteSpace(i)).Count() > 0) { - options.ResultsGuids = expressionGuids.Union(options.ResultsGuids); + options.ResultsGuids = expressionGuids.Union(options.ResultsGuids.Where(i => !string.IsNullOrWhiteSpace(i))); } else { @@ -62,7 +62,8 @@ public int Run(SuppressOptions options) Console.WriteLine($"{result}"); } #endif - } else + } + else { Console.WriteLine($"Suppressing {currentSarifLog.Runs.Sum(i => i.Results.Count)} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); } From 2db4ef0821d62219bbb7b2c2a1e433bae1c6535f Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 23 Aug 2022 16:51:48 -0500 Subject: [PATCH 27/61] bug: validate result guid values are not whitespace --- src/Sarif.Multitool.Library/SuppressCommand.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index b9849a70e..2b9c20e63 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -44,9 +44,9 @@ public int Run(SuppressOptions options) if (!string.IsNullOrWhiteSpace(options.Expression)) { var expressionGuids = ReturnQueryExpressionGuids(options); - if (options.ResultsGuids != null && options.ResultsGuids.Any()) + if (options.ResultsGuids != null && options.ResultsGuids.Where(i => !string.IsNullOrWhiteSpace(i)).Count() > 0) { - options.ResultsGuids = expressionGuids.Union(options.ResultsGuids); + options.ResultsGuids = expressionGuids.Union(options.ResultsGuids.Where(i => !string.IsNullOrWhiteSpace(i))); } else { @@ -62,7 +62,8 @@ public int Run(SuppressOptions options) Console.WriteLine($"{result}"); } #endif - } else + } + else { Console.WriteLine($"Suppressing {currentSarifLog.Runs.Sum(i => i.Results.Count)} of {currentSarifLog.Runs.Sum(i => i.Results.Count)} results."); } From 717ae81ab6a254379c823394dc644b488c0b8cb8 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 24 Aug 2022 14:53:37 -0500 Subject: [PATCH 28/61] bug: cis cat set unknown status to warning --- src/Sarif.Converters/CisCatConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index a0fccab5f..e9044efbc 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -132,8 +132,8 @@ internal Result CreateResult(CisCatRule rule) break; case "unknown": default: - result.Level = FailureLevel.None; - result.Kind = ResultKind.Review; + result.Level = FailureLevel.Warning; + result.Kind = ResultKind.Fail; break; }; From 80a03d0df990fe4bebccda2be74dc6fcf7c10c07 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 24 Aug 2022 14:53:37 -0500 Subject: [PATCH 29/61] bug: cis cat set unknown status to warning --- src/Sarif.Converters/CisCatConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index a0fccab5f..e9044efbc 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -132,8 +132,8 @@ internal Result CreateResult(CisCatRule rule) break; case "unknown": default: - result.Level = FailureLevel.None; - result.Kind = ResultKind.Review; + result.Level = FailureLevel.Warning; + result.Kind = ResultKind.Fail; break; }; From ee26de3315ba5a9716ca3c8a6375a1c11f3d7e97 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Thu, 25 Aug 2022 15:01:31 -0500 Subject: [PATCH 30/61] Feature: Add GenericSarifConvter to meet validation --- .../BuiltInConverterFactory.cs | 1 + src/Sarif.Converters/GenericSarifConverter.cs | 59 +++++++++++++ src/Sarif.Converters/ToolFormat.cs | 5 +- .../GenericSarifConverterTests.cs | 76 +++++++++++++++++ .../Test.UnitTests.Sarif.Converters.csproj | 20 +++++ .../ExpectedOutput/HelpNoMarkdown.sarif | 82 +++++++++++++++++++ .../ExpectedOutput/HelpNoText.sarif | 82 +++++++++++++++++++ .../ExpectedOutput/NoHelp.sarif | 78 ++++++++++++++++++ .../ExpectedOutput/NoResults.sarif | 32 ++++++++ .../ExpectedOutput/ValidResults.sarif | 82 +++++++++++++++++++ .../Inputs/HelpNoMarkdown.GenericSarif.sarif | 82 +++++++++++++++++++ .../Inputs/HelpNoText.GenericSarif.sarif | 82 +++++++++++++++++++ .../Inputs/NoHelp.GenericSarif.sarif | 78 ++++++++++++++++++ .../Inputs/NoResults.GenericSarif.sarif | 32 ++++++++ .../Inputs/ValidResults.GenericSarif.sarif | 82 +++++++++++++++++++ .../MergeCommandUnitTests.cs | 2 +- 16 files changed, 873 insertions(+), 2 deletions(-) create mode 100644 src/Sarif.Converters/GenericSarifConverter.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/GenericSarifConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoMarkdown.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoText.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoHelp.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoMarkdown.GenericSarif.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoText.GenericSarif.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoHelp.GenericSarif.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoResults.GenericSarif.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/ValidResults.GenericSarif.sarif diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 1c1bc09b7..ed22e0e63 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -34,6 +34,7 @@ private static Dictionary> CreateBuiltInConv CreateConverterRecord(result, ToolFormat.FortifyFpr); CreateConverterRecord(result, ToolFormat.FxCop); CreateConverterRecord(result, ToolFormat.FlawFinder); + CreateConverterRecord(result, ToolFormat.GenericSarif); CreateConverterRecord(result, ToolFormat.Hdf); CreateConverterRecord(result, ToolFormat.Nessus); CreateConverterRecord(result, ToolFormat.PREfast); diff --git a/src/Sarif.Converters/GenericSarifConverter.cs b/src/Sarif.Converters/GenericSarifConverter.cs new file mode 100644 index 000000000..31ae3f31b --- /dev/null +++ b/src/Sarif.Converters/GenericSarifConverter.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Serialization; + +using Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class GenericSarifConverter : ToolFileConverterBase + { + public override string ToolName => ToolFormat.GenericSarif; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + var serializer = new JsonSerializer() { }; + + using (JsonTextReader reader = new JsonTextReader(new StreamReader(input))) + { + var log = serializer.Deserialize(reader); + + foreach (var run in log.Runs) + { + if (run?.Tool?.Driver?.Rules?.Count > 0) + { + foreach (var rule in run.Tool.Driver.Rules) + { + if (rule.Help != null) + { + if (!string.IsNullOrWhiteSpace(rule.Help.Markdown) && string.IsNullOrWhiteSpace(rule.Help.Text)) + { + rule.Help.Text = rule.Help.Markdown; + } + else if (!string.IsNullOrWhiteSpace(rule.Help.Text) && string.IsNullOrWhiteSpace(rule.Help.Markdown)) + { + rule.Help.Markdown = rule.Help.Text; + } + } + + } + } + } + + PersistResults(output, log); + } + } + } +} diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index fa32b00d4..c33919146 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -39,6 +39,9 @@ public static class ToolFormat /// FxCop's file format. public const string FxCop = nameof(FxCop); + // Generic sarif format + public const string GenericSarif = nameof(GenericSarif); + /// Heimdall Tools's file format. public const string Hdf = nameof(Hdf); @@ -61,6 +64,6 @@ public static class ToolFormat public const string TSLint = nameof(TSLint); /// MSBuild's build log format, from which diagnostic messages can be parsed. - public const string MSBuild = nameof(MSBuild); + public const string MSBuild = nameof(MSBuild); } } diff --git a/src/Test.UnitTests.Sarif.Converters/GenericSarifConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/GenericSarifConverterTests.cs new file mode 100644 index 000000000..6582c8e10 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/GenericSarifConverterTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; +using FluentAssertions; +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class GenericSarifConverterTests : ConverterTestsBase + { + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new GenericSarifConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new GenericSarifConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.GenericSarif.sarif"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainingNoHelp_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoHelp.GenericSarif.sarif"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoHelp.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.GenericSarif.sarif"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsHelpWithoutText_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("HelpNoText.GenericSarif.sarif"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("HelpNoText.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsHelpWithoutMarkdown_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("HelpNoMarkdown.GenericSarif.sarif"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("HelpNoMarkdown.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(GenericSarifConverterTests)); + private const string ResourceNamePrefix = ToolFormat.GenericSarif; + } +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index f8bcd6c96..5c8341306 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -22,6 +22,16 @@ + + + + + + + + + + @@ -91,6 +101,16 @@ + + + + + + + + + + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoMarkdown.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoMarkdown.sarif new file mode 100644 index 000000000..058ad1972 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoMarkdown.sarif @@ -0,0 +1,82 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "help": { + "markdown": "test", + "text": "test" + }, + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoText.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoText.sarif new file mode 100644 index 000000000..d8c99aa9b --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoText.sarif @@ -0,0 +1,82 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "help": { + "markdown": "Markdown text", + "text": "Markdown text" + }, + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoHelp.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoHelp.sarif new file mode 100644 index 000000000..22602b6b2 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoHelp.sarif @@ -0,0 +1,78 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoResults.sarif new file mode 100644 index 000000000..91a17a053 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoResults.sarif @@ -0,0 +1,32 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "GenericSarif", + "fullName": "Generic Sarif Report", + "informationUri": "https://info", + "properties": { + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "GenericSarif", + "fullName": "Generic Sarif Report", + "informationUri": "https://info", + "properties": { + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/ValidResults.sarif new file mode 100644 index 000000000..7d7c8cbc7 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/ValidResults.sarif @@ -0,0 +1,82 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "help": { + "markdown": "Markdown text", + "text": "text" + }, + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoMarkdown.GenericSarif.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoMarkdown.GenericSarif.sarif new file mode 100644 index 000000000..b730f50b5 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoMarkdown.GenericSarif.sarif @@ -0,0 +1,82 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "help": { + "markdown": "", + "text": "test" + }, + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoText.GenericSarif.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoText.GenericSarif.sarif new file mode 100644 index 000000000..de16d14fd --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/HelpNoText.GenericSarif.sarif @@ -0,0 +1,82 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "help": { + "markdown": "Markdown text", + "text": "" + }, + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoHelp.GenericSarif.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoHelp.GenericSarif.sarif new file mode 100644 index 000000000..22602b6b2 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoHelp.GenericSarif.sarif @@ -0,0 +1,78 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoResults.GenericSarif.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoResults.GenericSarif.sarif new file mode 100644 index 000000000..73301b0e1 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/NoResults.GenericSarif.sarif @@ -0,0 +1,32 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "GenericSarif", + "fullName": "Generic Sarif Report", + "informationUri": "https://info", + "properties": { + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + }, + { + "tool": { + "driver": { + "name": "GenericSarif", + "fullName": "Generic Sarif Report", + "informationUri": "https://info", + "properties": { + } + } + }, + "results": [], + "columnKind": "utf16CodeUnits" + } + ] + } \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/ValidResults.GenericSarif.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/ValidResults.GenericSarif.sarif new file mode 100644 index 000000000..7d7c8cbc7 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/Inputs/ValidResults.GenericSarif.sarif @@ -0,0 +1,82 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SARIF2002", + "ruleIndex": 0, + "level": "error", + "message": { + "id": "Note_Default", + "arguments": [ + "runs[0].results[0].message" + ] + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif", + "index": 0 + }, + "region": { + "startLine": 31, + "startColumn": 22 + } + } + } + ] + } + ], + "tool": { + "driver": { + "name": "testhost", + "organization": "Microsoft Corporation", + "product": "Microsoft.TestHost", + "fullName": "testhost 15.0.0.0", + "version": "15.0.0.0", + "semanticVersion": "15.0.0", + "rules": [ + { + "id": "SARIF2002", + "fullDescription": { + "text": "In result messages, use the 'message.id' and 'message.arguments' properties rather than 'message.text'. This has several advantages. If 'text' is lengthy, using 'id' and 'arguments' makes the SARIF file smaller. If the rule metadata is stored externally to the SARIF log file, the message text can be improved (for example, by adding more text, clarifying the phrasing, or fixing typos), and the result messages will pick up the improvements the next time it is displayed. Finally, SARIF supports localizing messages into different languages, which is possible if the SARIF file contains 'message.id' and 'message.arguments', but not if it contains 'message.text' directly." + }, + "helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html", + "help": { + "markdown": "Markdown text", + "text": "text" + }, + "messageStrings": { + "Note_Default": { + "text": "{0}: The 'message' property of this result contains a 'text' property. Consider replacing it with 'id' and 'arguments' properties. This potentially reduces the log file size, allows the message text to be improved without modifying the log file, and enables localization." + } + }, + "name": "ProvideMessageArguments", + "defaultConfiguration": { + "level": "error" + } + } + ] + } + }, + "invocations": [ + { + "startTimeUtc": "2021-07-15T00:44:48.223Z", + "endTimeUtc": "2021-07-15T00:44:50.817Z", + "executionSuccessful": true + } + ], + "artifacts": [ + { + "location": { + "uri": "file:///C:/repo/Github.com/sarif-sdk/bld/bin/AnyCPU_Debug/Test.UnitTests.Sarif.Multitool.Library/netcoreapp3.1/ValidateSarif.sarif" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs index 607405459..5da9879cc 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs @@ -296,7 +296,7 @@ private Run CreateTestRun(int numberOfResult, bool createSubRule = false, string { string ruleId = createSubRule ? $"TESTRULE/00{i}" : $"TESTRULE00{i}"; run.Results.AddRange( - RandomSarifLogGenerator.GenerateFakeResults(this.random, new List { ruleId }, new List { artifactUri }, 1)); + RandomSarifLogGenerator.GenerateFakeResults(this.random, new List { ruleId }, new List { }, new List { artifactUri }, 1)); } return run; From 2d69e797099460f8c903dbb54bf2e76e8bf46a74 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Thu, 25 Aug 2022 15:24:07 -0500 Subject: [PATCH 31/61] test: GenericSarifConverter unit tests --- .../Test.UnitTests.Sarif.Converters.csproj | 20 +++++++++---------- .../HelpNoMarkdown.sarif | 0 .../HelpNoText.sarif | 0 .../NoHelp.sarif | 0 .../NoResults.sarif | 0 .../ValidResults.sarif | 0 6 files changed, 10 insertions(+), 10 deletions(-) rename src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/{ExpectedOutput => ExpectedOutputs}/HelpNoMarkdown.sarif (100%) rename src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/{ExpectedOutput => ExpectedOutputs}/HelpNoText.sarif (100%) rename src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/{ExpectedOutput => ExpectedOutputs}/NoHelp.sarif (100%) rename src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/{ExpectedOutput => ExpectedOutputs}/NoResults.sarif (100%) rename src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/{ExpectedOutput => ExpectedOutputs}/ValidResults.sarif (100%) diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index 5c8341306..877095644 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -22,11 +22,11 @@ - - - - - + + + + + @@ -101,11 +101,11 @@ - - - - - + + + + + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoMarkdown.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/HelpNoMarkdown.sarif similarity index 100% rename from src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoMarkdown.sarif rename to src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/HelpNoMarkdown.sarif diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoText.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/HelpNoText.sarif similarity index 100% rename from src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/HelpNoText.sarif rename to src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/HelpNoText.sarif diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoHelp.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/NoHelp.sarif similarity index 100% rename from src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoHelp.sarif rename to src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/NoHelp.sarif diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/NoResults.sarif similarity index 100% rename from src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/NoResults.sarif rename to src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/NoResults.sarif diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/ValidResults.sarif similarity index 100% rename from src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutput/ValidResults.sarif rename to src/Test.UnitTests.Sarif.Converters/TestData/GenericSarifConverter/ExpectedOutputs/ValidResults.sarif From e642f68ae39d43e8b079b38d6ee4da02559e2bb9 Mon Sep 17 00:00:00 2001 From: Andrew Guggenberger Date: Thu, 25 Aug 2022 15:57:31 -0500 Subject: [PATCH 32/61] chore: cleaned up code --- src/Sarif.Converters/GenericSarifConverter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Sarif.Converters/GenericSarifConverter.cs b/src/Sarif.Converters/GenericSarifConverter.cs index 31ae3f31b..a7bb74410 100644 --- a/src/Sarif.Converters/GenericSarifConverter.cs +++ b/src/Sarif.Converters/GenericSarifConverter.cs @@ -8,8 +8,6 @@ using System.Xml; using System.Xml.Serialization; -using Microsoft.CodeAnalysis.Sarif.Converters.NessusObjectModel; - using Newtonsoft.Json; using Newtonsoft.Json.Serialization; From 65c654be10662a200fb1baab8d4b4c8d6289ae62 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 29 Aug 2022 16:52:41 -0500 Subject: [PATCH 33/61] feat: set rank for critical capability --- src/Sarif.Converters/CisCatConverter.cs | 4 ++ src/Sarif.Converters/NessusConverter.cs | 62 +++++++++++++++++++------ src/Sarif.Converters/RankConstants.cs | 14 ++++++ 3 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 src/Sarif.Converters/RankConstants.cs diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index e9044efbc..07b3cac28 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -121,19 +121,23 @@ internal Result CreateResult(CisCatRule rule) case "fail": result.Level = FailureLevel.Error; result.Kind = ResultKind.Fail; + result.Rank = RankConstants.High; break; case "notchecked": result.Level = FailureLevel.None; result.Kind = ResultKind.NotApplicable; + result.Rank = RankConstants.None; break; case "informational": result.Level = FailureLevel.None; result.Kind = ResultKind.Informational; + result.Rank = RankConstants.None; break; case "unknown": default: result.Level = FailureLevel.Warning; result.Kind = ResultKind.Fail; + result.Rank = RankConstants.Medium; break; }; diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index 354f9cdae..cf4da5e32 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -142,11 +142,16 @@ internal Result CreateResult(ReportItem item, string hostName) if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) result.SetProperty("solution", item.Solution); - //set result level (risk rating) + //set result level and rank (Critical - Low risk rating) //ignoring risk factor (H/M/L) as it conflicts with severity //cvss3 base score overrides severity + FailureLevel level = FailureLevel.None; + double rank = RankConstants.None; + getResultSeverity(item.Cvss3BaseScore, item.Severity, out level, out rank); + result.Kind = ResultKind.Fail; - result.Level = getResultLevel(item.Cvss3BaseScore, item.Severity); + result.Level = level; + result.Rank = rank; //set severity value result.SetProperty("severity", item.Severity); @@ -212,34 +217,61 @@ internal Result CreateResult(ReportItem item, string hostName) return result; } - private FailureLevel getResultLevel(string cvss3BaseScore, string severity) + private void getResultSeverity(string cvss3BaseScore, string severity, out FailureLevel level, out double rank) { + // Default values + level = FailureLevel.None; + rank = RankConstants.None; + //Failure level by cvss score if (!string.IsNullOrWhiteSpace(cvss3BaseScore)) { double cvss3score = double.Parse(cvss3BaseScore); - if (cvss3score >= 7.0) - return FailureLevel.Error; + if (cvss3score >= 9.0) + { + level = FailureLevel.Error; + rank = cvss3score; + } + if (cvss3score >= 7.0 && cvss3score < 9.0) + { + level = FailureLevel.Error; + rank = cvss3score; + } else if (cvss3score >= 4.0 && cvss3score < 7.0) - return FailureLevel.Warning; + { + level = FailureLevel.Warning; + rank = cvss3score; + + } else if (cvss3score > 0 && cvss3score < 4.0) - return FailureLevel.Note; - else - return FailureLevel.None; + { + level = FailureLevel.Note; + rank = cvss3score; + } } else { if (severity == "4") - return FailureLevel.Error; + { + level = FailureLevel.Error; + rank = RankConstants.Critical; + } else if (severity == "3") - return FailureLevel.Error; + { + level = FailureLevel.Error; + rank = RankConstants.High; + } else if (severity == "2") - return FailureLevel.Warning; + { + level = FailureLevel.Warning; + rank = RankConstants.Medium; + } else if (severity == "1") - return FailureLevel.Note; - else - return FailureLevel.None; + { + level = FailureLevel.Note; + rank = RankConstants.Low; + } } } } diff --git a/src/Sarif.Converters/RankConstants.cs b/src/Sarif.Converters/RankConstants.cs new file mode 100644 index 000000000..a18e9e9a1 --- /dev/null +++ b/src/Sarif.Converters/RankConstants.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public static class RankConstants + { + public const double Critical = 10.0; + public const double High = 8.0; + public const double Medium = 6.0; + public const double Low = 3.0; + public const double None = -1.0; + } +} \ No newline at end of file From f50d46b364e3b54bfa40cc797aa5caf34fef7089 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 29 Aug 2022 17:32:55 -0500 Subject: [PATCH 34/61] chore: update test cases --- .../TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif | 1 + .../TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif index 175458bcb..b025a8d93 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif @@ -7,6 +7,7 @@ { "ruleId": "rule.id.2", "level": "error", + "rank": 8.0, "message": { "text": "Rule id 2 title" }, diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif index f9e0ce052..5f41dff1a 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -41,6 +41,7 @@ { "ruleId": "12345", "level": "note", + "rank": 3.0, "message": { "text": "plugin output text" }, @@ -97,6 +98,7 @@ { "ruleId": "12345", "level": "error", + "rank": 9.0, "message": { "text": "plugin output" }, From 982b1563acdf61024e34aed07798747196bbe343 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 29 Aug 2022 16:52:41 -0500 Subject: [PATCH 35/61] feat: set rank for critical capability --- src/Sarif.Converters/CisCatConverter.cs | 4 ++ src/Sarif.Converters/NessusConverter.cs | 62 +++++++++++++++++++------ src/Sarif.Converters/RankConstants.cs | 14 ++++++ 3 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 src/Sarif.Converters/RankConstants.cs diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index e9044efbc..07b3cac28 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -121,19 +121,23 @@ internal Result CreateResult(CisCatRule rule) case "fail": result.Level = FailureLevel.Error; result.Kind = ResultKind.Fail; + result.Rank = RankConstants.High; break; case "notchecked": result.Level = FailureLevel.None; result.Kind = ResultKind.NotApplicable; + result.Rank = RankConstants.None; break; case "informational": result.Level = FailureLevel.None; result.Kind = ResultKind.Informational; + result.Rank = RankConstants.None; break; case "unknown": default: result.Level = FailureLevel.Warning; result.Kind = ResultKind.Fail; + result.Rank = RankConstants.Medium; break; }; diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index 354f9cdae..cf4da5e32 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -142,11 +142,16 @@ internal Result CreateResult(ReportItem item, string hostName) if (!string.IsNullOrWhiteSpace(item.Solution) && !item.Solution.Equals("n/a")) result.SetProperty("solution", item.Solution); - //set result level (risk rating) + //set result level and rank (Critical - Low risk rating) //ignoring risk factor (H/M/L) as it conflicts with severity //cvss3 base score overrides severity + FailureLevel level = FailureLevel.None; + double rank = RankConstants.None; + getResultSeverity(item.Cvss3BaseScore, item.Severity, out level, out rank); + result.Kind = ResultKind.Fail; - result.Level = getResultLevel(item.Cvss3BaseScore, item.Severity); + result.Level = level; + result.Rank = rank; //set severity value result.SetProperty("severity", item.Severity); @@ -212,34 +217,61 @@ internal Result CreateResult(ReportItem item, string hostName) return result; } - private FailureLevel getResultLevel(string cvss3BaseScore, string severity) + private void getResultSeverity(string cvss3BaseScore, string severity, out FailureLevel level, out double rank) { + // Default values + level = FailureLevel.None; + rank = RankConstants.None; + //Failure level by cvss score if (!string.IsNullOrWhiteSpace(cvss3BaseScore)) { double cvss3score = double.Parse(cvss3BaseScore); - if (cvss3score >= 7.0) - return FailureLevel.Error; + if (cvss3score >= 9.0) + { + level = FailureLevel.Error; + rank = cvss3score; + } + if (cvss3score >= 7.0 && cvss3score < 9.0) + { + level = FailureLevel.Error; + rank = cvss3score; + } else if (cvss3score >= 4.0 && cvss3score < 7.0) - return FailureLevel.Warning; + { + level = FailureLevel.Warning; + rank = cvss3score; + + } else if (cvss3score > 0 && cvss3score < 4.0) - return FailureLevel.Note; - else - return FailureLevel.None; + { + level = FailureLevel.Note; + rank = cvss3score; + } } else { if (severity == "4") - return FailureLevel.Error; + { + level = FailureLevel.Error; + rank = RankConstants.Critical; + } else if (severity == "3") - return FailureLevel.Error; + { + level = FailureLevel.Error; + rank = RankConstants.High; + } else if (severity == "2") - return FailureLevel.Warning; + { + level = FailureLevel.Warning; + rank = RankConstants.Medium; + } else if (severity == "1") - return FailureLevel.Note; - else - return FailureLevel.None; + { + level = FailureLevel.Note; + rank = RankConstants.Low; + } } } } diff --git a/src/Sarif.Converters/RankConstants.cs b/src/Sarif.Converters/RankConstants.cs new file mode 100644 index 000000000..a18e9e9a1 --- /dev/null +++ b/src/Sarif.Converters/RankConstants.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public static class RankConstants + { + public const double Critical = 10.0; + public const double High = 8.0; + public const double Medium = 6.0; + public const double Low = 3.0; + public const double None = -1.0; + } +} \ No newline at end of file From da4c7355a9467d60e1cebb1ac8afbde5d13dbc16 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 29 Aug 2022 17:32:55 -0500 Subject: [PATCH 36/61] chore: update test cases --- .../TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif | 1 + .../TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif index 175458bcb..b025a8d93 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif @@ -7,6 +7,7 @@ { "ruleId": "rule.id.2", "level": "error", + "rank": 8.0, "message": { "text": "Rule id 2 title" }, diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif index f9e0ce052..5f41dff1a 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -41,6 +41,7 @@ { "ruleId": "12345", "level": "note", + "rank": 3.0, "message": { "text": "plugin output text" }, @@ -97,6 +98,7 @@ { "ruleId": "12345", "level": "error", + "rank": 9.0, "message": { "text": "plugin output" }, From bd9004ee03b81d33169b5d1bd98428bfcbd65f0c Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 5 Sep 2022 08:56:28 -0500 Subject: [PATCH 37/61] bug: empty search suppresses all results --- src/Sarif/Visitors/SuppressVisitor.cs | 6 +- .../Visitors/SuppressVisitorTests.cs | 119 +++++------------- 2 files changed, 36 insertions(+), 89 deletions(-) diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index 156acba92..48474c9c1 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -45,6 +45,8 @@ public override Result VisitResult(Result node) node.Suppressions = new List(); } + + var suppression = new Suppression { Status = suppressionStatus, @@ -72,7 +74,7 @@ public override Result VisitResult(Result node) suppression.SetProperty(nameof(expiryUtc), expiryUtc); } - if (this.resultsGuids != null && this.resultsGuids.Any()) + if (this.resultsGuids != null) { if (this.resultsGuids.Contains(node.Guid, StringComparer.OrdinalIgnoreCase)) { @@ -83,7 +85,7 @@ public override Result VisitResult(Result node) { node.Suppressions.Add(suppression); } - + return base.VisitResult(node); } } diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index e9240dafc..c0cfff6dd 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -17,94 +17,32 @@ public class SuppressVisitorTests { private const int DateTimeAssertPrecision = 500; - [Fact] - public void SuppressVisitor_ShouldFlowPropertiesCorrectly() + [Theory] + [InlineData("", "some suppress justification", false, false, 0, SuppressionStatus.Accepted, null)] + [InlineData("some alias", "some suppress justification", false, false, 0, SuppressionStatus.Accepted, null)] + [InlineData("some alias", "some suppress justification", true, false, 0, SuppressionStatus.Accepted, null)] + [InlineData("some alias", "some suppress justification", true, true, 0, SuppressionStatus.Accepted, null)] + [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.Accepted, null)] + [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.UnderReview, null)] + [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.Accepted, new object[] { new string[] { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" } })] + [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.Accepted, new object[] { new string[] { } })] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly(string alias, string justification, bool uuids, bool timestamps, int expiryInDays, SuppressionStatus suppressionStatus, params object[] resultsGuids) { - var guids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }; - var testCases = new[] + List guids = default(List); + if (resultsGuids != null) { - new - { - Alias = string.Empty, - Justification = "some suppress justification", - Uuids = false, - Timestamps = false, - ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted, - Guids = default(List) - }, - new - { - Alias = "some alias", - Justification = "some suppress justification", - Uuids = false, - Timestamps = false, - ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted, - Guids = new List() - }, - new - { - Alias = "some alias", - Justification = "some suppress justification", - Uuids = true, - Timestamps = false, - ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted, - Guids = new List() - }, - new - { - Alias = "some alias", - Justification = "some suppress justification", - Uuids = true, - Timestamps = true, - ExpiryInDays = 0, - SuppressionStatus = SuppressionStatus.Accepted, - Guids = new List() - }, - new - { - Alias = "some alias", - Justification = "some suppress justification", - Uuids = true, - Timestamps = true, - ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.Accepted, - Guids = new List() - }, - new - { - Alias = "some alias", - Justification = "some suppress justification", - Uuids = true, - Timestamps = true, - ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.UnderReview, - Guids = default(List) - }, - new - { - Alias = "some alias", - Justification = "some suppress justification", - Uuids = true, - Timestamps = true, - ExpiryInDays = 1, - SuppressionStatus = SuppressionStatus.Accepted, - Guids = guids.ToList() - }, - }; - - foreach (var testCase in testCases) - { - VerifySuppressVisitor(testCase.Alias, - testCase.Justification, - testCase.Uuids, - testCase.Timestamps, - testCase.ExpiryInDays, - testCase.SuppressionStatus, - testCase.Guids); + guids = new List(); + string[] items = resultsGuids.First() as string[]; + guids.AddRange(items); } + + VerifySuppressVisitor(alias, + justification, + uuids, + timestamps, + expiryInDays, + suppressionStatus, + guids); } private static void VerifySuppressVisitor(string alias, @@ -120,7 +58,7 @@ private static void VerifySuppressVisitor(string alias, uuids, timestamps, expiryInDays, - suppressionStatus, + suppressionStatus, guids); var random = new Random(); @@ -129,6 +67,13 @@ private static void VerifySuppressVisitor(string alias, IList results = suppressed.Runs[0].Results; foreach (Result result in results) { + //Suppressions will not exist if guids is an empty search + if (guids != null && !guids.Any()) + { + result.Suppressions.Should().BeNullOrEmpty(); + return; + } + result.Suppressions.Should().NotBeNullOrEmpty(); Suppression suppression = result.Suppressions[0]; @@ -156,7 +101,7 @@ private static void VerifySuppressVisitor(string alias, expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(expiryInDays), DateTimeAssertPrecision); } - if (guids != null && guids.Any()) + if (guids != null) { suppression.Should().Match(b => (guids.Contains(result.Guid, StringComparer.OrdinalIgnoreCase))); } From 63504c17041ba626356cadf1c1072956892ae322 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Mon, 5 Sep 2022 09:30:33 -0500 Subject: [PATCH 38/61] bug: skip duplicate suppression entries --- src/Sarif/Visitors/SuppressVisitor.cs | 7 ++- .../Visitors/SuppressVisitorTests.cs | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index 48474c9c1..71ea55db1 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -45,7 +45,12 @@ public override Result VisitResult(Result node) node.Suppressions = new List(); } - + // Skip if node is already suppressed + bool isSuppressed = false; + if (node.TryIsSuppressed(out isSuppressed) && isSuppressed) + { + return base.VisitResult(node); + } var suppression = new Suppression { diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index c0cfff6dd..ad819456d 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -45,6 +45,52 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly(string alias, string j guids); } + [Theory] + [InlineData("some alias", "some suppress justification", true, false, 0, SuppressionStatus.Accepted, null)] + public void SuppressVisitor_ShouldNotDuplicateEntries(string alias, string justification, bool uuids, bool timestamps, int expiryInDays, SuppressionStatus suppressionStatus, params object[] resultsGuids) + { + var random = new Random(); + SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); + + //First suppression entry + List guids = default(List); + if (resultsGuids != null) + { + guids = new List(); + string[] items = resultsGuids.First() as string[]; + guids.AddRange(items); + } + + var visitor = new SuppressVisitor(justification, + alias, + uuids, + timestamps, + expiryInDays, + suppressionStatus, + guids); + + SarifLog suppressed = visitor.VisitSarifLog(current); + IList results = suppressed.Runs[0].Results; + + // Verify suppression was added + foreach (Result result in results) + { + result.Suppressions.Should().NotBeNullOrEmpty(); + result.Suppressions.Count.Should().Be(1); + } + + // Suppress a second time + SarifLog suppressed2 = visitor.VisitSarifLog(suppressed); + + // Verify duplicate suppression was not added + foreach (Result result in results) + { + result.Suppressions.Should().NotBeNullOrEmpty(); + result.Suppressions.Count.Should().Be(1); + } + + } + private static void VerifySuppressVisitor(string alias, string justification, bool uuids, From ebd7327d7eb0f14b59dbcb10d446620c7d243e5f Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 6 Sep 2022 20:58:28 -0500 Subject: [PATCH 39/61] bug: suppress visitor multiple test cases --- .../Visitors/SuppressVisitorTests.cs | 241 +++++++++++++++--- 1 file changed, 199 insertions(+), 42 deletions(-) diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index ad819456d..a59492dfa 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -17,57 +17,214 @@ public class SuppressVisitorTests { private const int DateTimeAssertPrecision = 500; - [Theory] - [InlineData("", "some suppress justification", false, false, 0, SuppressionStatus.Accepted, null)] - [InlineData("some alias", "some suppress justification", false, false, 0, SuppressionStatus.Accepted, null)] - [InlineData("some alias", "some suppress justification", true, false, 0, SuppressionStatus.Accepted, null)] - [InlineData("some alias", "some suppress justification", true, true, 0, SuppressionStatus.Accepted, null)] - [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.Accepted, null)] - [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.UnderReview, null)] - [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.Accepted, new object[] { new string[] { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" } })] - [InlineData("some alias", "some suppress justification", true, true, 1, SuppressionStatus.Accepted, new object[] { new string[] { } })] - public void SuppressVisitor_ShouldFlowPropertiesCorrectly(string alias, string justification, bool uuids, bool timestamps, int expiryInDays, SuppressionStatus suppressionStatus, params object[] resultsGuids) + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_EmptyAlias() { - List guids = default(List); - if (resultsGuids != null) + var testCase = new { - guids = new List(); - string[] items = resultsGuids.First() as string[]; - guids.AddRange(items); - } + alias = string.Empty, + justification = "some suppress justification", + uuids = false, + timestamps = false, + expiryInDays = 0, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; - VerifySuppressVisitor(alias, - justification, - uuids, - timestamps, - expiryInDays, - suppressionStatus, - guids); + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); } - [Theory] - [InlineData("some alias", "some suppress justification", true, false, 0, SuppressionStatus.Accepted, null)] - public void SuppressVisitor_ShouldNotDuplicateEntries(string alias, string justification, bool uuids, bool timestamps, int expiryInDays, SuppressionStatus suppressionStatus, params object[] resultsGuids) + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Alias() { - var random = new Random(); - SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = false, + timestamps = false, + expiryInDays = 0, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } - //First suppression entry - List guids = default(List); - if (resultsGuids != null) + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Uuids() + { + var testCase = new { - guids = new List(); - string[] items = resultsGuids.First() as string[]; - guids.AddRange(items); - } + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = false, + expiryInDays = 0, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; - var visitor = new SuppressVisitor(justification, - alias, - uuids, - timestamps, - expiryInDays, - suppressionStatus, - guids); + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Timestamps() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = true, + expiryInDays = 0, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Expiry() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = true, + expiryInDays = 1, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_UnderReview() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = true, + expiryInDays = 1, + suppressionStatus = SuppressionStatus.UnderReview, + resultGuids = default(List), + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Result_Guids() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = true, + expiryInDays = 1, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }, + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_Empty_Result_Guids() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = true, + expiryInDays = 1, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = new List() { }, + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldNotDuplicateEntries() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = false, + expiryInDays = 0, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + var random = new Random(); + SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); + + var visitor = new SuppressVisitor(testCase.justification, + testCase.alias, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); SarifLog suppressed = visitor.VisitSarifLog(current); IList results = suppressed.Runs[0].Results; From 2d226b4a8ca53b4ab6bed9ee864f34d8a3265c6e Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 7 Sep 2022 08:50:50 -0500 Subject: [PATCH 40/61] bug: fixed merge command unit test failure --- .../MergeCommandUnitTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs index 607405459..61b118d2a 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/MergeCommandUnitTests.cs @@ -291,12 +291,13 @@ private Run CreateTestRun(int numberOfResult, bool createSubRule = false, string run.Results ??= new List(); var artifactUri = new Uri("path/to/file", UriKind.Relative); + var guid = Guid.NewGuid().ToString(); for (int i = 1; i <= numberOfResult; i++) { string ruleId = createSubRule ? $"TESTRULE/00{i}" : $"TESTRULE00{i}"; run.Results.AddRange( - RandomSarifLogGenerator.GenerateFakeResults(this.random, new List { ruleId }, new List { artifactUri }, 1)); + RandomSarifLogGenerator.GenerateFakeResults(this.random, new List { ruleId }, new List() { guid }, new List { artifactUri }, 1)); } return run; From 43104bedd1ec5c94fcbd33ea8b2550d28af582d5 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 7 Sep 2022 09:05:11 -0500 Subject: [PATCH 41/61] chore: formatting failures --- .../SuppressCommandTests.cs | 2 +- src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs | 2 +- src/Test.UnitTests.Sarif/Core/ResultTests.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 3bf9522a7..fb0c69974 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -197,7 +197,7 @@ private static void VerifySuppressCommand(SuppressOptions options) { RuleId = "Test0001", Guid = "GUID", - BaselineState = BaselineState.New + BaselineState = BaselineState.New } } } diff --git a/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs b/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs index 1f612a8d8..9498f1a88 100644 --- a/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs +++ b/src/Test.UnitTests.Sarif/Baseline/DefaultBaselineUnitTests.cs @@ -42,7 +42,7 @@ public void DefaultBaseline_NewResultAdded_New() Random random = RandomSarifLogGenerator.GenerateRandomAndLog(this.output); Run baseline = RandomSarifLogGenerator.GenerateRandomRunWithoutDuplicateIssues(random, DefaultBaseline.ResultBaselineEquals.DefaultInstance, random.Next(100) + 5); Run next = baseline.DeepClone(); - next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { "NEWGUID"}, new List() { new Uri(@"c:\test\testfile") }, 1).First()); + next.Results.Add(RandomSarifLogGenerator.GenerateFakeResults(random, new List() { "NEWTESTRESULT" }, new List() { "NEWGUID" }, new List() { new Uri(@"c:\test\testfile") }, 1).First()); Run result = defaultBaseliner.CreateBaselinedRun(baseline, next); diff --git a/src/Test.UnitTests.Sarif/Core/ResultTests.cs b/src/Test.UnitTests.Sarif/Core/ResultTests.cs index a2b40b12c..614dfd82c 100644 --- a/src/Test.UnitTests.Sarif/Core/ResultTests.cs +++ b/src/Test.UnitTests.Sarif/Core/ResultTests.cs @@ -56,8 +56,8 @@ public void Result_TryIsSuppressed_ShouldReturnTrueWhenSuppressionsAreAvailable( { var result = new Result { - Suppressions = new List() {} - }; + Suppressions = new List() { } + }; result.TryIsSuppressed(out bool isSuppressed).Should().BeTrue(); isSuppressed.Should().BeFalse(); From 8361a60a16e3494582da582fb74f9b4fb28d50fd Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 8 Sep 2022 16:47:42 -0500 Subject: [PATCH 42/61] feat: suppress expiryUtc command argument --- .../SuppressCommand.cs | 6 + .../SuppressOptions.cs | 8 +- src/Sarif/Visitors/SuppressVisitor.cs | 12 +- .../SuppressCommandTests.cs | 202 ++++++++++-------- .../Visitors/SuppressVisitorTests.cs | 56 ++++- 5 files changed, 194 insertions(+), 90 deletions(-) diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 2b9c20e63..2b2590191 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -73,6 +73,7 @@ public int Run(SuppressOptions options) options.Guids, options.Timestamps, options.ExpiryInDays, + options.ExpiryUtc, options.Status, options.ResultsGuids).VisitSarifLog(currentSarifLog); @@ -143,6 +144,11 @@ private bool ValidateOptions(SuppressOptions options) valid &= options.Validate(); valid &= options.ExpiryInDays >= 0; + if (options.ExpiryUtc.HasValue) + { + valid &= options.ExpiryUtc.Value > DateTime.UtcNow; + valid &= options.ExpiryInDays == 0; + } valid &= !string.IsNullOrWhiteSpace(options.Justification); valid &= (options.Status == SuppressionStatus.Accepted || options.Status == SuppressionStatus.UnderReview); valid &= DriverUtilities.ReportWhetherOutputFileCanBeCreated(options.OutputFilePath, options.Force, FileSystem); diff --git a/src/Sarif.Multitool.Library/SuppressOptions.cs b/src/Sarif.Multitool.Library/SuppressOptions.cs index bf156b062..e6a5de957 100644 --- a/src/Sarif.Multitool.Library/SuppressOptions.cs +++ b/src/Sarif.Multitool.Library/SuppressOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using CommandLine; @@ -48,9 +49,14 @@ public class SuppressOptions : SingleFileOptionsBase [Option( "expiryInDays", - HelpText = "The property 'expiryUtc' that will be associated with a suppression from the 'timeUtc'.")] + HelpText = "The property 'expiryUtc' that will be associated with a suppression from the 'timeUtc'. Cannot be used with 'expiryUtc'.")] public int ExpiryInDays { get; set; } + [Option( + "expiryUtc", + HelpText = "The property 'expiryUtc' that will be associated with a suppression. Cannot be used with 'expiryInDays'.")] + public DateTime? ExpiryUtc { get; set; } + [Option( "status", HelpText = "The status that will be used in the suppression. Valid values include Accepted and UnderReview.")] diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index 71ea55db1..f53ce07de 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -14,8 +14,8 @@ public class SuppressVisitor : SarifRewritingVisitor private readonly string alias; private readonly bool timestamps; private readonly DateTime timeUtc; - private readonly DateTime expiryUtc; private readonly int expiryInDays; + private readonly DateTime? expiryUtc; private readonly string justification; private readonly SuppressionStatus suppressionStatus; @@ -24,6 +24,7 @@ public SuppressVisitor(string justification, bool uuids, bool timestamps, int expiryInDays, + DateTime? expiryUtc, SuppressionStatus suppressionStatus, IEnumerable resultsGuids) { @@ -32,9 +33,9 @@ public SuppressVisitor(string justification, this.timestamps = timestamps; this.timeUtc = DateTime.UtcNow; this.expiryInDays = expiryInDays; + this.expiryUtc = expiryUtc; this.justification = justification; this.suppressionStatus = suppressionStatus; - this.expiryUtc = this.timeUtc.AddDays(expiryInDays); this.resultsGuids = resultsGuids; } @@ -76,7 +77,12 @@ public override Result VisitResult(Result node) if (expiryInDays > 0) { - suppression.SetProperty(nameof(expiryUtc), expiryUtc); + suppression.SetProperty(nameof(expiryUtc), timeUtc.AddDays(expiryInDays)); + } + + if (expiryUtc.HasValue) + { + suppression.SetProperty(nameof(expiryUtc), expiryUtc.Value); } if (this.resultsGuids != null) diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index fb0c69974..09f269e76 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -30,7 +30,7 @@ public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied() { new SuppressOptions { - ExpiryInDays = -1 + ExpiryInDays = 1 }, new SuppressOptions { @@ -65,6 +65,21 @@ public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied() Expression = "fail", Status = SuppressionStatus.Accepted }, + new SuppressOptions + { + ExpiryUtc = DateTime.UtcNow.AddDays(-1), + Justification = "some justification", + OutputFilePath = outputPath, + Status = SuppressionStatus.Accepted + }, + new SuppressOptions + { + ExpiryInDays = 1, + ExpiryUtc = DateTime.UtcNow.AddDays(1), + Justification = "some justification", + OutputFilePath = outputPath, + Status = SuppressionStatus.Accepted + }, }; var mock = new Mock(); @@ -83,90 +98,102 @@ public void SuppressCommand_ShouldReturnSuccess_WhenCorrectArgumentsAreSupplied( { var optionsTestCases = new SuppressOptions[] { - new SuppressOptions - { - Alias = "some alias", - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - Status = SuppressionStatus.UnderReview - }, + // new SuppressOptions + // { + // Alias = "some alias", + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // Status = SuppressionStatus.UnderReview + // }, + // new SuppressOptions + // { + // Guids = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // Guids = true, + // ExpiryInDays = 5, + // Timestamps = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // Guids = true, + // ExpiryInDays = 5, + // Timestamps = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // Expression = "BaseLineState = \"New\"", + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // Guids = true, + // ExpiryInDays = 5, + // Timestamps = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // ResultsGuids = new List() { "GUID"}, + // Expression = string.Empty, + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // Guids = true, + // ExpiryInDays = 5, + // Timestamps = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // ResultsGuids = new List() { "GUID", "GUID2"}, + // Expression = "BaseLineState = \"New\"", + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // Guids = true, + // ExpiryInDays = 5, + // Timestamps = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // ResultsGuids = new List() {}, + // Expression = "BaseLineState = \"New\"", + // Status = SuppressionStatus.Accepted + // }, + // new SuppressOptions + // { + // Guids = true, + // ExpiryInDays = 5, + // Timestamps = true, + // InputFilePath = @"C:\input.sarif", + // OutputFilePath = @"C:\output.sarif", + // Justification = "some justification", + // ResultsGuids = new List() {}, + // Expression = "IsSuppressed == False", + // Status = SuppressionStatus.Accepted + // }, new SuppressOptions { Guids = true, - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - Guids = true, - ExpiryInDays = 5, - Timestamps = true, - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - Guids = true, - ExpiryInDays = 5, - Timestamps = true, - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - Expression = "BaseLineState = \"New\"", - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - Guids = true, - ExpiryInDays = 5, - Timestamps = true, - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - ResultsGuids = new List() { "GUID"}, - Expression = string.Empty, - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - Guids = true, - ExpiryInDays = 5, - Timestamps = true, - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - ResultsGuids = new List() { "GUID", "GUID2"}, - Expression = "BaseLineState = \"New\"", - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - Guids = true, - ExpiryInDays = 5, - Timestamps = true, - InputFilePath = @"C:\input.sarif", - OutputFilePath = @"C:\output.sarif", - Justification = "some justification", - ResultsGuids = new List() {}, - Expression = "BaseLineState = \"New\"", - Status = SuppressionStatus.Accepted - }, - new SuppressOptions - { - Guids = true, - ExpiryInDays = 5, + ExpiryUtc = DateTime.UtcNow.AddDays(30), Timestamps = true, InputFilePath = @"C:\input.sarif", OutputFilePath = @"C:\output.sarif", @@ -254,9 +281,16 @@ private static void VerifySuppressCommand(SuppressOptions options) timeUtc.Should().BeCloseTo(DateTime.UtcNow, DateTimeAssertPrecision); } - if (options.ExpiryInDays > 0 && suppression.TryGetProperty("expiryUtc", out DateTime expiryUtc)) + if (options.ExpiryInDays > 0) + { + suppression.TryGetProperty("expiryUtc", out DateTime expiryInDaysUtc).Should().BeTrue(); + expiryInDaysUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(options.ExpiryInDays), DateTimeAssertPrecision); + } + + if (options.ExpiryUtc.HasValue) { - expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(options.ExpiryInDays), DateTimeAssertPrecision); + suppression.TryGetProperty("expiryUtc", out DateTime expiryUtc).Should().BeTrue(); + expiryUtc.Should().BeCloseTo(options.ExpiryUtc.Value, DateTimeAssertPrecision); } } } diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index a59492dfa..8c08dccfa 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -27,6 +27,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_EmptyAlias() uuids = false, timestamps = false, expiryInDays = 0, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = default(List), }; @@ -36,6 +37,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_EmptyAlias() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -50,6 +52,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Alias() uuids = false, timestamps = false, expiryInDays = 0, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = default(List), }; @@ -59,6 +62,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Alias() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -73,6 +77,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Uuids() uuids = true, timestamps = false, expiryInDays = 0, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = default(List), }; @@ -82,6 +87,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Uuids() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -96,6 +102,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Timestamps() uuids = true, timestamps = true, expiryInDays = 0, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = default(List), }; @@ -105,6 +112,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Timestamps() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -119,6 +127,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Expiry() uuids = true, timestamps = true, expiryInDays = 1, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = default(List), }; @@ -128,6 +137,32 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Expiry() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, + testCase.suppressionStatus, + testCase.resultGuids); + } + + [Fact] + public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_ExpiryDateUtc() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = true, + expiryInDays = 0, + expiryUtc = DateTime.UtcNow.AddDays(30), + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + VerifySuppressVisitor(testCase.alias, + testCase.justification, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -142,6 +177,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_UnderReview() uuids = true, timestamps = true, expiryInDays = 1, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.UnderReview, resultGuids = default(List), }; @@ -151,6 +187,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_UnderReview() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -165,6 +202,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Result_Guids() uuids = true, timestamps = true, expiryInDays = 1, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = new List() { "704cf481-0cfd-46ae-90cd-533cdc6c3bb4", "ecaa7988-5cef-411b-b468-6c20851d6994", "c65b76c7-3cd6-4381-9216-430bcc7fab2d", "04753e26-d297-43e2-a7f7-ae2d34c398c9", "54cb1f58-f401-4f8e-8f42-f2482a123b85" }, }; @@ -174,6 +212,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_With_Result_Guids() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -188,6 +227,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_Empty_Result_Guids() uuids = true, timestamps = true, expiryInDays = 1, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = new List() { }, }; @@ -197,6 +237,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_Empty_Result_Guids() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); } @@ -211,6 +252,7 @@ public void SuppressVisitor_ShouldNotDuplicateEntries() uuids = true, timestamps = false, expiryInDays = 0, + expiryUtc = default(DateTime?), suppressionStatus = SuppressionStatus.Accepted, resultGuids = default(List), }; @@ -223,6 +265,7 @@ public void SuppressVisitor_ShouldNotDuplicateEntries() testCase.uuids, testCase.timestamps, testCase.expiryInDays, + testCase.expiryUtc, testCase.suppressionStatus, testCase.resultGuids); @@ -253,6 +296,7 @@ private static void VerifySuppressVisitor(string alias, bool uuids, bool timestamps, int expiryInDays, + DateTime? expiryUtc, SuppressionStatus suppressionStatus, IEnumerable guids) { @@ -261,6 +305,7 @@ private static void VerifySuppressVisitor(string alias, uuids, timestamps, expiryInDays, + expiryUtc, suppressionStatus, guids); @@ -299,9 +344,16 @@ private static void VerifySuppressVisitor(string alias, timeUtc.Should().BeCloseTo(DateTime.UtcNow, DateTimeAssertPrecision); } - if (expiryInDays > 0 && suppression.TryGetProperty("expiryUtc", out DateTime expiryUtc)) + if (expiryInDays > 0) + { + suppression.TryGetProperty("expiryUtc", out DateTime expiryInDaysUtc).Should().BeTrue(); + expiryInDaysUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(expiryInDays), DateTimeAssertPrecision); + } + + if (expiryUtc.HasValue) { - expiryUtc.Should().BeCloseTo(DateTime.UtcNow.AddDays(expiryInDays), DateTimeAssertPrecision); + suppression.TryGetProperty("expiryUtc", out DateTime expiryTimestampUtc).Should().BeTrue(); + expiryTimestampUtc.Should().BeCloseTo(expiryUtc.Value, DateTimeAssertPrecision); } if (guids != null) From a4446d9bd3ba6e4b7742b424c34311fc9b007377 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 8 Sep 2022 16:51:09 -0500 Subject: [PATCH 43/61] bug: reverted bad test data --- .../SuppressCommandTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs index 09f269e76..ec84aaa39 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/SuppressCommandTests.cs @@ -30,7 +30,7 @@ public void SuppressCommand_ShouldReturnFailure_WhenBadArgumentsAreSupplied() { new SuppressOptions { - ExpiryInDays = 1 + ExpiryInDays = -1 }, new SuppressOptions { From 366bdde71d45614ed675379992d5745b573b1b53 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 20 Sep 2022 08:40:42 -0500 Subject: [PATCH 44/61] chore: codeql cleanup --- src/Sarif.Converters/CisCatConverter.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index 07b3cac28..90c21c8a1 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel; @@ -43,12 +44,9 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm } var results = new List(); - foreach (CisCatRule rule in log.Rules) + foreach (CisCatRule rule in log.Rules.Where(i => !i.IsPass())) { - if (!rule.IsPass()) - { - results.Add(CreateResult(rule)); - } + results.Add(CreateResult(rule)); } PersistResults(output, results, run); From 8269738fd80bf32aef415607f6de1a9e02f60e27 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 20 Sep 2022 08:46:23 -0500 Subject: [PATCH 45/61] chore: dotnet format errors --- src/Sarif.Converters/NessusObjectModel/Report.cs | 2 +- src/Sarif.Converters/RankConstants.cs | 2 +- .../CisCatConverterTests.cs | 8 +++++--- .../NessusConverterTests.cs | 8 +++++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Sarif.Converters/NessusObjectModel/Report.cs b/src/Sarif.Converters/NessusObjectModel/Report.cs index 923ba605e..4a0cdbf58 100644 --- a/src/Sarif.Converters/NessusObjectModel/Report.cs +++ b/src/Sarif.Converters/NessusObjectModel/Report.cs @@ -14,4 +14,4 @@ public class Report [XmlElement("ReportHost")] public List ReportHosts { get; set; } = new List(); } -} \ No newline at end of file +} diff --git a/src/Sarif.Converters/RankConstants.cs b/src/Sarif.Converters/RankConstants.cs index a18e9e9a1..e7af04fe8 100644 --- a/src/Sarif.Converters/RankConstants.cs +++ b/src/Sarif.Converters/RankConstants.cs @@ -11,4 +11,4 @@ public static class RankConstants public const double Low = 3.0; public const double None = -1.0; } -} \ No newline at end of file +} diff --git a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs index 3b33b9687..22abf99e6 100644 --- a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs +++ b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs @@ -4,12 +4,14 @@ using System; using System.IO; +using FluentAssertions; + using Microsoft.CodeAnalysis.Sarif.Writers; -using Xunit; -using FluentAssertions; using Newtonsoft.Json; +using Xunit; + namespace Microsoft.CodeAnalysis.Sarif.Converters { public class CisCatConverterTests : ConverterTestsBase @@ -59,4 +61,4 @@ public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(CisCatConverterTests)); private const string ResourceNamePrefix = ToolFormat.CisCat; } -} \ No newline at end of file +} diff --git a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs index c7eb093f2..e193100c4 100644 --- a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs +++ b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs @@ -4,12 +4,14 @@ using System; using System.IO; +using FluentAssertions; + using Microsoft.CodeAnalysis.Sarif.Writers; -using Xunit; -using FluentAssertions; using Newtonsoft.Json; +using Xunit; + namespace Microsoft.CodeAnalysis.Sarif.Converters { public class NessusConverterTests : ConverterTestsBase @@ -57,4 +59,4 @@ public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(NessusConverterTests)); private const string ResourceNamePrefix = ToolFormat.Nessus; } -} \ No newline at end of file +} From d90b62513906d349a04b717519a152d4209f8e70 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 20 Sep 2022 08:46:23 -0500 Subject: [PATCH 46/61] chore: dotnet format errors --- src/Sarif.Converters/NessusObjectModel/Report.cs | 2 +- src/Sarif.Converters/RankConstants.cs | 2 +- .../CisCatConverterTests.cs | 8 +++++--- .../NessusConverterTests.cs | 8 +++++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Sarif.Converters/NessusObjectModel/Report.cs b/src/Sarif.Converters/NessusObjectModel/Report.cs index 923ba605e..4a0cdbf58 100644 --- a/src/Sarif.Converters/NessusObjectModel/Report.cs +++ b/src/Sarif.Converters/NessusObjectModel/Report.cs @@ -14,4 +14,4 @@ public class Report [XmlElement("ReportHost")] public List ReportHosts { get; set; } = new List(); } -} \ No newline at end of file +} diff --git a/src/Sarif.Converters/RankConstants.cs b/src/Sarif.Converters/RankConstants.cs index a18e9e9a1..e7af04fe8 100644 --- a/src/Sarif.Converters/RankConstants.cs +++ b/src/Sarif.Converters/RankConstants.cs @@ -11,4 +11,4 @@ public static class RankConstants public const double Low = 3.0; public const double None = -1.0; } -} \ No newline at end of file +} diff --git a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs index 3b33b9687..22abf99e6 100644 --- a/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs +++ b/src/Test.UnitTests.Sarif.Converters/CisCatConverterTests.cs @@ -4,12 +4,14 @@ using System; using System.IO; +using FluentAssertions; + using Microsoft.CodeAnalysis.Sarif.Writers; -using Xunit; -using FluentAssertions; using Newtonsoft.Json; +using Xunit; + namespace Microsoft.CodeAnalysis.Sarif.Converters { public class CisCatConverterTests : ConverterTestsBase @@ -59,4 +61,4 @@ public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(CisCatConverterTests)); private const string ResourceNamePrefix = ToolFormat.CisCat; } -} \ No newline at end of file +} diff --git a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs index c7eb093f2..e193100c4 100644 --- a/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs +++ b/src/Test.UnitTests.Sarif.Converters/NessusConverterTests.cs @@ -4,12 +4,14 @@ using System; using System.IO; +using FluentAssertions; + using Microsoft.CodeAnalysis.Sarif.Writers; -using Xunit; -using FluentAssertions; using Newtonsoft.Json; +using Xunit; + namespace Microsoft.CodeAnalysis.Sarif.Converters { public class NessusConverterTests : ConverterTestsBase @@ -57,4 +59,4 @@ public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(NessusConverterTests)); private const string ResourceNamePrefix = ToolFormat.Nessus; } -} \ No newline at end of file +} From fe1f44709fa75eaf9b9cc7233894a27b5fd64350 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 20 Sep 2022 08:40:42 -0500 Subject: [PATCH 47/61] chore: codeql cleanup --- src/Sarif.Converters/CisCatConverter.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index 07b3cac28..90c21c8a1 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.CodeAnalysis.Sarif.Converters.CisCatObjectModel; @@ -43,12 +44,9 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm } var results = new List(); - foreach (CisCatRule rule in log.Rules) + foreach (CisCatRule rule in log.Rules.Where(i => !i.IsPass())) { - if (!rule.IsPass()) - { - results.Add(CreateResult(rule)); - } + results.Add(CreateResult(rule)); } PersistResults(output, results, run); From 2cbdeeb8584e2c4a8fdaa45cba9a2d0e10a57687 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 20 Sep 2022 16:55:35 -0500 Subject: [PATCH 48/61] feat: snyk oss converter --- docs/multitool-usage.md | 1 + .../BuiltInConverterFactory.cs | 1 + .../SnykOpenSourceConverter.cs | 274 ++++++++++++++++++ .../SnykOpenSourceObjectModel/Insight.cs | 13 + .../SnykOpenSourceObjectModel/Reference.cs | 16 + .../SnykOpenSourceObjectModel/Semver.cs | 15 + .../SnykOpenSourceReader.cs | 25 ++ .../SnykOpenSourceObjectModel/Test.cs | 27 ++ .../Vulnerability.cs | 82 ++++++ src/Sarif.Converters/ToolFormat.cs | 3 + src/Sarif.Multitool.Library/ConvertOptions.cs | 2 +- .../SnykOpenSourceConverterTests.cs | 58 ++++ .../Test.UnitTests.Sarif.Converters.csproj | 10 + .../ExpectedOutputs/NoResults.sarif | 19 ++ .../ExpectedOutputs/ValidResults.sarif | 86 ++++++ .../Inputs/InvalidResults.json | 30 ++ .../Inputs/NoResults.json | 1 + .../Inputs/ValidResults.json | 214 ++++++++++++++ 18 files changed, 876 insertions(+), 1 deletion(-) create mode 100644 src/Sarif.Converters/SnykOpenSourceConverter.cs create mode 100644 src/Sarif.Converters/SnykOpenSourceObjectModel/Insight.cs create mode 100644 src/Sarif.Converters/SnykOpenSourceObjectModel/Reference.cs create mode 100644 src/Sarif.Converters/SnykOpenSourceObjectModel/Semver.cs create mode 100644 src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs create mode 100644 src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs create mode 100644 src/Sarif.Converters/SnykOpenSourceObjectModel/Vulnerability.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/NoResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/InvalidResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/NoResults.json create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResults.json diff --git a/docs/multitool-usage.md b/docs/multitool-usage.md index e2326ebf1..89821dc5c 100644 --- a/docs/multitool-usage.md +++ b/docs/multitool-usage.md @@ -84,6 +84,7 @@ Run ```Sarif.Multitool convert --help``` for the current list. - Nessus - PREfast - Pylint +- SnykOpenSource - SemmleQL - StaticDriverVerifier - TSLint diff --git a/src/Sarif.Converters/BuiltInConverterFactory.cs b/src/Sarif.Converters/BuiltInConverterFactory.cs index 1c1bc09b7..0f73b4158 100644 --- a/src/Sarif.Converters/BuiltInConverterFactory.cs +++ b/src/Sarif.Converters/BuiltInConverterFactory.cs @@ -38,6 +38,7 @@ private static Dictionary> CreateBuiltInConv CreateConverterRecord(result, ToolFormat.Nessus); CreateConverterRecord(result, ToolFormat.PREfast); CreateConverterRecord(result, ToolFormat.Pylint); + CreateConverterRecord(result, ToolFormat.SnykOpenSource); CreateConverterRecord(result, ToolFormat.SemmleQL); CreateConverterRecord(result, ToolFormat.StaticDriverVerifier); CreateConverterRecord(result, ToolFormat.TSLint); diff --git a/src/Sarif.Converters/SnykOpenSourceConverter.cs b/src/Sarif.Converters/SnykOpenSourceConverter.cs new file mode 100644 index 000000000..45488dfc8 --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceConverter.cs @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class SnykOpenSourceConverter : ToolFileConverterBase + { + private readonly LogReader> logReader; + private const string _CWE_IDENTIFIER_KEY = "CWE"; + private const string _CVE_IDENTIFIER_KEY = "CVE"; + + public SnykOpenSourceConverter() + { + logReader = new SnykOpenSourceReader(); + } + + public override string ToolName => ToolFormat.SnykOpenSource; + + public override void Convert(Stream input, IResultLogWriter output, OptionallyEmittedData dataToInsert) + { + input = input ?? throw new ArgumentNullException(nameof(input)); + output = output ?? throw new ArgumentNullException(nameof(output)); + + //Read Snyk data + List snykTests = logReader.ReadLog(input); + + //Init objects + var run = new Run(); + run.Tool = new Tool(); + var results = new List(); + + //Set driver details + run.Tool.Driver = CreateDriver(); + + //Set the list of tool rules & results + run.Tool.Driver.Rules = new List(); + foreach (Test test in snykTests.Where(i => !i.Ok)) + { + foreach (Vulnerability vulnerability in test.Vulnerabilities) + { + //Add rule id if not exits in collection + if (!run.Tool.Driver.Rules.Any(i => i.Id == vulnerability.Id)) + { + run.Tool.Driver.Rules.Add(CreateReportDescriptor(vulnerability)); + } + + //Add result for the rule if does not previously exist (there are duplicates???) + if (!results.Any(i => i.RuleId == vulnerability.Id && i.Locations.Any(l => l.PhysicalLocation.ArtifactLocation.Uri.ToString().Equals(test.DisplayTargetFile)))) + { + results.Add(CreateResult(vulnerability, test)); + } + } + } + + PersistResults(output, results, run); + } + + private ToolComponent CreateDriver() + { + + var driver = new ToolComponent(); + + driver.Name = this.ToolName; + driver.FullName = this.ToolName; + //JSON schema has no version information. Pin to 1.0 for now. + driver.Version = "1.0.0"; + driver.SemanticVersion = "1.0.0"; + driver.InformationUri = new Uri("https://docs.snyk.io/products/snyk-open-source"); + + return driver; + } + + private ReportingDescriptor CreateReportDescriptor(Vulnerability item) + { + ReportingDescriptor descriptor = new ReportingDescriptor(); + + descriptor.Id = item.Id; + descriptor.Name = $"{item.Name}@{item.Version}"; + descriptor.ShortDescription = new MultiformatMessageString() + { + Text = $"{item.Title} in {item.Name}@{item.Version}", + Markdown = $"{item.Title} in {item.Name}@{item.Version}", + }; + descriptor.FullDescription = new MultiformatMessageString() + { + Text = item.Description, + Markdown = item.Description, + }; + + //Help text includes refs + triage advice + StringBuilder sbHelp = new StringBuilder(); + if (item.References.Count() > 0) + { + sbHelp.AppendLine("References:"); + foreach (Reference reference in item.References) + { + sbHelp.AppendFormat("{0}: {1}{2}", reference.Title, reference.Url, Environment.NewLine); + } + } + + if (!string.IsNullOrEmpty(item.Insights?.TriageAdvice)) + { + sbHelp.AppendLine(""); + sbHelp.AppendLine("Triage Advice:"); + sbHelp.Append(item.Insights.TriageAdvice); + } + + if (sbHelp.Length > 0) + { + descriptor.Help = new MultiformatMessageString() + { + Text = sbHelp.ToString(), + Markdown = sbHelp.ToString(), + }; + } + + //Use for GH Security Advisories + descriptor.SetProperty("security-severity", item.Cvss3BaseScore); + + //Tags for GH filtering + var tags = new List() + { + "security", + item.PackageManager, + }; + + if (item.Identifiers.ContainsKey(_CWE_IDENTIFIER_KEY)) + { + tags.AddRange(item.Identifiers[_CWE_IDENTIFIER_KEY]); + } + + descriptor.SetProperty("tags", tags); + + return descriptor; + } + + private Result CreateResult(Vulnerability item, Test test) + { + //set the result metadata + Result result = new Result + { + RuleId = item.Id, + Message = new Message + { + Text = $"This file introduces a vulnerable {item.PackageName} package with a {item.SeverityWithCritical} severity vulnerability." + }, + }; + + //Set the kind, level, and rank based on cvss3 score + FailureLevel level = FailureLevel.None; + double rank = RankConstants.None; + getResultSeverity(item.Cvss3BaseScore, out level, out rank); + + //Set the properties + result.Kind = ResultKind.Fail; + result.Level = level; + result.Rank = rank; + + //Set the location properties + PhysicalLocation location = new PhysicalLocation() + { + ArtifactLocation = new ArtifactLocation() + { + Uri = new Uri(test.DisplayTargetFile, UriKind.Relative), + }, + Region = new Region() + { + StartLine = 1, + } + }; + result.Locations = new List(); + result.Locations.Add(new Location() + { + PhysicalLocation = location, + }); + + //Set the unique fingerprint + var fingerprints = new List() { + item.Id, + item.PackageName, + item.Version, + test.DisplayTargetFile, + }; + + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(string.Join(".", fingerprints)).ToLower()); + + result.SetProperty("packageManager", item.PackageManager); + result.SetProperty("packageName", item.PackageName); + result.SetProperty("packageVersion", item.Version); + result.SetProperty("cvss3BaseScore", item.Cvss3BaseScore.ToString()); + result.SetProperty("cvss3Vector", item.Cvss3Vector); + result.SetProperty("vulnPublicationDate", item.PublicationTime); + + if (item.Semver.Vulnerable.Any()) + { + result.SetProperty("semanticVersion", item.Semver.Vulnerable); + } + + if (item.FixedIn.Any()) + { + result.SetProperty("patchedVersion", item.FixedIn); + } + + var cves = new List(); + if (item.Identifiers.ContainsKey(_CVE_IDENTIFIER_KEY)) + { + cves.AddRange(item.Identifiers[_CVE_IDENTIFIER_KEY]); + } + result.SetProperty("cve", cves); + + var cwes = new List(); + if (item.Identifiers.ContainsKey(_CWE_IDENTIFIER_KEY)) + { + cwes.AddRange(item.Identifiers[_CWE_IDENTIFIER_KEY]); + } + result.SetProperty("cwe", cwes); + + //Add other identifiers to the xref + var xrefs = new List(); + foreach (string key in item.Identifiers.Keys) + { + //Skip cve / cwe with dedicated elements + if (key.Equals(_CVE_IDENTIFIER_KEY) || key.Equals(_CWE_IDENTIFIER_KEY)) + { + continue; + } + + xrefs.AddRange(item.Identifiers[key]); + } + result.SetProperty("xref", xrefs); + + return result; + } + + private void getResultSeverity(double cvss3score, out FailureLevel level, out double rank) + { + // Default values + level = FailureLevel.None; + rank = RankConstants.None; + + //Failure level by cvss score + if (cvss3score >= 9.0) + { + level = FailureLevel.Error; + rank = cvss3score; + } + if (cvss3score >= 7.0 && cvss3score < 9.0) + { + level = FailureLevel.Error; + rank = cvss3score; + } + else if (cvss3score >= 4.0 && cvss3score < 7.0) + { + level = FailureLevel.Warning; + rank = cvss3score; + + } + else if (cvss3score > 0 && cvss3score < 4.0) + { + level = FailureLevel.Note; + rank = cvss3score; + } + } + } +} diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/Insight.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/Insight.cs new file mode 100644 index 000000000..9cbd23aae --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/Insight.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel +{ + public class Insight + { + [JsonProperty("triageAdvice")] + public string TriageAdvice { get; set; } + } +} diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/Reference.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/Reference.cs new file mode 100644 index 000000000..7a5a7fe9f --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/Reference.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel +{ + public class Reference + { + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + } +} diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/Semver.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/Semver.cs new file mode 100644 index 000000000..a06d4489e --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/Semver.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel +{ + public class Semver + { + [JsonProperty("vulnerable")] + public IEnumerable Vulnerable { get; set; } = new List(); + } +} diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs new file mode 100644 index 000000000..69eae2eba --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel +{ + public class SnykOpenSourceReader : LogReader> + { + public override List ReadLog(Stream input) + { + string reportData; + + using (TextReader streamReader = new StreamReader(input)) + { + reportData = streamReader.ReadToEnd(); + } + + return JsonConvert.DeserializeObject>(reportData); + } + } +} diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs new file mode 100644 index 000000000..e31934727 --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel +{ + public class Test + { + [JsonProperty("vulnerabilities")] + public IEnumerable Vulnerabilities { get; set; } = new List(); + + [JsonProperty("displayTargetFile")] + public string DisplayTargetFile { get; set; } + + [JsonProperty("projectName")] + public string ProjectName { get; set; } + + [JsonProperty("uniqueCount")] + public int UniqueCount { get; set; } + + [JsonProperty("ok")] + public bool Ok { get; set; } + } +} diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/Vulnerability.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/Vulnerability.cs new file mode 100644 index 000000000..508465359 --- /dev/null +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/Vulnerability.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Newtonsoft.Json; + +namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel +{ + public class Vulnerability + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("CVSSv3")] + public string Cvss3Vector { get; set; } + + [JsonProperty("cvssScore")] + public double Cvss3BaseScore { get; set; } + + [JsonProperty("creationTime")] + public DateTime? CreationTime { get; set; } + + [JsonProperty("disclosureTime")] + public DateTime? DisclosureTime { get; set; } + + [JsonProperty("modificationTime")] + public DateTime? ModificationTime { get; set; } + + [JsonProperty("publicationTime")] + public DateTime? PublicationTime { get; set; } + + [JsonProperty("description")] + public string Description { get; set; } + + [JsonProperty("language")] + public string Language { get; set; } + + [JsonProperty("packageName")] + public string PackageName { get; set; } + + [JsonProperty("packageManager")] + public string PackageManager { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("version")] + public string Version { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("severityWithCritical")] + public string SeverityWithCritical { get; set; } + + [JsonProperty("isUpgradable")] + public bool IsUpgradable { get; set; } + + [JsonProperty("isPatchable")] + public bool IsPatchable { get; set; } + + [JsonProperty("proprietary")] + public bool IsProprietary { get; set; } + + [JsonProperty("insights")] + public Insight Insights { get; set; } + + [JsonProperty("semver")] + public Semver Semver { get; set; } = new Semver(); + + [JsonProperty("fixedIn")] + public IEnumerable FixedIn { get; set; } = new List(); + + [JsonProperty("references")] + public IEnumerable References { get; set; } = new List(); + + [JsonProperty("identifiers")] + public Dictionary> Identifiers { get; set; } = new Dictionary>(); + } +} diff --git a/src/Sarif.Converters/ToolFormat.cs b/src/Sarif.Converters/ToolFormat.cs index fa32b00d4..7abee1a00 100644 --- a/src/Sarif.Converters/ToolFormat.cs +++ b/src/Sarif.Converters/ToolFormat.cs @@ -51,6 +51,9 @@ public static class ToolFormat /// Pylint file format. public const string Pylint = nameof(Pylint); + /// SnykOpenSource's file format. + public const string SnykOpenSource = nameof(SnykOpenSource); + /// Semmle's file format. public const string SemmleQL = nameof(SemmleQL); diff --git a/src/Sarif.Multitool.Library/ConvertOptions.cs b/src/Sarif.Multitool.Library/ConvertOptions.cs index 1fb406548..0f43f8add 100644 --- a/src/Sarif.Multitool.Library/ConvertOptions.cs +++ b/src/Sarif.Multitool.Library/ConvertOptions.cs @@ -13,7 +13,7 @@ public class ConvertOptions : SingleFileOptionsBase [Option( 't', "tool", - HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, Nessus, PREfast, Pylint, SemmleQL, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", + HelpText = "The tool format of the input file. Must be one of: AndroidStudio, CisCat, ClangAnalyzer, ClangTidy, CppCheck, ContrastSecurity, FlawFinder, Fortify, FortifyFpr, FxCop, Hdf, Nessus, PREfast, Pylint, SemmleQL, SnykOpenSource, StaticDriverVerifier, TSLint, or a tool format for which a plugin assembly provides the converter.", Required = true)] public string ToolFormat { get; set; } diff --git a/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs new file mode 100644 index 000000000..5be823a09 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +using FluentAssertions; + +using Microsoft.CodeAnalysis.Sarif.Writers; + +using Xunit; + +namespace Microsoft.CodeAnalysis.Sarif.Converters +{ + public class SnykOpenSourceConverterTests : ConverterTestsBase + { + [Fact] + public void Converter_RequiresInputStream() + { + var converter = new SnykOpenSourceConverter(); + Action action = () => converter.Convert(input: null, output: new ResultLogObjectWriter(), dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_RequiresResultLogWriter() + { + var converter = new SnykOpenSourceConverter(); + Action action = () => converter.Convert(input: new MemoryStream(), output: null, dataToInsert: OptionallyEmittedData.None); + action.Should().Throw(); + } + + [Fact] + public void Converter_WhenInputIsEmpty_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("NoResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenResultRowIsInvalid_ReturnsNoResults() + { + string input = Extractor.GetResourceInputText("InvalidResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("NoResults.sarif"); + RunTestCase(input, expectedOutput); + } + + [Fact] + public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResults.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(SnykOpenSourceConverterTests)); + private const string ResourceNamePrefix = ToolFormat.SnykOpenSource; + + } +} diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index f8bcd6c96..94a8b1685 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -57,6 +57,11 @@ + + + + + @@ -91,6 +96,11 @@ + + + + + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/NoResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/NoResults.sarif new file mode 100644 index 000000000..bfee5086a --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/NoResults.sarif @@ -0,0 +1,19 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [], + "tool": { + "driver": { + "name": "SnykOpenSource", + "fullName": "SnykOpenSource", + "version": "1.0.0", + "semanticVersion": "1.0.0", + "informationUri": "https://docs.snyk.io/products/snyk-open-source" + } + }, + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif new file mode 100644 index 000000000..19531e5e5 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif @@ -0,0 +1,86 @@ +{ + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version": "2.1.0", + "runs": [ + { + "results": [ + { + "ruleId": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", + "level": "error", + "message": { + "text": "This file introduces a vulnerable Newtonsoft.Json package with a high severity vulnerability." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/Common/packages.config", + "index": 0 + }, + "region": { + "startLine": 1 + } + } + } + ], + "fingerprints": { + "0": "8ea1e915fad81b081b2a960f83b28f71708dd67b03e724224a4e9fbc1bb72773" + }, + "rank": 7.5, + "properties": { + "packageManager": "nuget", + "packageName": "Newtonsoft.Json", + "packageVersion": "10.0.3", + "cvss3BaseScore": "7.5", + "cvss3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:P", + "vulnPublicationDate": "2022-04-24T10:58:25Z", + "semanticVersion": ["[,13.0.1)"], + "patchedVersion": ["13.0.1"], + "cve": [], + "cwe": ["CWE-755"], + "xref": ["GHSA-5crp-9r3c-p9vr"] + } + } + ], + "tool": { + "driver": { + "name": "SnykOpenSource", + "fullName": "SnykOpenSource", + "version": "1.0.0", + "semanticVersion": "1.0.0", + "informationUri": "https://docs.snyk.io/products/snyk-open-source", + "rules": [ + { + "id": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", + "name": "Newtonsoft.Json@10.0.3", + "fullDescription": { + "text": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n", + "markdown": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n" + }, + "help": { + "text": "References:\nGitHub Commit: https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66\nGitHub Issue: https://github.com/JamesNK/Newtonsoft.Json/issues/2457\nGitHub PR: https://github.com/JamesNK/Newtonsoft.Json/pull/2462\nSecurity Advisory: https://alephsecurity.com/vulns/aleph-2018004\nSecurity Article: https://alephsecurity.com/2018/10/22/StackOverflowException/\n\nTriage Advice:\nThis vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server", + "markdown": "References:\nGitHub Commit: https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66\nGitHub Issue: https://github.com/JamesNK/Newtonsoft.Json/issues/2457\nGitHub PR: https://github.com/JamesNK/Newtonsoft.Json/pull/2462\nSecurity Advisory: https://alephsecurity.com/vulns/aleph-2018004\nSecurity Article: https://alephsecurity.com/2018/10/22/StackOverflowException/\n\nTriage Advice:\nThis vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server" + }, + "shortDescription": { + "text": "Insecure Defaults in Newtonsoft.Json@10.0.3", + "markdown": "Insecure Defaults in Newtonsoft.Json@10.0.3" + }, + "properties": { + "security-severity": 7.5, + "tags": ["security","nuget","CWE-755"] + } + } + ] + } + }, + "artifacts": [ + { + "location": { + "uri": "src/Common/packages.config" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/InvalidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/InvalidResults.json new file mode 100644 index 000000000..66a048b37 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/InvalidResults.json @@ -0,0 +1,30 @@ +[ + { + "invalidvulnerabilities": [ + {} + ], + "notok": false, + "dependencyCount": 10, + "org": "test-org-1234", + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.0\nignore: {}\npatch: {}\n", + "isPrivate": true, + "packageManager": "nuget", + "ignoreSettings": { + "adminOnly": false, + "reasonRequired": false, + "disregardFilesystemIgnores": false + }, + "summary": "1 vulnerable dependency path", + "filesystemPolicy": false, + "filtered": { + "ignore": [], + "patch": [] + }, + "uniqueCount": 1, + "missingtargetFile": "src/Common/packages.config", + "projectName": "puma-prey", + "foundProjectCount": 8, + "nodisplayTargetFile": "src/Common/packages.config", + "wrongpath": "/Users/test/source/puma-prey" + } +] \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/NoResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/NoResults.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/NoResults.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResults.json b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResults.json new file mode 100644 index 000000000..ef7218419 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResults.json @@ -0,0 +1,214 @@ +[ + { + "vulnerabilities": [ + { + "CVSSv3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:P", + "alternativeIds": [], + "creationTime": "2022-04-24T08:33:35.726602Z", + "credit": [ + "Gil Mirmovitch" + ], + "cvssScore": 7.5, + "description": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n", + "disclosureTime": "2022-04-24T08:33:33Z", + "exploit": "Proof of Concept", + "functions": [], + "fixedIn": [ + "13.0.1" + ], + "id": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", + "identifiers": { + "CWE": [ + "CWE-755" + ], + "GHSA": [ + "GHSA-5crp-9r3c-p9vr" + ], + "CVE": [] + }, + "language": "dotnet", + "malicious": false, + "modificationTime": "2022-08-08T07:46:17.314095Z", + "moduleName": "newtonsoft.json", + "packageManager": "nuget", + "packageName": "Newtonsoft.Json", + "patches": [], + "proprietary": false, + "publicationTime": "2022-04-24T10:58:25Z", + "references": [ + { + "title": "GitHub Commit", + "url": "https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66" + }, + { + "title": "GitHub Issue", + "url": "https://github.com/JamesNK/Newtonsoft.Json/issues/2457" + }, + { + "title": "GitHub PR", + "url": "https://github.com/JamesNK/Newtonsoft.Json/pull/2462" + }, + { + "title": "Security Advisory", + "url": "https://alephsecurity.com/vulns/aleph-2018004" + }, + { + "title": "Security Article", + "url": "https://alephsecurity.com/2018/10/22/StackOverflowException/" + } + ], + "severity": "high", + "socialTrendAlert": false, + "title": "Insecure Defaults", + "insights": { + "triageAdvice": "This vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server" + }, + "cvssDetails": [], + "functions_new": [], + "semver": { + "vulnerable": [ + "[,13.0.1)" + ] + }, + "severityWithCritical": "high", + "from": [ + "puma-prey@0.0.0", + "Newtonsoft.Json@10.0.3" + ], + "upgradePath": [ + false, + "Newtonsoft.Json@13.0.1" + ], + "isUpgradable": true, + "isPatchable": false, + "name": "Newtonsoft.Json", + "version": "10.0.3" + } + ], + "ok": false, + "dependencyCount": 10, + "org": "test-org", + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.0\nignore: {}\npatch: {}\n", + "isPrivate": true, + "licensesPolicy": { + "severities": {}, + "orgLicenseRules": { + "AGPL-1.0": { + "licenseType": "AGPL-1.0", + "severity": "high", + "instructions": "" + }, + "AGPL-3.0": { + "licenseType": "AGPL-3.0", + "severity": "high", + "instructions": "" + }, + "Artistic-1.0": { + "licenseType": "Artistic-1.0", + "severity": "medium", + "instructions": "" + }, + "Artistic-2.0": { + "licenseType": "Artistic-2.0", + "severity": "medium", + "instructions": "" + }, + "CDDL-1.0": { + "licenseType": "CDDL-1.0", + "severity": "medium", + "instructions": "" + }, + "CPOL-1.02": { + "licenseType": "CPOL-1.02", + "severity": "high", + "instructions": "" + }, + "EPL-1.0": { + "licenseType": "EPL-1.0", + "severity": "medium", + "instructions": "" + }, + "GPL-2.0": { + "licenseType": "GPL-2.0", + "severity": "high", + "instructions": "" + }, + "GPL-3.0": { + "licenseType": "GPL-3.0", + "severity": "high", + "instructions": "" + }, + "LGPL-2.0": { + "licenseType": "LGPL-2.0", + "severity": "medium", + "instructions": "" + }, + "LGPL-2.1": { + "licenseType": "LGPL-2.1", + "severity": "medium", + "instructions": "" + }, + "LGPL-3.0": { + "licenseType": "LGPL-3.0", + "severity": "medium", + "instructions": "" + }, + "MPL-1.1": { + "licenseType": "MPL-1.1", + "severity": "medium", + "instructions": "" + }, + "MPL-2.0": { + "licenseType": "MPL-2.0", + "severity": "medium", + "instructions": "" + }, + "MS-RL": { + "licenseType": "MS-RL", + "severity": "medium", + "instructions": "" + }, + "SimPL-2.0": { + "licenseType": "SimPL-2.0", + "severity": "high", + "instructions": "" + } + } + }, + "packageManager": "nuget", + "ignoreSettings": { + "adminOnly": false, + "reasonRequired": false, + "disregardFilesystemIgnores": false + }, + "summary": "1 vulnerable dependency path", + "remediation": { + "unresolved": [], + "upgrade": { + "Newtonsoft.Json@10.0.3": { + "upgradeTo": "Newtonsoft.Json@13.0.1", + "upgrades": [ + "Newtonsoft.Json@10.0.3" + ], + "vulns": [ + "SNYK-DOTNET-NEWTONSOFTJSON-2774678" + ] + } + }, + "patch": {}, + "ignore": {}, + "pin": {} + }, + "filesystemPolicy": false, + "filtered": { + "ignore": [], + "patch": [] + }, + "uniqueCount": 1, + "targetFile": "src/Common/packages.config", + "projectName": "puma-prey", + "foundProjectCount": 8, + "displayTargetFile": "src/Common/packages.config", + "path": "/Users/test/source/puma-prey" + } +] \ No newline at end of file From aefe2e56824f2d805275c5a6d9294486fd96cee3 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 21 Sep 2022 12:54:33 -0500 Subject: [PATCH 49/61] bug: write entire sarif log to avoid location index + artifact compare updated bug --- src/Sarif.Converters/SnykOpenSourceConverter.cs | 12 ++++++++---- .../SnykOpenSourceObjectModel/Test.cs | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Sarif.Converters/SnykOpenSourceConverter.cs b/src/Sarif.Converters/SnykOpenSourceConverter.cs index 45488dfc8..7c04d2214 100644 --- a/src/Sarif.Converters/SnykOpenSourceConverter.cs +++ b/src/Sarif.Converters/SnykOpenSourceConverter.cs @@ -33,9 +33,11 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm List snykTests = logReader.ReadLog(input); //Init objects + var log = new SarifLog(); + log.Runs = new List(); var run = new Run(); run.Tool = new Tool(); - var results = new List(); + run.Results = new List(); //Set driver details run.Tool.Driver = CreateDriver(); @@ -53,14 +55,15 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm } //Add result for the rule if does not previously exist (there are duplicates???) - if (!results.Any(i => i.RuleId == vulnerability.Id && i.Locations.Any(l => l.PhysicalLocation.ArtifactLocation.Uri.ToString().Equals(test.DisplayTargetFile)))) + if (!run.Results.Any(i => i.RuleId == vulnerability.Id && i.Locations.Any(l => l.PhysicalLocation.ArtifactLocation.Uri.ToString().Equals(test.DisplayTargetFile)))) { - results.Add(CreateResult(vulnerability, test)); + run.Results.Add(CreateResult(vulnerability, test)); } } } - PersistResults(output, results, run); + log.Runs.Add(run); + PersistResults(output, log); } private ToolComponent CreateDriver() @@ -170,6 +173,7 @@ private Result CreateResult(Vulnerability item, Test test) ArtifactLocation = new ArtifactLocation() { Uri = new Uri(test.DisplayTargetFile, UriKind.Relative), + UriBaseId = "%SRCROOT%", }, Region = new Region() { diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs index e31934727..24fc3739a 100644 --- a/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/Test.cs @@ -15,6 +15,9 @@ public class Test [JsonProperty("displayTargetFile")] public string DisplayTargetFile { get; set; } + [JsonProperty("path")] + public string Path { get; set; } + [JsonProperty("projectName")] public string ProjectName { get; set; } From 9ad77d1a54f26dfe5e6fcfe37fa5d68f2d087174 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 21 Sep 2022 13:16:25 -0500 Subject: [PATCH 50/61] bug: fingerprint package mgr --- src/Sarif.Converters/SnykOpenSourceConverter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sarif.Converters/SnykOpenSourceConverter.cs b/src/Sarif.Converters/SnykOpenSourceConverter.cs index 7c04d2214..91346f915 100644 --- a/src/Sarif.Converters/SnykOpenSourceConverter.cs +++ b/src/Sarif.Converters/SnykOpenSourceConverter.cs @@ -189,6 +189,7 @@ private Result CreateResult(Vulnerability item, Test test) //Set the unique fingerprint var fingerprints = new List() { item.Id, + item.PackageManager, item.PackageName, item.Version, test.DisplayTargetFile, From 0014752a29d16c236786b541c0c567abd41b81e5 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 22 Sep 2022 14:39:27 -0500 Subject: [PATCH 51/61] bug: null help block if no data --- src/Sarif.Converters/GenericSarifConverter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Sarif.Converters/GenericSarifConverter.cs b/src/Sarif.Converters/GenericSarifConverter.cs index a7bb74410..b3d657adf 100644 --- a/src/Sarif.Converters/GenericSarifConverter.cs +++ b/src/Sarif.Converters/GenericSarifConverter.cs @@ -44,6 +44,10 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm { rule.Help.Markdown = rule.Help.Text; } + else if (string.IsNullOrEmpty(rule.Help.Text) && string.IsNullOrEmpty(rule.Help.Markdown)) + { + rule.Help = null; + } } } @@ -51,7 +55,7 @@ public override void Convert(Stream input, IResultLogWriter output, OptionallyEm } PersistResults(output, log); - } + } } } } From c86603b40a3b1df80af174dc46636276cd604dc9 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Thu, 22 Sep 2022 14:44:04 -0500 Subject: [PATCH 52/61] chore: docs --- docs/query-mode.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/query-mode.md b/docs/query-mode.md index a3e486746..7b6f7a2bb 100644 --- a/docs/query-mode.md +++ b/docs/query-mode.md @@ -47,10 +47,12 @@ NOT RuleId = SM00251 OR OccurrenceCount > 10 AND OccurrenceCount < 100 * CorrelationGuid * Guid * HostedViewerUri +* IsSuppressed * Kind * Level * Message.Text * OccurrenceCount +* Properties.[value] * Rank * RuleId * Uri \ No newline at end of file From 245831bdf329e9ecde6f73e6d456c2e8669367a5 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 23 Sep 2022 09:51:24 -0500 Subject: [PATCH 53/61] bug: suppression expiration no expiry logic flaw --- src/Sarif/Core/Result.cs | 4 +- src/Sarif/Visitors/SuppressVisitor.cs | 2 +- .../Visitors/SuppressVisitorTests.cs | 107 +++++++++++++++++- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/Sarif/Core/Result.cs b/src/Sarif/Core/Result.cs index 9abd313fb..b37b195d1 100644 --- a/src/Sarif/Core/Result.cs +++ b/src/Sarif/Core/Result.cs @@ -123,10 +123,10 @@ public bool TryIsSuppressed(out bool isSuppressed, bool checkExpired = false) // https://github.com/microsoft/sarif-tutorials/blob/main/docs/Displaying-results-in-a-viewer.md#determining-suppression-status isSuppressed = !suppressions.Any(s => s.Status == SuppressionStatus.UnderReview || s.Status == SuppressionStatus.Rejected); - // if it is suppressed and are expired + // if we have suppressions, check expiration if (isSuppressed && checkExpired) { - isSuppressed = suppressions.Any(s => (s.TryGetProperty("expiryUtc", out DateTime expiryUtc) && expiryUtc > DateTime.UtcNow) && s.Status == SuppressionStatus.Accepted); + isSuppressed = suppressions.Any(s => (!s.TryGetProperty("expiryUtc", out DateTime noExpiryUtc) || (s.TryGetProperty("expiryUtc", out DateTime expiryUtc) && expiryUtc > DateTime.UtcNow)) && s.Status == SuppressionStatus.Accepted); } return true; diff --git a/src/Sarif/Visitors/SuppressVisitor.cs b/src/Sarif/Visitors/SuppressVisitor.cs index f53ce07de..e4234c39a 100644 --- a/src/Sarif/Visitors/SuppressVisitor.cs +++ b/src/Sarif/Visitors/SuppressVisitor.cs @@ -48,7 +48,7 @@ public override Result VisitResult(Result node) // Skip if node is already suppressed bool isSuppressed = false; - if (node.TryIsSuppressed(out isSuppressed) && isSuppressed) + if (node.TryIsSuppressed(out isSuppressed, true) && isSuppressed) { return base.VisitResult(node); } diff --git a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs index 8c08dccfa..c4d4cbfb4 100644 --- a/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs +++ b/src/Test.UnitTests.Sarif/Visitors/SuppressVisitorTests.cs @@ -243,7 +243,7 @@ public void SuppressVisitor_ShouldFlowPropertiesCorrectly_Empty_Result_Guids() } [Fact] - public void SuppressVisitor_ShouldNotDuplicateEntries() + public void SuppressVisitor_ShouldNotDuplicateEntries_NoExpiry() { var testCase = new { @@ -291,6 +291,111 @@ public void SuppressVisitor_ShouldNotDuplicateEntries() } + [Fact] + public void SuppressVisitor_ShouldNotDuplicateEntries_ValidExpiry() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = false, + expiryInDays = 3, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + var random = new Random(); + SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); + + var visitor = new SuppressVisitor(testCase.justification, + testCase.alias, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + + SarifLog suppressed = visitor.VisitSarifLog(current); + IList results = suppressed.Runs[0].Results; + + // Verify suppression was added + foreach (Result result in results) + { + result.Suppressions.Should().NotBeNullOrEmpty(); + result.Suppressions.Count.Should().Be(1); + } + + // Suppress a second time + SarifLog suppressed2 = visitor.VisitSarifLog(suppressed); + IList results2 = suppressed2.Runs[0].Results; + + // Verify duplicate suppression was not added + foreach (Result result in results2) + { + result.Suppressions.Should().NotBeNullOrEmpty(); + result.Suppressions.Count.Should().Be(1); + } + + } + + [Fact] + public void SuppressVisitor_ShouldDuplicateEntries_InvalidExpiry() + { + var testCase = new + { + alias = "some alias", + justification = "some suppress justification", + uuids = true, + timestamps = false, + expiryInDays = 1, + suppressionStatus = SuppressionStatus.Accepted, + resultGuids = default(List), + }; + + var random = new Random(); + SarifLog current = RandomSarifLogGenerator.GenerateSarifLogWithRuns(random, runCount: 1, resultCount: 1); + + var visitor = new SuppressVisitor(testCase.justification, + testCase.alias, + testCase.uuids, + testCase.timestamps, + testCase.expiryInDays, + testCase.suppressionStatus, + testCase.resultGuids); + + SarifLog suppressed = visitor.VisitSarifLog(current); + IList results = suppressed.Runs[0].Results; + + // Verify suppression was added + foreach (Result result in results) + { + result.Suppressions.Should().NotBeNullOrEmpty(); + result.Suppressions.Count.Should().Be(1); + } + + // Force suppressions to be expired + foreach (Result result in results) + { + foreach (Suppression suppression in result.Suppressions) + { + suppression.SetProperty("expiryUtc", DateTime.UtcNow.AddDays(-1)); + } + } + + // Suppress a second time + SarifLog suppressed2 = visitor.VisitSarifLog(suppressed); + IList results2 = suppressed2.Runs[0].Results; + + // Verify new suppression not added + foreach (Result result in results2) + { + result.Suppressions.Should().NotBeNullOrEmpty(); + result.Suppressions.Count.Should().Be(2); + } + + } + private static void VerifySuppressVisitor(string alias, string justification, bool uuids, From d93fa1947a9164ecc8a79fe185f967cbb12020d7 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Fri, 23 Sep 2022 09:59:26 -0500 Subject: [PATCH 54/61] docs: release notes + query mode updates --- docs/query-mode.md | 3 ++- src/ReleaseHistory.md | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/query-mode.md b/docs/query-mode.md index 7b6f7a2bb..9cebdb051 100644 --- a/docs/query-mode.md +++ b/docs/query-mode.md @@ -52,7 +52,8 @@ NOT RuleId = SM00251 OR OccurrenceCount > 10 AND OccurrenceCount < 100 * Level * Message.Text * OccurrenceCount -* Properties.[value] +* properties.[value] * Rank +* rule.properties.[value] * RuleId * Uri \ No newline at end of file diff --git a/src/ReleaseHistory.md b/src/ReleaseHistory.md index 95911ee11..b5bf61557 100644 --- a/src/ReleaseHistory.md +++ b/src/ReleaseHistory.md @@ -7,6 +7,8 @@ * BUGFIX: Update `merge` command to properly produce runs by tool and version when passed the `--merge-runs` argument. [#2488](https://github.com/microsoft/sarif-sdk/pull/2488) * BUGFIX: Eliminate `IOException` and `DirectoryNotFoundException` exceptions thrown by `merge` command when splitting by rule (due to invalid file characters in rule ids). [#2513](https://github.com/microsoft/sarif-sdk/pull/2513) * BUGFIX: Fix classes inside NotYetAutoGenerated folder missing `virtual` keyword for public methods and properties, by regenerate and manually sync the changes. [#2537](https://github.com/microsoft/sarif-sdk/pull/2537) +* FEATURE: Enhancement to the `suppress` command to better support auditing results. New argument `--expression` provides the capability to suppress all results matching the expression. New argument `--results-guids` provides the capability to suppress one to many results by the `guid` value. With this update, previously suppressed (non-expired) results will not be suppressed again. [#2530](https://github.com/microsoft/sarif-sdk/pull/2530) +* FEATURE: Enhancement to the `query` command adding a new `IsSuppressed` expression option. This query expression allows auditors to filter results based on their suppression status. The expression finds all suppressed (non-expired) results. [#2530](https://github.com/microsoft/sarif-sdk/pull/2530) ## **v3.1.0** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/3.1.0) | [Driver](https://www.nuget.org/packages/Sarif.Driver/3.1.0) | [Converters](https://www.nuget.org/packages/Sarif.Converters/3.1.0) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/3.1.0) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/3.1.0) From d3f477ca4b26b9181a2edb25b1e2c34ec6393439 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 25 Oct 2022 05:08:50 -0500 Subject: [PATCH 55/61] bug: snyk report single test support --- .../SnykOpenSourceReader.cs | 21 +- .../SnykOpenSourceConverterTests.cs | 8 + .../Test.UnitTests.Sarif.Converters.csproj | 1 + .../ExpectedOutputs/ValidResults.sarif | 73 +++--- .../Inputs/ValidResult.json | 212 ++++++++++++++++++ 5 files changed, 274 insertions(+), 41 deletions(-) create mode 100644 src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResult.json diff --git a/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs b/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs index 69eae2eba..4dc945059 100644 --- a/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs +++ b/src/Sarif.Converters/SnykOpenSourceObjectModel/SnykOpenSourceReader.cs @@ -5,6 +5,7 @@ using System.IO; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Microsoft.CodeAnalysis.Sarif.Converters.SnykOpenSourceObjectModel { @@ -19,7 +20,25 @@ public override List ReadLog(Stream input) reportData = streamReader.ReadToEnd(); } - return JsonConvert.DeserializeObject>(reportData); + //Parse JSON + var token = JToken.Parse(reportData); + + //Return object + var tests = new List(); + + //Check start token for object type + //Handle appropriately + if (token is JObject) + { + var test = token.ToObject(); + tests.Add(test); + } + else if (token is JArray) + { + tests.AddRange(token.ToObject>()); + } + + return tests; } } } diff --git a/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs b/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs index 5be823a09..238eadc00 100644 --- a/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs +++ b/src/Test.UnitTests.Sarif.Converters/SnykOpenSourceConverterTests.cs @@ -51,6 +51,14 @@ public void Converter_WhenInputContainsValidResults_ReturnsExpectedOutput() RunTestCase(input, expectedOutput); } + [Fact] + public void Converter_WhenInputContainsValidResult_ReturnsExpectedOutput() + { + string input = Extractor.GetResourceInputText("ValidResult.json"); + string expectedOutput = Extractor.GetResourceExpectedOutputsText("ValidResults.sarif"); + RunTestCase(input, expectedOutput); + } + private static readonly TestAssetResourceExtractor Extractor = new TestAssetResourceExtractor(typeof(SnykOpenSourceConverterTests)); private const string ResourceNamePrefix = ToolFormat.SnykOpenSource; diff --git a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj index 85d6f8f59..e697649da 100644 --- a/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj +++ b/src/Test.UnitTests.Sarif.Converters/Test.UnitTests.Sarif.Converters.csproj @@ -119,6 +119,7 @@ + diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif index 19531e5e5..826c7e799 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/ExpectedOutputs/ValidResults.sarif @@ -3,6 +3,37 @@ "version": "2.1.0", "runs": [ { + "tool": { + "driver": { + "name": "SnykOpenSource", + "fullName": "SnykOpenSource", + "version": "1.0.0", + "semanticVersion": "1.0.0", + "informationUri": "https://docs.snyk.io/products/snyk-open-source", + "rules": [ + { + "id": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", + "name": "Newtonsoft.Json@10.0.3", + "fullDescription": { + "text": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n", + "markdown": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n" + }, + "help": { + "text": "References:\nGitHub Commit: https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66\nGitHub Issue: https://github.com/JamesNK/Newtonsoft.Json/issues/2457\nGitHub PR: https://github.com/JamesNK/Newtonsoft.Json/pull/2462\nSecurity Advisory: https://alephsecurity.com/vulns/aleph-2018004\nSecurity Article: https://alephsecurity.com/2018/10/22/StackOverflowException/\n\nTriage Advice:\nThis vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server", + "markdown": "References:\nGitHub Commit: https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66\nGitHub Issue: https://github.com/JamesNK/Newtonsoft.Json/issues/2457\nGitHub PR: https://github.com/JamesNK/Newtonsoft.Json/pull/2462\nSecurity Advisory: https://alephsecurity.com/vulns/aleph-2018004\nSecurity Article: https://alephsecurity.com/2018/10/22/StackOverflowException/\n\nTriage Advice:\nThis vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server" + }, + "shortDescription": { + "text": "Insecure Defaults in Newtonsoft.Json@10.0.3", + "markdown": "Insecure Defaults in Newtonsoft.Json@10.0.3" + }, + "properties": { + "security-severity": 7.5, + "tags": ["security","nuget","CWE-755"] + } + } + ] + } + }, "results": [ { "ruleId": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", @@ -15,7 +46,7 @@ "physicalLocation": { "artifactLocation": { "uri": "src/Common/packages.config", - "index": 0 + "uriBaseId": "%SRCROOT%" }, "region": { "startLine": 1 @@ -24,7 +55,7 @@ } ], "fingerprints": { - "0": "8ea1e915fad81b081b2a960f83b28f71708dd67b03e724224a4e9fbc1bb72773" + "0": "5159c77798702b76a3298bb89325f876afa9776ec89ba94a1345c9edf96d3a49" }, "rank": 7.5, "properties": { @@ -42,44 +73,6 @@ } } ], - "tool": { - "driver": { - "name": "SnykOpenSource", - "fullName": "SnykOpenSource", - "version": "1.0.0", - "semanticVersion": "1.0.0", - "informationUri": "https://docs.snyk.io/products/snyk-open-source", - "rules": [ - { - "id": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", - "name": "Newtonsoft.Json@10.0.3", - "fullDescription": { - "text": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n", - "markdown": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n" - }, - "help": { - "text": "References:\nGitHub Commit: https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66\nGitHub Issue: https://github.com/JamesNK/Newtonsoft.Json/issues/2457\nGitHub PR: https://github.com/JamesNK/Newtonsoft.Json/pull/2462\nSecurity Advisory: https://alephsecurity.com/vulns/aleph-2018004\nSecurity Article: https://alephsecurity.com/2018/10/22/StackOverflowException/\n\nTriage Advice:\nThis vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server", - "markdown": "References:\nGitHub Commit: https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66\nGitHub Issue: https://github.com/JamesNK/Newtonsoft.Json/issues/2457\nGitHub PR: https://github.com/JamesNK/Newtonsoft.Json/pull/2462\nSecurity Advisory: https://alephsecurity.com/vulns/aleph-2018004\nSecurity Article: https://alephsecurity.com/2018/10/22/StackOverflowException/\n\nTriage Advice:\nThis vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server" - }, - "shortDescription": { - "text": "Insecure Defaults in Newtonsoft.Json@10.0.3", - "markdown": "Insecure Defaults in Newtonsoft.Json@10.0.3" - }, - "properties": { - "security-severity": 7.5, - "tags": ["security","nuget","CWE-755"] - } - } - ] - } - }, - "artifacts": [ - { - "location": { - "uri": "src/Common/packages.config" - } - } - ], "columnKind": "utf16CodeUnits" } ] diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResult.json b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResult.json new file mode 100644 index 000000000..d608429b3 --- /dev/null +++ b/src/Test.UnitTests.Sarif.Converters/TestData/SnykOpenSourceConverter/Inputs/ValidResult.json @@ -0,0 +1,212 @@ +{ + "vulnerabilities": [ + { + "CVSSv3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:P", + "alternativeIds": [], + "creationTime": "2022-04-24T08:33:35.726602Z", + "credit": [ + "Gil Mirmovitch" + ], + "cvssScore": 7.5, + "description": "## Overview\n\nAffected versions of this package are vulnerable to Insecure Defaults due to improper handling of StackOverFlow exception (SOE) whenever nested expressions are being processed. Exploiting this vulnerability results in Denial Of Service (DoS), and it is exploitable when an attacker sends 5 requests that cause SOE in time frame of 5 minutes.\r\n\r\n**Note:**\r\nThis vulnerability is only applicable to systems deployed on IIS (Internet Information Services) web-server\r\n\r\n## PoC:\r\n\r\n```js\r\n\r\nusing System;\r\n\r\nusing System.IO;\r\n\r\nusing System.Linq;\r\n\r\nusing Newtonsoft.Json;\r\n\r\nusing Newtonsoft.Json.Linq;\r\n\r\n\r\nnamespace JsonTests\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)\r\n {\r\n //Create a string representation of an highly nested object (JSON serialized)\r\n int nRep = 24000;\r\n string json = string.Concat(Enumerable.Repeat(\"{a:\", nRep)) + \"1\" +\r\n string.Concat(Enumerable.Repeat(\"}\", nRep));\r\n\r\n //Parse this object (Parsing works well - no exception is being thrown)\r\n var parsedJson = JObject.Parse(json);\r\n \r\n using (var ms = new MemoryStream())\r\n using (var sWriter = new StreamWriter(ms))\r\n using (var jWriter = new JsonTextWriter(sWriter))\r\n {\r\n //Trying to serialize the object will result in StackOverflowException !!!\r\n parsedJson.WriteTo(jWriter);\r\n }\r\n\r\n //ToString throws StackOverflowException as well (ToString is very unefficient - even for smaller payloads, it will occupy a lot of CPU & Memory)\r\n //parsedJson.ToString();\r\n\r\n //JsonConvert.SerializeObject throws StackOverflowException as well\r\n //string a = JsonConvert.SerializeObject(parsedJson);\r\n\r\n }\r\n }\r\n}\r\n\r\n```\n## Remediation\nUpgrade `Newtonsoft.Json` to version 13.0.1 or higher.\n## References\n- [GitHub Commit](https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66)\n- [GitHub Issue](https://github.com/JamesNK/Newtonsoft.Json/issues/2457)\n- [GitHub PR](https://github.com/JamesNK/Newtonsoft.Json/pull/2462)\n- [Security Advisory](https://alephsecurity.com/vulns/aleph-2018004)\n- [Security Article](https://alephsecurity.com/2018/10/22/StackOverflowException/)\n", + "disclosureTime": "2022-04-24T08:33:33Z", + "exploit": "Proof of Concept", + "functions": [], + "fixedIn": [ + "13.0.1" + ], + "id": "SNYK-DOTNET-NEWTONSOFTJSON-2774678", + "identifiers": { + "CWE": [ + "CWE-755" + ], + "GHSA": [ + "GHSA-5crp-9r3c-p9vr" + ], + "CVE": [] + }, + "language": "dotnet", + "malicious": false, + "modificationTime": "2022-08-08T07:46:17.314095Z", + "moduleName": "newtonsoft.json", + "packageManager": "nuget", + "packageName": "Newtonsoft.Json", + "patches": [], + "proprietary": false, + "publicationTime": "2022-04-24T10:58:25Z", + "references": [ + { + "title": "GitHub Commit", + "url": "https://github.com/JamesNK/Newtonsoft.Json/commit/7e77bbe1beccceac4fc7b174b53abfefac278b66" + }, + { + "title": "GitHub Issue", + "url": "https://github.com/JamesNK/Newtonsoft.Json/issues/2457" + }, + { + "title": "GitHub PR", + "url": "https://github.com/JamesNK/Newtonsoft.Json/pull/2462" + }, + { + "title": "Security Advisory", + "url": "https://alephsecurity.com/vulns/aleph-2018004" + }, + { + "title": "Security Article", + "url": "https://alephsecurity.com/2018/10/22/StackOverflowException/" + } + ], + "severity": "high", + "socialTrendAlert": false, + "title": "Insecure Defaults", + "insights": { + "triageAdvice": "This vulnerability is only applicable on systems deployed on IIS (Internet Information Services) web-server" + }, + "cvssDetails": [], + "functions_new": [], + "semver": { + "vulnerable": [ + "[,13.0.1)" + ] + }, + "severityWithCritical": "high", + "from": [ + "puma-prey@0.0.0", + "Newtonsoft.Json@10.0.3" + ], + "upgradePath": [ + false, + "Newtonsoft.Json@13.0.1" + ], + "isUpgradable": true, + "isPatchable": false, + "name": "Newtonsoft.Json", + "version": "10.0.3" + } + ], + "ok": false, + "dependencyCount": 10, + "org": "test-org", + "policy": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.25.0\nignore: {}\npatch: {}\n", + "isPrivate": true, + "licensesPolicy": { + "severities": {}, + "orgLicenseRules": { + "AGPL-1.0": { + "licenseType": "AGPL-1.0", + "severity": "high", + "instructions": "" + }, + "AGPL-3.0": { + "licenseType": "AGPL-3.0", + "severity": "high", + "instructions": "" + }, + "Artistic-1.0": { + "licenseType": "Artistic-1.0", + "severity": "medium", + "instructions": "" + }, + "Artistic-2.0": { + "licenseType": "Artistic-2.0", + "severity": "medium", + "instructions": "" + }, + "CDDL-1.0": { + "licenseType": "CDDL-1.0", + "severity": "medium", + "instructions": "" + }, + "CPOL-1.02": { + "licenseType": "CPOL-1.02", + "severity": "high", + "instructions": "" + }, + "EPL-1.0": { + "licenseType": "EPL-1.0", + "severity": "medium", + "instructions": "" + }, + "GPL-2.0": { + "licenseType": "GPL-2.0", + "severity": "high", + "instructions": "" + }, + "GPL-3.0": { + "licenseType": "GPL-3.0", + "severity": "high", + "instructions": "" + }, + "LGPL-2.0": { + "licenseType": "LGPL-2.0", + "severity": "medium", + "instructions": "" + }, + "LGPL-2.1": { + "licenseType": "LGPL-2.1", + "severity": "medium", + "instructions": "" + }, + "LGPL-3.0": { + "licenseType": "LGPL-3.0", + "severity": "medium", + "instructions": "" + }, + "MPL-1.1": { + "licenseType": "MPL-1.1", + "severity": "medium", + "instructions": "" + }, + "MPL-2.0": { + "licenseType": "MPL-2.0", + "severity": "medium", + "instructions": "" + }, + "MS-RL": { + "licenseType": "MS-RL", + "severity": "medium", + "instructions": "" + }, + "SimPL-2.0": { + "licenseType": "SimPL-2.0", + "severity": "high", + "instructions": "" + } + } + }, + "packageManager": "nuget", + "ignoreSettings": { + "adminOnly": false, + "reasonRequired": false, + "disregardFilesystemIgnores": false + }, + "summary": "1 vulnerable dependency path", + "remediation": { + "unresolved": [], + "upgrade": { + "Newtonsoft.Json@10.0.3": { + "upgradeTo": "Newtonsoft.Json@13.0.1", + "upgrades": [ + "Newtonsoft.Json@10.0.3" + ], + "vulns": [ + "SNYK-DOTNET-NEWTONSOFTJSON-2774678" + ] + } + }, + "patch": {}, + "ignore": {}, + "pin": {} + }, + "filesystemPolicy": false, + "filtered": { + "ignore": [], + "patch": [] + }, + "uniqueCount": 1, + "targetFile": "src/Common/packages.config", + "projectName": "puma-prey", + "foundProjectCount": 8, + "displayTargetFile": "src/Common/packages.config", + "path": "/Users/test/source/puma-prey" +} \ No newline at end of file From e27540a1455913482ccf13822f21806d18f5927e Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 25 Oct 2022 05:56:43 -0500 Subject: [PATCH 56/61] feat: version suffix build prop --- src/build.props | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/build.props b/src/build.props index e3af94379..961aae46c 100644 --- a/src/build.props +++ b/src/build.props @@ -11,6 +11,7 @@ Microsoft SARIF SDK © Microsoft Corporation. All rights reserved. 3.1.0 + 3.1.0-beta1 @@ -49,23 +50,23 @@ + Label="Debug build"> full false DEBUG;TRACE;CODE_ANALYSIS + Label="Release build"> portable true TRACE + Label="x86 build"> x86 + Label="x64 build"> x64 @@ -119,4 +120,4 @@ runtime; build; native; contentfiles; analyzers - + \ No newline at end of file From f8a7a4637eb621ec2ce6e0ca82d88267dec708fa Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 25 Oct 2022 16:02:48 -0500 Subject: [PATCH 57/61] feat: gh property tags --- src/Sarif.Converters/CisCatConverter.cs | 89 +++++++++++++------ src/Sarif.Converters/NessusConverter.cs | 22 +++++ .../ExpectedOutputs/ValidResults.sarif | 11 ++- .../ExpectedOutputs/ValidResults.sarif | 15 ++-- .../Inputs/ValidResults.nessus.xml | 2 + 5 files changed, 107 insertions(+), 32 deletions(-) diff --git a/src/Sarif.Converters/CisCatConverter.cs b/src/Sarif.Converters/CisCatConverter.cs index 90c21c8a1..da98e93ca 100644 --- a/src/Sarif.Converters/CisCatConverter.cs +++ b/src/Sarif.Converters/CisCatConverter.cs @@ -93,6 +93,27 @@ internal ReportingDescriptor CreateReportDescriptor(CisCatRule rule) Markdown = rule.RuleTitle, }; + //Use for GH Security Advisories + //set result level and rank (Critical - Low risk rating) + FailureLevel level = FailureLevel.None; + ResultKind kind = ResultKind.None; + double rank = RankConstants.None; + getResultSeverity(rule.Result, out level, out kind, out rank); + + //Create only if a valid is assigned + if (rank != RankConstants.None) + { + descriptor.SetProperty("security-severity", rank); + } + + //Tags for GH filtering + var tags = new List() + { + "security", + }; + + descriptor.SetProperty("tags", tags); + return descriptor; } @@ -105,45 +126,63 @@ internal Result CreateResult(CisCatRule rule) Message = new Message { Text = rule.RuleTitle }, }; + //set result kind, level and rank (Critical - Low risk rating) + FailureLevel level = FailureLevel.None; + ResultKind kind = ResultKind.None; + double rank = RankConstants.None; + getResultSeverity(rule.Result, out level, out kind, out rank); + + //Set result object data + result.Level = level; + result.Kind = kind; + result.Rank = rank; + + //Set the unique fingerprint + result.Fingerprints = new Dictionary(); + result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); + + return result; + } + + private void getResultSeverity(string result, out FailureLevel level, out ResultKind kind, out double rank) + { + // Default values + level = FailureLevel.None; + kind = ResultKind.None; + rank = RankConstants.None; + //Kind & Level determine the status //Result: "fail": Level = Error, Kind = Fail //Result: "info|notchecked|pass|unknown": Level = None, Kind = Informational|NotApplicable|Pass|Review - switch (rule.Result) + switch (result) { - //PASS CASES ARE NOT INCLUDED IN THE RESULTS, AS MATCH FORWARD DOES NOT PRODUCE - //THE CORRECT ABSENT / NEW STATES WHEN THEY EXIST - // case "pass": - // result.Level = FailureLevel.None; - // result.Kind = ResultKind.Pass; - // break; + case "pass": + level = FailureLevel.None; + kind = ResultKind.Pass; + rank = RankConstants.None; + break; case "fail": - result.Level = FailureLevel.Error; - result.Kind = ResultKind.Fail; - result.Rank = RankConstants.High; + level = FailureLevel.Error; + kind = ResultKind.Fail; + rank = RankConstants.High; break; case "notchecked": - result.Level = FailureLevel.None; - result.Kind = ResultKind.NotApplicable; - result.Rank = RankConstants.None; + level = FailureLevel.None; + kind = ResultKind.NotApplicable; + rank = RankConstants.None; break; case "informational": - result.Level = FailureLevel.None; - result.Kind = ResultKind.Informational; - result.Rank = RankConstants.None; + level = FailureLevel.None; + kind = ResultKind.Informational; + rank = RankConstants.None; break; case "unknown": default: - result.Level = FailureLevel.Warning; - result.Kind = ResultKind.Fail; - result.Rank = RankConstants.Medium; + level = FailureLevel.Warning; + kind = ResultKind.Fail; + rank = RankConstants.Medium; break; }; - - //Set the unique fingerprint - result.Fingerprints = new Dictionary(); - result.Fingerprints.Add("0", HashUtilities.ComputeSha256HashValue(rule.RuleId).ToLower()); - - return result; } } } diff --git a/src/Sarif.Converters/NessusConverter.cs b/src/Sarif.Converters/NessusConverter.cs index cf4da5e32..302811f79 100644 --- a/src/Sarif.Converters/NessusConverter.cs +++ b/src/Sarif.Converters/NessusConverter.cs @@ -117,6 +117,28 @@ private ReportingDescriptor CreateReportDescriptor(ReportItem item) descriptor.SetProperty("pluginPublicationDate", item.PluginPublicationDate); descriptor.SetProperty("pluginType", item.PluginType); + //Use for GH Security Advisories + //set result level and rank (Critical - Low risk rating) + //ignoring risk factor (H/M/L) as it conflicts with severity + //cvss3 base score overrides severity + FailureLevel level = FailureLevel.None; + double rank = RankConstants.None; + getResultSeverity(item.Cvss3BaseScore, item.Severity, out level, out rank); + descriptor.SetProperty("security-severity", rank); + + //Tags for GH filtering + var tags = new List() + { + "security", + }; + + if (item.Cves.Any()) + { + tags.AddRange(item.Cves); + } + + descriptor.SetProperty("tags", tags); + return descriptor; } diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif index b025a8d93..952eb5ee3 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/CisCatConverter/ExpectedOutputs/ValidResults.sarif @@ -7,13 +7,13 @@ { "ruleId": "rule.id.2", "level": "error", - "rank": 8.0, "message": { "text": "Rule id 2 title" }, "fingerprints": { "0": "d23eea429d06cfe703ab69b8d0d0c1abd41fc9e26b4167575e3356733c97bf61" - } + }, + "rank": 8.0 } ], "tool": { @@ -34,6 +34,9 @@ "help": { "text": "Rule id 1 title", "markdown": "Rule id 1 title" + }, + "properties": { + "tags": ["security"] } }, { @@ -46,6 +49,10 @@ "help": { "text": "Rule id 2 title", "markdown": "Rule id 2 title" + }, + "properties": { + "security-severity": 8.0, + "tags": ["security"] } } ], diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif index 5f41dff1a..a534bf207 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/ExpectedOutputs/ValidResults.sarif @@ -28,7 +28,9 @@ "pluginFamily": "Family Name", "pluginModificationDate": "2022/08/15", "pluginPublicationDate": "2021/08/15", - "pluginType": "remote" + "pluginType": "remote", + "security-severity": 3.0, + "tags": ["security"] } } ], @@ -41,13 +43,13 @@ { "ruleId": "12345", "level": "note", - "rank": 3.0, "message": { "text": "plugin output text" }, "fingerprints": { "0": "1e6bb36ff29ed6c40f06c82791515391820f7b546e700886adbd88b2c897279c" }, + "rank": 3.0, "properties": { "port": "99999", "protocol": "tcp", @@ -85,7 +87,9 @@ "pluginFamily": "Family Name", "pluginModificationDate": "2022/08/15", "pluginPublicationDate": "2021/08/15", - "pluginType": "remote" + "pluginType": "remote", + "security-severity": 9.0, + "tags": ["security","CVE-2018-10858","CVE-2018-10919"] } } ], @@ -98,13 +102,13 @@ { "ruleId": "12345", "level": "error", - "rank": 9.0, "message": { "text": "plugin output" }, "fingerprints": { "0": "c630cd9cdc8784d7ecdbbbe2ae5b6e0e30609841d2df1cef336b5e646391d876" }, + "rank": 9.0, "properties": { "port": "0", "protocol": "tcp", @@ -113,7 +117,8 @@ "solution": "Solution text or generate a proper SSL certificate for this service.", "severity": "2", "cvss3BaseScore": "9.0", - "cvss3Vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N" + "cvss3Vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "cve": ["CVE-2018-10858","CVE-2018-10919"] } } ], diff --git a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml index 0f67ecbac..bb162aba9 100644 --- a/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml +++ b/src/Test.UnitTests.Sarif.Converters/TestData/NessusConverter/Inputs/ValidResults.nessus.xml @@ -76,6 +76,8 @@ AV:N/AC:L/AU:N/C:P/I:P/A:N description text Synopis text + CVE-2018-10858 + CVE-2018-10919 https://github.com/microsoft/sarif-sdk High 1.0 From 88107e008d1ba80a16ddd8c9aa87910e71f4ed90 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 25 Oct 2022 20:51:15 -0500 Subject: [PATCH 58/61] chore: query docs --- docs/query-mode.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/query-mode.md b/docs/query-mode.md index a3e486746..9cebdb051 100644 --- a/docs/query-mode.md +++ b/docs/query-mode.md @@ -47,10 +47,13 @@ NOT RuleId = SM00251 OR OccurrenceCount > 10 AND OccurrenceCount < 100 * CorrelationGuid * Guid * HostedViewerUri +* IsSuppressed * Kind * Level * Message.Text * OccurrenceCount +* properties.[value] * Rank +* rule.properties.[value] * RuleId * Uri \ No newline at end of file From 6cf080a7e2bc69fce8ac771593877deb86cff41d Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Tue, 25 Oct 2022 20:51:37 -0500 Subject: [PATCH 59/61] feat: date time property evaluator --- .../Query/Evaluators/DateTimeEvaluator.cs | 100 ++++++++++++++++++ .../Query/Evaluators/EvaluatorFactory.cs | 4 + .../PropertyBagPropertyEvaluator.cs | 21 +++- .../QueryCommandTests.cs | 32 ++++++ ...t.UnitTests.Sarif.Multitool.Library.csproj | 5 +- .../QueryCommand/WithProperties.sarif | 48 +++++++++ 6 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 src/Sarif/Query/Evaluators/DateTimeEvaluator.cs create mode 100644 src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif diff --git a/src/Sarif/Query/Evaluators/DateTimeEvaluator.cs b/src/Sarif/Query/Evaluators/DateTimeEvaluator.cs new file mode 100644 index 000000000..6b1ed99e7 --- /dev/null +++ b/src/Sarif/Query/Evaluators/DateTimeEvaluator.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.Sarif.Query.Evaluators +{ + /// + /// DateTimeEvaluator implements IExpressionEvaluator given a getter which can + /// get the desired Property Name as a DateTime. + /// + /// Usage: + /// if (String.Equals(term.PropertyName, "ID", StringComparison.OrdinalIgnoreCase)) + /// { + /// // Show the DateTimeEvaluator how to get the 'Timestamp' property as a DateTime, and it'll implement the term matching. + /// return new DateTimeEvaluator<Result>(result => result.ID, term); + /// } + /// + /// Type of Item Evaluator will evaluate. + public class DateTimeEvaluator : IExpressionEvaluator + { + private readonly Func _getter; + private readonly DateTime _value; + private readonly Action, BitArray> _evaluateSet; + + public DateTimeEvaluator(Func getter, TermExpression term) + { + _getter = getter; + + if (!DateTime.TryParse(term.Value, out DateTime parsedValue)) { throw new QueryParseException($"{term} value {term.Value} was not a valid DateTime format."); } + _value = parsedValue; + + _evaluateSet = Comparer(term); + } + + public void Evaluate(ICollection list, BitArray matches) + { + _evaluateSet(list, matches); + } + + private Action, BitArray> Comparer(TermExpression term) + { + switch (term.Operator) + { + case CompareOperator.Equals: + return EvaluateEquals; + case CompareOperator.NotEquals: + return EvaluateNotEquals; + case CompareOperator.LessThan: + return EvaluateLessThan; + case CompareOperator.GreaterThan: + return EvaluateGreaterThan; + default: + throw new QueryParseException($"{term} does not support operator {term.Operator}"); + } + } + + private void EvaluateEquals(ICollection list, BitArray matches) + { + int i = 0; + foreach (T item in list) + { + matches.Set(i, _getter(item).Equals(_value)); + i++; + } + } + + private void EvaluateNotEquals(ICollection list, BitArray matches) + { + int i = 0; + foreach (T item in list) + { + matches.Set(i, !_getter(item).Equals(_value)); + i++; + } + } + + private void EvaluateLessThan(ICollection list, BitArray matches) + { + int i = 0; + foreach (T item in list) + { + matches.Set(i, _getter(item) < _value); + i++; + } + } + + private void EvaluateGreaterThan(ICollection list, BitArray matches) + { + int i = 0; + foreach (T item in list) + { + matches.Set(i, _getter(item) > _value); + i++; + } + } + } +} \ No newline at end of file diff --git a/src/Sarif/Query/Evaluators/EvaluatorFactory.cs b/src/Sarif/Query/Evaluators/EvaluatorFactory.cs index 3fc495456..3489362d1 100644 --- a/src/Sarif/Query/Evaluators/EvaluatorFactory.cs +++ b/src/Sarif/Query/Evaluators/EvaluatorFactory.cs @@ -68,6 +68,10 @@ public static object BuildPrimitiveEvaluator(Type fieldType, TermExpression term { return new LongEvaluator(value => value, term); } + else if (fieldType == typeof(DateTime)) + { + return new DateTimeEvaluator(value => value, term); + } else if (fieldType == typeof(string)) { // Default StringComparison only diff --git a/src/Sarif/Query/Evaluators/PropertyBagPropertyEvaluator.cs b/src/Sarif/Query/Evaluators/PropertyBagPropertyEvaluator.cs index ab5b3b103..92068d817 100644 --- a/src/Sarif/Query/Evaluators/PropertyBagPropertyEvaluator.cs +++ b/src/Sarif/Query/Evaluators/PropertyBagPropertyEvaluator.cs @@ -58,10 +58,15 @@ public PropertyBagPropertyEvaluator(TermExpression term) // and numbers. If the value being compared parses as a number, assume that a numeric // comparison was intended, and create a numeric evaluator. Otherwise, create a string evaluator. // This could cause problems if the comparand is string that happens to look like a number. - private IExpressionEvaluator CreateEvaluator(TermExpression term) => - IsStringComparison(term) - ? new StringEvaluator(GetProperty, term, StringComparison.OrdinalIgnoreCase) as IExpressionEvaluator - : new DoubleEvaluator(GetProperty, term); + private IExpressionEvaluator CreateEvaluator(TermExpression term) + { + if (IsDateTimeComparison(term)) + return new DateTimeEvaluator(GetProperty, term); + else if (IsDoubleComparison(term)) + return new DoubleEvaluator(GetProperty, term); + else + return new StringEvaluator(GetProperty, term, StringComparison.OrdinalIgnoreCase) as IExpressionEvaluator; + } private static readonly ReadOnlyCollection s_stringSpecificOperators = new ReadOnlyCollection( @@ -73,7 +78,13 @@ private IExpressionEvaluator CreateEvaluator(TermExpression term) => }); private bool IsStringComparison(TermExpression term) - => s_stringSpecificOperators.Contains(term.Operator) || !double.TryParse(term.Value, out _); + => s_stringSpecificOperators.Contains(term.Operator); + + private bool IsDoubleComparison(TermExpression term) + => double.TryParse(term.Value, out _); + + private bool IsDateTimeComparison(TermExpression term) + => DateTime.TryParse(term.Value, out _); private T GetProperty(Result result) { diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs index 2dfce1018..54968ff23 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs @@ -51,6 +51,38 @@ public void QueryCommand_Basics() Assert.Equal(expected, actual); } + [Fact] + public void QueryCommand_Properties() + { + string filePath = "WithProperties.sarif"; + File.WriteAllText(filePath, s_extractor.GetResourceText(filePath)); + + // rule filter: string + RunAndVerifyCount(1, new QueryOptions() { Expression = "rule.properties.cwe == 'CWE-755'", InputFilePath = filePath }); + + // rule filter: double + RunAndVerifyCount(1, new QueryOptions() { Expression = "rule.properties.security-severity > 7.0", InputFilePath = filePath }); + + // rule filter: date time + RunAndVerifyCount(1, new QueryOptions() { Expression = "rule.properties.vulnPublicationDate > '2022-04-01T00:00:00'", InputFilePath = filePath }); + + // rule filter: date time + RunAndVerifyCount(1, new QueryOptions() { Expression = "rule.properties.vulnPublicationDate < '2022-04-30T00:00:00'", InputFilePath = filePath }); + + // result filter: string + RunAndVerifyCount(1, new QueryOptions() { Expression = "properties.packageManager == 'nuget'", InputFilePath = filePath }); + + // result filter: double + RunAndVerifyCount(0, new QueryOptions() { Expression = "properties.severity > 3.0", InputFilePath = filePath }); + + // result filter: date time + RunAndVerifyCount(1, new QueryOptions() { Expression = "properties.patchPublicationDate > '2022-06-01T00:00:00'", InputFilePath = filePath }); + + // result filter: date time + RunAndVerifyCount(0, new QueryOptions() { Expression = "properties.patchPublicationDate < '2022-04-25T00:00:00'", InputFilePath = filePath }); + } + + private void RunAndVerifyCount(int expectedCount, QueryOptions options) { options.ReturnCount = true; diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj b/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj index 0bbbfcf21..d02e51e59 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj +++ b/src/Test.UnitTests.Sarif.Multitool.Library/Test.UnitTests.Sarif.Multitool.Library.csproj @@ -19,7 +19,7 @@ - + @@ -54,6 +54,7 @@ + @@ -86,4 +87,4 @@ - + \ No newline at end of file diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif new file mode 100644 index 000000000..5a4f28c7d --- /dev/null +++ b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif @@ -0,0 +1,48 @@ +{ + "$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.1", + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Test", + "version": "1.0.0", + "rules": [ + { + "id": "TEST0001", + "name": "Test", + "shortDescription": { + "text": "Test description." + }, + "messageStrings": { + "default": { + "text": "Test description." + } + }, + "properties": { + "cwe": "CWE-755", + "security-severity": 8.0, + "vulnPublicationDate": "2022-04-24T10:58:25Z" + } + } + ] + } + }, + "results": [ + { + "ruleId": "TEST0001", + "ruleIndex": 0, + "message": { + "text": "Test text." + }, + "properties": { + "packageManager": "nuget", + "severity": 2.0, + "patchPublicationDate": "2022-06-01T10:58:25Z" + } + } + ], + "columnKind": "utf16CodeUnits" + } + ] +} \ No newline at end of file From dddffa4e1266fc52d6ca645dcba86f365795538c Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 26 Oct 2022 06:22:39 -0500 Subject: [PATCH 60/61] bug: null location guard check --- src/Sarif/ExtensionMethods.cs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Sarif/ExtensionMethods.cs b/src/Sarif/ExtensionMethods.cs index 2d7263a03..6c3ebcce3 100644 --- a/src/Sarif/ExtensionMethods.cs +++ b/src/Sarif/ExtensionMethods.cs @@ -288,21 +288,31 @@ public static string FormatForVisualStudio(this Result result, ReportingDescript var messageLines = new List(); - foreach (Location location in result.Locations) + var ruleMessage = string.Format( + CultureInfo.InvariantCulture, "{0} {1}: {2}", + result.Kind == ResultKind.Fail ? result.Level.FormatForVisualStudio() : result.Kind.FormatForVisualStudio(), + result.RuleId, + result.GetMessageText(rule) + ); + + if (result.Locations != null) { - Uri uri = location.PhysicalLocation.ArtifactLocation.Uri; - string path = uri.IsAbsoluteUri && uri.IsFile ? uri.LocalPath : uri.ToString(); - messageLines.Add( - string.Format( - CultureInfo.InvariantCulture, "{0}{1}: {2} {3}: {4}", + foreach (Location location in result.Locations) + { + Uri uri = location.PhysicalLocation.ArtifactLocation.Uri; + string path = uri.IsAbsoluteUri && uri.IsFile ? uri.LocalPath : uri.ToString(); + + ruleMessage = string.Format( + CultureInfo.InvariantCulture, "{0}{1}: {2}", path, location.PhysicalLocation.Region.FormatForVisualStudio(), - result.Kind == ResultKind.Fail ? result.Level.FormatForVisualStudio() : result.Kind.FormatForVisualStudio(), - result.RuleId, - result.GetMessageText(rule) - )); + ruleMessage + ); + } } + messageLines.Add(ruleMessage); + return string.Join(Environment.NewLine, messageLines); } From 81f02a7826091f7cf9b0cbd17f067ac6416cc845 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 26 Oct 2022 07:21:57 -0500 Subject: [PATCH 61/61] feat: enhanced query command test suite --- .../QueryCommandTests.cs | 6 ++-- .../QueryCommand/WithProperties.sarif | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs index 54968ff23..fd2d6e0c5 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/QueryCommandTests.cs @@ -67,16 +67,16 @@ public void QueryCommand_Properties() RunAndVerifyCount(1, new QueryOptions() { Expression = "rule.properties.vulnPublicationDate > '2022-04-01T00:00:00'", InputFilePath = filePath }); // rule filter: date time - RunAndVerifyCount(1, new QueryOptions() { Expression = "rule.properties.vulnPublicationDate < '2022-04-30T00:00:00'", InputFilePath = filePath }); + RunAndVerifyCount(2, new QueryOptions() { Expression = "rule.properties.vulnPublicationDate < '2022-04-30T00:00:00'", InputFilePath = filePath }); // result filter: string RunAndVerifyCount(1, new QueryOptions() { Expression = "properties.packageManager == 'nuget'", InputFilePath = filePath }); // result filter: double - RunAndVerifyCount(0, new QueryOptions() { Expression = "properties.severity > 3.0", InputFilePath = filePath }); + RunAndVerifyCount(1, new QueryOptions() { Expression = "properties.packageManager == 'npm' && properties.severity > 3.0", InputFilePath = filePath }); // result filter: date time - RunAndVerifyCount(1, new QueryOptions() { Expression = "properties.patchPublicationDate > '2022-06-01T00:00:00'", InputFilePath = filePath }); + RunAndVerifyCount(2, new QueryOptions() { Expression = "properties.patchPublicationDate > '2022-06-01T00:00:00'", InputFilePath = filePath }); // result filter: date time RunAndVerifyCount(0, new QueryOptions() { Expression = "properties.patchPublicationDate < '2022-04-25T00:00:00'", InputFilePath = filePath }); diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif index 5a4f28c7d..b9fe87d7a 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif +++ b/src/Test.UnitTests.Sarif.Multitool.Library/TestData/QueryCommand/WithProperties.sarif @@ -24,6 +24,23 @@ "security-severity": 8.0, "vulnPublicationDate": "2022-04-24T10:58:25Z" } + }, + { + "id": "TEST0002", + "name": "Test 2", + "shortDescription": { + "text": "Test description 2." + }, + "messageStrings": { + "default": { + "text": "Test description 2." + } + }, + "properties": { + "cwe": "CWE-766", + "security-severity": 3.0, + "vulnPublicationDate": "2022-01-15T10:58:25Z" + } } ] } @@ -40,6 +57,18 @@ "severity": 2.0, "patchPublicationDate": "2022-06-01T10:58:25Z" } + }, + { + "ruleId": "TEST0002", + "ruleIndex": 1, + "message": { + "text": "Test text 2." + }, + "properties": { + "packageManager": "npm", + "severity": 9.0, + "patchPublicationDate": "2022-10-01T10:58:25Z" + } } ], "columnKind": "utf16CodeUnits"