private void AppendOutput(IOutputBuilder outputBuilder, PackageURL purl, HealthMetrics healthMetrics) { switch (currentOutputFormat) { case OutputFormat.text: default: outputBuilder.AppendOutput(new List <string>() { $"Health for {purl} (via {purl})", healthMetrics.ToString() }); break; case OutputFormat.sarifv1: case OutputFormat.sarifv2: outputBuilder.AppendOutput(healthMetrics.toSarif()); break; } }
/// <summary> /// Calculates project risk based on health and characteristics. /// </summary> /// <param name="purl">Package URL to load</param> /// <param name="targetDirectory">Target directory to download content to (default: temporary location)</param> /// <param name="doCaching">Cache the project for later processing (default: false)</param> /// <returns></returns> public async Task <double> CalculateRisk(PackageURL purl, string?targetDirectory, bool doCaching = false, bool checkHealth = true) { Logger.Trace("CalculateRisk({0})", purl.ToString()); CharacteristicTool?characteristicTool = new CharacteristicTool(ProjectManagerFactory); CharacteristicTool.Options? cOptions = new CharacteristicTool.Options(); Dictionary <string, AnalyzeResult?>?characteristics = characteristicTool.AnalyzePackage(cOptions, purl, targetDirectory, doCaching).Result; double aggregateRisk = 0.0; if (checkHealth) { HealthTool? healthTool = new HealthTool(ProjectManagerFactory); HealthMetrics?healthMetrics = await healthTool.CheckHealth(purl); if (healthMetrics == null) { Logger.Warn("Unable to determine health metrics, will use a default of 0."); healthMetrics = new HealthMetrics(purl) { SecurityIssueHealth = 0, CommitHealth = 0, ContributorHealth = 0, IssueHealth = 0, ProjectSizeHealth = 0, PullRequestHealth = 0, RecentActivityHealth = 0, ReleaseHealth = 0 }; } Logger.Trace("Health Metrics:\n{}", healthMetrics); // Risk calculation algorithm: Weight each of the health scores. aggregateRisk = 1.0 - ( 5.0 * healthMetrics.SecurityIssueHealth / 100.0 + 1.0 * healthMetrics.CommitHealth / 100.0 + 3.0 * healthMetrics.IssueHealth / 100.0 + 2.0 * healthMetrics.PullRequestHealth / 100.0 + 0.25 * healthMetrics.RecentActivityHealth / 100.0 + 1.0 * healthMetrics.ContributorHealth / 100.0 ) / 12.25; Logger.Trace("Aggregate Health Risk: {}", aggregateRisk); } string[]? highRiskTags = new string[] { "Cryptography.", "Authentication.", "Authorization.", "Data.Deserialization." }; Dictionary <string, int>?highRiskTagsSeen = new Dictionary <string, int>(); foreach (AnalyzeResult?analyzeResult in characteristics.Values) { foreach (MatchRecord?match in analyzeResult?.Metadata?.Matches ?? new List <MatchRecord>()) { foreach (string?tag in match.Tags ?? Array.Empty <string>()) { foreach (string?highRiskTag in highRiskTags) { if (tag.StartsWith(highRiskTag)) { if (!highRiskTagsSeen.ContainsKey(highRiskTag)) { highRiskTagsSeen[highRiskTag] = 0; } highRiskTagsSeen[highRiskTag]++; } } } } } if (Logger.IsTraceEnabled) { Logger.Trace("Found {} high-risk tags over {} categories.", highRiskTagsSeen.Values.Sum(), highRiskTagsSeen.Keys.Count()); } double highRiskTagRisk = ( 0.4 * highRiskTagsSeen.Keys.Count() + 0.6 * Math.Min(highRiskTagsSeen.Values.Sum(), 5) ); highRiskTagRisk = highRiskTagRisk > 1.0 ? 1.0 : highRiskTagRisk; aggregateRisk = ( 0.7 * aggregateRisk + 0.7 * highRiskTagRisk ); aggregateRisk = aggregateRisk > 1.0 ? 1.0 : aggregateRisk; Logger.Trace("Final Risk: {}", aggregateRisk); return(aggregateRisk); }