Skip to content

Commit 6bf9ff0

Browse files
fix(vex-rules): added fix for VEX rules not getting applied properly, removed ROOT as wild card and handled changes in match expression flow
Signed-off-by: gauravshinde1729 <shindegauravpict@gmail.com>
1 parent 5a07627 commit 6bf9ff0

2 files changed

Lines changed: 29 additions & 17 deletions

File tree

dtos/path_pattern_test.go

Lines changed: 9 additions & 7 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,12 +96,14 @@ 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},
103-
// Direct dependency: pattern created from the graph includes ROOT but VulnerabilityPath does not.
104-
// ["*", "ROOT", "pkg:..."] must match ["pkg:..."] because ROOT is a wildcard that matches zero elements.
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},
105107
{"wildcard ROOT pkg matches direct dependency path", PathPattern{"*", normalize.GraphRootNodeID, "pkg:golang/go-jose@v4"}, []string{"pkg:golang/go-jose@v4"}, true},
106108
}
107109

dtos/vex_rule_dto.go

Lines changed: 20 additions & 10 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,12 +103,10 @@ func matchPatternExact(pattern, path []string) bool {
98103
return true
99104
}
100105

101-
// If the next pattern element is also a wildcard (e.g. ROOT), try
102-
// matching it at the current path position (zero-element match for
103-
// this wildcard). This handles patterns like ["*", "ROOT", "pkg:..."]
104-
// against paths like ["pkg:..."] where ROOT never appears in
105-
// component-only vulnerability paths.
106-
if IsWildcard(pattern[pIdx+1]) {
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 {
107110
if matchPatternExact(pattern[pIdx+1:], path[pathIdx:]) {
108111
return true
109112
}
@@ -136,6 +139,13 @@ func matchPatternExact(pattern, path []string) bool {
136139
return pathIdx == len(path)
137140
}
138141

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+
139149
// Literal match
140150
if pathIdx >= len(path) || pattern[pIdx] != path[pathIdx] {
141151
return false

0 commit comments

Comments
 (0)