java-pom-cataloger reports dependencies from profiles that cannot be active
What happened
java-pom-cataloger catalogs the dependencies of every <profile> in a pom, without looking at the profile's <activation>. Profiles that cannot be active in any build, and profiles explicitly marked <activeByDefault>false</activeByDefault>, contribute packages to the SBOM.
How to reproduce
A single project pom at the scan root, no jars and no META-INF anywhere:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>probe-app</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>32.1.2-jre</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>never-active</id>
<activation><jdk>[99,)</jdk></activation>
<dependencies>
<dependency>
<groupId>com.example</groupId><artifactId>only-on-jdk99</artifactId><version>9.9.9</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>explicit-off</id>
<activation><activeByDefault>false</activeByDefault></activation>
<dependencies>
<dependency>
<groupId>com.example</groupId><artifactId>off-by-default</artifactId><version>0.0.1</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project>$ syft scan dir:. -q
NAME VERSION TYPE
guava 32.1.2-jre java-archive
off-by-default 0.0.1 java-archive
only-on-jdk99 9.9.9 java-archive
probe-app 1.0.0 java-archiveonly-on-jdk99 requires JDK 99 or later. off-by-default is switched off in the pom. Both are reported.
What I expected
guava and probe-app. A profile requiring a JDK range that no JDK satisfies cannot contribute dependencies to any build, and one marked activeByDefault=false with no other activator is off unless requested by -P.
Where
syft/pkg/cataloger/java/internal/maven/resolver.go (main at 392539d):
// DirectPomDependencies returns all dependencies directly defined in a project, including all defined in profiles.
// This does not resolve any parent or transitive dependencies
func DirectPomDependencies(pom *Project) []Dependency {
dependencies := deref(pom.Dependencies)
for _, profile := range deref(pom.Profiles) {
dependencies = append(dependencies, deref(profile.Dependencies)...)
}
return dependencies
}The comment says "including all defined in profiles", so this looks deliberate, which is why I am raising it as a question rather than asserting a mistake. pomManagedDependencies merges profile <dependencyManagement> the same way; that one seems harmless, since managed entries only supply versions for dependencies that are declared elsewhere.
Why it matters
Inactive-profile dependencies are packages a vulnerability matcher will act on, for software that is not there. This is not a synthetic concern — I found it in a released artifact. xmlsec-2.3.4.jar from Maven Central carries a jdk19-plus profile, so scanning it unpacked reports jakarta.xml.bind-api 2.3.3, jaxb-runtime 2.3.3 and saaj-impl 1.5.3 regardless of the JDK involved, and two of those three are <scope>test</scope> as well. Profiles keyed on <os>, <jdk> or a build property are common in libraries that ship platform-specific or JDK-specific code paths, and by construction at most one of a set of mutually exclusive profiles can apply to a given build.
The trade-off, as I understand it
Being inclusive is defensible: Syft scans an artifact or a tree, not a build invocation, so it cannot know which profile a build would activate, and dropping profile dependencies would lose real ones for anybody who does build with -P. That argues for reporting them, and it is presumably the reason for the current code.
Against it: some profiles are knowably inapplicable from the pom alone, without guessing anything about the build.
So there is a cheap subset that needs no policy decision about profiles in general — skip a profile when its activation makes it impossible or explicitly off:
<activeByDefault>false</activeByDefault>and no other activation element,- a
<jdk>range that no released JDK satisfies.
Anything beyond that (evaluating <os>, <property>, <file>, or honouring activeByDefault=true semantics the way Maven does, where a default profile is suppressed as soon as another profile in the pom activates) is a larger behaviour change, and I would rather hear which direction you want before writing it.
Options as I see them, smallest first:
- Skip only the impossible and explicitly-off profiles above.
- Evaluate activation against the scanning environment, the way
mvnwould. - Keep the current behaviour and document it, so consumers know pom-derived packages include conditional ones.
I am happy to implement whichever you prefer, tests included. Note that my account is at this repository's open pull request limit, so it may arrive as a draft until one of my open PRs is resolved.
Anything else
Independent of #4832 and #5258, which concern META-INF/maven/ embedded poms. The xmlsec numbers above came up while measuring those, but this reproduces on an ordinary project pom with no archive involved.
Environment
syft version: built frommainat 392539d (SchemaVersion 16.1.10, go1.27.0)- OS: Ubuntu 24.04 (WSL2)
Generated with Claude Code
Source: anchore/syft