From ec24640626619eabb19f8c66862501371acd7c73 Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 28 Dec 2023 00:05:50 +0000 Subject: [PATCH 01/21] add a small library that allows for adding code context to issues --- api/proto/v1/issue.pb.go | 117 ++++++++------ api/proto/v1/issue.proto | 3 + components/producers/golang-gosec/BUILD | 2 + components/producers/golang-gosec/main.go | 21 ++- .../producers/golang-gosec/main_test.go | 56 +++++-- pkg/context/BUILD | 22 +++ pkg/context/context.go | 80 +++++++++ pkg/context/context_test.go | 152 ++++++++++++++++++ 8 files changed, 379 insertions(+), 74 deletions(-) create mode 100644 pkg/context/BUILD create mode 100644 pkg/context/context.go create mode 100644 pkg/context/context_test.go diff --git a/api/proto/v1/issue.pb.go b/api/proto/v1/issue.pb.go index 3e65e7f31..02c6309c6 100644 --- a/api/proto/v1/issue.pb.go +++ b/api/proto/v1/issue.pb.go @@ -179,6 +179,8 @@ type Issue struct { Uuid string `protobuf:"bytes,10,opt,name=uuid,proto3" json:"uuid,omitempty"` // optional field that allows us to also encode a bill of materials in an issue CycloneDXSBOM *string `protobuf:"bytes,11,opt,name=cyclone_d_x_s_b_o_m,json=cycloneDXSBOM,proto3,oneof" json:"cyclone_d_x_s_b_o_m,omitempty"` + // optional string that allows producers to communicate relevant code/request segments + ContextSegment *string `protobuf:"bytes,12,opt,name=context_segment,json=contextSegment,proto3,oneof" json:"context_segment,omitempty"` } func (x *Issue) Reset() { @@ -290,6 +292,13 @@ func (x *Issue) GetCycloneDXSBOM() string { return "" } +func (x *Issue) GetContextSegment() string { + if x != nil && x.ContextSegment != nil { + return *x.ContextSegment + } + return "" +} + // Represents an issue that has been enriched with metadata from the enrichment service type EnrichedIssue struct { state protoimpl.MessageState @@ -400,8 +409,8 @@ var file_api_proto_v1_issue_proto_rawDesc = []byte{ 0x73, 0x73, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x64, 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfe, - 0x02, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc0, + 0x03, 0x0a, 0x05, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, @@ -423,56 +432,60 @@ var file_api_proto_v1_issue_proto_rawDesc = []byte{ 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x79, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x64, 0x5f, 0x78, 0x5f, 0x73, 0x5f, 0x62, 0x5f, 0x6f, 0x5f, 0x6d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x79, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x44, 0x58, - 0x53, 0x42, 0x4f, 0x4d, 0x88, 0x01, 0x01, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x63, 0x79, 0x63, 0x6c, - 0x6f, 0x6e, 0x65, 0x5f, 0x64, 0x5f, 0x78, 0x5f, 0x73, 0x5f, 0x62, 0x5f, 0x6f, 0x5f, 0x6d, 0x22, - 0xa2, 0x03, 0x0a, 0x0d, 0x45, 0x6e, 0x72, 0x69, 0x63, 0x68, 0x65, 0x64, 0x49, 0x73, 0x73, 0x75, - 0x65, 0x12, 0x35, 0x0a, 0x09, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x64, - 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x52, 0x08, - 0x72, 0x61, 0x77, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, 0x72, 0x73, - 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x66, 0x69, 0x72, 0x73, 0x74, 0x53, - 0x65, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x6c, - 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0d, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, - 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, - 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, - 0x53, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x64, - 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x69, 0x63, 0x68, 0x65, - 0x64, 0x49, 0x73, 0x73, 0x75, 0x65, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x96, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x64, 0x65, - 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, 0x43, - 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, - 0x13, 0x0a, 0x0f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x49, 0x4e, - 0x46, 0x4f, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, - 0x43, 0x45, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x46, - 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x03, 0x12, - 0x13, 0x0a, 0x0f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x48, 0x49, - 0x47, 0x48, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, - 0x43, 0x45, 0x5f, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x05, 0x2a, 0x88, 0x01, - 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x45, - 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, - 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x56, 0x45, 0x52, - 0x49, 0x54, 0x59, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x56, - 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x03, 0x12, 0x11, - 0x0a, 0x0d, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x48, 0x49, 0x47, 0x48, 0x10, - 0x04, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x43, 0x52, - 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x05, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x64, - 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x53, 0x42, 0x4f, 0x4d, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x5f, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x01, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, + 0x6e, 0x74, 0x88, 0x01, 0x01, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x63, 0x79, 0x63, 0x6c, 0x6f, 0x6e, + 0x65, 0x5f, 0x64, 0x5f, 0x78, 0x5f, 0x73, 0x5f, 0x62, 0x5f, 0x6f, 0x5f, 0x6d, 0x42, 0x12, 0x0a, + 0x10, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x22, 0xa2, 0x03, 0x0a, 0x0d, 0x45, 0x6e, 0x72, 0x69, 0x63, 0x68, 0x65, 0x64, 0x49, 0x73, + 0x73, 0x75, 0x65, 0x12, 0x35, 0x0a, 0x09, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x2e, 0x64, 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, + 0x52, 0x08, 0x72, 0x61, 0x77, 0x49, 0x73, 0x73, 0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x66, 0x69, 0x72, 0x73, + 0x74, 0x53, 0x65, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x66, + 0x61, 0x6c, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0d, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x76, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x12, 0x53, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x2e, 0x64, 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x72, 0x69, 0x63, + 0x68, 0x65, 0x64, 0x49, 0x73, 0x73, 0x75, 0x65, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x96, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, + 0x4e, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, + 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, + 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, + 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, + 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, 0x45, 0x4e, 0x43, 0x45, 0x5f, + 0x48, 0x49, 0x47, 0x48, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x44, + 0x45, 0x4e, 0x43, 0x45, 0x5f, 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x05, 0x2a, + 0x88, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x14, + 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, + 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, + 0x54, 0x59, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x45, 0x56, + 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, + 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x03, + 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x48, 0x49, 0x47, + 0x48, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, + 0x43, 0x52, 0x49, 0x54, 0x49, 0x43, 0x41, 0x4c, 0x10, 0x05, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x2f, 0x64, 0x72, 0x61, 0x63, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/v1/issue.proto b/api/proto/v1/issue.proto index e18a72282..33aaa2f4a 100644 --- a/api/proto/v1/issue.proto +++ b/api/proto/v1/issue.proto @@ -62,6 +62,9 @@ message Issue { string uuid = 10; // optional field that allows us to also encode a bill of materials in an issue optional string cyclone_d_x_s_b_o_m = 11; + + // optional string that allows producers to communicate relevant code/request segments + optional string context_segment = 12; } /* Represents an issue that has been enriched with metadata from the enrichment service */ diff --git a/components/producers/golang-gosec/BUILD b/components/producers/golang-gosec/BUILD index 9635a50a3..9e43448ca 100644 --- a/components/producers/golang-gosec/BUILD +++ b/components/producers/golang-gosec/BUILD @@ -12,6 +12,7 @@ go_binary( deps = [ "//api/proto/v1", "//components/producers", + "//pkg/context", ], ) @@ -24,6 +25,7 @@ go_test( deps = [ "//api/proto/v1", "//components/producers", + "//pkg/context", "//third_party/go/github.com/stretchr/testify", ], ) diff --git a/components/producers/golang-gosec/main.go b/components/producers/golang-gosec/main.go index 820f64370..b9cfaeef3 100644 --- a/components/producers/golang-gosec/main.go +++ b/components/producers/golang-gosec/main.go @@ -5,6 +5,7 @@ import ( "log" v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/pkg/context" "github.com/ocurity/dracon/components/producers" ) @@ -24,8 +25,10 @@ func main() { log.Fatal(err) } - issues := parseIssues(&results) - + issues, err := parseIssues(&results) + if err != nil { + log.Fatal(err) + } if err := producers.WriteDraconOut( "gosec", issues, @@ -34,10 +37,10 @@ func main() { } } -func parseIssues(out *GoSecOut) []*v1.Issue { +func parseIssues(out *GoSecOut) ([]*v1.Issue, error) { issues := []*v1.Issue{} for _, r := range out.Issues { - issues = append(issues, &v1.Issue{ + iss := &v1.Issue{ Target: fmt.Sprintf("%s:%v", r.File, r.Line), Type: r.RuleID, Title: r.Details, @@ -45,9 +48,15 @@ func parseIssues(out *GoSecOut) []*v1.Issue { Cvss: 0.0, Confidence: v1.Confidence(v1.Confidence_value[fmt.Sprintf("CONFIDENCE_%s", r.Confidence)]), Description: r.Code, - }) + } + code, err := context.ExtractCode(iss) + if err != nil { + return nil, err + } + iss.ContextSegment = &code + issues = append(issues, iss) } - return issues + return issues, nil } // GoSecOut represents the output of a GoSec run. diff --git a/components/producers/golang-gosec/main_test.go b/components/producers/golang-gosec/main_test.go index ae64e8b01..84b5c45dd 100644 --- a/components/producers/golang-gosec/main_test.go +++ b/components/producers/golang-gosec/main_test.go @@ -2,6 +2,8 @@ package main import ( "encoding/json" + "fmt" + "os" "testing" v1 "github.com/ocurity/dracon/api/proto/v1" @@ -9,7 +11,30 @@ import ( "github.com/stretchr/testify/assert" ) -const exampleOutput = ` +var code = `func GetProducts(ctx context.Context, db *sql.DB, category string) ([]Product, error) { + rows, err := db.QueryContext(ctx, "SELECT * FROM product WHERE category='"+category+"'") + if err != nil { + return nil, err + } + defer rows.Close() + var products []Product + for rows.Next() { + var product Product + if err := rows.Scan(&product.Id, &product.Name, &product.Category, &product.Price); err != nil { + return nil, err + }` + +func TestParseIssues(t *testing.T) { + file, err := os.CreateTemp("", "dracon_context_test") + if err != nil { + t.Errorf("could not setup tests for context pkg, could not create temporary files") + } + defer os.Remove(file.Name()) + if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { + t.Errorf("could not setup tests for context pk, could not write temporary file") + } + + exampleOutput := fmt.Sprintf(` { "Issues": [ { @@ -17,9 +42,9 @@ const exampleOutput = ` "confidence": "HIGH", "rule_id": "G304", "details": "Potential file inclusion via variable", - "file": "/tmp/source/foo.go", + "file": "%s", "code": "ioutil.ReadFile(path)", - "line": "33", + "line": "2", "column": "44" } ], @@ -29,23 +54,22 @@ const exampleOutput = ` "nosec": 0, "found": 1 } -}` - -func TestParseIssues(t *testing.T) { +}`, file.Name()) var results GoSecOut - err := json.Unmarshal([]byte(exampleOutput), &results) + err = json.Unmarshal([]byte(exampleOutput), &results) assert.Nil(t, err) - issues := parseIssues(&results) - + issues, err := parseIssues(&results) + assert.Nil(t, err) expectedIssue := &v1.Issue{ - Target: "/tmp/source/foo.go:33", - Type: "G304", - Title: "Potential file inclusion via variable", - Severity: v1.Severity_SEVERITY_MEDIUM, - Cvss: 0.0, - Confidence: v1.Confidence_CONFIDENCE_HIGH, - Description: "ioutil.ReadFile(path)", + Target: fmt.Sprintf("%s:2", file.Name()), + Type: "G304", + Title: "Potential file inclusion via variable", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_HIGH, + Description: "ioutil.ReadFile(path)", + ContextSegment: &code, } assert.Equal(t, []*v1.Issue{expectedIssue}, issues) diff --git a/pkg/context/BUILD b/pkg/context/BUILD new file mode 100644 index 000000000..c234178ae --- /dev/null +++ b/pkg/context/BUILD @@ -0,0 +1,22 @@ +go_library( + name = "context", + srcs = [ + "context.go", + ], + visibility = ["PUBLIC"], + deps = [ + "//api/proto/v1", + ], +) + +go_test( + name = "context_test", + srcs = [ + "context.go", + "context_test.go", + ], + deps = [ + "//api/proto/v1", + "//third_party/go/github.com/stretchr/testify", + ], +) diff --git a/pkg/context/context.go b/pkg/context/context.go new file mode 100644 index 000000000..4c6e3fb2b --- /dev/null +++ b/pkg/context/context.go @@ -0,0 +1,80 @@ +// Package context offers a set of methods which permit components to +// add information about the context of each vulnerability +// this information is highly depended on the actual vulnerability and the component. +// For example for SAST components, context can be a call graph or +// a few lines of code before and after the line that triggered the finding. +// For DAST components it can be a serialised request/response. + +package context + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + + v1 "github.com/ocurity/dracon/api/proto/v1" +) + +const DefaultLineRange = 10 + +// ExtractCode takes a path and line (or line range) for a vulnerable code segment and +// returns the DefaultLineRange lines above and the DefaultLineRange lines below the vulnerability. +// It does not take into account end of function. +func ExtractCode(finding *v1.Issue) (string, error) { + path := "" + lineRange := "" + lineFrom := 0 + lineTo := 0 + + split := strings.Split(finding.Target, ":") + if len(split) < 2 { + path = finding.Target + lineRange = "0" + } else { + path = split[0] + lineRange = split[1] + } + if strings.Contains(lineRange, "-") { + lines := strings.Split(lineRange, "-") + lf, err := strconv.Atoi(lines[0]) + if err != nil { + return "", err + } + lineFrom = lf + lt, err := strconv.Atoi(lines[1]) + if err != nil { + return "", err + } + lineTo = lt + + } else { + lf, err := strconv.Atoi(lineRange) + if err != nil { + return "", err + } + lineFrom = lf + lineTo = lf + } + if lineFrom < DefaultLineRange { + lineFrom = 0 + } else { + lineFrom = lineFrom - DefaultLineRange + } + lineTo = lineTo + DefaultLineRange + handle, err := os.Open(path) + if err != nil { + return "", fmt.Errorf("context pkg could not open file in path %s, err:%v", path, err) + } + sc := bufio.NewScanner(handle) + pos := 0 + lines := []string{} + for sc.Scan() { + if lineFrom <= pos && pos < lineTo { + lines = append(lines, sc.Text()) + } + pos++ + } + return strings.Join(lines, "\n"), nil +} diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go new file mode 100644 index 000000000..f56b8f7d4 --- /dev/null +++ b/pkg/context/context_test.go @@ -0,0 +1,152 @@ +package context + +import ( + "fmt" + "os" + "strings" + "testing" + + v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/stretchr/testify/assert" +) + +func TestExtractCodeLineRange(t *testing.T) { + file, err := os.CreateTemp("", "dracon_context_test") + if err != nil { + t.Errorf("could not setup tests for context pkg, could not create temporary files") + } + defer os.Remove(file.Name()) + if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { + t.Errorf("could not setup tests for context pk, could not write temporary file") + } + issue := v1.Issue{ + Target: fmt.Sprintf("%s:%d-%d", file.Name(), 15, 18), + Type: "id:985", + Title: "SQLI", + Severity: v1.Severity_SEVERITY_HIGH, + Cvss: 9.00, + Confidence: v1.Confidence_CONFIDENCE_INFO, + Description: "found sqli", + Source: "", + } + codeRange, err := ExtractCode(&issue) + assert.Nil(t, err) + assert.Equal(t, strings.Join(strings.Split(code, "\n")[15-DefaultLineRange:18+DefaultLineRange], "\n"), codeRange) +} + +func TestExtractCodeLineRangeLessThanDefault(t *testing.T) { + file, err := os.CreateTemp("", "dracon_context_test") + if err != nil { + t.Errorf("could not setup tests for context pkg, could not create temporary files") + } + defer os.Remove(file.Name()) + if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { + t.Errorf("could not setup tests for context pk, could not write temporary file") + } + issue := v1.Issue{ + Target: fmt.Sprintf("%s:%d-%d", file.Name(), 3, 18), + Type: "id:985", + Title: "SQLI", + Severity: v1.Severity_SEVERITY_HIGH, + Cvss: 9.00, + Confidence: v1.Confidence_CONFIDENCE_INFO, + Description: "found sqli", + Source: "", + } + codeRange, err := ExtractCode(&issue) + assert.Nil(t, err) + assert.Equal(t, strings.Join(strings.Split(code, "\n")[:18+DefaultLineRange], "\n"), codeRange) +} +func TestExtractCodeLine(t *testing.T) { + file, err := os.CreateTemp("", "dracon_context_test") + if err != nil { + t.Errorf("could not setup tests for context pkg, could not create temporary files") + } + defer os.Remove(file.Name()) + if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { + t.Errorf("could not setup tests for context pk, could not write temporary file") + } + issue := v1.Issue{ + Target: fmt.Sprintf("%s:%d", file.Name(), 17), + Type: "id:985", + Title: "SQLI", + Severity: v1.Severity_SEVERITY_HIGH, + Cvss: 9.00, + Confidence: v1.Confidence_CONFIDENCE_INFO, + Description: "found sqli", + Source: "", + } + codeRange, err := ExtractCode(&issue) + assert.Nil(t, err) + assert.Equal(t, strings.Join(strings.Split(code, "\n")[17-DefaultLineRange:17+DefaultLineRange], "\n"), codeRange) +} +func TestExtractCodeInvalidTarget(t *testing.T) { + // target is ip, url or file that does not exist + + issue := v1.Issue{ + Target: "/foo/bar:15", + Type: "id:985", + Title: "SQLI", + Severity: v1.Severity_SEVERITY_HIGH, + Cvss: 9.00, + Confidence: v1.Confidence_CONFIDENCE_INFO, + Description: "found sqli", + Source: "", + } + _, err := ExtractCode(&issue) + assert.NotNil(t, err) + + issue.Target = "192.168.1.1" + _, err = ExtractCode(&issue) + assert.NotNil(t, err) + + issue.Target = "https://www.example.com?a=9-2" + _, err = ExtractCode(&issue) + assert.NotNil(t, err) +} + +const code = `from typing import Optional, NamedTuple +from aiopg.connection import Connection + +class Student(NamedTuple): + id: int + name: str + + @classmethod + def from_raw(cls, raw: tuple): + return cls(*raw) if raw else None + + @staticmethod + async def get(conn: Connection, id_: int): + async with conn.cursor() as cur: + await cur.execute( + 'SELECT id, name FROM students WHERE id = %s', + (id_,), + ) + r = await cur.fetchone() + return Student.from_raw(r) + + @staticmethod + async def get_many(conn: Connection, limit: Optional[int] = None, + offset: Optional[int] = None): + q = 'SELECT id, name FROM students' + params = {} + if limit is not None: + q += ' LIMIT + %(limit)s ' + params['limit'] = limit + if offset is not None: + q += ' OFFSET + %(offset)s ' + params['offset'] = offset + async with conn.cursor() as cur: + await cur.execute(q, params) + results = await cur.fetchall() + return [Student.from_raw(r) for r in results] + + @staticmethod + async def create(conn: Connection, name: str): + q = ("INSERT INTO students (name) " + "VALUES ('%(name)s')" % {'name': name}) + async with conn.cursor() as cur: + await cur.execute(q) + +` From 7f40c74cfce2ca77cafb82b28ac2bf40903d2ab9 Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 28 Dec 2023 00:12:50 +0000 Subject: [PATCH 02/21] Update pkg/context/context_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- pkg/context/context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index f56b8f7d4..9b8d8b40b 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -57,6 +57,7 @@ func TestExtractCodeLineRangeLessThanDefault(t *testing.T) { assert.Nil(t, err) assert.Equal(t, strings.Join(strings.Split(code, "\n")[:18+DefaultLineRange], "\n"), codeRange) } + func TestExtractCodeLine(t *testing.T) { file, err := os.CreateTemp("", "dracon_context_test") if err != nil { From 0800c63c3bb9db2d6f5e081c16c5e0ce18f5762d Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 28 Dec 2023 00:13:03 +0000 Subject: [PATCH 03/21] Update pkg/context/context_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- pkg/context/context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index 9b8d8b40b..38c2d6065 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -81,6 +81,7 @@ func TestExtractCodeLine(t *testing.T) { assert.Nil(t, err) assert.Equal(t, strings.Join(strings.Split(code, "\n")[17-DefaultLineRange:17+DefaultLineRange], "\n"), codeRange) } + func TestExtractCodeInvalidTarget(t *testing.T) { // target is ip, url or file that does not exist From bcc8957de04fabd73b8eb1b31cce02b03a70b159 Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 28 Dec 2023 00:13:14 +0000 Subject: [PATCH 04/21] Update pkg/context/context_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- pkg/context/context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index 38c2d6065..87fc94b72 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -58,6 +58,7 @@ func TestExtractCodeLineRangeLessThanDefault(t *testing.T) { assert.Equal(t, strings.Join(strings.Split(code, "\n")[:18+DefaultLineRange], "\n"), codeRange) } + func TestExtractCodeLine(t *testing.T) { file, err := os.CreateTemp("", "dracon_context_test") if err != nil { From e641f41f41b28027f722d81e9741568636feac9d Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 28 Dec 2023 00:13:23 +0000 Subject: [PATCH 05/21] Update pkg/context/context_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- pkg/context/context_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index 87fc94b72..41b118437 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -83,6 +83,7 @@ func TestExtractCodeLine(t *testing.T) { assert.Equal(t, strings.Join(strings.Split(code, "\n")[17-DefaultLineRange:17+DefaultLineRange], "\n"), codeRange) } + func TestExtractCodeInvalidTarget(t *testing.T) { // target is ip, url or file that does not exist From fd3dd2de4e0b1297e6bbca29c4fce3abdf9cb471 Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 28 Dec 2023 11:23:03 +0000 Subject: [PATCH 06/21] Update pkg/context/context_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- pkg/context/context_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index 41b118437..4aecdfb9d 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -58,7 +58,6 @@ func TestExtractCodeLineRangeLessThanDefault(t *testing.T) { assert.Equal(t, strings.Join(strings.Split(code, "\n")[:18+DefaultLineRange], "\n"), codeRange) } - func TestExtractCodeLine(t *testing.T) { file, err := os.CreateTemp("", "dracon_context_test") if err != nil { From 8ad121f51d25daafb018c6ae40c6019b272963b7 Mon Sep 17 00:00:00 2001 From: Spyros Date: Thu, 28 Dec 2023 11:23:13 +0000 Subject: [PATCH 07/21] Update pkg/context/context_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- pkg/context/context_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index 4aecdfb9d..38c2d6065 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -82,7 +82,6 @@ func TestExtractCodeLine(t *testing.T) { assert.Equal(t, strings.Join(strings.Split(code, "\n")[17-DefaultLineRange:17+DefaultLineRange], "\n"), codeRange) } - func TestExtractCodeInvalidTarget(t *testing.T) { // target is ip, url or file that does not exist From 80e1d6e63e1346ceda10ac018f64f96d340b2a9f Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 28 Dec 2023 19:26:17 +0000 Subject: [PATCH 08/21] migrate producers to add context segments --- components/producers/golang-gosec/BUILD | 1 + .../producers/golang-gosec/main_test.go | 26 +-- components/producers/kics/BUILD | 3 + components/producers/kics/main.go | 22 ++- components/producers/kics/main_test.go | 160 +++++++++++++++--- components/producers/python-bandit/BUILD | 8 + components/producers/python-bandit/main.go | 26 ++- .../producers/python-bandit/main_test.go | 116 +++++++++++++ components/producers/semgrep/BUILD | 3 + components/producers/semgrep/main.go | 23 ++- components/producers/semgrep/main_test.go | 67 +++++--- pkg/context/BUILD | 1 + pkg/context/context.go | 13 +- pkg/context/context_test.go | 25 ++- pkg/testutil/BUILD | 9 + pkg/testutil/createTmp.go | 20 +++ 16 files changed, 430 insertions(+), 93 deletions(-) create mode 100644 pkg/testutil/BUILD create mode 100644 pkg/testutil/createTmp.go diff --git a/components/producers/golang-gosec/BUILD b/components/producers/golang-gosec/BUILD index 9e43448ca..bc2de7a8a 100644 --- a/components/producers/golang-gosec/BUILD +++ b/components/producers/golang-gosec/BUILD @@ -26,6 +26,7 @@ go_test( "//api/proto/v1", "//components/producers", "//pkg/context", + "//pkg/testutil", "//third_party/go/github.com/stretchr/testify", ], ) diff --git a/components/producers/golang-gosec/main_test.go b/components/producers/golang-gosec/main_test.go index 84b5c45dd..c93c969ca 100644 --- a/components/producers/golang-gosec/main_test.go +++ b/components/producers/golang-gosec/main_test.go @@ -7,6 +7,7 @@ import ( "testing" v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/pkg/testutil" "github.com/stretchr/testify/assert" ) @@ -24,17 +25,7 @@ var code = `func GetProducts(ctx context.Context, db *sql.DB, category string) ( return nil, err }` -func TestParseIssues(t *testing.T) { - file, err := os.CreateTemp("", "dracon_context_test") - if err != nil { - t.Errorf("could not setup tests for context pkg, could not create temporary files") - } - defer os.Remove(file.Name()) - if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { - t.Errorf("could not setup tests for context pk, could not write temporary file") - } - - exampleOutput := fmt.Sprintf(` +var gosecout = ` { "Issues": [ { @@ -54,7 +45,16 @@ func TestParseIssues(t *testing.T) { "nosec": 0, "found": 1 } -}`, file.Name()) +}` + +func TestParseIssues(t *testing.T) { + + f, err := testutil.CreateFile("gosec_tests_vuln_code", code) + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + exampleOutput := fmt.Sprintf(gosecout, f.Name()) var results GoSecOut err = json.Unmarshal([]byte(exampleOutput), &results) assert.Nil(t, err) @@ -62,7 +62,7 @@ func TestParseIssues(t *testing.T) { issues, err := parseIssues(&results) assert.Nil(t, err) expectedIssue := &v1.Issue{ - Target: fmt.Sprintf("%s:2", file.Name()), + Target: fmt.Sprintf("%s:2", f.Name()), Type: "G304", Title: "Potential file inclusion via variable", Severity: v1.Severity_SEVERITY_MEDIUM, diff --git a/components/producers/kics/BUILD b/components/producers/kics/BUILD index 7f4b6a76f..b7ed6dd8b 100644 --- a/components/producers/kics/BUILD +++ b/components/producers/kics/BUILD @@ -14,6 +14,7 @@ go_binary( "//api/proto/v1", "//components/producers", "//components/producers/kics/types", + "//pkg/context", "//pkg/sarif", ], ) @@ -30,6 +31,8 @@ go_test( "//components/producers/kics/types", "//pkg/sarif", "//third_party/go/github.com/stretchr/testify", + "//pkg/testutil", + "//pkg/context", ], ) diff --git a/components/producers/kics/main.go b/components/producers/kics/main.go index c42504a74..3efed513e 100644 --- a/components/producers/kics/main.go +++ b/components/producers/kics/main.go @@ -9,6 +9,7 @@ import ( v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers" "github.com/ocurity/dracon/components/producers/kics/types" + "github.com/ocurity/dracon/pkg/context" "github.com/ocurity/dracon/pkg/sarif" ) @@ -48,14 +49,18 @@ func main() { if err := producers.ParseJSON(inFile, &results); err != nil { log.Fatal(err) } - if err := producers.WriteDraconOut("KICS", parseOut(results)); err != nil { + res, err := parseOut(results) + if err != nil { + log.Fatal(err) + } + if err := producers.WriteDraconOut("KICS", res); err != nil { log.Fatal(err) } } } -func parseOut(results types.KICSOut) []*v1.Issue { +func parseOut(results types.KICSOut) ([]*v1.Issue, error) { issues := []*v1.Issue{} for _, query := range results.Queries { queryCopy := query @@ -64,8 +69,7 @@ func parseOut(results types.KICSOut) []*v1.Issue { for _, file := range query.Files { queryCopy.Files = []types.KICSFile{file} description, _ := json.Marshal(queryCopy) - - issues = append(issues, &v1.Issue{ + iss := &v1.Issue{ Target: fmt.Sprintf("%s:%d", file.FileName, file.Line), Type: file.IssueType, Severity: KICSSeverityToDracon(query.Severity), @@ -75,11 +79,17 @@ func parseOut(results types.KICSOut) []*v1.Issue { file.ResourceType, file.ResourceName), Description: string(description), - }) + } + cs, err := context.ExtractCode(iss) + if err != nil { + return nil, err + } + iss.ContextSegment = &cs + issues = append(issues, iss) } } - return issues + return issues, nil } // KICSSeverityToDracon maps KCIS Severity Strings to dracon struct. diff --git a/components/producers/kics/main_test.go b/components/producers/kics/main_test.go index 0431d35ee..742edefd6 100644 --- a/components/producers/kics/main_test.go +++ b/components/producers/kics/main_test.go @@ -2,52 +2,74 @@ package main import ( "encoding/json" + "os" + "strings" "testing" "github.com/stretchr/testify/assert" v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers/kics/types" + "github.com/ocurity/dracon/pkg/testutil" ) func TestParseOut(t *testing.T) { - var results types.KICSOut - err := json.Unmarshal([]byte(exampleOutput), &results) - if err != nil { - t.Logf(err.Error()) - t.Fail() - } - issues := parseOut(results) + expectedIssues := []*v1.Issue{ { - Target: "../../code/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive.yaml:10", + Target: "sql_server_predictable_admin_account_name:10", Type: "MissingAttribute", Title: "Insecure Configurations azure_rm_sqlserver Create (or update) SQL Server2", Severity: 4, - Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"b176e927-bbe2-44a6-a9c3-041417137e5f\",\"query_url\":\"https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlserver_module.html#parameter-ad_user\",\"severity\":\"HIGH\",\"platform\":\"Ansible\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"afa96f09\",\"files\":[{\"file_name\":\"../../code/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive.yaml\",\"similarity_id\":\"819786035c5ea3943e303532e747fabac9f8beaddddfd6ecb863382481c50c0c\",\"line\":10,\"resource_type\":\"azure_rm_sqlserver\",\"resource_name\":\"Create (or update) SQL Server2\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"name={{Create (or update) SQL Server2}}.{{azure_rm_sqlserver}}\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"azure_rm_sqlserver.ad_user should be defined\",\"actual_value\":\"azure_rm_sqlserver.ad_user is undefined\"}]}", + Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"b176e927-bbe2-44a6-a9c3-041417137e5f\",\"query_url\":\"https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlserver_module.html#parameter-ad_user\",\"severity\":\"HIGH\",\"platform\":\"Ansible\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"afa96f09\",\"files\":[{\"file_name\":\"sql_server_predictable_admin_account_name\",\"similarity_id\":\"819786035c5ea3943e303532e747fabac9f8beaddddfd6ecb863382481c50c0c\",\"line\":10,\"resource_type\":\"azure_rm_sqlserver\",\"resource_name\":\"Create (or update) SQL Server2\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"name={{Create (or update) SQL Server2}}.{{azure_rm_sqlserver}}\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"azure_rm_sqlserver.ad_user should be defined\",\"actual_value\":\"azure_rm_sqlserver.ad_user is undefined\"}]}", }, { - Target: "../../code/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive.yaml:3", + Target: "ad_admin_not_configured_for_sql_server:3", Type: "MissingAttribute", Title: "Insecure Configurations azure_rm_sqlserver Create (or update) SQL Server", Severity: 4, - Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"b176e927-bbe2-44a6-a9c3-041417137e5f\",\"query_url\":\"https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlserver_module.html#parameter-ad_user\",\"severity\":\"HIGH\",\"platform\":\"Ansible\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"afa96f09\",\"files\":[{\"file_name\":\"../../code/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive.yaml\",\"similarity_id\":\"e2acb4c0c4afe11e1908953bb768bab35e0ca53d4d20bc6af02f1c5350cbe587\",\"line\":3,\"resource_type\":\"azure_rm_sqlserver\",\"resource_name\":\"Create (or update) SQL Server\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"name={{Create (or update) SQL Server}}.{{azure_rm_sqlserver}}\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"azure_rm_sqlserver.ad_user should be defined\",\"actual_value\":\"azure_rm_sqlserver.ad_user is undefined\"}]}", + Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"b176e927-bbe2-44a6-a9c3-041417137e5f\",\"query_url\":\"https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlserver_module.html#parameter-ad_user\",\"severity\":\"HIGH\",\"platform\":\"Ansible\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"afa96f09\",\"files\":[{\"file_name\":\"ad_admin_not_configured_for_sql_server\",\"similarity_id\":\"e2acb4c0c4afe11e1908953bb768bab35e0ca53d4d20bc6af02f1c5350cbe587\",\"line\":3,\"resource_type\":\"azure_rm_sqlserver\",\"resource_name\":\"Create (or update) SQL Server\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"name={{Create (or update) SQL Server}}.{{azure_rm_sqlserver}}\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"azure_rm_sqlserver.ad_user should be defined\",\"actual_value\":\"azure_rm_sqlserver.ad_user is undefined\"}]}", }, { - Target: "../../code/assets/queries/terraform/azure/small_msql_server_audit_retention/test/positive.tf:54", + Target: "small_msql_server_audit_retention:54", Type: "MissingAttribute", Title: "Insecure Configurations azurerm_sql_server sqlserver", Severity: 4, - Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"a3a055d2-9a2e-4cc9-b9fb-12850a1a3a4b\",\"query_url\":\"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/sql_active_directory_administrator\",\"severity\":\"HIGH\",\"platform\":\"Terraform\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"bccbda19\",\"files\":[{\"file_name\":\"../../code/assets/queries/terraform/azure/small_msql_server_audit_retention/test/positive.tf\",\"similarity_id\":\"a34bdc92c802268f18d3d56d43da1744d74d2dcdb8de3ca74ea2a92471eb7f5e\",\"line\":54,\"resource_type\":\"azurerm_sql_server\",\"resource_name\":\"sqlserver\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"azurerm_sql_server[positive4]\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"A 'azurerm_sql_active_directory_administrator' should be defined for 'azurerm_sql_server[positive4]'\",\"actual_value\":\"A 'azurerm_sql_active_directory_administrator' is not defined for 'azurerm_sql_server[positive4]'\"}]}", + Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"a3a055d2-9a2e-4cc9-b9fb-12850a1a3a4b\",\"query_url\":\"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/sql_active_directory_administrator\",\"severity\":\"HIGH\",\"platform\":\"Terraform\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"bccbda19\",\"files\":[{\"file_name\":\"small_msql_server_audit_retention\",\"similarity_id\":\"a34bdc92c802268f18d3d56d43da1744d74d2dcdb8de3ca74ea2a92471eb7f5e\",\"line\":54,\"resource_type\":\"azurerm_sql_server\",\"resource_name\":\"sqlserver\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"azurerm_sql_server[positive4]\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"A 'azurerm_sql_active_directory_administrator' should be defined for 'azurerm_sql_server[positive4]'\",\"actual_value\":\"A 'azurerm_sql_active_directory_administrator' is not defined for 'azurerm_sql_server[positive4]'\"}]}", }, { - Target: "../../code/assets/queries/terraform/azure/sql_server_predictable_admin_account_name/test/positive.tf:35", + Target: "sql_server_predictable_admin_account_name:35", Type: "MissingAttribute", Title: "Insecure Configurations azurerm_sql_server mssqlserver", Severity: 4, - Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"a3a055d2-9a2e-4cc9-b9fb-12850a1a3a4b\",\"query_url\":\"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/sql_active_directory_administrator\",\"severity\":\"HIGH\",\"platform\":\"Terraform\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"bccbda19\",\"files\":[{\"file_name\":\"../../code/assets/queries/terraform/azure/sql_server_predictable_admin_account_name/test/positive.tf\",\"similarity_id\":\"86e39f354b76020e5e4d3fa85469a87509ffff482eaada3567df0f873b1642de\",\"line\":35,\"resource_type\":\"azurerm_sql_server\",\"resource_name\":\"mssqlserver\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"azurerm_sql_server[positive4]\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"A 'azurerm_sql_active_directory_administrator' should be defined for 'azurerm_sql_server[positive4]'\",\"actual_value\":\"A 'azurerm_sql_active_directory_administrator' is not defined for 'azurerm_sql_server[positive4]'\"}]}", + Description: "{\"query_name\":\"AD Admin Not Configured For SQL Server\",\"query_id\":\"a3a055d2-9a2e-4cc9-b9fb-12850a1a3a4b\",\"query_url\":\"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/sql_active_directory_administrator\",\"severity\":\"HIGH\",\"platform\":\"Terraform\",\"cloud_provider\":\"AZURE\",\"category\":\"Insecure Configurations\",\"description\":\"The Active Directory Administrator is not configured for a SQL server\",\"description_id\":\"bccbda19\",\"files\":[{\"file_name\":\"sql_server_predictable_admin_account_name\",\"similarity_id\":\"86e39f354b76020e5e4d3fa85469a87509ffff482eaada3567df0f873b1642de\",\"line\":35,\"resource_type\":\"azurerm_sql_server\",\"resource_name\":\"mssqlserver\",\"issue_type\":\"MissingAttribute\",\"search_key\":\"azurerm_sql_server[positive4]\",\"search_line\":0,\"search_value\":\"\",\"expected_value\":\"A 'azurerm_sql_active_directory_administrator' should be defined for 'azurerm_sql_server[positive4]'\",\"actual_value\":\"A 'azurerm_sql_active_directory_administrator' is not defined for 'azurerm_sql_server[positive4]'\"}]}", }, } + // setup relevant filenames + for name, content := range dummyCode { + f, err := testutil.CreateFile(name, content) + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + for i, iss := range expectedIssues { + if strings.HasPrefix(iss.Target, name) { + expectedIssues[i].Target = strings.Replace(iss.Target, name, f.Name(), -1) + expectedIssues[i].Description = strings.Replace(iss.Description, name, f.Name(), -1) + expectedIssues[i].ContextSegment = &content + } + } + exampleOutput = strings.ReplaceAll(exampleOutput, name, f.Name()) + } + + var results types.KICSOut + err := json.Unmarshal([]byte(exampleOutput), &results) + if err != nil { + t.Logf(err.Error()) + t.Fail() + } + issues, err := parseOut(results) + assert.Nil(t, err) found := 0 assert.Equal(t, len(expectedIssues), len(issues)) @@ -70,6 +92,106 @@ func TestParseOut(t *testing.T) { assert.Equal(t, found, len(issues)) // assert everything has been found } +var dummyCode = map[string]string{ + "sql_server_predictable_admin_account_name": `#this is a problematic code where the query should report a result(s) + - name: Create (or update) SQL Server1 + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name1 + location: westus + admin_username: "" + admin_password: Testpasswordxyz12! + - name: Create (or update) SQL Server2 + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name2 + location: westus + admin_username: + admin_password: Testpasswordxyz12! + - name: Create (or update) SQL Server3 + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name3 + location: westus + admin_username: admin + admin_password: Testpasswordxyz12!`, + "ad_admin_not_configured_for_sql_server": `- name: Create (or update) SQL Server + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name + location: westus + admin_username: mylogin + admin_password: Testpasswordxyz12! + ad_user: sqladmin`, + "small_msql_server_audit_retention": `resource "azurerm_sql_database" "positive1" { + name = "myexamplesqldatabase" + resource_group_name = azurerm_resource_group.example.name + location = "West US" + server_name = azurerm_sql_server.example.name + + extended_auditing_policy { + storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint + storage_account_access_key = azurerm_storage_account.example.primary_access_key + storage_account_access_key_is_secondary = true + } + + tags = { + environment = "production" + } + } + + resource "azurerm_sql_database" "positive2" { + name = "myexamplesqldatabase" + resource_group_name = azurerm_resource_group.example.name + location = "West US" + server_name = azurerm_sql_server.example.name + + extended_auditing_policy { + storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint + storage_account_access_key = azurerm_storage_account.example.primary_access_key + storage_account_access_key_is_secondary = true + retention_in_days = 90 + } + + tags = { + environment = "production" + } + } + + resource "azurerm_sql_database" "positive3" { + name = "myexamplesqldatabase" + resource_group_name = azurerm_resource_group.example.name + location = "West US" + server_name = azurerm_sql_server.example.name + + extended_auditing_policy { + storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint + storage_account_access_key = azurerm_storage_account.example.primary_access_key + storage_account_access_key_is_secondary = true + retention_in_days = 0 + } + + tags = { + environment = "production" + } + } + + resource "azurerm_sql_server" "positive4" { + name = "sqlserver" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + version = "12.0" + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" + + extended_auditing_policy { + storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint + storage_account_access_key = azurerm_storage_account.example.primary_access_key + storage_account_access_key_is_secondary = true + retention_in_days = 20 + } + }`, +} var exampleOutput = ` { "kics_version": "v1.6.5", @@ -109,7 +231,7 @@ var exampleOutput = ` "description_id": "afa96f09", "files": [ { - "file_name": "../../code/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive.yaml", + "file_name": "sql_server_predictable_admin_account_name", "similarity_id": "819786035c5ea3943e303532e747fabac9f8beaddddfd6ecb863382481c50c0c", "line": 10, "resource_type": "azure_rm_sqlserver", @@ -122,7 +244,7 @@ var exampleOutput = ` "actual_value": "azure_rm_sqlserver.ad_user is undefined" }, { - "file_name": "../../code/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive.yaml", + "file_name": "ad_admin_not_configured_for_sql_server", "similarity_id": "e2acb4c0c4afe11e1908953bb768bab35e0ca53d4d20bc6af02f1c5350cbe587", "line": 3, "resource_type": "azure_rm_sqlserver", @@ -148,7 +270,7 @@ var exampleOutput = ` "description_id": "bccbda19", "files": [ { - "file_name": "../../code/assets/queries/terraform/azure/small_msql_server_audit_retention/test/positive.tf", + "file_name": "small_msql_server_audit_retention", "similarity_id": "a34bdc92c802268f18d3d56d43da1744d74d2dcdb8de3ca74ea2a92471eb7f5e", "line": 54, "resource_type": "azurerm_sql_server", @@ -161,7 +283,7 @@ var exampleOutput = ` "actual_value": "A 'azurerm_sql_active_directory_administrator' is not defined for 'azurerm_sql_server[positive4]'" }, { - "file_name": "../../code/assets/queries/terraform/azure/sql_server_predictable_admin_account_name/test/positive.tf", + "file_name": "sql_server_predictable_admin_account_name", "similarity_id": "86e39f354b76020e5e4d3fa85469a87509ffff482eaada3567df0f873b1642de", "line": 35, "resource_type": "azurerm_sql_server", diff --git a/components/producers/python-bandit/BUILD b/components/producers/python-bandit/BUILD index 5f53739f8..fb5ddebae 100644 --- a/components/producers/python-bandit/BUILD +++ b/components/producers/python-bandit/BUILD @@ -18,8 +18,16 @@ go_binary( go_test( name = "python-bandit_test", srcs = [ + "main.go", "main_test.go", ], + deps = [ + "//api/proto/v1", + "//components/producers", + "//pkg/context", + "//pkg/testutil", + "//third_party/go/github.com/stretchr/testify", + ], ) buildkit_distroless_image( diff --git a/components/producers/python-bandit/main.go b/components/producers/python-bandit/main.go index 84af36447..1f6304ccb 100644 --- a/components/producers/python-bandit/main.go +++ b/components/producers/python-bandit/main.go @@ -3,8 +3,10 @@ package main import ( "fmt" "log" + "strings" v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/pkg/context" "github.com/ocurity/dracon/components/producers" ) @@ -26,7 +28,11 @@ func main() { issues := []*v1.Issue{} for _, res := range results.Results { - issues = append(issues, parseResult(res)) + iss, err := parseResult(res) + if err != nil { + log.Fatal(err) + } + issues = append(issues, iss) } if err := producers.WriteDraconOut( @@ -37,16 +43,26 @@ func main() { } } -func parseResult(r *BanditResult) *v1.Issue { - return &v1.Issue{ - Target: fmt.Sprintf("%s:%v", r.Filename, r.LineRange), +func parseResult(r *BanditResult) (*v1.Issue, error) { + rng := []string{} + for _, r := range r.LineRange { + rng = append(rng, fmt.Sprintf("%d", r)) + } + iss := v1.Issue{ + Target: fmt.Sprintf("%s:%s", r.Filename, strings.Join(rng, "-")), Type: r.TestID, Title: r.TestName, Severity: v1.Severity(v1.Severity_value[fmt.Sprintf("SEVERITY_%s", r.IssueSeverity)]), Cvss: 0.0, Confidence: v1.Confidence(v1.Confidence_value[fmt.Sprintf("CONFIDENCE_%s", r.IssueConfidence)]), - Description: r.IssueText, + Description: fmt.Sprintf("%s\ncode:%s", r.IssueText, r.Code), + } + code, err := context.ExtractCode(&iss) // Bandit only extracts a small code sample, we think it's better to have more + if err != nil { + return nil, err } + iss.ContextSegment = &code + return &iss, nil } // BanditOut represents the output of a bandit run. diff --git a/components/producers/python-bandit/main_test.go b/components/producers/python-bandit/main_test.go index 06ab7d0f9..f7f5cb124 100644 --- a/components/producers/python-bandit/main_test.go +++ b/components/producers/python-bandit/main_test.go @@ -1 +1,117 @@ package main + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/pkg/testutil" + + "github.com/stretchr/testify/assert" +) + +var code = `q += ' LIMIT + %(limit)s ' + params['limit'] = limit + if offset is not None: + q += ' OFFSET + %(offset)s ' + params['offset'] = offset + async with conn.cursor() as cur: + await cur.execute(q, params) + results = await cur.fetchall() + return [Student.from_raw(r) for r in results]` + +func TestParseIssues(t *testing.T) { + + f, err := testutil.CreateFile("bandit_tests_vuln_code", code) + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + exampleOutput := fmt.Sprintf(sampleOut, f.Name(), f.Name()) + var results BanditOut + err = json.Unmarshal([]byte(exampleOutput), &results) + assert.Nil(t, err) + + issues := []*v1.Issue{} + for _, res := range results.Results { + iss, err := parseResult(res) + assert.Nil(t, err, fmt.Sprintf("%s", err)) + issues = append(issues, iss) + } + expectedIssues := []*v1.Issue{ + { + Target: f.Name() + ":5", + Type: "B404", + Title: "blacklist", + Severity: v1.Severity_SEVERITY_LOW, + Cvss: 0, + Confidence: v1.Confidence_CONFIDENCE_HIGH, + Description: "Consider possible security implications associated with the subprocess module.\ncode:17 import shutil\n18 import subprocess\n19 import sys\n", + Source: "", + Cve: "", + Uuid: "", + ContextSegment: &code, + }, + { + Target: f.Name() + ":6", + Type: "B603", + Title: "subprocess_without_shell_equals_true", + Severity: v1.Severity_SEVERITY_LOW, + Cvss: 0, + Confidence: v1.Confidence_CONFIDENCE_HIGH, + Description: "subprocess call - check for execution of untrusted input.\ncode:104 try:\n105 output = subprocess.check_output(bandit_command)\n106 except subprocess.CalledProcessError as e:\n", + Source: "", + Cve: "", + Uuid: "", + ContextSegment: &code, + }} + + assert.Equal(t, expectedIssues, issues) +} + +var sampleOut = `{ + "results": [ + { + "code": "17 import shutil\n18 import subprocess\n19 import sys\n", + "col_offset": 0, + "end_col_offset": 17, + "filename": "%s", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 78, + "link": "https://cwe.mitre.org/data/definitions/78.html" + }, + "issue_severity": "LOW", + "issue_text": "Consider possible security implications associated with the subprocess module.", + "line_number": 5, + "line_range": [ + 5 + ], + "more_info": "https://bandit.readthedocs.io/en/1.7.5/blacklists/blacklist_imports.html#b404-import-subprocess", + "test_id": "B404", + "test_name": "blacklist" + }, + { + "code": "104 try:\n105 output = subprocess.check_output(bandit_command)\n106 except subprocess.CalledProcessError as e:\n", + "col_offset": 25, + "end_col_offset": 64, + "filename": "%s", + "issue_confidence": "HIGH", + "issue_cwe": { + "id": 78, + "link": "https://cwe.mitre.org/data/definitions/78.html" + }, + "issue_severity": "LOW", + "issue_text": "subprocess call - check for execution of untrusted input.", + "line_number": 6, + "line_range": [ + 6 + ], + "more_info": "https://bandit.readthedocs.io/en/1.7.5/plugins/b603_subprocess_without_shell_equals_true.html", + "test_id": "B603", + "test_name": "subprocess_without_shell_equals_true" + } + ] +}` diff --git a/components/producers/semgrep/BUILD b/components/producers/semgrep/BUILD index 4cc85c658..3ece429ee 100644 --- a/components/producers/semgrep/BUILD +++ b/components/producers/semgrep/BUILD @@ -12,6 +12,7 @@ go_binary( "//api/proto/v1", "//components/producers", "//components/producers/semgrep/types", + "//pkg/context", ], ) @@ -25,6 +26,8 @@ go_test( "//api/proto/v1", "//components/producers", "//components/producers/semgrep/types", + "//pkg/context", + "//pkg/testutil", "//third_party/go/github.com/stretchr/testify", ], ) diff --git a/components/producers/semgrep/main.go b/components/producers/semgrep/main.go index 800132401..5215bd180 100644 --- a/components/producers/semgrep/main.go +++ b/components/producers/semgrep/main.go @@ -6,6 +6,7 @@ import ( v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers/semgrep/types" + "github.com/ocurity/dracon/pkg/context" "github.com/ocurity/dracon/components/producers" ) @@ -25,7 +26,10 @@ func main() { log.Fatal(err) } - issues := parseIssues(results) + issues, err := parseIssues(results) + if err != nil { + log.Fatal(err) + } if err := producers.WriteDraconOut( "semgrep", issues, @@ -34,7 +38,7 @@ func main() { } } -func parseIssues(out types.SemgrepResults) []*v1.Issue { +func parseIssues(out types.SemgrepResults) ([]*v1.Issue, error) { issues := []*v1.Issue{} results := out.Results @@ -49,16 +53,21 @@ func parseIssues(out types.SemgrepResults) []*v1.Issue { } sev := severityMap[r.Extra.Severity] - - issues = append(issues, &v1.Issue{ + iss := &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, - }) + Description: fmt.Sprintf("%s\n extra lines: %s", r.Extra.Message, r.Extra.Lines), + } + cs, err := context.ExtractCode(iss) + if err != nil { + return nil, err + } + iss.ContextSegment = &cs + issues = append(issues, iss) } - return issues + return issues, nil } diff --git a/components/producers/semgrep/main_test.go b/components/producers/semgrep/main_test.go index 5ac412687..644379a6a 100644 --- a/components/producers/semgrep/main_test.go +++ b/components/producers/semgrep/main_test.go @@ -2,10 +2,13 @@ package main import ( "encoding/json" + "fmt" + "os" "testing" v1 "github.com/ocurity/dracon/api/proto/v1" types "github.com/ocurity/dracon/components/producers/semgrep/types" + "github.com/ocurity/dracon/pkg/testutil" "github.com/stretchr/testify/assert" ) @@ -15,9 +18,9 @@ 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}, + "path": "%s", + "start": {"line": 3, "col": 11}, + "end": {"line": 3, "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": {}, @@ -28,9 +31,9 @@ const exampleOutput = ` }, { "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}, + "path": "%s", + "start": {"line": 4, "col": 5}, + "end": {"line": 4, "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": { @@ -53,33 +56,53 @@ const exampleOutput = ` } ` +var code = `q += ' LIMIT + %(limit)s ' + params['limit'] = limit + if offset is not None: + q += ' OFFSET + %(offset)s ' + params['offset'] = offset + async with conn.cursor() as cur: + await cur.execute(q, params) + results = await cur.fetchall() + return [Student.from_raw(r) for r in results]` + func TestParseIssues(t *testing.T) { + + f, err := testutil.CreateFile("semgrep_tests_vuln_code", code) + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + semgrepResults := types.SemgrepResults{} - err := json.Unmarshal([]byte(exampleOutput), &semgrepResults) + err = json.Unmarshal([]byte(fmt.Sprintf(exampleOutput, f.Name(), f.Name())), &semgrepResults) assert.Nil(t, err) - issues := parseIssues(semgrepResults) + issues, err := parseIssues(semgrepResults) + assert.Nil(t, err) 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)", + Target: f.Name() + ":3-3", + 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: "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\n extra lines: \t\t\treturn template.HTML(revStr)", + ContextSegment: &code, } 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))", + Target: f.Name() + ":4-4", + 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: "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\n extra lines: insecure_server.add_insecure_port('[::]:{}'.format(flags.port))", + ContextSegment: &code, } assert.Equal(t, expectedIssue2, issues[1]) diff --git a/pkg/context/BUILD b/pkg/context/BUILD index c234178ae..33742146c 100644 --- a/pkg/context/BUILD +++ b/pkg/context/BUILD @@ -17,6 +17,7 @@ go_test( ], deps = [ "//api/proto/v1", + "//pkg/testutil", "//third_party/go/github.com/stretchr/testify", ], ) diff --git a/pkg/context/context.go b/pkg/context/context.go index 4c6e3fb2b..18bbb0e4f 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -1,10 +1,10 @@ // Package context offers a set of methods which permit components to -// add information about the context of each vulnerability -// this information is highly depended on the actual vulnerability and the component. -// For example for SAST components, context can be a call graph or -// a few lines of code before and after the line that triggered the finding. -// For DAST components it can be a serialised request/response. - +// +// add information about the context of each vulnerability +// this information is highly depended on the actual vulnerability and the component. +// For example for SAST components, context can be a call graph or +// a few lines of code before and after the line that triggered the finding. +// For DAST components it can be a serialised request/response. package context import ( @@ -17,6 +17,7 @@ import ( v1 "github.com/ocurity/dracon/api/proto/v1" ) +// DefaultLineRange controls how many lines of code context will be returned by default const DefaultLineRange = 10 // ExtractCode takes a path and line (or line range) for a vulnerable code segment and diff --git a/pkg/context/context_test.go b/pkg/context/context_test.go index 38c2d6065..41d1d31c6 100644 --- a/pkg/context/context_test.go +++ b/pkg/context/context_test.go @@ -7,18 +7,17 @@ import ( "testing" v1 "github.com/ocurity/dracon/api/proto/v1" + "github.com/ocurity/dracon/pkg/testutil" "github.com/stretchr/testify/assert" ) func TestExtractCodeLineRange(t *testing.T) { - file, err := os.CreateTemp("", "dracon_context_test") + file, err := testutil.CreateFile("dracon_context_test", code) if err != nil { - t.Errorf("could not setup tests for context pkg, could not create temporary files") + t.Error(err) } defer os.Remove(file.Name()) - if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { - t.Errorf("could not setup tests for context pk, could not write temporary file") - } + issue := v1.Issue{ Target: fmt.Sprintf("%s:%d-%d", file.Name(), 15, 18), Type: "id:985", @@ -35,14 +34,12 @@ func TestExtractCodeLineRange(t *testing.T) { } func TestExtractCodeLineRangeLessThanDefault(t *testing.T) { - file, err := os.CreateTemp("", "dracon_context_test") + file, err := testutil.CreateFile("dracon_context_test", code) if err != nil { - t.Errorf("could not setup tests for context pkg, could not create temporary files") + t.Error(err) } defer os.Remove(file.Name()) - if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { - t.Errorf("could not setup tests for context pk, could not write temporary file") - } + issue := v1.Issue{ Target: fmt.Sprintf("%s:%d-%d", file.Name(), 3, 18), Type: "id:985", @@ -59,14 +56,12 @@ func TestExtractCodeLineRangeLessThanDefault(t *testing.T) { } func TestExtractCodeLine(t *testing.T) { - file, err := os.CreateTemp("", "dracon_context_test") + file, err := testutil.CreateFile("dracon_context_test", code) if err != nil { - t.Errorf("could not setup tests for context pkg, could not create temporary files") + t.Error(err) } defer os.Remove(file.Name()) - if err := os.WriteFile(file.Name(), []byte(code), os.ModeAppend); err != nil { - t.Errorf("could not setup tests for context pk, could not write temporary file") - } + issue := v1.Issue{ Target: fmt.Sprintf("%s:%d", file.Name(), 17), Type: "id:985", diff --git a/pkg/testutil/BUILD b/pkg/testutil/BUILD new file mode 100644 index 000000000..bdbaf3bf7 --- /dev/null +++ b/pkg/testutil/BUILD @@ -0,0 +1,9 @@ +go_library( + name = "testutil", + srcs = [ + "createTmp.go", + ], + visibility = ["PUBLIC"], + deps = [ + ], +) diff --git a/pkg/testutil/createTmp.go b/pkg/testutil/createTmp.go new file mode 100644 index 000000000..a2869da38 --- /dev/null +++ b/pkg/testutil/createTmp.go @@ -0,0 +1,20 @@ +// Package test contains helper functions and subpackages to make testing the project easier +package testutil + +import ( + "fmt" + "os" +) + +// CreateFile creates a temporary file with the contents passed in the relevant param +// deleting the file is the responsibility of the caller +func CreateFile(filename, content string) (*os.File, error) { + file, err := os.CreateTemp("", filename) + if err != nil { + return nil, fmt.Errorf("could not setup tests for pkg, could not create temporary files") + } + if err := os.WriteFile(file.Name(), []byte(content), os.ModeAppend); err != nil { + return nil, fmt.Errorf("could not setup tests for pkg, could not write temporary file") + } + return file, nil +} From 87fb704e509558b1d1e3898bdf6b6237599a704a Mon Sep 17 00:00:00 2001 From: Spyros Date: Fri, 29 Dec 2023 11:41:11 +0000 Subject: [PATCH 09/21] Update components/producers/golang-gosec/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/golang-gosec/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/golang-gosec/main_test.go b/components/producers/golang-gosec/main_test.go index c93c969ca..886ddc690 100644 --- a/components/producers/golang-gosec/main_test.go +++ b/components/producers/golang-gosec/main_test.go @@ -48,7 +48,6 @@ var gosecout = ` }` func TestParseIssues(t *testing.T) { - f, err := testutil.CreateFile("gosec_tests_vuln_code", code) if err != nil { t.Error(err) From 64c969efc9c1ebcbad33f4fff910fa15a80a6bb0 Mon Sep 17 00:00:00 2001 From: Spyros Date: Fri, 29 Dec 2023 11:41:26 +0000 Subject: [PATCH 10/21] Update components/producers/kics/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/kics/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/kics/main_test.go b/components/producers/kics/main_test.go index 742edefd6..5571a87e0 100644 --- a/components/producers/kics/main_test.go +++ b/components/producers/kics/main_test.go @@ -14,7 +14,6 @@ import ( ) func TestParseOut(t *testing.T) { - expectedIssues := []*v1.Issue{ { Target: "sql_server_predictable_admin_account_name:10", From 49321578ddb8c8dfd0fb5411bfd26bb6865b9b15 Mon Sep 17 00:00:00 2001 From: Spyros Date: Fri, 29 Dec 2023 11:41:34 +0000 Subject: [PATCH 11/21] Update components/producers/python-bandit/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/python-bandit/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/python-bandit/main_test.go b/components/producers/python-bandit/main_test.go index f7f5cb124..f37ca6c62 100644 --- a/components/producers/python-bandit/main_test.go +++ b/components/producers/python-bandit/main_test.go @@ -23,7 +23,6 @@ var code = `q += ' LIMIT + %(limit)s ' return [Student.from_raw(r) for r in results]` func TestParseIssues(t *testing.T) { - f, err := testutil.CreateFile("bandit_tests_vuln_code", code) if err != nil { t.Error(err) From da98e58f78a6cab1e67bcacc05a647dea2178edd Mon Sep 17 00:00:00 2001 From: Spyros Date: Fri, 29 Dec 2023 11:41:42 +0000 Subject: [PATCH 12/21] Update components/producers/python-bandit/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/python-bandit/main_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/producers/python-bandit/main_test.go b/components/producers/python-bandit/main_test.go index f37ca6c62..49e6fbb8f 100644 --- a/components/producers/python-bandit/main_test.go +++ b/components/producers/python-bandit/main_test.go @@ -65,7 +65,8 @@ func TestParseIssues(t *testing.T) { Cve: "", Uuid: "", ContextSegment: &code, - }} + }, + } assert.Equal(t, expectedIssues, issues) } From 5d08f2897afe91da98f7bd43c53f470e5a800756 Mon Sep 17 00:00:00 2001 From: Spyros Date: Fri, 29 Dec 2023 11:41:51 +0000 Subject: [PATCH 13/21] Update components/producers/semgrep/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/semgrep/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/semgrep/main_test.go b/components/producers/semgrep/main_test.go index 644379a6a..0d4f76eb1 100644 --- a/components/producers/semgrep/main_test.go +++ b/components/producers/semgrep/main_test.go @@ -67,7 +67,6 @@ var code = `q += ' LIMIT + %(limit)s ' return [Student.from_raw(r) for r in results]` func TestParseIssues(t *testing.T) { - f, err := testutil.CreateFile("semgrep_tests_vuln_code", code) if err != nil { t.Error(err) From df2538607ddcd877b5947484d3e0828aa90dfa5e Mon Sep 17 00:00:00 2001 From: foobar Date: Thu, 28 Dec 2023 19:44:29 +0000 Subject: [PATCH 14/21] tfsec --- components/producers/terraform-tfsec/BUILD | 3 ++ components/producers/terraform-tfsec/main.go | 21 ++++++-- .../producers/terraform-tfsec/main_test.go | 53 +++++++++++++------ 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/components/producers/terraform-tfsec/BUILD b/components/producers/terraform-tfsec/BUILD index aed004f7c..f42e41ede 100644 --- a/components/producers/terraform-tfsec/BUILD +++ b/components/producers/terraform-tfsec/BUILD @@ -14,6 +14,7 @@ go_binary( "//api/proto/v1", "//components/producers", "//components/producers/terraform-tfsec/types", + "//pkg/context", "//pkg/sarif", ], ) @@ -28,7 +29,9 @@ go_test( "//api/proto/v1", "//components/producers", "//components/producers/terraform-tfsec/types", + "//pkg/context", "//pkg/sarif", + "//pkg/testutil", "//third_party/go/github.com/stretchr/testify", ], ) diff --git a/components/producers/terraform-tfsec/main.go b/components/producers/terraform-tfsec/main.go index 55d781da6..62dd0dc97 100644 --- a/components/producers/terraform-tfsec/main.go +++ b/components/producers/terraform-tfsec/main.go @@ -9,6 +9,7 @@ import ( v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers" "github.com/ocurity/dracon/components/producers/terraform-tfsec/types" + "github.com/ocurity/dracon/pkg/context" "github.com/ocurity/dracon/pkg/sarif" ) @@ -48,18 +49,22 @@ func main() { if err := producers.ParseJSON(inFile, &results); err != nil { log.Fatal(err) } - if err := producers.WriteDraconOut("tfsec", parseOut(results)); err != nil { + issues, err := parseOut(results) + if err != nil { + log.Fatal(err) + } + if err := producers.WriteDraconOut("tfsec", issues); err != nil { log.Fatal(err) } } } -func parseOut(results types.TfSecOut) []*v1.Issue { +func parseOut(results types.TfSecOut) ([]*v1.Issue, error) { issues := []*v1.Issue{} for _, res := range results.Results { description, _ := json.Marshal(res) - issues = append(issues, &v1.Issue{ + iss := &v1.Issue{ Target: fmt.Sprintf("%s:%d-%d", res.Location.Filename, res.Location.StartLine, @@ -69,9 +74,15 @@ func parseOut(results types.TfSecOut) []*v1.Issue { Severity: TfSecSeverityToDracon(res.Severity), Confidence: v1.Confidence_CONFIDENCE_MEDIUM, Description: string(description), - }) + } + cs, err := context.ExtractCode(iss) + if err != nil { + return nil, err + } + iss.ContextSegment = &cs + issues = append(issues, iss) } - return issues + return issues, nil } // TfSecSeverityToDracon maps tfsec Severity Strings to dracon struct. diff --git a/components/producers/terraform-tfsec/main_test.go b/components/producers/terraform-tfsec/main_test.go index 9ce76b68d..bc287d54d 100644 --- a/components/producers/terraform-tfsec/main_test.go +++ b/components/producers/terraform-tfsec/main_test.go @@ -2,38 +2,46 @@ package main import ( "encoding/json" + "fmt" + "os" "testing" "github.com/stretchr/testify/assert" v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers/terraform-tfsec/types" + "github.com/ocurity/dracon/pkg/testutil" ) func TestParseOut(t *testing.T) { - var results types.TfSecOut - err := json.Unmarshal([]byte(exampleOutput), &results) + + f, err := testutil.CreateFile("tfsec_tests_vuln_code", code) if err != nil { - t.Logf(err.Error()) - t.Fail() + t.Error(err) } - issues := parseOut(results) + defer os.Remove(f.Name()) + + var results types.TfSecOut + err = json.Unmarshal([]byte(fmt.Sprintf(exampleOutput, f.Name(), f.Name())), &results) + assert.Nil(t, err) + issues, err := parseOut(results) + assert.Nil(t, err) expectedIssues := []*v1.Issue{ { - Target: "/home/foobar/go/pkg/mod/github.com/aquasecurity/tfsec@v1.28.1/_examples/main.tf:41-41", + Target: f.Name() + ":4-4", Type: "aws-api-gateway-use-secure-tls-policy", Title: "API Gateway domain name uses outdated SSL/TLS protocols.", Severity: 4, Confidence: 3, - Description: "{\"rule_id\":\"AVD-AWS-0005\",\"long_id\":\"aws-api-gateway-use-secure-tls-policy\",\"rule_description\":\"API Gateway domain name uses outdated SSL/TLS protocols.\",\"rule_provider\":\"aws\",\"rule_service\":\"api-gateway\",\"impact\":\"Outdated SSL policies increase exposure to known vulnerabilities\",\"resolution\":\"Use the most modern TLS/SSL policies available\",\"links\":[\"https://aquasecurity.github.io/tfsec/latest/checks/aws/api-gateway/use-secure-tls-policy/\",\"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_domain_name#security_policy\"],\"description\":\"Domain name is configured with an outdated TLS policy.\",\"severity\":\"HIGH\",\"warning\":false,\"status\":0,\"resource\":\"aws_api_gateway_domain_name.outdated_security_policy\",\"location\":{\"filename\":\"/home/foobar/go/pkg/mod/github.com/aquasecurity/tfsec@v1.28.1/_examples/main.tf\",\"start_line\":41,\"end_line\":41}}", + Description: "{\"rule_id\":\"AVD-AWS-0005\",\"long_id\":\"aws-api-gateway-use-secure-tls-policy\",\"rule_description\":\"API Gateway domain name uses outdated SSL/TLS protocols.\",\"rule_provider\":\"aws\",\"rule_service\":\"api-gateway\",\"impact\":\"Outdated SSL policies increase exposure to known vulnerabilities\",\"resolution\":\"Use the most modern TLS/SSL policies available\",\"links\":[\"https://aquasecurity.github.io/tfsec/latest/checks/aws/api-gateway/use-secure-tls-policy/\",\"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_domain_name#security_policy\"],\"description\":\"Domain name is configured with an outdated TLS policy.\",\"severity\":\"HIGH\",\"warning\":false,\"status\":0,\"resource\":\"aws_api_gateway_domain_name.outdated_security_policy\",\"location\":{\"filename\":\"" + f.Name() + "\",\"start_line\":4,\"end_line\":4}}", }, { - Target: "/home/foobar/go/pkg/mod/github.com/aquasecurity/tfsec@v1.28.1/_examples/main.tf:37-37", + Target: f.Name() + ":3-3", Type: "aws-api-gateway-use-secure-tls-policy", Title: "API Gateway domain name uses outdated SSL/TLS protocols.", Severity: 4, Confidence: 3, - Description: "{\"rule_id\":\"AVD-AWS-0005\",\"long_id\":\"aws-api-gateway-use-secure-tls-policy\",\"rule_description\":\"API Gateway domain name uses outdated SSL/TLS protocols.\",\"rule_provider\":\"aws\",\"rule_service\":\"api-gateway\",\"impact\":\"Outdated SSL policies increase exposure to known vulnerabilities\",\"resolution\":\"Use the most modern TLS/SSL policies available\",\"links\":[\"https://aquasecurity.github.io/tfsec/latest/checks/aws/api-gateway/use-secure-tls-policy/\",\"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_domain_name#security_policy\"],\"description\":\"Domain name is configured with an outdated TLS policy.\",\"severity\":\"HIGH\",\"warning\":false,\"status\":0,\"resource\":\"aws_api_gateway_domain_name.empty_security_policy\",\"location\":{\"filename\":\"/home/foobar/go/pkg/mod/github.com/aquasecurity/tfsec@v1.28.1/_examples/main.tf\",\"start_line\":37,\"end_line\":37}}", + Description: "{\"rule_id\":\"AVD-AWS-0005\",\"long_id\":\"aws-api-gateway-use-secure-tls-policy\",\"rule_description\":\"API Gateway domain name uses outdated SSL/TLS protocols.\",\"rule_provider\":\"aws\",\"rule_service\":\"api-gateway\",\"impact\":\"Outdated SSL policies increase exposure to known vulnerabilities\",\"resolution\":\"Use the most modern TLS/SSL policies available\",\"links\":[\"https://aquasecurity.github.io/tfsec/latest/checks/aws/api-gateway/use-secure-tls-policy/\",\"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_domain_name#security_policy\"],\"description\":\"Domain name is configured with an outdated TLS policy.\",\"severity\":\"HIGH\",\"warning\":false,\"status\":0,\"resource\":\"aws_api_gateway_domain_name.empty_security_policy\",\"location\":{\"filename\":\"" + f.Name() + "\",\"start_line\":3,\"end_line\":3}}", }, } @@ -58,6 +66,21 @@ func TestParseOut(t *testing.T) { assert.Equal(t, found, len(issues)) // assert everything has been found } +var code = `resource "azurerm_sql_server" "positive4" { + name = "sqlserver" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + version = "12.0" + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" + + extended_auditing_policy { + storage_endpoint = azurerm_storage_account.example.primary_blob_endpoint + storage_account_access_key = azurerm_storage_account.example.primary_access_key + storage_account_access_key_is_secondary = true + retention_in_days = 20 + } +}` var exampleOutput = ` { "results": [ @@ -81,9 +104,9 @@ var exampleOutput = ` "status": 0, "resource": "aws_api_gateway_domain_name.outdated_security_policy", "location": { - "filename": "/home/foobar/go/pkg/mod/github.com/aquasecurity/tfsec@v1.28.1/_examples/main.tf", - "start_line": 41, - "end_line": 41 + "filename": "%s", + "start_line": 4, + "end_line": 4 } }, { @@ -104,8 +127,8 @@ var exampleOutput = ` "status": 0, "resource": "aws_api_gateway_domain_name.empty_security_policy", "location": { - "filename": "/home/foobar/go/pkg/mod/github.com/aquasecurity/tfsec@v1.28.1/_examples/main.tf", - "start_line": 37, - "end_line": 37 + "filename": "%s", + "start_line": 3, + "end_line": 3 } }]}` From c8a525c3c64e07ad969bb7ed0cd181db507a92b5 Mon Sep 17 00:00:00 2001 From: foobar Date: Sat, 30 Dec 2023 00:40:33 +0000 Subject: [PATCH 15/21] trufflehog --- components/producers/trufflehog/BUILD | 3 ++ components/producers/trufflehog/main.go | 31 +++++++++------- components/producers/trufflehog/main_test.go | 37 +++++++++++--------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/components/producers/trufflehog/BUILD b/components/producers/trufflehog/BUILD index cd1df4e7b..d6c04b305 100644 --- a/components/producers/trufflehog/BUILD +++ b/components/producers/trufflehog/BUILD @@ -12,6 +12,7 @@ go_binary( deps = [ "//api/proto/v1", "//components/producers", + "//pkg/context", "//third_party/go/github.com/mitchellh/mapstructure", ], ) @@ -26,6 +27,8 @@ go_test( deps = [ "//api/proto/v1", "//components/producers", + "//pkg/context", + "//pkg/testutil", "//third_party/go/github.com/mitchellh/mapstructure", "//third_party/go/github.com/stretchr/testify", ], diff --git a/components/producers/trufflehog/main.go b/components/producers/trufflehog/main.go index ec58fe1f7..3bafe18a5 100644 --- a/components/producers/trufflehog/main.go +++ b/components/producers/trufflehog/main.go @@ -35,8 +35,10 @@ func main() { } truffleResults[i] = x } - issues := parseIssues(truffleResults) - + issues, err := parseIssues(truffleResults) + if err != nil { + log.Fatal(err) + } if err := producers.WriteDraconOut( "trufflehog", issues, @@ -45,7 +47,7 @@ func main() { } } -func parseIssues(out []TrufflehogOut) []*v1.Issue { +func parseIssues(out []TrufflehogOut) ([]*v1.Issue, error) { issues := []*v1.Issue{} for _, r := range out { @@ -61,17 +63,20 @@ func parseIssues(out []TrufflehogOut) []*v1.Issue { target = fmt.Sprintf("%s:%s:%s:%d", r.SourceMetadata.Data.Git.Repository, r.SourceMetadata.Data.Git.Commit, r.SourceMetadata.Data.Git.File, r.SourceMetadata.Data.Git.Line) description = fmt.Sprintf("Raw:%s\nRedacted:%s\nTimestamp:%s\nEmail:%s\n", r.Raw, r.Redacted, r.SourceMetadata.Data.Git.Timestamp, r.SourceMetadata.Data.Git.Email) } - issues = append(issues, &v1.Issue{ - Target: target, - Type: r.SourceName, - Title: fmt.Sprintf("%s - %s", r.DecoderName, r.DetectorName), - Severity: v1.Severity_SEVERITY_UNSPECIFIED, - Cvss: 0.0, - Confidence: confidence, - Description: description, - }) + raw := r.Raw + iss := &v1.Issue{ + Target: target, + Type: r.SourceName, + Title: fmt.Sprintf("%s - %s", r.DecoderName, r.DetectorName), + Severity: v1.Severity_SEVERITY_UNSPECIFIED, + Cvss: 0.0, + Confidence: confidence, + Description: description, + ContextSegment: &raw, + } + issues = append(issues, iss) } - return issues + return issues, nil } // TrufflehogOut represents the output of a Trufflehog run diff --git a/components/producers/trufflehog/main_test.go b/components/producers/trufflehog/main_test.go index f661b8438..eb470765f 100644 --- a/components/producers/trufflehog/main_test.go +++ b/components/producers/trufflehog/main_test.go @@ -11,6 +11,7 @@ import ( ) func TestParseIssues(t *testing.T) { + results, err := producers.ParseMultiJSONMessages([]byte(exampleOutput)) assert.Nil(t, err) @@ -20,26 +21,30 @@ func TestParseIssues(t *testing.T) { mapstructure.Decode(r, &x) truffleResults[i] = x } - issues := parseIssues(truffleResults) - + issues, err := parseIssues(truffleResults) + assert.Nil(t, err) + cs0 := "https://admin:admin@the-internet.herokuapp.com/basic_auth" + CS1 := "wellnessbirdie-jaworskironni-bennettliz" expectedIssues := []v1.Issue{ { - Target: "/code/keys", - Type: "trufflehog - filesystem", - Title: "PLAIN - URI", - Severity: v1.Severity_SEVERITY_UNSPECIFIED, - Cvss: 0.0, - Confidence: v1.Confidence_CONFIDENCE_HIGH, - Description: "Raw:https://admin:admin@the-internet.herokuapp.com/basic_auth\nRedacted:https://*****:*****@the-internet.herokuapp.com/basic_auth\n", + Target: "/code/keys", + Type: "trufflehog - filesystem", + Title: "PLAIN - URI", + Severity: v1.Severity_SEVERITY_UNSPECIFIED, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_HIGH, + Description: "Raw:https://admin:admin@the-internet.herokuapp.com/basic_auth\nRedacted:https://*****:*****@the-internet.herokuapp.com/basic_auth\n", + ContextSegment: &cs0, }, { - Target: "https://github.com/foo/bar:c27298c30acdf69e611563b51ef2222f6324c916:src/org/zaproxy/zap/extension/directorylistv2_3/files/fuzzers/dirbuster/directory-list-2.3-big.txt:530222", - Type: "trufflehog - git", - Title: "BASE64 - Blogger", - Severity: v1.Severity_SEVERITY_UNSPECIFIED, - Cvss: 0.0, - Confidence: v1.Confidence_CONFIDENCE_MEDIUM, - Description: "Raw:wellnessbirdie-jaworskironni-bennettliz\nRedacted:\nTimestamp:2015-04-13 16:07:20 +0000 UTC\nEmail:foo@example.com \n", + Target: "https://github.com/foo/bar:c27298c30acdf69e611563b51ef2222f6324c916:src/org/zaproxy/zap/extension/directorylistv2_3/files/fuzzers/dirbuster/directory-list-2.3-big.txt:530222", + Type: "trufflehog - git", + Title: "BASE64 - Blogger", + Severity: v1.Severity_SEVERITY_UNSPECIFIED, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: "Raw:wellnessbirdie-jaworskironni-bennettliz\nRedacted:\nTimestamp:2015-04-13 16:07:20 +0000 UTC\nEmail:foo@example.com \n", + ContextSegment: &CS1, }, } assert.Equal(t, expectedIssues[0], *issues[0]) From d943a3ca22f1a4f83588c117ebc2b66bef17006d Mon Sep 17 00:00:00 2001 From: foobar Date: Sat, 30 Dec 2023 00:40:56 +0000 Subject: [PATCH 16/21] eslint --- components/producers/typescript-eslint/BUILD | 3 + .../producers/typescript-eslint/main.go | 21 +++++-- .../producers/typescript-eslint/main_test.go | 59 ++++++++++++------- .../typescript-eslint/types/eslint-issue.go | 1 + 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/components/producers/typescript-eslint/BUILD b/components/producers/typescript-eslint/BUILD index f18680004..09d69f3d8 100644 --- a/components/producers/typescript-eslint/BUILD +++ b/components/producers/typescript-eslint/BUILD @@ -12,6 +12,7 @@ go_binary( "//api/proto/v1", "//components/producers", "//components/producers/typescript-eslint/types", + "//pkg/context", ], ) @@ -25,6 +26,8 @@ go_test( "//api/proto/v1", "//components/producers", "//components/producers/typescript-eslint/types", + "//pkg/context", + "//pkg/testutil", "//third_party/go/github.com/stretchr/testify", ], ) diff --git a/components/producers/typescript-eslint/main.go b/components/producers/typescript-eslint/main.go index 5549f7c26..dfcff8628 100644 --- a/components/producers/typescript-eslint/main.go +++ b/components/producers/typescript-eslint/main.go @@ -6,6 +6,7 @@ import ( v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers/typescript-eslint/types" + "github.com/ocurity/dracon/pkg/context" "github.com/ocurity/dracon/components/producers" ) @@ -24,7 +25,10 @@ func main() { if err := producers.ParseJSON(inFile, &results); err != nil { log.Fatal(err) } - issues := parseIssues(results) + issues, err := parseIssues(results) + if err != nil { + log.Fatal(err) + } if err := producers.WriteDraconOut( "eslint", issues, @@ -33,7 +37,7 @@ func main() { } } -func parseIssues(out []types.ESLintIssue) []*v1.Issue { +func parseIssues(out []types.ESLintIssue) ([]*v1.Issue, error) { issues := []*v1.Issue{} for _, r := range out { for _, msg := range r.Messages { @@ -43,8 +47,12 @@ func parseIssues(out []types.ESLintIssue) []*v1.Issue { } else if msg.Severity == 2 { sev = v1.Severity_SEVERITY_HIGH } + target := fmt.Sprintf("%s:%d", r.FilePath, msg.Line) + if msg.EndLine != 0 { + target = fmt.Sprintf("%s:%d-%d", r.FilePath, msg.Line, msg.EndLine) + } iss := &v1.Issue{ - Target: fmt.Sprintf("%s:%v:%v", r.FilePath, msg.Line, msg.Column), + Target: target, Type: msg.RuleID, Title: msg.RuleID, Severity: sev, @@ -52,8 +60,13 @@ func parseIssues(out []types.ESLintIssue) []*v1.Issue { Confidence: v1.Confidence_CONFIDENCE_MEDIUM, Description: msg.Message, } + cs, err := context.ExtractCode(iss) + if err != nil { + return nil, err + } + iss.ContextSegment = &cs issues = append(issues, iss) } } - return issues + return issues, nil } diff --git a/components/producers/typescript-eslint/main_test.go b/components/producers/typescript-eslint/main_test.go index 5588f9027..4e8f4691e 100644 --- a/components/producers/typescript-eslint/main_test.go +++ b/components/producers/typescript-eslint/main_test.go @@ -2,27 +2,30 @@ package main import ( "encoding/json" + "fmt" + "os" "testing" v1 "github.com/ocurity/dracon/api/proto/v1" "github.com/ocurity/dracon/components/producers/typescript-eslint/types" + "github.com/ocurity/dracon/pkg/testutil" "github.com/stretchr/testify/assert" ) -const exampleOutput = `[{"filePath":"/foo/bar/js/types/File.ts", +const exampleOutput = `[{"filePath":"%s", "messages":[ {"ruleId":"@typescript-eslint/explicit-module-boundary-types", "severity":1, "message":"Missing return type on function.", - "line":21, + "line":1, "column":46, "nodeType":"ArrowFunctionExpression", "messageId":"missingReturnType", - "endLine":160,"endColumn":104}, + "endLine":2,"endColumn":104}, {"ruleId":"jsdoc/require-jsdoc","severity":1,"message":"Missing JSDoc comment.", - "line":29,"column":1,"nodeType":"FunctionDeclaration", + "line":3,"column":1,"nodeType":"FunctionDeclaration", "messageId":"missingJsDoc","endColumn":null, "fix":{"range":[890,890],"text":"/**\n *\n */\n"}}], @@ -34,29 +37,45 @@ const exampleOutput = `[{"filePath":"/foo/bar/js/types/File.ts", "usedDeprecatedRules":[{"ruleId":"jsx-a11y/accessible-emoji","replacedBy":[]},{"ruleId":"jsx-a11y/label-has-for","replacedBy":[]} ] }]` +var code = `some +vulnerable +typescript +or +javascript` + func TestParseIssues(t *testing.T) { + + f, err := testutil.CreateFile("tfsec_tests_vuln_code", code) + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + var results []types.ESLintIssue - err := json.Unmarshal([]byte(exampleOutput), &results) + err = json.Unmarshal([]byte(fmt.Sprintf(exampleOutput, f.Name())), &results) + assert.Nil(t, err) + issues, err := parseIssues(results) assert.Nil(t, err) - issues := parseIssues(results) expectedIssue := &v1.Issue{ - Target: "/foo/bar/js/types/File.ts:21:46", - Type: "@typescript-eslint/explicit-module-boundary-types", - Title: "@typescript-eslint/explicit-module-boundary-types", - Severity: v1.Severity_SEVERITY_MEDIUM, - Cvss: 0.0, - Confidence: v1.Confidence_CONFIDENCE_MEDIUM, - Description: "Missing return type on function.", + Target: f.Name() + ":1-2", + Type: "@typescript-eslint/explicit-module-boundary-types", + Title: "@typescript-eslint/explicit-module-boundary-types", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: "Missing return type on function.", + ContextSegment: &code, } issue2 := &v1.Issue{ - Target: "/foo/bar/js/types/File.ts:29:1", - Type: "jsdoc/require-jsdoc", - Title: "jsdoc/require-jsdoc", - Severity: v1.Severity_SEVERITY_MEDIUM, - Cvss: 0.0, - Confidence: v1.Confidence_CONFIDENCE_MEDIUM, - Description: "Missing JSDoc comment.", + Target: f.Name() + ":3", + Type: "jsdoc/require-jsdoc", + Title: "jsdoc/require-jsdoc", + Severity: v1.Severity_SEVERITY_MEDIUM, + Cvss: 0.0, + Confidence: v1.Confidence_CONFIDENCE_MEDIUM, + Description: "Missing JSDoc comment.", + ContextSegment: &code, } assert.Equal(t, expectedIssue, issues[0]) assert.Equal(t, issue2, issues[1]) diff --git a/components/producers/typescript-eslint/types/eslint-issue.go b/components/producers/typescript-eslint/types/eslint-issue.go index dc9bd56f4..6694dc4dd 100644 --- a/components/producers/typescript-eslint/types/eslint-issue.go +++ b/components/producers/typescript-eslint/types/eslint-issue.go @@ -7,6 +7,7 @@ type Message struct { Message string `json:"message"` Line int `json:"line"` Column int `json:"column"` + EndLine int `json:"endLine"` } // ESLintIssue represents a ESLint Result. From 1925c9470c995c20c573e5c8cc14376e62085f2b Mon Sep 17 00:00:00 2001 From: foobar Date: Sat, 30 Dec 2023 00:41:13 +0000 Subject: [PATCH 17/21] lint --- pkg/testutil/createTmp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/testutil/createTmp.go b/pkg/testutil/createTmp.go index a2869da38..f573a4775 100644 --- a/pkg/testutil/createTmp.go +++ b/pkg/testutil/createTmp.go @@ -1,4 +1,4 @@ -// Package test contains helper functions and subpackages to make testing the project easier +// Package testutil contains helper functions and subpackages to make testing the project easier package testutil import ( From 5277194b854b03d5aa9e4f9df259dd86a2802472 Mon Sep 17 00:00:00 2001 From: Spyros Date: Tue, 2 Jan 2024 19:59:14 +0000 Subject: [PATCH 18/21] Update components/producers/terraform-tfsec/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/terraform-tfsec/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/terraform-tfsec/main_test.go b/components/producers/terraform-tfsec/main_test.go index bc287d54d..f53f8a09c 100644 --- a/components/producers/terraform-tfsec/main_test.go +++ b/components/producers/terraform-tfsec/main_test.go @@ -14,7 +14,6 @@ import ( ) func TestParseOut(t *testing.T) { - f, err := testutil.CreateFile("tfsec_tests_vuln_code", code) if err != nil { t.Error(err) From 3c80b7d97123b5a00c0883cf2a5f564994cb6407 Mon Sep 17 00:00:00 2001 From: Spyros Date: Tue, 2 Jan 2024 19:59:22 +0000 Subject: [PATCH 19/21] Update components/producers/trufflehog/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/trufflehog/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/trufflehog/main_test.go b/components/producers/trufflehog/main_test.go index eb470765f..076af1048 100644 --- a/components/producers/trufflehog/main_test.go +++ b/components/producers/trufflehog/main_test.go @@ -11,7 +11,6 @@ import ( ) func TestParseIssues(t *testing.T) { - results, err := producers.ParseMultiJSONMessages([]byte(exampleOutput)) assert.Nil(t, err) From a102192157e989dac192de935b4205d8f1fd36ac Mon Sep 17 00:00:00 2001 From: Spyros Date: Tue, 2 Jan 2024 19:59:30 +0000 Subject: [PATCH 20/21] Update components/producers/typescript-eslint/main_test.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/typescript-eslint/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/producers/typescript-eslint/main_test.go b/components/producers/typescript-eslint/main_test.go index 4e8f4691e..790f23534 100644 --- a/components/producers/typescript-eslint/main_test.go +++ b/components/producers/typescript-eslint/main_test.go @@ -44,7 +44,6 @@ or javascript` func TestParseIssues(t *testing.T) { - f, err := testutil.CreateFile("tfsec_tests_vuln_code", code) if err != nil { t.Error(err) From 09d68e7125ccea1eb817c5d23e4962ad91b33e34 Mon Sep 17 00:00:00 2001 From: Spyros Date: Tue, 2 Jan 2024 19:59:38 +0000 Subject: [PATCH 21/21] Update components/producers/typescript-eslint/types/eslint-issue.go Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- components/producers/typescript-eslint/types/eslint-issue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/producers/typescript-eslint/types/eslint-issue.go b/components/producers/typescript-eslint/types/eslint-issue.go index 6694dc4dd..16c84d241 100644 --- a/components/producers/typescript-eslint/types/eslint-issue.go +++ b/components/producers/typescript-eslint/types/eslint-issue.go @@ -7,7 +7,7 @@ type Message struct { Message string `json:"message"` Line int `json:"line"` Column int `json:"column"` - EndLine int `json:"endLine"` + EndLine int `json:"endLine"` } // ESLintIssue represents a ESLint Result.