diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go index c0e35269925..8f6987ac9da 100644 --- a/cmd/grype/cli/commands/root.go +++ b/cmd/grype/cli/commands/root.go @@ -359,6 +359,7 @@ func getProviderConfig(opts *options.Grype) pkg.ProviderConfig { Platform: opts.Platform, Name: opts.Name, DefaultImagePullSource: opts.DefaultImagePullSource, + Sources: opts.From, }, SynthesisConfig: pkg.SynthesisConfig{ GenerateMissingCPEs: opts.GenerateMissingCPEs, diff --git a/cmd/grype/cli/options/grype.go b/cmd/grype/cli/options/grype.go index 26a07fb02b9..1db48bfb6c3 100644 --- a/cmd/grype/cli/options/grype.go +++ b/cmd/grype/cli/options/grype.go @@ -2,6 +2,7 @@ package options import ( "fmt" + "strings" "github.com/anchore/clio" "github.com/anchore/grype/grype/match" @@ -34,6 +35,7 @@ type Grype struct { SortBy SortBy `yaml:",inline" json:",inline" mapstructure:",squash"` Name string `yaml:"name" json:"name" mapstructure:"name"` DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` + From []string `yaml:"from" json:"from" mapstructure:"from"` VexDocuments []string `yaml:"vex-documents" json:"vex-documents" mapstructure:"vex-documents"` VexAdd []string `yaml:"vex-add" json:"vex-add" mapstructure:"vex-add"` // GRYPE_VEX_ADD MatchUpstreamKernelHeaders bool `yaml:"match-upstream-kernel-headers" json:"match-upstream-kernel-headers" mapstructure:"match-upstream-kernel-headers"` // Show matches on kernel-headers packages where the match is on kernel upstream instead of marking them as ignored, default=false @@ -149,6 +151,11 @@ func (o *Grype) AddFlags(flags clio.FlagSet) { "an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')", ) + flags.StringArrayVarP(&o.From, + "from", "", + "specify the source behavior to use (e.g. docker, registry, podman, oci-dir, ...)", + ) + flags.StringArrayVarP(&o.VexDocuments, "vex", "", "a list of VEX documents to consider when producing scanning results", @@ -156,6 +163,8 @@ func (o *Grype) AddFlags(flags clio.FlagSet) { } func (o *Grype) PostLoad() error { + o.From = flatten(o.From) + if o.FailOn != "" { failOnSeverity := *o.FailOnSeverity() if failOnSeverity == vulnerability.UnknownSeverity { @@ -207,3 +216,14 @@ func (o Grype) FailOnSeverity() *vulnerability.Severity { severity := vulnerability.ParseSeverity(o.FailOn) return &severity } + +// flatten takes a list of comma-separated entries and returns a flattened list of trimmed values (preserving order) +func flatten(commaSeparatedEntries []string) []string { + var out []string + for _, v := range commaSeparatedEntries { + for _, s := range strings.Split(v, ",") { + out = append(out, strings.TrimSpace(s)) + } + } + return out +} diff --git a/cmd/grype/cli/options/grype_test.go b/cmd/grype/cli/options/grype_test.go new file mode 100644 index 00000000000..38ce29ce519 --- /dev/null +++ b/cmd/grype/cli/options/grype_test.go @@ -0,0 +1,48 @@ +package options + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_flatten(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + name: "single value", + input: []string{"docker"}, + expected: []string{"docker"}, + }, + { + name: "comma-separated values", + input: []string{"docker,registry"}, + expected: []string{"docker", "registry"}, + }, + { + name: "multiple entries with commas", + input: []string{"docker,registry", "podman"}, + expected: []string{"docker", "registry", "podman"}, // preserves order + }, + { + name: "whitespace trimming", + input: []string{" docker , registry "}, + expected: []string{"docker", "registry"}, + }, + { + name: "empty input", + input: []string{}, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := flatten(tt.input) + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/grype/pkg/provider_config.go b/grype/pkg/provider_config.go index d18a5a19015..ebaae4fe094 100644 --- a/grype/pkg/provider_config.go +++ b/grype/pkg/provider_config.go @@ -18,6 +18,7 @@ type SyftProviderConfig struct { Exclusions []string Name string DefaultImagePullSource string + Sources []string } type SynthesisConfig struct { diff --git a/grype/pkg/syft_provider.go b/grype/pkg/syft_provider.go index 7f43aba16e8..118ccf6913b 100644 --- a/grype/pkg/syft_provider.go +++ b/grype/pkg/syft_provider.go @@ -70,11 +70,15 @@ func getSource(userInput string, config ProviderConfig) (source.Source, error) { } } - var sources []string - schemeSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceTags()...) - if schemeSource != "" { - sources = []string{schemeSource} - userInput = newUserInput + // prioritize explicitly specified sources from --from flag + sources := config.Sources + if len(sources) == 0 { + // fallback to extracting from scheme if --from not specified (for backward compatibility) + schemeSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceTags()...) + if schemeSource != "" { + sources = []string{schemeSource} + userInput = newUserInput + } } return syft.GetSource(context.Background(), userInput, syft.DefaultGetSourceConfig(). diff --git a/test/quality/test-db b/test/quality/test-db index 9cde1ec6349..55e5441e74c 100644 --- a/test/quality/test-db +++ b/test/quality/test-db @@ -1 +1 @@ -vulnerability-db_v6.1.1_2025-10-01T01:30:08Z_1759323862.tar.zst +vulnerability-db_v6.1.3_2025-11-01T00:25:05Z_1761981068.tar.zst