Skip to content

Commit

Permalink
feat(policies): include code excerpt in policy breach display (#164)
Browse files Browse the repository at this point in the history
* feat: include code excerpt in policy breach display

* feat: tidy up output

* fix: omit empty parents
  • Loading branch information
elsapet committed Nov 28, 2022
1 parent ea8b692 commit 977b62f
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 49 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/bearer/curio
go 1.18

require (
github.com/TwiN/go-color v1.4.0
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible
github.com/gertd/go-pluralize v0.2.1
github.com/ghodss/yaml v1.0.0
Expand Down Expand Up @@ -69,6 +68,7 @@ require (
)

require (
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.24.1 // indirect
Expand Down
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/TwiN/go-color v1.4.0 h1:fNbOwOrvup5oj934UragnW0B1WKaAkkB85q19Y7h4ng=
github.com/TwiN/go-color v1.4.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
Expand Down Expand Up @@ -85,6 +83,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/semgroup v1.2.0 h1:h/OLXwEM+3NNyAdZEpMiH1OzfplU09i2qXPVThGZvyg=
github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
Expand Down Expand Up @@ -219,8 +219,10 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down Expand Up @@ -472,6 +474,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
5 changes: 4 additions & 1 deletion integration/flags/.snapshots/TestReportFlags-report-policies
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
critical:
- policy_name: Logger leaking
policy_description: Logger leaks detected
filename: users.rb
line_number: 1
filename: testdata/policiesusers.rb
category_group: Personal data
parent_line_number: 1
parent_content: logger.info(user.address)


--
Expand Down
4 changes: 3 additions & 1 deletion pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err
}()

scanSettings, err := settings.FromOptions(opts)
scanSettings.Target = opts.Target

if err != nil {
return err
}
Expand Down Expand Up @@ -147,7 +149,7 @@ func (r *runner) Report(config settings.Config, report types.Report) error {
}

if config.Report.Report == flag.ReportPolicies && config.Report.Format == "" {
// for polict report, default report format is NOT JSON
// for policy report, default report format is NOT JSON
err := reportoutput.ReportPolicies(report, logger, config)
if err != nil {
return fmt.Errorf("error generating report %w", err)
Expand Down
10 changes: 8 additions & 2 deletions pkg/commands/process/settings/policies/logger_leaks.rego
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ high[item] {
location = data_type.locations[_]
item := {
"category_group": category.group_name,
"filename": location.filename
"filename": location.filename,
"line_number": location.line_number,
"parent_line_number": data_type.parent.line_number,
"parent_content": data_type.parent.content
}
}

Expand All @@ -35,6 +38,9 @@ critical[item] {
location = data_type.locations[_]
item := {
"category_group": category.group_name,
"filename": location.filename
"filename": location.filename,
"line_number": location.line_number,
"parent_line_number": data_type.parent.line_number,
"parent_content": data_type.parent.content
}
}
1 change: 1 addition & 0 deletions pkg/commands/process/settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Config struct {
Report flag.ReportOptions `json:"report" yaml:"report"`
CustomDetector map[string]Rule `json:"custom_detector" yaml:"custom_detector"`
Policies map[string]*Policy `json:"policies" yaml:"policies"`
Target string `json:"target" yaml:"target"`
}

type PolicyLevel string
Expand Down
7 changes: 6 additions & 1 deletion pkg/report/output/dataflow/datatypes/datatypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ func New(isInternal bool) *Holder {
}

func (holder *Holder) AddSchema(detection detections.Detection, extras *extraFields) error {
classification, err := detectiondecoder.GetSchemaClassification(detection)
schema, err := detectiondecoder.GetSchema(detection)
if err != nil {
return err
}

classification, err := detectiondecoder.GetSchemaClassification(schema, detection)
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,33 @@ import (
"github.com/bearer/curio/pkg/report/schema"
)

func GetSchemaClassification(detection detections.Detection) (schemaclassification.Classification, error) {
// decode schema
var value schema.Schema
func GetSchemaClassification(schema schema.Schema, detection detections.Detection) (schemaclassification.Classification, error) {
// decode classification
var classification schemaclassification.Classification
buf := bytes.NewBuffer(nil)
err := json.NewEncoder(buf).Encode(detection.Value)
err := json.NewEncoder(buf).Encode(schema.Classification)
if err != nil {
return schemaclassification.Classification{}, fmt.Errorf("expect detection to have value of type schema %#v", detection.Value)
return schemaclassification.Classification{}, fmt.Errorf("expecting classification got %#v", schema.Classification)
}
err = json.NewDecoder(buf).Decode(&value)
err = json.NewDecoder(buf).Decode(&classification)
if err != nil {
return schemaclassification.Classification{}, fmt.Errorf("expect detection to have value of type schema %#v", detection.Value)
return schemaclassification.Classification{}, fmt.Errorf("expecting classification got %#v", schema.Classification)
}

// decode classification
var classification schemaclassification.Classification
buf = bytes.NewBuffer(nil)
err = json.NewEncoder(buf).Encode(value.Classification)
return classification, nil
}

func GetSchema(detection detections.Detection) (schema.Schema, error) {
var value schema.Schema
buf := bytes.NewBuffer(nil)
err := json.NewEncoder(buf).Encode(detection.Value)
if err != nil {
return schemaclassification.Classification{}, fmt.Errorf("expecting classification got %#v", value.Classification)
return schema.Schema{}, fmt.Errorf("expect detection to have value of type schema %#v", detection.Value)
}
err = json.NewDecoder(buf).Decode(&classification)
err = json.NewDecoder(buf).Decode(&value)
if err != nil {
return schemaclassification.Classification{}, fmt.Errorf("expecting classification got %#v", value.Classification)
return schema.Schema{}, fmt.Errorf("expect detection to have value of type schema %#v", detection.Value)
}

return classification, nil
return value, nil
}
15 changes: 12 additions & 3 deletions pkg/report/output/dataflow/risks/risks.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/bearer/curio/pkg/commands/process/settings"
"github.com/bearer/curio/pkg/report/output/dataflow/detectiondecoder"
"github.com/bearer/curio/pkg/report/output/dataflow/types"
"github.com/bearer/curio/pkg/report/schema"

"github.com/bearer/curio/pkg/report/detections"
"github.com/bearer/curio/pkg/util/classify"
Expand All @@ -25,6 +26,7 @@ type datatypeHolder struct {
name string
uuid string
categoryUUID string
parent *schema.Parent
files map[string]*fileHolder // group files by filename
}
type fileHolder struct {
Expand All @@ -41,20 +43,25 @@ func New(config settings.Config, isInternal bool) *Holder {
}

func (holder *Holder) AddSchema(detection detections.Detection) error {
classification, err := detectiondecoder.GetSchemaClassification(detection)
schema, err := detectiondecoder.GetSchema(detection)
if err != nil {
return err
}

classification, err := detectiondecoder.GetSchemaClassification(schema, detection)
if err != nil {
return err
}

if classification.Decision.State == classify.Valid {
holder.addDatatype(string(detection.DetectorType), classification.DataType, detection.Source.Filename, *detection.Source.LineNumber)
holder.addDatatype(string(detection.DetectorType), classification.DataType, detection.Source.Filename, *detection.Source.LineNumber, schema.Parent)
}

return nil
}

// addDatatype adds detector to hash list and at the same time blocks duplicates
func (holder *Holder) addDatatype(ruleName string, datatype *db.DataType, fileName string, lineNumber int) {
func (holder *Holder) addDatatype(ruleName string, datatype *db.DataType, fileName string, lineNumber int, parent *schema.Parent) {
// create detector entry if it doesn't exist
if _, exists := holder.detectors[ruleName]; !exists {
holder.detectors[ruleName] = detectorHolder{
Expand All @@ -71,6 +78,7 @@ func (holder *Holder) addDatatype(ruleName string, datatype *db.DataType, fileNa
name: datatype.Name,
uuid: datatype.UUID,
categoryUUID: datatype.CategoryUUID,
parent: parent,
files: make(map[string]*fileHolder),
}
} else {
Expand Down Expand Up @@ -118,6 +126,7 @@ func (holder *Holder) ToDataFlow() []types.RiskDetector {
Name: datatype.name,
UUID: datatype.uuid,
CategoryUUID: datatype.categoryUUID,
Parent: datatype.parent,
Stored: stored,
Locations: make([]types.RiskLocation, 0),
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/report/output/dataflow/types/risks.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package types

import "github.com/bearer/curio/pkg/report/schema"

type RiskDetector struct {
DetectorID string `json:"detector_id" yaml:"detector_id"`
DataTypes []RiskDatatype `json:"data_types" yaml:"data_types"`
Expand All @@ -11,6 +13,7 @@ type RiskDatatype struct {
CategoryUUID string `json:"category_uuid,omitempty" yaml:"category_uuid,omitempty"`
Stored bool `json:"stored" yaml:"stored"`
Locations []RiskLocation `json:"locations" yaml:"locations"`
Parent *schema.Parent `json:"parent,omitempty" yaml:"parent,omitempty"`
}

type RiskLocation struct {
Expand Down
67 changes: 49 additions & 18 deletions pkg/report/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"strings"

"github.com/TwiN/go-color"
"github.com/bearer/curio/pkg/commands/process/settings"
"github.com/bearer/curio/pkg/flag"
"github.com/bearer/curio/pkg/report/output/dataflow"
Expand All @@ -15,13 +14,20 @@ import (
"github.com/bearer/curio/pkg/report/output/policies"
"github.com/bearer/curio/pkg/report/output/stats"
"github.com/bearer/curio/pkg/types"

"github.com/fatih/color"
"gopkg.in/yaml.v3"

"github.com/rs/zerolog"
)

var ErrUndefinedFormat = errors.New("undefined output format")
var underline = color.New(color.Underline).SprintFunc()
var severityColorFns = map[string]func(x ...interface{}) string{
settings.LevelCritical: color.New(color.FgRed).SprintFunc(),
settings.LevelHigh: color.New(color.FgHiRed).SprintFunc(),
settings.LevelMedium: color.New(color.FgYellow).SprintFunc(),
settings.LevelLow: color.New(color.FgBlue).SprintFunc(),
}

func ReportPolicies(report types.Report, output *zerolog.Event, config settings.Config) error {
outputPolicies, err := getPolicyReportOutput(report, config)
Expand All @@ -33,19 +39,19 @@ func ReportPolicies(report types.Report, output *zerolog.Event, config settings.
outputStr.WriteString("===============================")

for _, policyBreach := range outputPolicies[settings.LevelCritical] {
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelCritical, color.Red)
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelCritical)
}

for _, policyBreach := range outputPolicies[settings.LevelHigh] {
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelHigh, color.Yellow)
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelHigh)
}

for _, policyBreach := range outputPolicies[settings.LevelMedium] {
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelMedium, color.Cyan)
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelMedium)
}

for _, policyBreach := range outputPolicies[settings.LevelLow] {
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelLow, color.Blue)
writePolicyBreachToOutput(outputStr, policyBreach, settings.LevelLow)
}

output.Msg(outputStr.String())
Expand Down Expand Up @@ -134,18 +140,6 @@ func getPolicyReportOutput(report types.Report, config settings.Config) (map[str
return policies.GetOutput(dataflow, config)
}

func writePolicyBreachToOutput(outputStr *strings.Builder, policyBreach policies.PolicyResult, policySeverity string, displayColor string) {
outputStr.WriteString("\n")
outputStr.WriteString("\n")
outputStr.WriteString(color.With(displayColor, strings.ToUpper(policySeverity)) + ": ")
outputStr.WriteString(policyBreach.PolicyName + ", policy breached with " + policyBreach.CategoryGroup + "\n")
outputStr.WriteString(policyBreach.PolicyDescription + "\n")
outputStr.WriteString("Filename: " + policyBreach.Filename)
outputStr.WriteString("\n")
outputStr.WriteString("\n")
outputStr.WriteString("===============================")
}

func getDataflow(report types.Report, config settings.Config, isInternal bool) (*dataflow.DataFlow, error) {
reportedDetections, err := detectors.GetOutput(report)
if err != nil {
Expand All @@ -154,3 +148,40 @@ func getDataflow(report types.Report, config settings.Config, isInternal bool) (

return dataflow.GetOutput(reportedDetections, config, isInternal)
}

func writePolicyBreachToOutput(outputStr *strings.Builder, policyBreach policies.PolicyResult, policySeverity string) {
outputStr.WriteString("\n\n")
outputStr.WriteString(formatSeverity(policySeverity))
outputStr.WriteString(policyBreach.PolicyName + " policy breach with " + policyBreach.CategoryGroup + "\n")
outputStr.WriteString(color.HiBlackString(policyBreach.PolicyDescription + "\n"))
outputStr.WriteString("\n")
outputStr.WriteString(color.HiBlueString("File: " + underline(policyBreach.Filename+":"+fmt.Sprint(policyBreach.LineNumber)) + "\n"))
outputStr.WriteString("\n")
outputStr.WriteString(highlightCodeExtract(policyBreach.LineNumber, policyBreach.ParentLineNumber, policyBreach.ParentContent))
outputStr.WriteString("\n\n")
outputStr.WriteString("=====================================")
}

func formatSeverity(policySeverity string) string {
severityColorFn, ok := severityColorFns[policySeverity]
if !ok {
return strings.ToUpper(policySeverity)
}
return severityColorFn(strings.ToUpper(policySeverity + ": "))
}

func highlightCodeExtract(lineNumber int, extractStartLineNumber int, extract string) string {
result := ""
targetIndex := lineNumber - extractStartLineNumber
for index, line := range strings.Split(extract, "\n") {
if index == targetIndex {
result += color.MagentaString(" " + fmt.Sprint(extractStartLineNumber+index) + " ")
result += color.MagentaString(line) + "\n"
} else {
result += " " + fmt.Sprint(extractStartLineNumber+index) + " "
result += line + "\n"
}
}

return result
}
Loading

0 comments on commit 977b62f

Please sign in to comment.