Skip to content

Commit cee343b

Browse files
authored
Merge pull request #1874 from gauravshinde1729/fix/vex-rules-not-working-for-direct-dependacies
fix(1872): added fix for vex rule not working in case of direct dependancy
2 parents d214dd6 + 6bf9ff0 commit cee343b

2 files changed

Lines changed: 35 additions & 9 deletions

File tree

dtos/path_pattern_test.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestIsWildcard(t *testing.T) {
3434
{"literal pkg:npm/foo", "pkg:npm/foo@1.0.0", false},
3535
{"empty string", "", false},
3636
{"triple star", "***", false},
37-
{"ROOT is wildcard", normalize.GraphRootNodeID, true},
37+
{"ROOT is not a wildcard", normalize.GraphRootNodeID, false},
3838
}
3939

4040
for _, tt := range tests {
@@ -96,10 +96,15 @@ func TestRootPathPattern(t *testing.T) {
9696
path []string
9797
expected bool
9898
}{
99-
{"ROOT matches ROOT", PathPattern{normalize.GraphRootNodeID}, []string{normalize.GraphRootNodeID}, true},
100-
{"ROOT matches any path with ROOT at end", PathPattern{normalize.GraphRootNodeID}, []string{"A", "B", normalize.GraphRootNodeID}, true},
101-
{"ROOT DOES match path without ROOT", PathPattern{normalize.GraphRootNodeID}, []string{"A", "B", "C"}, true},
102-
{"ROOT does not lead to all paths matching", PathPattern{normalize.GraphRootNodeID, "X"}, []string{"A", "B", "C"}, false},
99+
// ROOT is a stop marker: [root, pkg:A] matches only direct dependencies.
100+
// VulnerabilityPath never contains ROOT, so ROOT in the pattern consumes
101+
// zero path elements and anchors the match to position 0 (no suffix scan).
102+
{"root pkg:A matches direct dependency", PathPattern{normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:A"}, true},
103+
{"root pkg:A does not match transitive dependency", PathPattern{normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:B", "pkg:A"}, false},
104+
// [*, ROOT, pkg:A] is equivalent to [ROOT, pkg:A]: ROOT absorbs the wildcard prefix.
105+
{"wildcard root pkg:A matches direct dependency", PathPattern{"*", normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:A"}, true},
106+
{"wildcard root pkg:A does not match transitive dependency", PathPattern{"*", normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:B", "pkg:A"}, false},
107+
{"wildcard ROOT pkg matches direct dependency path", PathPattern{"*", normalize.GraphRootNodeID, "pkg:golang/go-jose@v4"}, []string{"pkg:golang/go-jose@v4"}, true},
103108
}
104109

105110
for _, tt := range tests {

dtos/vex_rule_dto.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ type PathPattern []string
3737

3838
// IsWildcard returns true if the element is a wildcard (*).
3939
func IsWildcard(elem string) bool {
40-
// ROOT is a special element in the path
41-
// this means, THE CURRENT application does not call the vulnerable code
42-
// therefore, we can just replace it with a wildcard for matching purposes
43-
return elem == PathPatternWildcard || elem == normalize.GraphRootNodeID
40+
return elem == PathPatternWildcard
4441
}
4542

4643
// MatchesSuffix checks if the given path's suffix matches this pattern using suffix matching.
@@ -57,6 +54,14 @@ func (p PathPattern) MatchesSuffix(path []string) bool {
5754
return true
5855
}
5956

57+
// ROOT is a stop marker meaning "direct dependency only". When the pattern
58+
// contains ROOT, skip suffix scanning and match the full path from position 0.
59+
for _, elem := range p {
60+
if elem == normalize.GraphRootNodeID {
61+
return matchPatternExact(p, path)
62+
}
63+
}
64+
6065
// For suffix matching, we try increasingly longer suffixes
6166
// Count non-wildcard elements to determine minimum suffix length
6267
minLen := 0
@@ -98,6 +103,15 @@ func matchPatternExact(pattern, path []string) bool {
98103
return true
99104
}
100105

106+
// If the next pattern element is ROOT (stop marker), try a zero-length
107+
// match for this wildcard. [*, ROOT, pkg:A] is equivalent to [ROOT, pkg:A]:
108+
// ROOT anchors the remainder to the current path position.
109+
if pattern[pIdx+1] == normalize.GraphRootNodeID {
110+
if matchPatternExact(pattern[pIdx+1:], path[pathIdx:]) {
111+
return true
112+
}
113+
}
114+
101115
// Try to find the next pattern element in the path
102116
nextPattern := pattern[pIdx+1]
103117

@@ -125,6 +139,13 @@ func matchPatternExact(pattern, path []string) bool {
125139
return pathIdx == len(path)
126140
}
127141

142+
// ROOT is a stop marker — it is never stored in VulnerabilityPath,
143+
// so skip it in the pattern without consuming a path element.
144+
if pattern[pIdx] == normalize.GraphRootNodeID {
145+
pIdx++
146+
continue
147+
}
148+
128149
// Literal match
129150
if pathIdx >= len(path) || pattern[pIdx] != path[pathIdx] {
130151
return false

0 commit comments

Comments
 (0)