diff --git a/producers/semgrep/BUILD b/producers/semgrep/BUILD new file mode 100644 index 0000000..891a8a6 --- /dev/null +++ b/producers/semgrep/BUILD @@ -0,0 +1,36 @@ +subinclude("@third_party/subrepos/pleasings//docker") + +go_binary( + name = "semgrep", + srcs = [ + "main.go", + ], + deps = [ + "//api/proto:v1", + "//producers", + "//producers/semgrep/types:semgrep-issue" + ], +) + +go_test( + name = "semgrep_test", + srcs = [ + "main.go", + "main_test.go", + ], + deps = [ + "//api/proto:v1", + "//producers", + "//third_party/go:stretchr_testify", + "//producers/semgrep/types:semgrep-issue", + ], +) + +docker_image( + name = "image", + srcs = [ + ":semgrep", + ], + base_image = "//build/docker:dracon-base-go", + image = "dracon-producer-semgrep", +) diff --git a/producers/semgrep/Dockerfile b/producers/semgrep/Dockerfile new file mode 100644 index 0000000..5eda40c --- /dev/null +++ b/producers/semgrep/Dockerfile @@ -0,0 +1,5 @@ +FROM //build/docker:dracon-base-go + +COPY semgrep /semgrep + +ENTRYPOINT ["/semgrep"] \ No newline at end of file diff --git a/producers/semgrep/main.go b/producers/semgrep/main.go new file mode 100644 index 0000000..093e119 --- /dev/null +++ b/producers/semgrep/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "log" + + v1 "github.com/thought-machine/dracon/api/proto/v1" + types "github.com/thought-machine/dracon/producers/semgrep/types/semgrep-issue" + + "github.com/thought-machine/dracon/producers" +) + +func main() { + if err := producers.ParseFlags(); err != nil { + log.Fatal(err) + } + var results types.SemgrepResults + if err := producers.ParseInFileJSON(&results); err != nil { + log.Fatal(err) + } + + issues := parseIssues(results) + if err := producers.WriteDraconOut( + "semgrep", + issues, + ); err != nil { + log.Fatal(err) + } +} + +func parseIssues(out types.SemgrepResults) []*v1.Issue { + issues := []*v1.Issue{} + + results := out.Results + + for _, r := range results { + + // Map the semgrep severity levels to dracon severity levels + severityMap := map[string]v1.Severity{ + "INFO": v1.Severity_SEVERITY_INFO, + "WARNING": v1.Severity_SEVERITY_MEDIUM, + "ERROR": v1.Severity_SEVERITY_HIGH, + } + + sev := severityMap[r.Extra.Severity] + + issues = append(issues, &v1.Issue{ + Target: fmt.Sprintf("%s:%v-%v", r.Path, r.Start.Line, r.End.Line), + Type: r.Extra.Message, + Title: r.CheckID, + Severity: sev, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: r.Extra.Lines, + }) + } + return issues +} diff --git a/producers/semgrep/main_test.go b/producers/semgrep/main_test.go new file mode 100644 index 0000000..529bcd0 --- /dev/null +++ b/producers/semgrep/main_test.go @@ -0,0 +1,87 @@ +package main + +import ( + v1 "github.com/thought-machine/dracon/api/proto/v1" + types "github.com/thought-machine/dracon/producers/semgrep/types/semgrep-issue" + + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +const exampleOutput = ` +{ + "results": [ + { + "check_id": "rules.go.xss.Go using template.HTML", + "path": "/src/go/xss/template-html.go", + "start": {"line": 10, "col": 11}, + "end": {"line": 10, "col": 32}, + "extra": { + "message": "Use of this type presents a security risk: the encapsulated content should come from a trusted source, \nas it will be included verbatim in the template output.\nhttps://blogtitle.github.io/go-safe-html/\n", + "metavars": {}, + "metadata": {}, + "severity": "WARNING", + "lines": "\t\t\treturn template.HTML(revStr)" + } + }, + { + "check_id": "rules.python.grpc.GRPC Insecure Port", + "path": "/src/python/grpc/grpc_insecure_port.py", + "start": {"line": 19, "col": 5}, + "end": {"line": 19, "col": 68}, + "extra": { + "message": "The gRPC server listening port is configured insecurely, this offers no encryption and authentication.\nPlease review and ensure that this is appropriate for the communication. \n", + "metavars": { + "$VAR": { + "start": {"line": 19, "col": 5, "offset": 389}, + "end": {"line": 19, "col": 20, "offset": 404}, + "abstract_content": "insecure_server", + "unique_id": { + "type": "id", "value": "insecure_server", + "kind": "Local", "sid": 8 + } + } + }, + "metadata": {}, + "severity": "WARNING", + "lines": " insecure_server.add_insecure_port('[::]:{}'.format(flags.port))" + } + } + ] +} +` + +func TestParseIssues(t *testing.T) { + semgrepResults := types.SemgrepResults{} + err := json.Unmarshal([]byte(exampleOutput), &semgrepResults) + + assert.Nil(t, err) + issues := parseIssues(semgrepResults) + + expectedIssue := &v1.Issue{ + Target: "/src/go/xss/template-html.go:10-10", + Type: "Use of this type presents a security risk: the encapsulated content should come from a trusted source, \nas it will be included verbatim in the template output.\nhttps://blogtitle.github.io/go-safe-html/\n", + Title: "rules.go.xss.Go using template.HTML", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: "\t\t\treturn template.HTML(revStr)", + } + + assert.Equal(t, expectedIssue, issues[0]) + + expectedIssue2 := &v1.Issue{ + Target: "/src/python/grpc/grpc_insecure_port.py:19-19", + Type: "The gRPC server listening port is configured insecurely, this offers no encryption and authentication.\nPlease review and ensure that this is appropriate for the communication. \n", + Title: "rules.python.grpc.GRPC Insecure Port", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: " insecure_server.add_insecure_port('[::]:{}'.format(flags.port))", + } + + assert.Equal(t, expectedIssue2, issues[1]) + +} \ No newline at end of file diff --git a/producers/semgrep/types/BUILD b/producers/semgrep/types/BUILD new file mode 100644 index 0000000..35cff82 --- /dev/null +++ b/producers/semgrep/types/BUILD @@ -0,0 +1,9 @@ + +go_library( + name = "semgrep-issue", + srcs = [ + "semgrep-issue.go", + ], + visibility = ["//producers/semgrep/..."] + +) diff --git a/producers/semgrep/types/semgrep-issue.go b/producers/semgrep/types/semgrep-issue.go new file mode 100644 index 0000000..e6d5a46 --- /dev/null +++ b/producers/semgrep/types/semgrep-issue.go @@ -0,0 +1,38 @@ +package types + +// Position represents where in the file the finding is located +type Position struct { + Col int `json:"col"` + Line int `json:"line"` +} + +// Extra contains extra info needed for semgrep issue +type Extra struct { + Message string `json:"message"` + Metavars Metavars `json:"metavars"` + Metadata Metadata `json:"metadata"` + Severity string `json:"severity"` + Lines string `json:"lines"` +} + +// SemgrepIssue represents a semgrep issue +type SemgrepIssue struct { + CheckID string `json:"check_id"` + Path string `json:"path"` + Start Position `json:"start"` + End Position `json:"end"` + Extra Extra `json:"extra"` +} + +// SemgrepResults represents a series of semgrep issues +type SemgrepResults struct { + Results []SemgrepIssue `'json:"results"` +} + +// Metavars currently is empty but could represent more metavariables for semgrep +type Metavars struct { +} + +// Metadata currently is empty, however, could represent semgrep issue metadata going forward. +type Metadata struct { +}