Add cataloger for Dart pubspec (#3292)

* Add cataloger for Dart pubspec

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>

* capture pubspec specific fields

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Laurent Goderre 2025-05-13 17:51:49 -04:00 committed by GitHub
parent f77d503892
commit 175a6719a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 3520 additions and 97 deletions

View File

@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.30"
JSONSchemaVersion = "16.0.31"
)

View File

@ -77,6 +77,7 @@ func DefaultPackageTaskFactories() Factories {
// language-specific package declared catalogers ///////////////////////////////////////////////////////////////////////////
newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"),
newSimplePackageTaskFactory(dart.NewPubspecLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
newSimplePackageTaskFactory(dart.NewPubspecCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "dart"),
newSimplePackageTaskFactory(elixir.NewMixLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "elixir"),
newSimplePackageTaskFactory(erlang.NewRebarLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang"),
newSimplePackageTaskFactory(erlang.NewOTPCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "erlang", "otp"),

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.30/document",
"$id": "anchore.io/schema/syft/json/16.0.31/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@ -408,6 +408,49 @@
"path"
]
},
"DartPubspec": {
"properties": {
"homepage": {
"type": "string"
},
"repository": {
"type": "string"
},
"documentation": {
"type": "string"
},
"publish_to": {
"type": "string"
},
"environment": {
"$ref": "#/$defs/DartPubspecEnvironment"
},
"platforms": {
"items": {
"type": "string"
},
"type": "array"
},
"ignored_advisories": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"DartPubspecEnvironment": {
"properties": {
"sdk": {
"type": "string"
},
"flutter": {
"type": "string"
}
},
"type": "object"
},
"DartPubspecLockEntry": {
"properties": {
"name": {
@ -1851,6 +1894,9 @@
{
"$ref": "#/$defs/CocoaPodfileLockEntry"
},
{
"$ref": "#/$defs/DartPubspec"
},
{
"$ref": "#/$defs/DartPubspecLockEntry"
},

View File

@ -19,6 +19,7 @@ func Test_OriginatorSupplier(t *testing.T) {
pkg.ConanfileEntry{},
pkg.ConaninfoEntry{},
pkg.DartPubspecLockEntry{},
pkg.DartPubspec{},
pkg.DotnetDepsEntry{},
pkg.DotnetPackagesLockEntry{},
pkg.ELFBinaryPackageNoteJSONPayload{},

View File

@ -16,6 +16,7 @@ func AllTypes() []any {
pkg.ConanV2LockEntry{},
pkg.ConanfileEntry{},
pkg.ConaninfoEntry{},
pkg.DartPubspec{},
pkg.DartPubspecLockEntry{},
pkg.DotnetDepsEntry{},
pkg.DotnetPackagesLockEntry{},

View File

@ -72,6 +72,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"),
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
jsonNames(pkg.DartPubspec{}, "dart-pubspec"),
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
jsonNames(pkg.DpkgArchiveEntry{}, "dpkg-archive-entry"),

View File

@ -13,3 +13,9 @@ func NewPubspecLockCataloger() pkg.Cataloger {
return generic.NewCataloger("dart-pubspec-lock-cataloger").
WithParserByGlobs(parsePubspecLock, "**/pubspec.lock")
}
// NewPubspecCataloger returns a new Dartlang cataloger object base on pubspec files.
func NewPubspecCataloger() pkg.Cataloger {
return generic.NewCataloger("dart-pubspec-cataloger").
WithParserByGlobs(parsePubspec, "**/pubspec.yml", "**/pubspec.yaml")
}

View File

@ -6,15 +6,15 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func TestCataloger_Globs(t *testing.T) {
func TestPubspecLockCataloger_Globs(t *testing.T) {
tests := []struct {
name string
fixture string
expected []string
}{
{
name: "obtain pubspec files",
fixture: "test-fixtures/glob-paths",
name: "obtain pubspec lock files",
fixture: "test-fixtures/glob-paths/lock",
expected: []string{
"src/pubspec.lock",
},
@ -30,3 +30,29 @@ func TestCataloger_Globs(t *testing.T) {
})
}
}
func TestPubspecCataloger_Globs(t *testing.T) {
tests := []struct {
name string
fixture string
expected []string
}{
{
name: "obtain pubspec files",
fixture: "test-fixtures/glob-paths/spec",
expected: []string{
"pubspec.yml",
"pubspec.yaml",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
TestCataloger(t, NewPubspecCataloger())
})
}
}

View File

@ -18,7 +18,7 @@ func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...fil
Name: name,
Version: raw.Version,
Locations: file.NewLocationSet(locations...),
PURL: packageURL(metadata),
PURL: packageURLFromPubspecLock(metadata),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: metadata,
@ -29,7 +29,39 @@ func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...fil
return p
}
func packageURL(m pkg.DartPubspecLockEntry) string {
func newPubspecPackage(raw pubspecPackage, locations ...file.Location) pkg.Package {
var env *pkg.DartPubspecEnvironment
if raw.Environment.SDK != "" || raw.Environment.Flutter != "" {
// this is required only after pubspec v2, but might have been optional before this
env = &pkg.DartPubspecEnvironment{
SDK: raw.Environment.SDK,
Flutter: raw.Environment.Flutter,
}
}
p := pkg.Package{
Name: raw.Name,
Version: raw.Version,
Locations: file.NewLocationSet(locations...),
PURL: packageURLFromPubspec(raw.Name, raw.Version),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspec{
Homepage: raw.Homepage,
Repository: raw.Repository,
Documentation: raw.Documentation,
PublishTo: raw.PublishTo,
Environment: env,
Platforms: raw.Platforms,
IgnoredAdvisories: raw.IgnoredAdvisories,
},
}
p.SetID()
return p
}
func packageURLFromPubspecLock(m pkg.DartPubspecLockEntry) string {
var qualifiers packageurl.Qualifiers
if m.HostedURL != "" {
@ -53,3 +85,16 @@ func packageURL(m pkg.DartPubspecLockEntry) string {
"",
).ToString()
}
func packageURLFromPubspec(name, version string) string {
var qualifiers packageurl.Qualifiers
return packageurl.NewPackageURL(
packageurl.TypePub,
"",
name,
version,
qualifiers,
"",
).ToString()
}

View File

@ -0,0 +1,50 @@
package dart
import (
"context"
"fmt"
"gopkg.in/yaml.v3"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type pubspecPackage struct {
Name string `mapstructure:"name" yaml:"name"`
Version string `mapstructure:"version" yaml:"version"`
Homepage string `mapstructure:"homepage" yaml:"homepage"`
Repository string `mapstructure:"repository" yaml:"repository"`
Documentation string `mapstructure:"documentation" yaml:"documentation"`
PublishTo string `mapstructure:"publish_to" yaml:"publish_to"`
Environment dartPubspecEnvironment `mapstructure:"environment" yaml:"environment"`
Platforms []string `mapstructure:"platforms" yaml:"platforms"`
IgnoredAdvisories []string `mapstructure:"ignored_advisories" yaml:"ignored_advisories"`
}
type dartPubspecEnvironment struct {
SDK string `mapstructure:"sdk" yaml:"sdk"`
Flutter string `mapstructure:"flutter" yaml:"flutter"`
}
func parsePubspec(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
dec := yaml.NewDecoder(reader)
var p pubspecPackage
if err := dec.Decode(&p); err != nil {
return nil, nil, fmt.Errorf("failed to parse pubspec.yml file: %w", err)
}
pkgs = append(pkgs,
newPubspecPackage(
p,
reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
)
return pkgs, nil, nil
}

View File

@ -12,14 +12,21 @@ import (
)
func TestParsePubspecLock(t *testing.T) {
fixture := "test-fixtures/pubspec.lock"
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
expected := []pkg.Package{
tests := []struct {
name string
fixture string
expectedPackages []pkg.Package
expectedRelationships []artifact.Relationship
}{
{
name: "standard pubspec.lock",
fixture: "test-fixtures/pubspec_locks/pubspec.lock",
expectedPackages: []pkg.Package{
{
Name: "ale",
Version: "3.3.0",
PURL: "pkg:pub/ale@3.3.0?hosted_url=pub.hosted.org",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -32,7 +39,7 @@ func TestParsePubspecLock(t *testing.T) {
Name: "analyzer",
Version: "0.40.7",
PURL: "pkg:pub/analyzer@0.40.7",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -44,7 +51,7 @@ func TestParsePubspecLock(t *testing.T) {
Name: "ansicolor",
Version: "1.1.1",
PURL: "pkg:pub/ansicolor@1.1.1",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -56,7 +63,7 @@ func TestParsePubspecLock(t *testing.T) {
Name: "archive",
Version: "2.0.13",
PURL: "pkg:pub/archive@2.0.13",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -68,7 +75,7 @@ func TestParsePubspecLock(t *testing.T) {
Name: "args",
Version: "1.6.0",
PURL: "pkg:pub/args@1.6.0",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -80,7 +87,7 @@ func TestParsePubspecLock(t *testing.T) {
Name: "flutter",
Version: "3.24.5",
PURL: "pkg:pub/flutter@3.24.5",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -92,7 +99,7 @@ func TestParsePubspecLock(t *testing.T) {
Name: "key_binder",
Version: "1.11.20",
PURL: "pkg:pub/key_binder@1.11.20?vcs_url=git%40github.com%3AWorkiva%2Fkey_binder.git%403f7b3a6350e73c7dcac45301c0e18fbd42af02f7",
Locations: fixtureLocationSet,
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspec_locks/pubspec.lock")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspecLockEntry{
@ -101,12 +108,16 @@ func TestParsePubspecLock(t *testing.T) {
VcsURL: "git@github.com:Workiva/key_binder.git@3f7b3a6350e73c7dcac45301c0e18fbd42af02f7",
},
},
},
expectedRelationships: nil,
},
}
// TODO: relationships are not under test
var expectedRelationships []artifact.Relationship
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.TestFileParser(t, test.fixture, parsePubspecLock, test.expectedPackages, test.expectedRelationships)
})
}
}
func Test_corruptPubspecLock(t *testing.T) {

View File

@ -0,0 +1,70 @@
package dart
import (
"testing"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func TestParsePubspec(t *testing.T) {
tests := []struct {
name string
fixture string
expectedPackages []pkg.Package
expectedRelationships []artifact.Relationship
}{
{
name: "_macros",
fixture: "test-fixtures/pubspecs/macros.pubspec.yaml",
expectedPackages: []pkg.Package{
{
Name: "_macros",
Version: "0.3.2",
PURL: "pkg:pub/_macros@0.3.2",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspecs/macros.pubspec.yaml")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspec{
Repository: "https://github.com/dart-lang/sdk/tree/main/pkg/_macros",
PublishTo: "none",
Environment: &pkg.DartPubspecEnvironment{
SDK: "^3.4.0-256.0.dev",
},
},
},
},
expectedRelationships: nil,
},
{
name: "_macros",
fixture: "test-fixtures/pubspecs/appainter.pubspec.yaml",
expectedPackages: []pkg.Package{
{
Name: "appainter",
Version: "2.4.8",
PURL: "pkg:pub/appainter@2.4.8",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/pubspecs/appainter.pubspec.yaml")),
Language: pkg.Dart,
Type: pkg.DartPubPkg,
Metadata: pkg.DartPubspec{
PublishTo: "none",
Environment: &pkg.DartPubspecEnvironment{
SDK: ">=3.0.0 <4.0.0",
Flutter: "3.29.3",
},
},
},
},
expectedRelationships: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.TestFileParser(t, test.fixture, parsePubspec, test.expectedPackages, test.expectedRelationships)
})
}
}

View File

@ -0,0 +1 @@
bogus pubspec.yml

View File

@ -0,0 +1 @@
bogus pubspec.yml

View File

@ -0,0 +1,75 @@
name: appainter
description: A material theme editor and generator for Flutter to configure and
preview the overall visual theme of your material app
publish_to: "none"
version: 2.4.8
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: 3.29.3
dependencies:
appainter_annotations:
path: packages/annotations
bloc: 9.0.0
collection: ^1.17.2
copy_with_extension: 6.0.1
cupertino_icons: 1.0.8
device_preview_plus: 2.3.5
dio: 5.8.0+1
dropdown_search: 6.0.2
enum_to_string: 2.2.1
equatable: 2.0.7
expandable: 5.0.1
file_picker: 10.1.2
firebase_analytics: 11.4.5
firebase_auth: 5.5.3
firebase_core: 3.13.0
flex_color_picker: 3.7.1
flutter:
sdk: flutter
flutter_bloc: 9.1.1
flutter_markdown: 0.7.7
google_fonts: 6.2.1
intl: 0.19.0
json_theme: 8.0.0
material_color_utilities: ^0.11.1
material_design_icons_flutter: 7.0.7296
ndialog: 4.4.1+1
path_provider: 2.1.5
pretty_json: 2.0.0
random_color_scheme: 0.1.4
sentry_flutter: 8.14.2
shared_preferences: 2.5.3
universal_html: 2.2.4
universal_io: 2.2.2
url_launcher: 6.3.1
window_manager: 0.4.3
# have comments!
dev_dependencies:
appainter_builder:
path: packages/builder
bloc_test: 10.0.0
build_runner: 2.4.15
copy_with_extension_gen: 6.0.1
flutter_lints: 5.0.0
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
mocktail: 1.0.4
path_provider_platform_interface: 2.1.2
remove_from_coverage: 2.0.0
sentry_dart_plugin: 2.4.1
flutter:
uses-material-design: true
assets:
- assets/icon.png
sentry:
upload_native_symbols: true
upload_source_maps: true
include_native_sources: true
commits: false

View File

@ -0,0 +1,16 @@
name: _macros
version: 0.3.2
description: >-
This is a private SDK vendored package, which is re-exported by the public
`macros` package, which is a pub package. Every change to this package is
treated as a release, see CONTRIBUTING.md for full instructions.
publish_to: none
repository: https://github.com/dart-lang/sdk/tree/main/pkg/_macros
environment:
sdk: ^3.4.0-256.0.dev
# Note that as an SDK vendored package, pub package dependencies are only
# allowed in the dev_dependencies section.
dev_dependencies:
test: any

View File

@ -7,3 +7,19 @@ type DartPubspecLockEntry struct {
HostedURL string `mapstructure:"hosted_url" json:"hosted_url,omitempty"`
VcsURL string `mapstructure:"vcs_url" json:"vcs_url,omitempty"`
}
// DartPubspec is a struct that represents a package described in a pubspec.yaml file
type DartPubspec struct {
Homepage string `mapstructure:"homepage" json:"homepage,omitempty"`
Repository string `mapstructure:"repository" json:"repository,omitempty"`
Documentation string `mapstructure:"documentation" json:"documentation,omitempty"`
PublishTo string `mapstructure:"publish_to" json:"publish_to,omitempty"`
Environment *DartPubspecEnvironment `mapstructure:"environment" json:"environment,omitempty"`
Platforms []string `mapstructure:"platforms" json:"platforms,omitempty"`
IgnoredAdvisories []string `mapstructure:"ignored_advisories" json:"ignored_advisories,omitempty"`
}
type DartPubspecEnvironment struct {
SDK string `mapstructure:"sdk" json:"sdk,omitempty"`
Flutter string `mapstructure:"flutter" json:"flutter,omitempty"`
}