#5269·syft

java-pom-cataloger reports dependencies from profiles that cannot be active

Author: EljeesCreated Sep 11, 2026Updated Sep 17, 2026

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:

xml
<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-archive

only-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):

go
// 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:

  1. Skip only the impossible and explicitly-off profiles above.
  2. Evaluate activation against the scanning environment, the way mvn would.
  3. 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 from main at 392539d (SchemaVersion 16.1.10, go1.27.0)
  • OS: Ubuntu 24.04 (WSL2)

Generated with Claude Code

https://claude.ai/code/session_015CsUTRwsaFCwavDJTPwEpW