diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 67cd8c135b9d..0ee39bbd3155 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -69,6 +69,16 @@ The vulnerability database will be downloaded anyway. !!! Warning Trivy may skip some dependencies (that were not found on your local machine) when the `--offline-scan` flag is passed. +### empty dependency version +There are cases when Trivy cannot determine the version of dependencies: + +- Unable to determine the version from the parent because the parent is not reachable; +- The dependency uses a [hard requirement][version-requirement] with more than one version. + +In these cases, Trivy uses an empty version for the dependency. + +!!! Warning + Trivy doesn't detect child dependencies for dependencies without a version. ### maven-invoker-plugin Typically, the integration tests directory (`**/[src|target]/it/*/pom.xml`) of [maven-invoker-plugin][maven-invoker-plugin] doesn't contain actual `pom.xml` files and should be skipped to avoid noise. @@ -120,3 +130,4 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [maven-pom-repos]: https://maven.apache.org/settings.html#repositories [sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock [detection-priority]: ../../scanner/vulnerability.md#detection-priority +[version-requirement]: https://maven.apache.org/pom.html#dependency-version-requirement-specification diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index b2e97efb229b..73ebe6b21407 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -6,15 +6,22 @@ import ( "regexp" "slices" "strings" + "sync" "github.com/samber/lo" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/version/doc" ) var ( - varRegexp = regexp.MustCompile(`\${(\S+?)}`) + varRegexp = regexp.MustCompile(`\${(\S+?)}`) + emptyVersionWarn = sync.OnceFunc(func() { + log.WithPrefix("pom").Warn("Dependency version cannot be determined. Child dependencies will not be found.", + // e.g. https://aquasecurity.github.io/trivy/latest/docs/coverage/language/java/#empty-dependency-version + log.String("details", doc.URL("/docs/coverage/language/java/", "empty-dependency-version"))) + }) ) type artifact struct { @@ -42,7 +49,17 @@ func newArtifact(groupID, artifactID, version string, licenses []string, props m } func (a artifact) IsEmpty() bool { - return a.GroupID == "" || a.ArtifactID == "" || a.Version.String() == "" + if a.GroupID == "" || a.ArtifactID == "" { + return true + } + if a.Version.String() == "" { + emptyVersionWarn() + log.WithPrefix("pom").Debug("Dependency version cannot be determined.", + log.String("GroupID", a.GroupID), + log.String("ArtifactID", a.ArtifactID), + ) + } + return false } func (a artifact) Equal(o artifact) bool { diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index 76ca37f2dd05..542abcac3c8a 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -292,6 +292,14 @@ func (p *Parser) resolve(art artifact, rootDepManagement []pomDependency) (analy return *result, nil } + // We can't resolve a dependency without a version. + // So let's just keep this dependency. + if art.Version.String() == "" { + return analysisResult{ + artifact: art, + }, nil + } + p.logger.Debug("Resolving...", log.String("group_id", art.GroupID), log.String("artifact_id", art.ArtifactID), log.String("version", art.Version.String())) pomContent, err := p.tryRepository(art.GroupID, art.ArtifactID, art.Version.String()) diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index 9fa97ffd3672..82604846a4f3 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -809,6 +809,17 @@ func TestPom_Parse(t *testing.T) { Licenses: []string{"Apache 2.0"}, Relationship: ftypes.RelationshipRoot, }, + { + ID: "org.example:example-api", + Name: "org.example:example-api", + Relationship: ftypes.RelationshipDirect, + Locations: []ftypes.Location{ + { + StartLine: 28, + EndLine: 32, + }, + }, + }, }, }, { @@ -1499,6 +1510,30 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + name: "dependency without version", + inputFile: filepath.Join("testdata", "dep-without-version", "pom.xml"), + local: true, + want: []ftypes.Package{ + { + ID: "com.example:dep-without-version:1.0.0", + Name: "com.example:dep-without-version", + Version: "1.0.0", + Relationship: ftypes.RelationshipRoot, + }, + { + ID: "org.example:example-api", + Name: "org.example:example-api", + Relationship: ftypes.RelationshipDirect, + Locations: ftypes.Locations{ + { + StartLine: 19, + EndLine: 22, + }, + }, + }, + }, + }, // [INFO] com.example:root-depManagement-in-parent:jar:1.0.0 // [INFO] \- org.example:example-dependency:jar:2.0.0:compile // [INFO] \- org.example:example-api:jar:1.0.1:compile diff --git a/pkg/dependency/parser/java/pom/testdata/dep-without-version/pom.xml b/pkg/dependency/parser/java/pom/testdata/dep-without-version/pom.xml new file mode 100644 index 000000000000..d7da70dd4735 --- /dev/null +++ b/pkg/dependency/parser/java/pom/testdata/dep-without-version/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + no-parent + Parent not found + + + com.example + wrong-parent + 1.0.0 + + + com.example + dep-without-version + 1.0.0 + + + + org.example + example-api + + + diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index b3aaf43a82cf..ea147a4d1141 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -147,6 +147,17 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { Licenses: []string{"Apache-2.0"}, Relationship: types.RelationshipRoot, }, + { + ID: "org.example:example-api", + Name: "org.example:example-api", + Relationship: types.RelationshipDirect, + Locations: []types.Location{ + { + StartLine: 21, + EndLine: 25, + }, + }, + }, }, }, },