private static object[] BuildDefaultTestCase(FailureLevel level, ResultKind kind = ResultKind.Fail) { string lineLabel = level != FailureLevel.None ? level.ToString().ToLowerInvariant() : kind.ToString().ToLowerInvariant(); if (kind == ResultKind.Informational) { // Console reporting historically abbreviates this term lineLabel = "info"; } if (level == FailureLevel.None && kind == ResultKind.None) { // No good information? Mark it as informational. lineLabel = "info"; } return(new object[] { level, kind, MultiLineTestRegion, $"{TestData.TestAnalysisTarget}(2,4,3,5): {lineLabel} {TestData.TestRuleId}: First: 42, Second: 54", TestData.TestAnalysisTarget }); }
private static string GetMessageText( string toolName, Uri uri, Region region, string ruleId, string message, ResultKind kind, FailureLevel level) { string path = ConstructPathFromUri(uri); string issueType = null; switch (level) { case FailureLevel.Note: issueType = "note"; break; case FailureLevel.Error: issueType = "error"; break; case FailureLevel.Warning: issueType = "warning"; break; case FailureLevel.None: issueType = kind.ToString().ToLowerInvariant(); // Shorten to 'info' for compatibility with previous behavior. if (issueType == "informational") { issueType = "info"; } break; default: throw new InvalidOperationException("Unknown message level:" + level.ToString()); } string detailedDiagnosis = NormalizeMessage(message, enquote: false); string location = ""; if (region != null) { // TODO: FormatForVisualStudio doesn't handle // binary and char offsets only. location = region.FormatForVisualStudio(); } return((path != null ? (path + location) : toolName) + $": {issueType} " + (!string.IsNullOrEmpty(ruleId) ? (ruleId + ": ") : "") + detailedDiagnosis); }
public void SarifWorkItemExtensions_CreateWorkItemTitle_HandlesSingleResultWithRuleIdOnly() { var sb = new StringBuilder(); foreach (Tuple <string, Result> tuple in ResultsWithVariousRuleExpressions) { Result result = tuple.Item2; SarifLog sarifLog = CreateLogWithEmptyRun(); Run run = sarifLog.Runs[0]; run.Results.Add(tuple.Item2); string title = sarifLog.Runs[0].CreateWorkItemTitle(false); string ruleId = result.ResolvedRuleId(run); if (!title.Contains(ToolName)) { sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain generated tool name."); } if (!title.Contains(ruleId)) { sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain expected rule id '{ruleId}'."); } FailureLevel level = result.Level; if (!title.Contains(level.ToString())) { sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain expected failure level '{level}'."); } string location = result.Locations?[0].PhysicalLocation?.ArtifactLocation?.Uri?.OriginalString; if (!string.IsNullOrEmpty(location) && !title.Contains(location)) { sb.AppendLine($"[Test case '{tuple.Item1}']: Title did not contain expected location '{location}'."); } } sb.Length.Should().Be(0, because: Environment.NewLine + sb.ToString()); }
private static string GetMessageText( Uri uri, Region region, string ruleId, string message, ResultKind kind, FailureLevel level) { string path = null; ValidateKindAndLevel(kind, level); if (uri != null) { // If a path refers to a URI of form file://blah, we will convert to the local path if (uri.IsAbsoluteUri && uri.Scheme == Uri.UriSchemeFile) { path = uri.LocalPath; } else { path = uri.ToString(); } } string issueType = null; switch (level) { case FailureLevel.Note: issueType = "info"; break; case FailureLevel.Error: issueType = "error"; break; case FailureLevel.Warning: issueType = "warning"; break; case FailureLevel.None: issueType = kind.ToString().ToLowerInvariant(); break; default: throw new InvalidOperationException("Unknown message kind:" + level.ToString()); } string detailedDiagnosis = NormalizeMessage(message, enquote: false); string location = ""; if (region != null) { // TODO if (region.CharOffset > 0 || region.ByteOffset > 0 || region.StartColumn == 0) { return(string.Empty); } if (region.StartLine == 0) { throw new InvalidOperationException(); } location = region.FormatForVisualStudio(); } string result = (path != null ? (path + location + ": ") : "") + issueType + (!string.IsNullOrEmpty(ruleId) ? " " : "") + (!string.IsNullOrEmpty(ruleId) ? (ruleId + ": ") : "") + detailedDiagnosis; return(result); }
private static string GetMessageText( string toolName, Uri uri, Region region, string ruleId, string message, ResultKind kind, FailureLevel level) { string path = null; ValidateKindAndLevel(kind, level); path = ConstructPathFromUri(uri); string issueType = null; switch (level) { case FailureLevel.Note: issueType = "note"; break; case FailureLevel.Error: issueType = "error"; break; case FailureLevel.Warning: issueType = "warning"; break; case FailureLevel.None: issueType = kind.ToString().ToLowerInvariant(); // Shorten to 'info' for compat reasons with previous behaviors. if (issueType == "informational") { issueType = "info"; } ; break; default: throw new InvalidOperationException("Unknown message level:" + level.ToString()); } string detailedDiagnosis = NormalizeMessage(message, enquote: false); string location = ""; if (region != null) { // TODO if (region.CharOffset > 0 || region.ByteOffset > 0 || region.StartColumn == 0) { return(string.Empty); } if (region.StartLine == 0) { throw new InvalidOperationException(); } location = region.FormatForVisualStudio(); } string messageText = (path != null ? (path + location) : toolName) + ": " + issueType + (!string.IsNullOrEmpty(ruleId) ? " " : "") + (!string.IsNullOrEmpty(ruleId) ? (ruleId + ": ") : "") + detailedDiagnosis; return(messageText); }
private void RunMatchExpressionForFileNameRegex(AnalyzeContext context, MatchExpression matchExpression, FailureLevel level) { ReportingDescriptor reportingDescriptor = this; string levelText = level.ToString(); string fingerprint = null; IDictionary <string, string> groups = new Dictionary <string, string>(); string filePath = context.TargetUri.LocalPath; string fingerprintText = null, validatorMessage = null; string validationPrefix = string.Empty, validationSuffix = string.Empty; if (_validators != null && matchExpression.IsValidatorEnabled) { Validation state = _validators.Validate(reportingDescriptor.Name, context.DynamicValidation, ref filePath, ref groups, ref levelText, ref fingerprintText, ref validatorMessage, out bool pluginSupportsDynamicValidation); if (!Enum.TryParse <FailureLevel>(levelText, out level)) { // An illegal failure level '{0}' was returned running check '{1}' against '{2}'. context.Logger.LogToolNotification( Errors.CreateNotification( context.TargetUri, "ERR998.ValidatorReturnedIllegalResultLevel", context.Rule.Id, FailureLevel.Error, exception: null, persistExceptionStack: false, messageFormat: SpamResources.ERR998_ValidatorReturnedIllegalResultLevel, levelText, context.Rule.Id, context.TargetUri.GetFileName())); // If we don't understand the failure level, elevate it to error. level = FailureLevel.Error; } switch (state) { case Validation.NoMatch: { // The validator determined the match is a false positive. // i.e., it is not the kind of artifact we're looking for. // We should suspend processing and move to the next match. return; } case Validation.None: case Validation.ValidatorReturnedIllegalValidationState: { // An illegal state was returned running check '{0}' against '{1}' ({2}). context.Logger.LogToolNotification( Errors.CreateNotification( context.TargetUri, "ERR998.ValidatorReturnedIllegalValidationState", context.Rule.Id, FailureLevel.Error, exception: null, persistExceptionStack: false, messageFormat: SpamResources.ERR998_ValidatorReturnedIllegalValidationState, context.Rule.Id, context.TargetUri.GetFileName(), validatorMessage)); level = FailureLevel.Error; return; } case Validation.Authorized: { level = FailureLevel.Error; // Contributes to building a message fragment such as: // 'SomeFile.txt' is an exposed SomeSecret file [...]. validationPrefix = "an exposed "; break; } case Validation.Expired: { level = FailureLevel.Warning; // Contributes to building a message fragment such as: // 'SomeFile.txt' contains an expired SomeApi token[...]. validationPrefix = "an expired "; break; } case Validation.PasswordProtected: { level = FailureLevel.Warning; // Contributes to building a message fragment such as: // 'SomeFile.txt' contains a password-protected SomeSecret file // which could be exfiltrated and potentially brute-forced offline. validationPrefix = "a password-protected "; validationSuffix = " which could be exfiltrated and potentially brute-forced offline"; break; } case Validation.UnknownHost: case Validation.Unauthorized: case Validation.InvalidForConsultedAuthorities: { throw new InvalidOperationException(); } case Validation.Unknown: { level = FailureLevel.Warning; validationPrefix = "an apparent "; if (!context.DynamicValidation) { if (pluginSupportsDynamicValidation) { // This indicates that dynamic validation was disabled but we // passed this result to a validator that could have performed // this work. validationSuffix = ". No validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match"; } else { // No validation was requested. The plugin indicated // that is can't perform this work in any case. validationSuffix = string.Empty; } } else if (pluginSupportsDynamicValidation) { validationSuffix = ", the validity of which could not be determined by runtime analysis"; } else { // Validation was requested. But the plugin indicated // that it can't perform this work in any case. validationSuffix = string.Empty; } break; } case Validation.ValidatorNotFound: { // TODO: should we have an explicit indicator in // all cases that tells us whether this is an // expected condition or not? validationPrefix = "an apparent "; break; } default: { throw new InvalidOperationException($"Unrecognized validation value '{state}'."); } } } Dictionary <string, string> messageArguments = matchExpression.MessageArguments != null ? new Dictionary <string, string>(matchExpression.MessageArguments) : new Dictionary <string, string>(); messageArguments["validationPrefix"] = validationPrefix; messageArguments["validationSuffix"] = validationSuffix; IList <string> arguments = GetMessageArguments( match: null, matchExpression.ArgumentNameToIndexMap, context.TargetUri.LocalPath, validatorMessage: NormalizeValidatorMessage(validatorMessage), messageArguments); Result result = this.ConstructResult( context.TargetUri, reportingDescriptor.Id, level, region: null, flexMatch: null, fingerprint, matchExpression, arguments); context.Logger.Log(reportingDescriptor, result); }
private void RunMatchExpressionForContentsRegex( FlexMatch binary64DecodedMatch, AnalyzeContext context, MatchExpression matchExpression, FailureLevel level) { string filePath = context.TargetUri.GetFilePath(); string searchText = binary64DecodedMatch != null ? Decode(binary64DecodedMatch.Value) : context.FileContents; foreach (FlexMatch flexMatch in _engine.Matches(searchText, matchExpression.ContentsRegex)) { if (!flexMatch.Success) { continue; } ReportingDescriptor reportingDescriptor = this; Regex regex = CachedDotNetRegex.GetOrCreateRegex(matchExpression.ContentsRegex, RegexDefaults.DefaultOptionsCaseInsensitive); Match match = regex.Match(flexMatch.Value); string refinedMatchedPattern = match.Groups["refine"].Value; IDictionary <string, string> groups = match.Groups.CopyToDictionary(regex.GetGroupNames()); Debug.Assert(!groups.ContainsKey("scanTargetFullPath")); groups["scanTargetFullPath"] = filePath; if (matchExpression.Properties != null) { foreach (KeyValuePair <string, string> kv in matchExpression.Properties) { // We will never allow a group returned by a dynamically executing // regex to overwrite a static value in the match expression. This // allows the match expression to provide a default value that // may be replaced by the analysis. if (!groups.ContainsKey(kv.Key)) { groups[kv.Key] = kv.Value; } } } if (string.IsNullOrEmpty(refinedMatchedPattern)) { refinedMatchedPattern = flexMatch.Value; } string levelText = level.ToString(); Validation state = 0; string fingerprint = null; string validatorMessage = null; string validationPrefix = string.Empty; string validationSuffix = string.Empty; if (_validators != null && matchExpression.IsValidatorEnabled) { state = _validators.Validate(reportingDescriptor.Name, context.DynamicValidation, ref refinedMatchedPattern, ref groups, ref levelText, ref fingerprint, ref validatorMessage, out bool pluginSupportsDynamicValidation); if (!Enum.TryParse <FailureLevel>(levelText, out level)) { // An illegal failure level '{0}' was returned running check '{1}' against '{2}'. context.Logger.LogToolNotification( Errors.CreateNotification( context.TargetUri, "ERR998.ValidatorReturnedIllegalResultLevel", context.Rule.Id, FailureLevel.Error, exception: null, persistExceptionStack: false, messageFormat: SpamResources.ERR998_ValidatorReturnedIllegalResultLevel, levelText, context.Rule.Id, context.TargetUri.GetFileName())); // If we don't understand the failure level, elevate it to error. level = FailureLevel.Error; } SetPropertiesBasedOnValidationState(state, context, ref level, ref validationPrefix, ref validationSuffix, ref validatorMessage, pluginSupportsDynamicValidation); if (state == Validation.None || state == Validation.NoMatch || state == Validation.ValidatorReturnedIllegalValidationState) { continue; } } // If we're matching against decoded contents, the region should // relate to the base64-encoded scan target content. We do use // the decoded content for the fingerprint, however. FlexMatch regionFlexMatch = binary64DecodedMatch ?? flexMatch; Region region = ConstructRegion(context, regionFlexMatch, refinedMatchedPattern); Dictionary <string, string> messageArguments = matchExpression.MessageArguments != null ? new Dictionary <string, string>(matchExpression.MessageArguments) : new Dictionary <string, string>(); messageArguments["encoding"] = binary64DecodedMatch != null ? "base64-encoded" : string.Empty; // We don't bother to report a value for plaintext content messageArguments["validationPrefix"] = validationPrefix; messageArguments["validationSuffix"] = validationSuffix; IList <string> arguments = GetMessageArguments(match, matchExpression.ArgumentNameToIndexMap, filePath, validatorMessage: NormalizeValidatorMessage(validatorMessage), messageArguments); Result result = ConstructResult(context.TargetUri, reportingDescriptor.Id, level, region, flexMatch, fingerprint, matchExpression, arguments); // This skimmer instance mutates its reporting descriptor state, // for example, the sub-id may change for every match // expression. We will therefore generate a snapshot of // current ReportingDescriptor state when logging. context.Logger.Log(reportingDescriptor, result); } }