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
9 changes: 5 additions & 4 deletions config.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
title = "Scope-guardian configuration file"
title = "ScopeGuardian configuration file"

protected_branches = ["main", "master"]
path = "./WebGoat"
protected_branches = ["main"]
path = "."

[kics]
platform = "Dockerfile"
# exclude = ["**/vendor/**", "**/node_modules/**"]

[grype]
ignore_states = "not-fixed,unknown,wont-fix"
syft_exclude = ["**/*_test.go"]
transitive_libraries = false
# syft_exclude = ["**/src/test/**"] # glob patterns passed to Syft --exclude to skip paths during SBOM generation

Expand All @@ -22,4 +23,4 @@ exclude_rule = []
# http_proxy = "http://proxy.company.com:3128"
# https_proxy = "http://proxy.company.com:3128"
# no_proxy = "localhost,127.0.0.1"
# ssl_cert_file = "/path/to/ca.pem" # PEM-encoded CA certificate (e.g. Burp Suite CA)
# ssl_cert_file = "/path/to/ca.pem" # PEM-encoded CA certificate (e.g. Burp Suite CA)
6 changes: 5 additions & 1 deletion domains/models/finding.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ func FilterFindingsByStatus(findings []Finding, statuses []string) []Finding {
// hash(lower(severity) | lower(sinkFile) | sinkLine | lower(recommendation))
//
// Scanner-specific notes:
// - Grype: recommendation is the "Upgrade to X" string derived from fix.versions.
// - Grype: the fourth field passed is the CVE/GHSA id (VulnId), not
// Recommendation. DefectDojo's Anchore Grype parser synthesizes its own
// Mitigation wording (e.g. "Upgrade to version: X"), so matching on that
// free text is unreliable; the vulnerability id is copied through
// verbatim and returned via vulnerability_ids.
// - OpenGrep: recommendation is always "" because DefectDojo's Semgrep parser stores
// extra.message in description, not mitigation. The hash is additionally
// injected into extra.fingerprint before upload so that DefectDojo stores
Expand Down
7 changes: 6 additions & 1 deletion features/scans/grype/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ func (s *GrypeServiceImpl) LoadFindings() ([]models.Finding, error) {
SinkFile: sinkFile,
Recommendation: recommendation,
}
f.Hash = models.ComputeFindingHash(f.Severity, f.SinkFile, f.SinkLine, f.Recommendation)
// Hashed on the CVE/GHSA id rather than Recommendation: DefectDojo's Anchore
// Grype parser synthesizes its own mitigation wording (e.g. "Upgrade to
// version: X" instead of "Upgrade to X"), so matching on that text is
// unreliable. The vulnerability id is copied through verbatim by DD's
// parser and returned in vulnerability_ids, giving a stable matching key.
f.Hash = models.ComputeFindingHash(f.Severity, f.SinkFile, f.SinkLine, cveId)
findings = append(findings, f)
}

Expand Down
21 changes: 16 additions & 5 deletions features/sync/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,18 @@ func GetEngagementFindings(ddService defectdojo.DefectDojoService, projectName s
// findings just created by the sync import). All local findings are returned —
// nothing is filtered out.
//
// Two complementary hash strategies are used to match a local finding to its DD
// Three complementary hash strategies are used to match a local finding to its DD
// counterpart:
//
// 1. hash(severity|filePath|line|mitigation) — primary path for Grype and KICS.
// 2. UniqueIdFromTool — covers OpenGrep (hash injected into extra.fingerprint
// 1. hash(severity|filePath|line|mitigation) — primary path for KICS, whose
// Recommendation is a raw field (expected_value) that DD's parser passes
// through verbatim into Mitigation.
// 2. hash(severity|filePath|line|vulnerability_id) — primary path for Grype.
// DefectDojo's Anchore Grype parser synthesizes its own Mitigation wording
// (e.g. "Upgrade to version: X" instead of ScopeGuardian's "Upgrade to X"),
// so matching on that text is unreliable. The CVE/GHSA id, however, is
// copied through verbatim and returned in vulnerability_ids.
// 3. UniqueIdFromTool — covers OpenGrep (hash injected into extra.fingerprint
// before upload; DD's Semgrep parser stores it as unique_id_from_tool).
func MarkFindingsByDDFindings(local []models.Finding, ddFindings []defectdojo.Finding) []models.Finding {
type ddStatus struct {
Expand All @@ -228,9 +235,13 @@ func MarkFindingsByDDFindings(local []models.Finding, ddFindings []defectdojo.Fi
riskAccepted: f.RiskAccepted,
falseP: f.FalseP,
}
// Strategy 1: hash from API fields — covers Grype and KICS.
// Strategy 1: hash from API fields — covers KICS.
ddMap[models.ComputeFindingHash(f.Severity, f.FilePath, f.Line, f.Mitigation)] = s
// Strategy 2: UniqueIdFromTool — covers OpenGrep.
// Strategy 2: hash from vulnerability id — covers Grype.
for _, v := range f.VulnerabilityIds {
ddMap[models.ComputeFindingHash(f.Severity, f.FilePath, f.Line, v.VulnerabilityId)] = s
}
// Strategy 3: UniqueIdFromTool — covers OpenGrep.
if f.UniqueIdFromTool != "" {
ddMap[f.UniqueIdFromTool] = s
}
Expand Down
35 changes: 35 additions & 0 deletions features/sync/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,41 @@ func TestMarkFindingsByDDFindings(t *testing.T) {
assert.Equal(t, "Missing User Instruction", result[0].Name)
})

t.Run("Should match Grype finding via vulnerability id despite mismatched DD mitigation wording", func(t *testing.T) {
// Grype's LoadFindings hashes on VulnId (CVE/GHSA), not Recommendation —
// see features/scans/grype/service.go.
local := []models.Finding{
{
Severity: "HIGH",
Name: "github.com/docker/docker v28.5.2",
VulnId: "CVE-2026-34040",
SinkFile: "/LAMA/go.mod",
SinkLine: 0,
Recommendation: "Upgrade to 29.3.1",
Hash: models.ComputeFindingHash("HIGH", "/LAMA/go.mod", 0, "CVE-2026-34040"),
},
}
ddFindings := []defectdojo.Finding{
{
Title: "github.com/docker/docker v28.5.2",
Severity: "High",
FilePath: "/LAMA/go.mod",
Line: 0,
Mitigation: "Upgrade to version: 29.3.1",
VulnerabilityIds: []defectdojo.VulnerabilityId{
{VulnerabilityId: "CVE-2026-34040"},
},
Active: true,
Duplicate: false,
},
}

result := MarkFindingsByDDFindings(local, ddFindings)

assert.Len(t, result, 1)
assert.Equal(t, models.FindingStatusActive, result[0].Status)
})

t.Run("Should return empty slice when local findings list is empty", func(t *testing.T) {
ddFindings := []defectdojo.Finding{
{Title: "SQL Injection", Severity: "High", FilePath: "src/db.go", Line: 42,
Expand Down
Loading