From 5a076271434a3684b96628635ed03ef4c3c15759 Mon Sep 17 00:00:00 2001 From: gauravshinde1729 Date: Sun, 12 Apr 2026 08:09:38 +0530 Subject: [PATCH 1/2] fix(1872): added fix for vex rul not working in case of direct dependancy --- dtos/path_pattern_test.go | 3 +++ dtos/vex_rule_dto.go | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/dtos/path_pattern_test.go b/dtos/path_pattern_test.go index 14fcad590..f2701175a 100644 --- a/dtos/path_pattern_test.go +++ b/dtos/path_pattern_test.go @@ -100,6 +100,9 @@ func TestRootPathPattern(t *testing.T) { {"ROOT matches any path with ROOT at end", PathPattern{normalize.GraphRootNodeID}, []string{"A", "B", normalize.GraphRootNodeID}, true}, {"ROOT DOES match path without ROOT", PathPattern{normalize.GraphRootNodeID}, []string{"A", "B", "C"}, true}, {"ROOT does not lead to all paths matching", PathPattern{normalize.GraphRootNodeID, "X"}, []string{"A", "B", "C"}, false}, + // Direct dependency: pattern created from the graph includes ROOT but VulnerabilityPath does not. + // ["*", "ROOT", "pkg:..."] must match ["pkg:..."] because ROOT is a wildcard that matches zero elements. + {"wildcard ROOT pkg matches direct dependency path", PathPattern{"*", normalize.GraphRootNodeID, "pkg:golang/go-jose@v4"}, []string{"pkg:golang/go-jose@v4"}, true}, } for _, tt := range tests { diff --git a/dtos/vex_rule_dto.go b/dtos/vex_rule_dto.go index fcb33032c..2ebe07499 100644 --- a/dtos/vex_rule_dto.go +++ b/dtos/vex_rule_dto.go @@ -98,6 +98,17 @@ func matchPatternExact(pattern, path []string) bool { return true } + // If the next pattern element is also a wildcard (e.g. ROOT), try + // matching it at the current path position (zero-element match for + // this wildcard). This handles patterns like ["*", "ROOT", "pkg:..."] + // against paths like ["pkg:..."] where ROOT never appears in + // component-only vulnerability paths. + if IsWildcard(pattern[pIdx+1]) { + if matchPatternExact(pattern[pIdx+1:], path[pathIdx:]) { + return true + } + } + // Try to find the next pattern element in the path nextPattern := pattern[pIdx+1] From 6bf9ff04111f6c7fea493baf3be051f5838eccda Mon Sep 17 00:00:00 2001 From: gauravshinde1729 Date: Fri, 17 Apr 2026 22:12:51 +0530 Subject: [PATCH 2/2] 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 --- dtos/path_pattern_test.go | 16 +++++++++------- dtos/vex_rule_dto.go | 30 ++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/dtos/path_pattern_test.go b/dtos/path_pattern_test.go index f2701175a..f5af206b8 100644 --- a/dtos/path_pattern_test.go +++ b/dtos/path_pattern_test.go @@ -34,7 +34,7 @@ func TestIsWildcard(t *testing.T) { {"literal pkg:npm/foo", "pkg:npm/foo@1.0.0", false}, {"empty string", "", false}, {"triple star", "***", false}, - {"ROOT is wildcard", normalize.GraphRootNodeID, true}, + {"ROOT is not a wildcard", normalize.GraphRootNodeID, false}, } for _, tt := range tests { @@ -96,12 +96,14 @@ func TestRootPathPattern(t *testing.T) { path []string expected bool }{ - {"ROOT matches ROOT", PathPattern{normalize.GraphRootNodeID}, []string{normalize.GraphRootNodeID}, true}, - {"ROOT matches any path with ROOT at end", PathPattern{normalize.GraphRootNodeID}, []string{"A", "B", normalize.GraphRootNodeID}, true}, - {"ROOT DOES match path without ROOT", PathPattern{normalize.GraphRootNodeID}, []string{"A", "B", "C"}, true}, - {"ROOT does not lead to all paths matching", PathPattern{normalize.GraphRootNodeID, "X"}, []string{"A", "B", "C"}, false}, - // Direct dependency: pattern created from the graph includes ROOT but VulnerabilityPath does not. - // ["*", "ROOT", "pkg:..."] must match ["pkg:..."] because ROOT is a wildcard that matches zero elements. + // ROOT is a stop marker: [root, pkg:A] matches only direct dependencies. + // VulnerabilityPath never contains ROOT, so ROOT in the pattern consumes + // zero path elements and anchors the match to position 0 (no suffix scan). + {"root pkg:A matches direct dependency", PathPattern{normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:A"}, true}, + {"root pkg:A does not match transitive dependency", PathPattern{normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:B", "pkg:A"}, false}, + // [*, ROOT, pkg:A] is equivalent to [ROOT, pkg:A]: ROOT absorbs the wildcard prefix. + {"wildcard root pkg:A matches direct dependency", PathPattern{"*", normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:A"}, true}, + {"wildcard root pkg:A does not match transitive dependency", PathPattern{"*", normalize.GraphRootNodeID, "pkg:A"}, []string{"pkg:B", "pkg:A"}, false}, {"wildcard ROOT pkg matches direct dependency path", PathPattern{"*", normalize.GraphRootNodeID, "pkg:golang/go-jose@v4"}, []string{"pkg:golang/go-jose@v4"}, true}, } diff --git a/dtos/vex_rule_dto.go b/dtos/vex_rule_dto.go index 2ebe07499..149fb338c 100644 --- a/dtos/vex_rule_dto.go +++ b/dtos/vex_rule_dto.go @@ -37,10 +37,7 @@ type PathPattern []string // IsWildcard returns true if the element is a wildcard (*). func IsWildcard(elem string) bool { - // ROOT is a special element in the path - // this means, THE CURRENT application does not call the vulnerable code - // therefore, we can just replace it with a wildcard for matching purposes - return elem == PathPatternWildcard || elem == normalize.GraphRootNodeID + return elem == PathPatternWildcard } // 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 { return true } + // ROOT is a stop marker meaning "direct dependency only". When the pattern + // contains ROOT, skip suffix scanning and match the full path from position 0. + for _, elem := range p { + if elem == normalize.GraphRootNodeID { + return matchPatternExact(p, path) + } + } + // For suffix matching, we try increasingly longer suffixes // Count non-wildcard elements to determine minimum suffix length minLen := 0 @@ -98,12 +103,10 @@ func matchPatternExact(pattern, path []string) bool { return true } - // If the next pattern element is also a wildcard (e.g. ROOT), try - // matching it at the current path position (zero-element match for - // this wildcard). This handles patterns like ["*", "ROOT", "pkg:..."] - // against paths like ["pkg:..."] where ROOT never appears in - // component-only vulnerability paths. - if IsWildcard(pattern[pIdx+1]) { + // If the next pattern element is ROOT (stop marker), try a zero-length + // match for this wildcard. [*, ROOT, pkg:A] is equivalent to [ROOT, pkg:A]: + // ROOT anchors the remainder to the current path position. + if pattern[pIdx+1] == normalize.GraphRootNodeID { if matchPatternExact(pattern[pIdx+1:], path[pathIdx:]) { return true } @@ -136,6 +139,13 @@ func matchPatternExact(pattern, path []string) bool { return pathIdx == len(path) } + // ROOT is a stop marker — it is never stored in VulnerabilityPath, + // so skip it in the pattern without consuming a path element. + if pattern[pIdx] == normalize.GraphRootNodeID { + pIdx++ + continue + } + // Literal match if pathIdx >= len(path) || pattern[pIdx] != path[pathIdx] { return false