diff --git a/pkg/report/output/output.go b/pkg/report/output/output.go index 44c35b603..07c04fca0 100644 --- a/pkg/report/output/output.go +++ b/pkg/report/output/output.go @@ -52,14 +52,17 @@ func ReportSummary(report types.Report, output *zerolog.Event, config settings.C return } - summaryResults, err := getSummaryReportOutput(report, config) + dataflow, err := getDataflow(report, config, true) + if err != nil { + return + } + + summaryResults, err := summary.GetOutput(dataflow, config) if err != nil { return } - outputToFile := config.Report.Output != "" - severityForFailure := config.Report.Severity - reportStr, reportPassed := summary.BuildReportString(config.Rules, summaryResults, severityForFailure, outputToFile) + reportStr, reportPassed := summary.BuildReportString(config, summaryResults, lineOfCodeOutput, dataflow) output.Msg(reportStr.String()) @@ -108,13 +111,18 @@ func ReportYAML(report types.Report, output *zerolog.Event, config settings.Conf } func getReportOutput(report types.Report, config settings.Config) (any, error) { + switch config.Report.Report { case flag.ReportDetectors: return detectors.GetOutput(report, config) case flag.ReportDataFlow: return getDataflow(report, config, false) case flag.ReportSummary: - return getSummaryReportOutput(report, config) + dataflow, err := getDataflow(report, config, true) + if err != nil { + return nil, err + } + return summary.GetOutput(dataflow, config) case flag.ReportPrivacy: return getPrivacyReportOutput(report, config) case flag.ReportStats: @@ -148,15 +156,6 @@ func getPrivacyReportOutput(report types.Report, config settings.Config) (*priva return privacy.GetOutput(dataflow, config) } -func getSummaryReportOutput(report types.Report, config settings.Config) (map[string][]summary.Result, error) { - dataflow, err := getDataflow(report, config, true) - if err != nil { - return nil, err - } - - return summary.GetOutput(dataflow, config) -} - func getDataflow(report types.Report, config settings.Config, isInternal bool) (*dataflow.DataFlow, error) { reportedDetections, err := detectors.GetOutput(report, config) if err != nil { diff --git a/pkg/report/output/stats/stats.go b/pkg/report/output/stats/stats.go index 54acf00a9..63a4e3f34 100644 --- a/pkg/report/output/stats/stats.go +++ b/pkg/report/output/stats/stats.go @@ -112,35 +112,21 @@ func getDataGroupNames(config settings.Config, dataTypes []DataType) []string { return maputil.SortedStringKeys(dataGroups) } -func GetPlaceholderOutput(inputgocloc *gocloc.Result, inputDataflow *dataflow.DataFlow, config settings.Config) (outputStr *strings.Builder, err error) { - outputStr = &strings.Builder{} - statistics, err := GetOutput(inputgocloc, inputDataflow, config) +func AnythingFoundFor(statistics *Stats) bool { + return statistics.NumberOfDataTypes != 0 || + statistics.NumberOfDatabases != 0 || + statistics.NumberOfExternalAPIs != 0 || + statistics.NumberOfInternalAPIs != 0 +} +func WriteStatsToString(outputStr *strings.Builder, statistics *Stats) { totalDataTypeOccurrences := 0 for _, dataType := range statistics.DataTypes { totalDataTypeOccurrences += dataType.Occurrences } - supportURL := "https://curio.sh/explanations/reports/" - outputStr.WriteString(fmt.Sprintf(` -The policy report is not yet available for your stack. Learn more at %s`, - supportURL)) - - anythingFound := - statistics.NumberOfDataTypes != 0 || - statistics.NumberOfDatabases != 0 || - statistics.NumberOfExternalAPIs != 0 || - statistics.NumberOfInternalAPIs != 0 - if anythingFound { - outputStr.WriteString(` - -Though this doesn’t mean the curious bear comes empty-handed, it found: -`) - } - if statistics.NumberOfDataTypes != 0 { - outputStr.WriteString(fmt.Sprintf(` -- %d unique data type(s), representing %d occurrences, including %s.`, + outputStr.WriteString(fmt.Sprintf(`- %d unique data type(s), representing %d occurrences, including %s.`, statistics.NumberOfDataTypes, totalDataTypeOccurrences, strings.Join(statistics.DataGroups, ", "))) @@ -172,6 +158,26 @@ Though this doesn’t mean the curious bear comes empty-handed, it found: - %d internal URL(s).`, statistics.NumberOfInternalAPIs)) } +} + +func GetPlaceholderOutput(inputgocloc *gocloc.Result, inputDataflow *dataflow.DataFlow, config settings.Config) (outputStr *strings.Builder, err error) { + outputStr = &strings.Builder{} + statistics, err := GetOutput(inputgocloc, inputDataflow, config) + + supportURL := "https://curio.sh/explanations/reports/" + outputStr.WriteString(fmt.Sprintf(` +The policy report is not yet available for your stack. Learn more at %s`, + supportURL)) + + if AnythingFoundFor(statistics) { + outputStr.WriteString(` + +Though this doesn’t mean the curious bear comes empty-handed, it found: + +`) + } + + WriteStatsToString(outputStr, statistics) suggestedCommand := color.New(color.Italic).Sprintf("curio scan %s --report dataflow", config.Target) outputStr.WriteString(fmt.Sprintf(` diff --git a/pkg/report/output/summary/summary.go b/pkg/report/output/summary/summary.go index 52c709acf..f6b872297 100644 --- a/pkg/report/output/summary/summary.go +++ b/pkg/report/output/summary/summary.go @@ -13,10 +13,12 @@ import ( "github.com/bearer/curio/pkg/util/output" "github.com/bearer/curio/pkg/util/rego" "github.com/fatih/color" + "github.com/hhatto/gocloc" "golang.org/x/exp/maps" "golang.org/x/exp/slices" "github.com/bearer/curio/pkg/report/output/dataflow" + stats "github.com/bearer/curio/pkg/report/output/stats" ) var underline = color.New(color.Underline).SprintFunc() @@ -135,8 +137,12 @@ func GetOutput(dataflow *dataflow.DataFlow, config settings.Config) (map[string] return result, nil } -func BuildReportString(rules map[string]*settings.Rule, results map[string][]Result, severityForFailure map[string]bool, withoutColor bool) (*strings.Builder, bool) { +func BuildReportString(config settings.Config, results map[string][]Result, lineOfCodeOutput *gocloc.Result, dataflow *dataflow.DataFlow) (*strings.Builder, bool) { + rules := config.Rules + withoutColor := config.Report.Output != "" + severityForFailure := config.Report.Severity reportStr := &strings.Builder{} + reportStr.WriteString("\n\nSummary Report\n") reportStr.WriteString("\n=====================================") @@ -170,7 +176,18 @@ func BuildReportString(rules map[string]*settings.Rule, results map[string][]Res } } - writeSummaryToString(reportStr, results, len(rules), failures, severityForFailure) + if reportPassed { + reportStr.WriteString("\nNeed to add your own custom rule? Check out the guide: https://curio.sh/guides/custom-rule\n") + } + + noFailureSummary := checkAndWriteFailureSummaryToString(reportStr, results, len(rules), failures, severityForFailure) + + if noFailureSummary { + writeSuccessToString(len(rules), reportStr) + writeStatsToString(reportStr, config, lineOfCodeOutput, dataflow) + } + + reportStr.WriteString("\nNeed help or want to discuss the output? Join the Community https://discord.gg/eaHZBJUXRF\n") color.NoColor = initialColorSetting @@ -198,6 +215,23 @@ func FindHighestSeverity(groups []string, severity map[string]string) string { return severity["default"] } +func writeStatsToString( + reportStr *strings.Builder, + config settings.Config, + lineOfCodeOutput *gocloc.Result, + dataflow *dataflow.DataFlow, +) { + statistics, err := stats.GetOutput(lineOfCodeOutput, dataflow, config) + if err != nil { + return + } + if stats.AnythingFoundFor(statistics) { + reportStr.WriteString("\nCurio found:\n") + stats.WriteStatsToString(reportStr, statistics) + reportStr.WriteString("\n") + } +} + func writeRuleListToString( reportStr *strings.Builder, rules map[string]*settings.Rule) { @@ -216,19 +250,22 @@ func writeRuleListToString( reportStr.WriteString(strings.Join(ruleList, "")) } -func writeSummaryToString( +func writeSuccessToString(policyCount int, reportStr *strings.Builder) { + reportStr.WriteString("\n\n") + reportStr.WriteString(color.HiGreenString("SUCCESS\n\n")) + reportStr.WriteString(fmt.Sprint(policyCount) + " checks were run and no failures were detected. Great job! 👏\n") +} + +func checkAndWriteFailureSummaryToString( reportStr *strings.Builder, policyResults map[string][]Result, policyCount int, policyFailures map[string]map[string]bool, severityForFailure map[string]bool, -) { +) bool { reportStr.WriteString("\n=====================================") if len(policyResults) == 0 { - reportStr.WriteString("\n\n") - reportStr.WriteString(color.HiGreenString("SUCCESS\n\n")) - reportStr.WriteString(fmt.Sprint(policyCount) + " checks were run and no failures were detected.\n\n") - return + return true } // give summary including counts @@ -246,11 +283,7 @@ func writeSummaryToString( } if failureCount == 0 && warningCount == 0 { - // no failures and no warnings : success - reportStr.WriteString("\n\n") - reportStr.WriteString(color.HiGreenString("SUCCESS\n\n")) - reportStr.WriteString(fmt.Sprint(policyCount) + " checks were run and no failures were detected.\n\n") - return + return true } reportStr.WriteString("\n\n") @@ -281,6 +314,8 @@ func writeSummaryToString( } reportStr.WriteString("\n") + + return false } func writeFailureToString(reportStr *strings.Builder, result Result, policySeverity string) {