Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dart): use first version of constraint for dependencies using SDK version #6239

Merged
24 changes: 21 additions & 3 deletions docs/docs/coverage/language/dart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand Down
68 changes: 61 additions & 7 deletions pkg/dependency/parser/dart/pub/parse.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package pub

import (
"strings"

"golang.org/x/xerrors"
"gopkg.in/yaml.v3"

"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"
)

Expand All @@ -16,37 +19,61 @@ 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 {
return nil, nil, xerrors.Errorf("failed to decode pubspec.lock: %w", err)
}
var pkgs []ftypes.Package
for name, dep := range l.Packages {
// 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
version := dep.Version
if version == "0.0.0" && dep.Source == "sdk" {
if constraint, ok := l.Sdks[string(dep.Description)]; ok {
if v := firstVersionOfConstrain(constraint); v != "" {
p.logger.Info("The first version of the constraint from the sdk source was used.", log.String("dep", name), log.String("constraint", constraint))
version = v
}
}
}

// 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)
Expand All @@ -64,3 +91,30 @@ func (p Parser) relationship(dep string) ftypes.Relationship {
}
return ftypes.RelationshipUnknown
}

// firstVersionOfConstrain returns the first acceptable version for constraint
func firstVersionOfConstrain(constraint string) string {
// cf. https://dart.dev/tools/pub/dependencies#traditional-syntax
switch {
case strings.HasPrefix(constraint, ">="):
constraint = strings.TrimPrefix(constraint, ">=")
constraint, _, _ = strings.Cut(constraint, " ")
return constraint
case strings.HasPrefix(constraint, "^"):
return strings.TrimPrefix(constraint, "^")
}
return ""
}
Copy link
Collaborator

@knqyf263 knqyf263 May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to parse the constraints in a more stable way. What if we export constraint and add methods like func (c *Constraint) Version() string and func (c *Constraint) Operator() string in go-version?
https://github.com/aquasecurity/go-version/blob/637058cfe4921e395b56f195224538d7eac61520/pkg/version/constraint.go#L55-L59

type Constraint struct {
	version  Version
	operator string // e.g. "=", ">=", "^"
	operatorFunc operatorFunc
	original string
}

func (c *Constraint) Version() string {
    return c.version
}

func (c *Constraint) Operator() string {
    return c.operator
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!
Created aquasecurity/go-version#6
And updated this PR - 0bbe7c5

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this PR -91c2b1b


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
}
4 changes: 2 additions & 2 deletions pkg/dependency/parser/dart/pub/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
{
Expand Down
2 changes: 1 addition & 1 deletion pkg/dependency/parser/dart/pub/testdata/happy.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"