Skip to content

Commit 042d6b0

Browse files
feat(dart): use first version of constraint for dependencies using SDK version (#6239)
Signed-off-by: knqyf263 <knqyf263@gmail.com> Co-authored-by: knqyf263 <knqyf263@gmail.com>
1 parent 8141a13 commit 042d6b0

File tree

6 files changed

+109
-15
lines changed

6 files changed

+109
-15
lines changed

docs/docs/coverage/language/dart.md

+21-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Trivy supports [Dart][dart].
44

55
The following scanners are supported.
66

7-
| Package manager | SBOM | Vulnerability | License |
8-
|-------------------------| :---: | :-----------: |:-------:|
9-
| [Dart][dart-repository] | || - |
7+
| Package manager | SBOM | Vulnerability | License |
8+
|-------------------------|:----:|:-------------:|:-------:|
9+
| [Dart][dart-repository] ||| - |
1010

1111
The following table provides an outline of the features Trivy offers.
1212

@@ -21,6 +21,24 @@ In order to detect dependencies, Trivy searches for `pubspec.lock`.
2121
Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options to separate root and dev transitive dependencies.
2222
So Trivy includes all dependencies in report.
2323

24+
### SDK dependencies
25+
Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). It is not possible to accurately determine the versions of these dependencies.
26+
27+
Therefore, we use the first version of the constraint for the SDK.
28+
29+
For example in this case the version of `flutter` should be `3.3.0`:
30+
```yaml
31+
flutter:
32+
dependency: "direct main"
33+
description: flutter
34+
source: sdk
35+
version: "0.0.0"
36+
sdks:
37+
dart: ">=2.18.0 <3.0.0"
38+
flutter: "^3.3.0"
39+
```
40+
41+
### Dependency tree
2442
To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only).
2543
!!! note
2644
Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
2222
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
2323
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
24-
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492
24+
github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d
2525
github.com/aquasecurity/loading v0.0.5
2626
github.com/aquasecurity/table v1.8.0
2727
github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334

go.sum

+2-1
Original file line numberDiff line numberDiff line change
@@ -760,8 +760,9 @@ github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod
760760
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4=
761761
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg=
762762
github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
763-
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M=
764763
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
764+
github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d h1:4zour5Sh9chOg+IqIinIcJ3qtr3cIf8FdFY6aArlXBw=
765+
github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d/go.mod h1:1cPOp4BaQZ1G2F5fnw4dFz6pkOyXJI9KTuak8ghIl3U=
765766
github.com/aquasecurity/loading v0.0.5 h1:2iq02sPSSMU+ULFPmk0v0lXnK/eZ2e0dRAj/Dl5TvuM=
766767
github.com/aquasecurity/loading v0.0.5/go.mod h1:NSHeeq1JTDTFuXAe87q4yQ2DX57pXiaQMqq8Zm9HCJA=
767768
github.com/aquasecurity/table v1.8.0 h1:9ntpSwrUfjrM6/YviArlx/ZBGd6ix8W+MtojQcM7tv0=

pkg/dependency/parser/dart/pub/parse.go

+82-7
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"golang.org/x/xerrors"
55
"gopkg.in/yaml.v3"
66

7+
goversion "github.com/aquasecurity/go-version/pkg/version"
78
"github.com/aquasecurity/trivy/pkg/dependency"
89
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
10+
"github.com/aquasecurity/trivy/pkg/log"
911
xio "github.com/aquasecurity/trivy/pkg/x/io"
1012
)
1113

@@ -16,37 +18,51 @@ const (
1618
)
1719

1820
// Parser is a parser for pubspec.lock
19-
type Parser struct{}
21+
type Parser struct {
22+
logger *log.Logger
23+
}
2024

2125
func NewParser() *Parser {
22-
return &Parser{}
26+
return &Parser{
27+
logger: log.WithPrefix("pub"),
28+
}
2329
}
2430

2531
type lock struct {
26-
Packages map[string]Dep `yaml:"packages"`
32+
Packages map[string]Dep `yaml:"packages"`
33+
Sdks map[string]string `yaml:"sdks"`
2734
}
2835

2936
type Dep struct {
30-
Dependency string `yaml:"dependency"`
31-
Version string `yaml:"version"`
37+
Dependency string `yaml:"dependency"`
38+
Version string `yaml:"version"`
39+
Source string `yaml:"source"`
40+
Description Description `yaml:"description"`
3241
}
3342

43+
type Description string
44+
3445
func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
3546
l := &lock{}
3647
if err := yaml.NewDecoder(r).Decode(&l); err != nil {
3748
return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err)
3849
}
3950
var pkgs []ftypes.Package
4051
for name, dep := range l.Packages {
52+
version := dep.Version
53+
if version == "0.0.0" && dep.Source == "sdk" {
54+
version = p.findSDKVersion(l, name, dep)
55+
}
56+
4157
// We would like to exclude dev dependencies, but we cannot identify
4258
// which indirect dependencies were introduced by dev dependencies
4359
// as there are 3 dependency types, "direct main", "direct dev" and "transitive".
4460
// It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies.
4561
// We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev".
4662
pkg := ftypes.Package{
47-
ID: dependency.ID(ftypes.Pub, name, dep.Version),
63+
ID: dependency.ID(ftypes.Pub, name, version),
4864
Name: name,
49-
Version: dep.Version,
65+
Version: version,
5066
Relationship: p.relationship(dep.Dependency),
5167
}
5268
pkgs = append(pkgs, pkg)
@@ -55,6 +71,31 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency
5571
return pkgs, nil, nil
5672
}
5773

74+
// findSDKVersion detects the first version of the SDK constraint specified in the Description.
75+
// If the constraint is not found, it returns the original version.
76+
func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string {
77+
// Some dependencies use one of the SDK versions.
78+
// In this case dep.Version == `0.0.0`.
79+
// We can't get versions for these dependencies.
80+
// Therefore, we use the first version of the SDK constraint specified in the Description.
81+
// See https://github.com/aquasecurity/trivy/issues/6017
82+
constraint, ok := l.Sdks[string(dep.Description)]
83+
if !ok {
84+
return dep.Version
85+
}
86+
87+
v, err := firstVersionOfConstrain(constraint)
88+
if err != nil {
89+
p.logger.Warn("Unable to get sdk version from constraint", log.Err(err))
90+
return dep.Version
91+
} else if v == "" {
92+
return dep.Version
93+
}
94+
p.logger.Info("Using the first version of the constraint from the sdk source", log.String("dep", name),
95+
log.String("constraint", constraint))
96+
return v
97+
}
98+
5899
func (p Parser) relationship(dep string) ftypes.Relationship {
59100
switch dep {
60101
case directMain, directDev:
@@ -64,3 +105,37 @@ func (p Parser) relationship(dep string) ftypes.Relationship {
64105
}
65106
return ftypes.RelationshipUnknown
66107
}
108+
109+
// firstVersionOfConstrain returns the first acceptable version for constraint
110+
func firstVersionOfConstrain(constraint string) (string, error) {
111+
css, err := goversion.NewConstraints(constraint)
112+
if err != nil {
113+
return "", xerrors.Errorf("unable to parse constraints: %w", err)
114+
}
115+
116+
// Dart uses only `>=` and `^` operators:
117+
// cf. https://dart.dev/tools/pub/dependencies#traditional-syntax
118+
constraints := css.List()
119+
if len(constraints) == 0 || len(constraints[0]) == 0 {
120+
return "", nil
121+
}
122+
// We only need to get the first version from the range
123+
if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" {
124+
return "", nil
125+
}
126+
127+
return constraints[0][0].Version(), nil
128+
}
129+
130+
func (d *Description) UnmarshalYAML(value *yaml.Node) error {
131+
var tmp any
132+
if err := value.Decode(&tmp); err != nil {
133+
return err
134+
}
135+
// Description can be a string or a struct
136+
// We only need a string value for SDK mapping
137+
if desc, ok := tmp.(string); ok {
138+
*d = Description(desc)
139+
}
140+
return nil
141+
}

pkg/dependency/parser/dart/pub/parse_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ func TestParser_Parse(t *testing.T) {
3131
Relationship: ftypes.RelationshipDirect,
3232
},
3333
{
34-
ID: "flutter_test@0.0.0",
34+
ID: "flutter_test@3.3.0",
3535
Name: "flutter_test",
36-
Version: "0.0.0",
36+
Version: "3.3.0",
3737
Relationship: ftypes.RelationshipDirect,
3838
},
3939
{

pkg/dependency/parser/dart/pub/testdata/happy.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ packages:
2222
version: "3.0.6"
2323
sdks:
2424
dart: ">=2.18.0 <3.0.0"
25-
flutter: ">=3.3.0"
25+
flutter: "^3.3.0"

0 commit comments

Comments
 (0)