diff --git a/producers/yarn_audit/README.md b/producers/yarn_audit/README.md index 74676f9..486b679 100644 --- a/producers/yarn_audit/README.md +++ b/producers/yarn_audit/README.md @@ -1 +1 @@ -For use with output of `yarn audit --json` \ No newline at end of file +For use with output of `yarn audit --json | jq -cs .` diff --git a/producers/yarn_audit/main.go b/producers/yarn_audit/main.go index f816037..7eb8964 100644 --- a/producers/yarn_audit/main.go +++ b/producers/yarn_audit/main.go @@ -12,27 +12,20 @@ func main() { log.Fatal(err) } - inLines, err := producers.ReadLines() + in, err := producers.ReadInFile() if err != nil { log.Fatal(err) } - report, errors := types.NewReport(inLines) - - // Individual errors should already be printed to logs - if len(errors) > 0 { - errorMessage := "Errors creating Yarn Audit report: %d" - if report != nil{ - log.Printf(errorMessage, len(errors)) - } else { - log.Fatalf(errorMessage, len(errors)) - } + yarnReport, err := types.NewReport(in) + if err != nil { + log.Fatal(err) } - if report != nil { + if yarnReport.AuditAdvisories != nil { if err := producers.WriteDraconOut( "yarn-audit", - report.AsIssues(), + yarnReport.AuditAdvisories.AsIssues(), ); err != nil { log.Fatal(err) } diff --git a/producers/yarn_audit/types/yarn-issue.go b/producers/yarn_audit/types/yarn-issue.go index fb368b6..f9da168 100644 --- a/producers/yarn_audit/types/yarn-issue.go +++ b/producers/yarn_audit/types/yarn-issue.go @@ -6,13 +6,9 @@ import ( "strings" v1 "github.com/thought-machine/dracon/api/proto/v1" - "github.com/thought-machine/dracon/producers" - - "log" ) func yarnToIssueSeverity(severity string) v1.Severity { - switch severity { case "low": return v1.Severity_SEVERITY_LOW @@ -28,64 +24,66 @@ func yarnToIssueSeverity(severity string) v1.Severity { } } -type yarnAuditLine struct { - Type string `json:"type"` - Data interface{} `json:"data"` +// AuditAction represents the action type within yarn audit output +type AuditAction struct { + Type string `json:"type"` + Data auditActionData `json:"data"` } -func (yl *yarnAuditLine) UnmarshalJSON(data []byte) error { - var typ struct { - Type string `json:"type"` - } +// AuditActions is a slice of AuditAction type +type AuditActions []AuditAction - if err := json.Unmarshal(data, &typ); err != nil { - return err +// Unmarshal attempts to unmarshal a raw JSON message into the AuditAction struct +func (audit *AuditAction) Unmarshal(raw json.RawMessage) bool { + if err := json.Unmarshal(raw, audit); err != nil { + return false } + return audit.Type == "auditAction" +} - switch typ.Type { - case "auditSummary": - yl.Data = new(auditSummaryData) - case "auditAdvisory": - yl.Data = new(auditAdvisoryData) - case "auditAction": - yl.Data = new(auditActionData) - default: - log.Printf("Parsed unsupported type: %s", typ.Type) +// AuditAdvisory represents the advisory type within yarn audit output +type AuditAdvisory struct { + Type string `json:"type"` + Data auditAdvisoryData `json:"data"` +} + +// AuditAdvisories is a slice of AuditAdvisory type +type AuditAdvisories []AuditAdvisory + +// Unmarshal attempts to unmarshal a raw JSON message into the AuditAdvisory struct +func (audit *AuditAdvisory) Unmarshal(raw json.RawMessage) bool { + if err := json.Unmarshal(raw, audit); err != nil { + return false } + return audit.Type == "auditAdvisory" +} - type tmp yarnAuditLine // avoids infinite recursion - return json.Unmarshal(data, (*tmp)(yl)) +// AuditSummary represents the summary type within yarn audit output +type AuditSummary struct { + Type string `json:"type"` + Data auditSummaryData `json:"data"` +} + +// AuditSummaries is a slice of AuditSummary type +type AuditSummaries []AuditSummary +// Unmarshal attempts to unmarshal a raw JSON message into the AuditSummary struct +func (audit *AuditSummary) Unmarshal(raw json.RawMessage) bool { + if err := json.Unmarshal(raw, audit); err != nil { + return false + } + return audit.Type == "auditSummary" } type auditActionData struct { - Cmd string `json:"cmd"` - IsBreaking bool `json:"isBreaking"` - Action auditAction `json:"action"` + Cmd string `json:"cmd"` + IsBreaking bool `json:"isBreaking"` + Action auditActionAction `json:"action"` } type auditAdvisoryData struct { Resolution auditResolution `json:"resolution"` - Advisory yarnAdvisory `json:"advisory"` -} - -// AsIssue returns data as a Dracon v1.Issue -func (audit *auditAdvisoryData) AsIssue() *v1.Issue { - var targetName string - if audit.Resolution.Path != "" { - targetName = audit.Resolution.Path + ": " - } - targetName += audit.Advisory.ModuleName - - return &v1.Issue{ - Target: targetName, - Type: audit.Advisory.Cwe, - Title: audit.Advisory.Title, - Severity: yarnToIssueSeverity(audit.Advisory.Severity), - Confidence: v1.Confidence_CONFIDENCE_HIGH, - Description: fmt.Sprintf("%s", audit.Advisory.GetDescription()), - Cve: strings.Join(audit.Advisory.Cves, ", "), - } + Advisory yarnAdvisory `json:"advisory"` } type auditSummaryData struct { @@ -96,7 +94,7 @@ type auditSummaryData struct { TotalDependencies int `json:"totalDependencies"` } -type auditAction struct { +type auditActionAction struct { Action string `json:"action"` Module string `json:"module"` Target string `json:"target"` @@ -122,9 +120,10 @@ type yarnAdvisory struct { Cves []string `json:"cves"` Access string `json:"access"` PatchedVersions string `json:"patched_versions"` + Cvss cvss `json:"cvss"` Updated string `json:"updated"` Recommendation string `json:"recommendation"` - Cwe string `json:"cwe"` + Cwe []string `json:"cwe"` FoundBy *contact `json:"found_by"` Deleted bool `json:"deleted"` ID int `json:"id"` @@ -132,20 +131,14 @@ type yarnAdvisory struct { Created string `json:"created"` ReportedBy *contact `json:"reported_by"` Title string `json:"title"` - NpmAdvisoryID interface{} `json:"npm_advisory_id"` + NpmAdvisoryID *interface{} `json:"npm_advisory_id"` Overview string `json:"overview"` URL string `json:"url"` } -func (advisory *yarnAdvisory) GetDescription() string { - return fmt.Sprintf( - "Vulnerable Versions: %s\nRecommendation: %s\nOverview: %s\nReferences:\n%s\nAdvisory URL: %s\n", - advisory.VulnerableVersions, - advisory.Recommendation, - advisory.Overview, - advisory.References, - advisory.URL, - ) +type cvss struct { + Score json.Number `json:"score"` + VectorString string `json:"vectorString"` } type finding struct { @@ -166,7 +159,7 @@ type auditResolution struct { type advisoryMetaData struct { ModuleType string `json:"module_type"` - Exploitability int `json:"exploitability"` + Exploitability int `json:"exploitability"` AffectedComponents string `json:"affected_components"` } @@ -174,51 +167,88 @@ type contact struct { Name string `json: name` } -// YarnAuditReport includes yarn audit data grouped by advisories, actions and summary -type YarnAuditReport struct { - AuditAdvisories []*auditAdvisoryData - AuditActions []*auditActionData - AuditSummary *auditSummaryData +// YarnReport holds the actions/advisories/summaries from yarn audit input JSON +type YarnReport struct { + AuditActions AuditActions + AuditAdvisories AuditAdvisories + AuditSummaries AuditSummaries } -// NewReport returns a YarnAuditReport, assuming each line is jsonline and returns any errors -func NewReport(reportLines [][]byte) (*YarnAuditReport, []error) { +// NewReport transforms input yarn audit JSON into a YarnReport +func NewReport(report []byte) (YarnReport, error) { + var raws []json.RawMessage + yarnReport := YarnReport{} - var report YarnAuditReport + if err := json.Unmarshal(report, &raws); err != nil { + return YarnReport{}, err + } - var errors []error + for _, raw := range raws { + auditAction := new(AuditAction) + if auditAction.Unmarshal(raw) { + yarnReport.AuditActions = append(yarnReport.AuditActions, *auditAction) + continue + } - for _, line := range reportLines { - var auditLine yarnAuditLine - if err := producers.ParseJSON(line, &auditLine); err != nil { - log.Printf("Error parsing JSON line '%s': %s\n", line, err) - errors = append(errors, err) - } else { + auditAdvisory := new(AuditAdvisory) + if auditAdvisory.Unmarshal(raw) { + yarnReport.AuditAdvisories = append(yarnReport.AuditAdvisories, *auditAdvisory) + continue + } - switch auditLine.Data.(type) { - case *auditSummaryData: - report.AuditSummary = auditLine.Data.(*auditSummaryData) - case *auditAdvisoryData: - report.AuditAdvisories = append(report.AuditAdvisories, auditLine.Data.(*auditAdvisoryData)) - case *auditActionData: - report.AuditActions = append(report.AuditActions, auditLine.Data.(*auditActionData)) - } + auditSummary := new(AuditSummary) + if auditSummary.Unmarshal(raw) { + yarnReport.AuditSummaries = append(yarnReport.AuditSummaries, *auditSummary) + continue } + + err := fmt.Errorf("Unable to unmarshal JSON into known structure: %s", raw) + return YarnReport{}, err } - if report.AuditAdvisories != nil && len(report.AuditAdvisories) > 0 { - return &report, errors + return yarnReport, nil +} + +func (advisory *yarnAdvisory) GetDescription() string { + return fmt.Sprintf( + "Vulnerable Versions: %s\nRecommendation: %s\nOverview: %s\nReferences:\n%s\nAdvisory URL: %s\n", + advisory.VulnerableVersions, + advisory.Recommendation, + advisory.Overview, + advisory.References, + advisory.URL, + ) +} + +// AsIssue returns data as a Dracon v1.Issue +func (audit *auditAdvisoryData) AsIssue() *v1.Issue { + var targetName string + if audit.Resolution.Path != "" { + targetName = audit.Resolution.Path + ": " } + targetName += audit.Advisory.ModuleName + + // yarn audit now outputs CWEs as an array. if there is at least one CWE provide a comma-separated list + // to issue constructor, else provide empty string + cwe := strings.Join(audit.Advisory.Cwe, ", ") - return nil, errors + return &v1.Issue{ + Target: targetName, + Type: cwe, + Title: audit.Advisory.Title, + Severity: yarnToIssueSeverity(audit.Advisory.Severity), + Confidence: v1.Confidence_CONFIDENCE_HIGH, + Description: fmt.Sprintf("%s", audit.Advisory.GetDescription()), + Cve: strings.Join(audit.Advisory.Cves, ", "), + } } -// AsIssues returns the YarnAuditReport as Dracon v1.Issue list. Currently only converts the YarnAuditReport.AuditAdvisories -func (r *YarnAuditReport) AsIssues() []*v1.Issue { +// AsIssues returns an auditAdvisory as Dracon v1.Issue list +func (advisories AuditAdvisories) AsIssues() []*v1.Issue { issues := make([]*v1.Issue, 0) - for _, audit := range r.AuditAdvisories { - issues = append(issues, audit.AsIssue()) + for _, audit := range advisories { + issues = append(issues, audit.Data.AsIssue()) } return issues diff --git a/producers/yarn_audit/types/yarn-issue_test.go b/producers/yarn_audit/types/yarn-issue_test.go index 3ad1c1c..9f22e72 100644 --- a/producers/yarn_audit/types/yarn-issue_test.go +++ b/producers/yarn_audit/types/yarn-issue_test.go @@ -9,23 +9,64 @@ import ( "github.com/stretchr/testify/assert" ) -var invalidJSON = `Not a valid JSON object` +var invalidJSON []byte = []byte(`Not a valid JSON object`) func TestParseInvalidJSON(t *testing.T) { - oneLine := []byte(invalidJSON) - report, err := NewReport([][]byte{ - oneLine, - oneLine, - }) + report, err := NewReport(invalidJSON) - assert.Nil(t, report) + assert.Nil(t, report.AuditActions) + assert.Nil(t, report.AuditAdvisories) + assert.Nil(t, report.AuditSummaries) - assert.Len(t, err, 2) + assert.NotNil(t, err) +} + +var unsupportedTypeJSON []byte = []byte( + `[{ + "type": "unsupported", + "data": { + "vulnerabilities": { + "info": 1, + "low": 10, + "moderate": 177, + "high": 94, + "critical": 4 + }, + "dependencies": 6274, + "devDependencies": 0, + "optionalDependencies": 0, + "totalDependencies": 6274 + } + }]`) + +func TestParseUnsupportedTypeJSON(t *testing.T) { + report, err := NewReport(unsupportedTypeJSON) + + assert.Nil(t, report.AuditActions) + assert.Nil(t, report.AuditAdvisories) + assert.Nil(t, report.AuditSummaries) + + assert.NotNil(t, err) +} + +var completelyUnsupportedJSON []byte = []byte( + `{ + "completely": "unsupported" + },`) + +func TestParseCompletelyUnsupportedJSON(t *testing.T) { + report, err := NewReport(completelyUnsupportedJSON) + + assert.Nil(t, report.AuditActions) + assert.Nil(t, report.AuditAdvisories) + assert.Nil(t, report.AuditSummaries) + + assert.NotNil(t, err) } // In reality these would be single lines, but for readability in test these should also work -var fullYarnJSONLines [][]byte = [][]byte{ - []byte(`{ +var fullYarnJSONLines []byte = []byte( + `[{ "type": "auditAdvisory", "data": { "resolution": { @@ -63,7 +104,7 @@ var fullYarnJSONLines [][]byte = [][]byte{ "patched_versions": ">=5.0.1", "updated": "2021-09-23T15:45:50.000Z", "recommendation": "Upgrade to version 5.0.1 or later", - "cwe": "CWE-918", + "cwe": ["CWE-918"], "found_by": null, "deleted": null, "id": 1004946, @@ -76,24 +117,8 @@ var fullYarnJSONLines [][]byte = [][]byte{ "url": "https://advisory.1.url" } } - }`), - []byte(`{ - "type": "unsupported", - "data": { - "vulnerabilities": { - "info": 1, - "low": 10, - "moderate": 177, - "high": 94, - "critical": 4 - }, - "dependencies": 6274, - "devDependencies": 0, - "optionalDependencies": 0, - "totalDependencies": 6274 - } - }`), - []byte(`{ + }, + { "type": "auditAdvisory", "data": { "resolution": { @@ -131,7 +156,7 @@ var fullYarnJSONLines [][]byte = [][]byte{ "patched_versions": ">=1.2.0", "updated": "2021-09-23T15:45:50.000Z", "recommendation": "Upgrade to version 1.2.0 or later", - "cwe": "CWE-920", + "cwe": ["CWE-920"], "found_by": null, "deleted": null, "id": 1004947, @@ -144,8 +169,8 @@ var fullYarnJSONLines [][]byte = [][]byte{ "url": "https://advisory.2.url" } } - }`), - []byte(`{ + }, + { "type":"auditAction", "data":{ "cmd":"action command", @@ -166,11 +191,8 @@ var fullYarnJSONLines [][]byte = [][]byte{ ] } } - }`), - []byte(`{ - "completely": "unsupported" - }`), - []byte(`{ + }, + { "type": "auditSummary", "data": { "vulnerabilities": { @@ -185,156 +207,161 @@ var fullYarnJSONLines [][]byte = [][]byte{ "optionalDependencies": 0, "totalDependencies": 6274 } - }`), -} + }]`) func TestParseValidReportContainsAllSupportedFields(t *testing.T) { - report, err := NewReport( - fullYarnJSONLines, - ) + report, err := NewReport(fullYarnJSONLines) assert.Nil(t, err) - assert.NotNil(t, report) - assert.NotNil(t, report.AuditSummary) - assert.Len(t, report.AuditAdvisories, 2) assert.Len(t, report.AuditActions, 1) + assert.Len(t, report.AuditAdvisories, 2) + assert.Len(t, report.AuditSummaries, 1) } func TestParseValidReportSummary(t *testing.T) { - report, err := NewReport( - fullYarnJSONLines, - ) + report, err := NewReport(fullYarnJSONLines) assert.Nil(t, err) - assert.NotNil(t, report) - assert.NotNil(t, report.AuditSummary) + assert.Len(t, report.AuditSummaries, 1) - expectedSummaryData := auditSummaryData{ - Vulnerabilities: vulnerabilities{ - Info: 1, - Low: 10, - Moderate: 177, - High: 94, - Critical: 4, + expectedSummaries := AuditSummaries{ + { + Type: "auditSummary", + Data: auditSummaryData{ + Vulnerabilities: vulnerabilities{ + Info: 1, + Low: 10, + Moderate: 177, + High: 94, + Critical: 4, + }, + Dependencies: 6274, + DevDependencies: 0, + OptionalDependencies: 0, + TotalDependencies: 6274, + }, }, - Dependencies: 6274, - DevDependencies: 0, - OptionalDependencies: 0, - TotalDependencies: 6274, } - assert.True(t, reflect.DeepEqual(&expectedSummaryData, report.AuditSummary), report.AuditSummary) + assert.True(t, reflect.DeepEqual(expectedSummaries, report.AuditSummaries), report.AuditSummaries) } func TestParseValidReportAdvisories(t *testing.T) { - report, err := NewReport( - fullYarnJSONLines, - ) + report, err := NewReport(fullYarnJSONLines) assert.Nil(t, err) - assert.NotNil(t, report) assert.Len(t, report.AuditAdvisories, 2) - expectedAdvisories := []*auditAdvisoryData{ + expectedAdvisories := AuditAdvisories{ { - Resolution: auditResolution{ - ID: 1004946, - Path: "advisory1Path", - Dev: false, - Optional: false, - Bundled: false, - }, - Advisory: yarnAdvisory{ - Findings: []finding{ - { - Version: "5.0.0", - Paths: []string{ - "some/path", - "another/path", + Type: "auditAdvisory", + Data: auditAdvisoryData{ + Resolution: auditResolution{ + ID: 1004946, + Path: "advisory1Path", + Dev: false, + Optional: false, + Bundled: false, + }, + Advisory: yarnAdvisory{ + Findings: []finding{ + { + Version: "5.0.0", + Paths: []string{ + "some/path", + "another/path", + }, }, - }, - { - Version: "5.0.0", - Paths: []string{ - "more/findings/path", + { + Version: "5.0.0", + Paths: []string{ + "more/findings/path", + }, }, }, + Metadata: nil, + VulnerableVersions: ">2.1.1 <5.0.1", + ModuleName: "super-awesome-module", + Severity: "moderate", + GithubAdvisoryID: "GHSA-93q8-gq69-wqmw", + Cves: []string{ + "CVE-2022-0001", + }, + Access: "public", + PatchedVersions: ">=5.0.1", + Updated: "2021-09-23T15:45:50.000Z", + Recommendation: "Upgrade to version 5.0.1 or later", + Cwe: []string{ + "CWE-918", + }, + FoundBy: nil, + Deleted: false, + ID: 1004946, + References: "- https://advisory1.test.url/Ref1\n- https://advisory1.test.url/Ref2", + Created: "2021-11-18T16:00:48.472Z", + ReportedBy: nil, + Title: "ADVISORY 1 TITLE", + NpmAdvisoryID: nil, + Overview: "Advisory 1 overview", + URL: "https://advisory.1.url", }, - Metadata: nil, - VulnerableVersions: ">2.1.1 <5.0.1", - ModuleName: "super-awesome-module", - Severity: "moderate", - GithubAdvisoryID: "GHSA-93q8-gq69-wqmw", - Cves: []string{ - "CVE-2022-0001", - }, - Access: "public", - PatchedVersions: ">=5.0.1", - Updated: "2021-09-23T15:45:50.000Z", - Recommendation: "Upgrade to version 5.0.1 or later", - Cwe: "CWE-918", - FoundBy: nil, - Deleted: false, - ID: 1004946, - References: "- https://advisory1.test.url/Ref1\n- https://advisory1.test.url/Ref2", - Created: "2021-11-18T16:00:48.472Z", - ReportedBy: nil, - Title: "ADVISORY 1 TITLE", - NpmAdvisoryID: nil, - Overview: "Advisory 1 overview", - URL: "https://advisory.1.url", }, }, { - Resolution: auditResolution{ - ID: 1004947, - Path: "advisory2Path", - Dev: true, - Optional: false, - Bundled: false, - }, - Advisory: yarnAdvisory{ - Findings: []finding{ - { - Version: "1.1.0", - Paths: []string{ - "some/path", - "another/path", + Type: "auditAdvisory", + Data: auditAdvisoryData{ + Resolution: auditResolution{ + ID: 1004947, + Path: "advisory2Path", + Dev: true, + Optional: false, + Bundled: false, + }, + Advisory: yarnAdvisory{ + Findings: []finding{ + { + Version: "1.1.0", + Paths: []string{ + "some/path", + "another/path", + }, }, - }, - { - Version: "1.1.0", - Paths: []string{ - "more/findings/path", + { + Version: "1.1.0", + Paths: []string{ + "more/findings/path", + }, }, }, + Metadata: nil, + VulnerableVersions: ">1.1.1 <1.2.0", + ModuleName: "not-so-awesome-module", + Severity: "low", + GithubAdvisoryID: "GHSA-93q8-gq69-wqmw", + Cves: []string{ + "CVE-2022-0002", + }, + Access: "public", + PatchedVersions: ">=1.2.0", + Updated: "2021-09-23T15:45:50.000Z", + Recommendation: "Upgrade to version 1.2.0 or later", + Cwe: []string{ + "CWE-920", + }, + FoundBy: nil, + Deleted: false, + ID: 1004947, + References: "- https://advisory2.test.url/Ref1\n- https://advisory2.test.url/Ref2\n- https://advisory2.test.url/Ref3", + Created: "2021-11-18T16:00:48.472Z", + ReportedBy: nil, + Title: "ADVISORY 2 TITLE", + NpmAdvisoryID: nil, + Overview: "Advisory 2 overview", + URL: "https://advisory.2.url", }, - Metadata: nil, - VulnerableVersions: ">1.1.1 <1.2.0", - ModuleName: "not-so-awesome-module", - Severity: "low", - GithubAdvisoryID: "GHSA-93q8-gq69-wqmw", - Cves: []string{ - "CVE-2022-0002", - }, - Access: "public", - PatchedVersions: ">=1.2.0", - Updated: "2021-09-23T15:45:50.000Z", - Recommendation: "Upgrade to version 1.2.0 or later", - Cwe: "CWE-920", - FoundBy: nil, - Deleted: false, - ID: 1004947, - References: "- https://advisory2.test.url/Ref1\n- https://advisory2.test.url/Ref2\n- https://advisory2.test.url/Ref3", - Created: "2021-11-18T16:00:48.472Z", - ReportedBy: nil, - Title: "ADVISORY 2 TITLE", - NpmAdvisoryID: nil, - Overview: "Advisory 2 overview", - URL: "https://advisory.2.url", }, }, } @@ -343,52 +370,52 @@ func TestParseValidReportAdvisories(t *testing.T) { } func TestParseValidReportActions(t *testing.T) { - report, err := NewReport( - fullYarnJSONLines, - ) + report, err := NewReport(fullYarnJSONLines) assert.Nil(t, err) - assert.NotNil(t, report) assert.Len(t, report.AuditActions, 1) - expectedActionData := auditActionData{ - Cmd: "action command", - IsBreaking: false, - Action: auditAction{ - Action: "action string", - Module: "action module string", - Target: "action target", - IsMajor: true, - Resolves: []auditResolution{ - { - ID: 1, - Path: "action reolve path", - Dev: true, - Optional: true, - Bundled: true, + expectedActions := AuditActions{ + { + Type: "auditAction", + Data: auditActionData{ + Cmd: "action command", + IsBreaking: false, + Action: auditActionAction{ + Action: "action string", + Module: "action module string", + Target: "action target", + IsMajor: true, + Resolves: []auditResolution{ + { + ID: 1, + Path: "action reolve path", + Dev: true, + Optional: true, + Bundled: true, + }, + }, }, }, }, } - assert.True(t, reflect.DeepEqual(&expectedActionData, report.AuditActions[0]), report.AuditActions[0]) + assert.True(t, reflect.DeepEqual(expectedActions, report.AuditActions), report.AuditActions) } func TestParseValidReportAsIssues(t *testing.T) { - report, err := NewReport( - fullYarnJSONLines, - ) + report, err := NewReport(fullYarnJSONLines) assert.Nil(t, err) assert.Len(t, report.AuditAdvisories, 2) - issues := report.AsIssues() + issues := report.AuditAdvisories.AsIssues() assert.Len(t, issues, 2) expectedIssues := []*v1.Issue{ - &v1.Issue{ + { Target: "advisory1Path: super-awesome-module", Type: "CWE-918", Title: "ADVISORY 1 TITLE", @@ -404,7 +431,7 @@ Advisory URL: https://advisory.1.url `, Cve: "CVE-2022-0001", }, - &v1.Issue{ + { Target: "advisory2Path: not-so-awesome-module", Type: "CWE-920", Title: "ADVISORY 2 TITLE",