mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-01-31 13:53:14 +08:00
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:
@@ -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
2
go.mod
@@ -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
3
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user