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>
This commit is contained in:
DmitriyLewen
2024-06-05 12:51:19 +06:00
committed by GitHub
parent 8141a137ba
commit 042d6b08c2
6 changed files with 109 additions and 15 deletions

View File

@@ -4,9 +4,9 @@ Trivy supports [Dart][dart].
The following scanners are supported.
| Package manager | SBOM | Vulnerability | License |
|-------------------------| :---: | :-----------: |:-------:|
| [Dart][dart-repository] | ✓ | ✓ | - |
| Package manager | SBOM | Vulnerability | License |
|-------------------------|:----:|:-------------:|:-------:|
| [Dart][dart-repository] | ✓ | ✓ | - |
The following table provides an outline of the features Trivy offers.
@@ -21,6 +21,24 @@ In order to detect dependencies, Trivy searches for `pubspec.lock`.
Trivy marks indirect dependencies, but `pubspec.lock` file doesn't have options to separate root and dev transitive dependencies.
So Trivy includes all dependencies in report.
### SDK dependencies
Dart uses version `0.0.0` for SDK dependencies (e.g. Flutter). It is not possible to accurately determine the versions of these dependencies.
Therefore, we use the first version of the constraint for the SDK.
For example in this case the version of `flutter` should be `3.3.0`:
```yaml
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
sdks:
dart: ">=2.18.0 <3.0.0"
flutter: "^3.3.0"
```
### Dependency tree
To build `dependency tree` Trivy parses [cache directory][cache-directory]. Currently supported default directories and `PUB_CACHE` environment (absolute path only).
!!! note
Make sure the cache directory contains all the dependencies installed in your application. To download missing dependencies, use `dart pub get` command.

2
go.mod
View File

@@ -21,7 +21,7 @@ require (
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492
github.com/aquasecurity/go-version v0.0.0-20240603093900-cf8a8d29271d
github.com/aquasecurity/loading v0.0.5
github.com/aquasecurity/table v1.8.0
github.com/aquasecurity/testdocker v0.0.0-20240419073403-90bd43849334

3
go.sum
View File

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

View File

@@ -4,8 +4,10 @@ import (
"golang.org/x/xerrors"
"gopkg.in/yaml.v3"
goversion "github.com/aquasecurity/go-version/pkg/version"
"github.com/aquasecurity/trivy/pkg/dependency"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -16,21 +18,30 @@ const (
)
// Parser is a parser for pubspec.lock
type Parser struct{}
type Parser struct {
logger *log.Logger
}
func NewParser() *Parser {
return &Parser{}
return &Parser{
logger: log.WithPrefix("pub"),
}
}
type lock struct {
Packages map[string]Dep `yaml:"packages"`
Packages map[string]Dep `yaml:"packages"`
Sdks map[string]string `yaml:"sdks"`
}
type Dep struct {
Dependency string `yaml:"dependency"`
Version string `yaml:"version"`
Dependency string `yaml:"dependency"`
Version string `yaml:"version"`
Source string `yaml:"source"`
Description Description `yaml:"description"`
}
type Description string
func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
l := &lock{}
if err := yaml.NewDecoder(r).Decode(&l); err != nil {
@@ -38,15 +49,20 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency
}
var pkgs []ftypes.Package
for name, dep := range l.Packages {
version := dep.Version
if version == "0.0.0" && dep.Source == "sdk" {
version = p.findSDKVersion(l, name, dep)
}
// We would like to exclude dev dependencies, but we cannot identify
// which indirect dependencies were introduced by dev dependencies
// as there are 3 dependency types, "direct main", "direct dev" and "transitive".
// It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies.
// We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev".
pkg := ftypes.Package{
ID: dependency.ID(ftypes.Pub, name, dep.Version),
ID: dependency.ID(ftypes.Pub, name, version),
Name: name,
Version: dep.Version,
Version: version,
Relationship: p.relationship(dep.Dependency),
}
pkgs = append(pkgs, pkg)
@@ -55,6 +71,31 @@ func (p Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency
return pkgs, nil, nil
}
// findSDKVersion detects the first version of the SDK constraint specified in the Description.
// If the constraint is not found, it returns the original version.
func (p Parser) findSDKVersion(l *lock, name string, dep Dep) string {
// Some dependencies use one of the SDK versions.
// In this case dep.Version == `0.0.0`.
// We can't get versions for these dependencies.
// Therefore, we use the first version of the SDK constraint specified in the Description.
// See https://github.com/aquasecurity/trivy/issues/6017
constraint, ok := l.Sdks[string(dep.Description)]
if !ok {
return dep.Version
}
v, err := firstVersionOfConstrain(constraint)
if err != nil {
p.logger.Warn("Unable to get sdk version from constraint", log.Err(err))
return dep.Version
} else if v == "" {
return dep.Version
}
p.logger.Info("Using the first version of the constraint from the sdk source", log.String("dep", name),
log.String("constraint", constraint))
return v
}
func (p Parser) relationship(dep string) ftypes.Relationship {
switch dep {
case directMain, directDev:
@@ -64,3 +105,37 @@ func (p Parser) relationship(dep string) ftypes.Relationship {
}
return ftypes.RelationshipUnknown
}
// firstVersionOfConstrain returns the first acceptable version for constraint
func firstVersionOfConstrain(constraint string) (string, error) {
css, err := goversion.NewConstraints(constraint)
if err != nil {
return "", xerrors.Errorf("unable to parse constraints: %w", err)
}
// Dart uses only `>=` and `^` operators:
// cf. https://dart.dev/tools/pub/dependencies#traditional-syntax
constraints := css.List()
if len(constraints) == 0 || len(constraints[0]) == 0 {
return "", nil
}
// We only need to get the first version from the range
if constraints[0][0].Operator() != ">=" && constraints[0][0].Operator() != "^" {
return "", nil
}
return constraints[0][0].Version(), nil
}
func (d *Description) UnmarshalYAML(value *yaml.Node) error {
var tmp any
if err := value.Decode(&tmp); err != nil {
return err
}
// Description can be a string or a struct
// We only need a string value for SDK mapping
if desc, ok := tmp.(string); ok {
*d = Description(desc)
}
return nil
}

View File

@@ -31,9 +31,9 @@ func TestParser_Parse(t *testing.T) {
Relationship: ftypes.RelationshipDirect,
},
{
ID: "flutter_test@0.0.0",
ID: "flutter_test@3.3.0",
Name: "flutter_test",
Version: "0.0.0",
Version: "3.3.0",
Relationship: ftypes.RelationshipDirect,
},
{

View File

@@ -22,4 +22,4 @@ packages:
version: "3.0.6"
sdks:
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0"
flutter: "^3.3.0"