Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ require (
github.com/jfrog/build-info-go v1.13.1-0.20260216093441-40a4dc563294
github.com/jfrog/froggit-go v1.21.0
github.com/jfrog/gofrog v1.7.6
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260227101327-7478579b5f25
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260218080258-3bf55ed18973
github.com/jfrog/jfrog-cli-security v1.26.2
github.com/jfrog/jfrog-cli-security v1.26.3
github.com/jfrog/jfrog-client-go v1.55.1-0.20260225080504-17057750d47b
github.com/owenrumney/go-sarif/v3 v3.2.3
github.com/stretchr/testify v1.11.1
Expand Down Expand Up @@ -65,6 +64,7 @@ require (
github.com/jedib0t/go-pretty/v6 v6.7.5 // indirect
github.com/jfrog/archiver/v3 v3.6.1 // indirect
github.com/jfrog/jfrog-apps-config v1.0.1 // indirect
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260227101327-7478579b5f25 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
Expand Down Expand Up @@ -128,7 +128,7 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security v1.26.3-0.20260302131045-5f088ae1204c
// replace github.com/jfrog/jfrog-cli-security => github.com/jfrog/jfrog-cli-security dev

// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260227101327-7478579b5f25 h1:o
github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260227101327-7478579b5f25/go.mod h1:P9ZywyTQzp+WsNmeb4IiMQOdVb++eQUD5oXd18LRVj8=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260218080258-3bf55ed18973 h1:fOlWUGkCuujnIcE3166gpTdvicwv1wAZhLrfbm+f6rY=
github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260218080258-3bf55ed18973/go.mod h1:GDveG1xAoiM12JlSx8RE0OcJ6Ov+xcmpmGv84we3pMA=
github.com/jfrog/jfrog-cli-security v1.26.3-0.20260302131045-5f088ae1204c h1:XL7RC5H7HW+wRoUzM04pDFQzWr+VOWMJNDMl8fHto94=
github.com/jfrog/jfrog-cli-security v1.26.3-0.20260302131045-5f088ae1204c/go.mod h1:PSf39yzsu1qp1lXJ3QOSBOw4dzqLW2r/6Q616lY+x/o=
github.com/jfrog/jfrog-cli-security v1.26.3 h1:991m5HZrFxR8GOg5ALxTGxih73+wTPmLvlLG0VaXDxk=
github.com/jfrog/jfrog-cli-security v1.26.3/go.mod h1:eZLjW37Z6f1DbeKCsL+NnYSm41hQnV1wV6NpLfIOwLw=
github.com/jfrog/jfrog-client-go v1.55.1-0.20260225080504-17057750d47b h1:mSxcMTXtnrYMVhCGk7ui2ERh6yLoUVUQhXaNwd3FhL8=
github.com/jfrog/jfrog-client-go v1.55.1-0.20260225080504-17057750d47b/go.mod h1:sCE06+GngPoyrGO0c+vmhgMoVSP83UMNiZnIuNPzU8U=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@


---
## 📜 Public Code Snippet Violation

---

The highlighted snippet was copied from:
[https://github.com/example/repo/blob/main/file.go#L10-L30](https://github.com/example/repo/blob/main/file.go#L10-L30)
| Severity | License | Watch Name | Policies |
| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: |
| High | MIT | watch1 | policy1 |
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

## 📜 Public Code Snippet Violation

The highlighted snippet was copied from:
[https://github.com/example/repo/blob/main/file.go#L10-L30](https://github.com/example/repo/blob/main/file.go#L10-L30)
<div align='center'>

| Severity | License | Watch Name | Policies |
| :---------------------: | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: |
| ![high](https://raw.githubusercontent.com/jfrog/frogbot/master/resources/v2/applicableHighSeverity.png)<br> High | MIT | watch1 | policy1 |

</div>
117 changes: 117 additions & 0 deletions utils/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"context"
"errors"
"fmt"
"regexp"
"sort"
"strconv"

"github.com/jfrog/froggit-go/vcsclient"
"github.com/jfrog/gofrog/datastructures"
"github.com/jfrog/jfrog-cli-security/utils/formats"
"github.com/jfrog/jfrog-cli-security/utils/results"
"github.com/jfrog/jfrog-client-go/utils/log"
Expand All @@ -28,7 +31,9 @@ const (
IacComment ReviewCommentType = "Iac"
SastComment ReviewCommentType = "Sast"
SecretComment ReviewCommentType = "Secrets"
SnippetComment ReviewCommentType = "Snippet"

snippetVersionMarker = "snippet"
commentRemovalErrorMsg = "An error occurred while attempting to remove older Frogbot pull request comments:"
)

Expand Down Expand Up @@ -186,6 +191,9 @@ func getFrogbotComments(existingComments []vcsclient.CommentInfo) (reviewComment

func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection) (commentsToAdd []ReviewComment) {
writer := repo.OutputWriter

commentsToAdd = append(commentsToAdd, generateSnippetReviewComment(issues, writer)...)

// CVE Applicable Evidence review comments
for _, applicableEvidences := range issues.GetApplicableEvidences() {
commentsToAdd = append(commentsToAdd, generateReviewComment(ApplicableComment, applicableEvidences.Evidence.Location, generateApplicabilityReviewContent(applicableEvidences, writer)))
Expand All @@ -206,6 +214,7 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection
commentsToAdd = append(commentsToAdd, generateReviewComment(SastComment, similarSastIssues.Location, generateSourceCodeReviewContent(SastComment, true, writer, similarSastIssues.issues...)))
}
}

// Secrets review comments
if !repo.FrogbotConfig.ShowSecretsAsPrComment {
return
Expand All @@ -218,9 +227,99 @@ func getNewReviewComments(repo *Repository, issues *issues.ScansIssuesCollection
commentsToAdd = append(commentsToAdd, generateReviewComment(SecretComment, similarSecretsIssues.Location, generateSourceCodeReviewContent(SecretComment, true, writer, similarSecretsIssues.issues...)))
}
}

return
}

func generateSnippetReviewComment(issues *issues.ScansIssuesCollection, writer outputwriter.OutputWriter) (commentsToAdd []ReviewComment) {
type key struct {
File string
Line int
}

locToOrigin := make(map[key]*datastructures.Set[string])
licensesBySnippet := make(map[key][]formats.LicenseViolationRow)
for _, lic := range issues.LicensesViolations {
if lic.ImpactedDependencyVersion != snippetVersionMarker {
continue
}
// Extract license violation information that are related to a snippet
for _, ipath := range lic.ImpactPaths {
if len(ipath) == 0 {
continue
}

// Leaf node of the impact path is the snippet location
snippet := ipath[len(ipath)-1]

// Map evidence to snippet location
for _, evidence := range snippet.Evidences {
k := key{File: evidence.File, Line: evidence.StartLine}
licensesBySnippet[k] = append(licensesBySnippet[k], lic)
if locToOrigin[k] == nil {
locToOrigin[k] = datastructures.MakeSet[string]()
}
locToOrigin[k].AddElements(evidence.ExternalReferences...)
}
}
}
// Sort snippet locations by file and line
sortedKeys := make([]key, 0, len(licensesBySnippet))
for k := range licensesBySnippet {
sortedKeys = append(sortedKeys, k)
}
sort.Slice(sortedKeys, func(i, j int) bool {
if sortedKeys[i].File != sortedKeys[j].File {
return sortedKeys[i].File < sortedKeys[j].File
}
return sortedKeys[i].Line < sortedKeys[j].Line
})
// Generate review comments for each snippet location
for _, loc := range sortedKeys {
licenses := licensesBySnippet[loc]
refSlice := locToOrigin[loc].ToSlice()
commentsToAdd = append(commentsToAdd, generateReviewComment(
SnippetComment,
formats.Location{
File: loc.File,
StartLine: loc.Line,
EndLine: loc.Line + snippetLineDeltaFromRef(refSlice),
},
generateComponentReviewContent(
SnippetComment,
true,
writer,
licenses,
refSlice),
))
}
return
}

var snippetLineRangeRe = regexp.MustCompile(`#L(\d+)-L(\d+)$`)

const defaultSnippetLineDelta = 20

// snippetLineDeltaFromRef parses a GitHub-style line range fragment (#L<start>-L<end>)
// from the first matching external reference URL and returns end−start (the delta to
// add to a start line to compute the end line).
// Falls back to defaultSnippetLineDelta when no URL contains a parseable range.
func snippetLineDeltaFromRef(refs []string) int {
for _, ref := range refs {
m := snippetLineRangeRe.FindStringSubmatch(ref)
if len(m) != 3 {
continue
}
start, err1 := strconv.Atoi(m[1])
end, err2 := strconv.Atoi(m[2])
if err1 != nil || err2 != nil || end <= start {
continue
}
return end - start
}
return defaultSnippetLineDelta
}

type jasCommentIssues struct {
// The location of the issue that the comment will be added to.
formats.Location
Expand Down Expand Up @@ -284,6 +383,24 @@ func generateSourceCodeReviewContent(commentType ReviewCommentType, violation bo
return
}

func generateComponentReviewContent(
commentType ReviewCommentType,
violation bool,
writer outputwriter.OutputWriter,
licenses []formats.LicenseViolationRow,
externalReferences []string,
) (content string) {
if commentType == SnippetComment {
return outputwriter.GenerateReviewCommentContent(outputwriter.SnippetReviewContent(
violation,
writer,
licenses,
externalReferences,
), writer)
}
return
}

func createPullRequestDiff(location formats.Location) vcsclient.PullRequestDiff {
return vcsclient.PullRequestDiff{
OriginalFilePath: location.File,
Expand Down
Loading
Loading