@@ -10,13 +10,15 @@ import (
10
10
"path"
11
11
"path/filepath"
12
12
"regexp"
13
+ "slices"
13
14
"sort"
14
15
"strings"
15
16
16
17
"github.com/hashicorp/go-multierror"
17
18
"github.com/samber/lo"
18
19
"golang.org/x/xerrors"
19
20
21
+ "github.com/aquasecurity/trivy/pkg/dependency"
20
22
"github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson"
21
23
"github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/yarn"
22
24
"github.com/aquasecurity/trivy/pkg/detector/library/compare/npm"
@@ -42,7 +44,6 @@ var fragmentRegexp = regexp.MustCompile(`(\S+):(@?.*?)(@(.*?)|)$`)
42
44
type yarnAnalyzer struct {
43
45
logger * log.Logger
44
46
packageJsonParser * packagejson.Parser
45
- lockParser language.Parser
46
47
comparer npm.Comparer
47
48
license * license.License
48
49
}
@@ -51,12 +52,21 @@ func newYarnAnalyzer(opt analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error
51
52
return & yarnAnalyzer {
52
53
logger : log .WithPrefix ("yarn" ),
53
54
packageJsonParser : packagejson .NewParser (),
54
- lockParser : yarn .NewParser (),
55
55
comparer : npm.Comparer {},
56
56
license : license .NewLicense (opt .LicenseScannerOption .ClassifierConfidenceLevel ),
57
57
}, nil
58
58
}
59
59
60
+ type parserWithPatterns struct {
61
+ patterns map [string ][]string
62
+ }
63
+
64
+ func (p * parserWithPatterns ) Parse (r xio.ReadSeekerAt ) ([]types.Package , []types.Dependency , error ) {
65
+ pkgs , deps , patterns , err := yarn .NewParser ().Parse (r )
66
+ p .patterns = patterns
67
+ return pkgs , deps , err
68
+ }
69
+
60
70
func (a yarnAnalyzer ) PostAnalyze (_ context.Context , input analyzer.PostAnalysisInput ) (* analyzer.AnalysisResult , error ) {
61
71
var apps []types.Application
62
72
@@ -65,8 +75,9 @@ func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis
65
75
}
66
76
67
77
err := fsutils .WalkDir (input .FS , "." , required , func (filePath string , d fs.DirEntry , r io.Reader ) error {
78
+ parser := & parserWithPatterns {}
68
79
// Parse yarn.lock
69
- app , err := a . parseYarnLock ( filePath , r )
80
+ app , err := language . Parse ( types . Yarn , filePath , r , parser )
70
81
if err != nil {
71
82
return xerrors .Errorf ("parse error: %w" , err )
72
83
} else if app == nil {
@@ -79,7 +90,7 @@ func (a yarnAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis
79
90
}
80
91
81
92
// Parse package.json alongside yarn.lock to find direct deps and mark dev deps
82
- if err = a .analyzeDependencies (input .FS , path .Dir (filePath ), app ); err != nil {
93
+ if err = a .analyzeDependencies (input .FS , path .Dir (filePath ), app , parser . patterns ); err != nil {
83
94
a .logger .Warn ("Unable to parse package.json to remove dev dependencies" ,
84
95
log .FilePath (path .Join (path .Dir (filePath ), types .NpmPkg )), log .Err (err ))
85
96
}
@@ -147,13 +158,9 @@ func (a yarnAnalyzer) Version() int {
147
158
return version
148
159
}
149
160
150
- func (a yarnAnalyzer ) parseYarnLock (filePath string , r io.Reader ) (* types.Application , error ) {
151
- return language .Parse (types .Yarn , filePath , r , a .lockParser )
152
- }
153
-
154
161
// analyzeDependencies analyzes the package.json file next to yarn.lock,
155
162
// distinguishing between direct and transitive dependencies as well as production and development dependencies.
156
- func (a yarnAnalyzer ) analyzeDependencies (fsys fs.FS , dir string , app * types.Application ) error {
163
+ func (a yarnAnalyzer ) analyzeDependencies (fsys fs.FS , dir string , app * types.Application , patterns map [ string ][] string ) error {
157
164
packageJsonPath := path .Join (dir , types .NpmPkg )
158
165
directDeps , directDevDeps , err := a .parsePackageJsonDependencies (fsys , packageJsonPath )
159
166
if errors .Is (err , fs .ErrNotExist ) {
@@ -170,13 +177,13 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App
170
177
})
171
178
172
179
// Walk prod dependencies
173
- pkgs , err := a .walkDependencies (app .Packages , pkgIDs , directDeps , false )
180
+ pkgs , err := a .walkDependencies (app .Packages , pkgIDs , directDeps , patterns , false )
174
181
if err != nil {
175
182
return xerrors .Errorf ("unable to walk dependencies: %w" , err )
176
183
}
177
184
178
185
// Walk dev dependencies
179
- devPkgs , err := a .walkDependencies (app .Packages , pkgIDs , directDevDeps , true )
186
+ devPkgs , err := a .walkDependencies (app .Packages , pkgIDs , directDevDeps , patterns , true )
180
187
if err != nil {
181
188
return xerrors .Errorf ("unable to walk dependencies: %w" , err )
182
189
}
@@ -194,7 +201,7 @@ func (a yarnAnalyzer) analyzeDependencies(fsys fs.FS, dir string, app *types.App
194
201
}
195
202
196
203
func (a yarnAnalyzer ) walkDependencies (pkgs []types.Package , pkgIDs map [string ]types.Package ,
197
- directDeps map [string ]string , dev bool ) (map [string ]types.Package , error ) {
204
+ directDeps map [string ]string , patterns map [ string ][] string , dev bool ) (map [string ]types.Package , error ) {
198
205
199
206
// Identify direct dependencies
200
207
directPkgs := make (map [string ]types.Package )
@@ -211,11 +218,16 @@ func (a yarnAnalyzer) walkDependencies(pkgs []types.Package, pkgIDs map[string]t
211
218
constraint = m [4 ]
212
219
}
213
220
214
- // npm has own comparer to compare versions
215
- if match , err := a .comparer .MatchVersion (pkg .Version , constraint ); err != nil {
216
- return nil , xerrors .Errorf ("unable to match version for %s" , pkg .Name )
217
- } else if ! match {
218
- continue
221
+ // Try to find an exact match to the pattern.
222
+ // In some cases, patterns from yarn.lock and package.json may not match (e.g., yarn v2 uses the allowed version for ID).
223
+ // Therefore, if the patterns don't match - compare versions.
224
+ if pkgPatterns , found := patterns [pkg .ID ]; ! found || ! slices .Contains (pkgPatterns , dependency .ID (types .Yarn , pkg .Name , constraint )) {
225
+ // npm has own comparer to compare versions
226
+ if match , err := a .comparer .MatchVersion (pkg .Version , constraint ); err != nil {
227
+ return nil , xerrors .Errorf ("unable to match version for %s" , pkg .Name )
228
+ } else if ! match {
229
+ continue
230
+ }
219
231
}
220
232
221
233
// Mark as a direct dependency
0 commit comments