Skip to content

cyclonedx-gomod creates circular dependency when package depends on older version of itself #720

@Kump3r

Description

@Kump3r

Description

When a Go module depends on two different versions of the same package (e.g., a newer version depends on an older version), cyclonedx-gomod incorrectly merges them into a single component and creates a circular dependency reference instead of creating separate component entries for each version.

Environment

  • cyclonedx-gomod version: v1.9.0
  • Go version: 1.25.0
  • OS: macOS (also likely affects other platforms)
  • Command used:
    cyclonedx-gomod mod -licenses -test -output sbom.yml -output-version "1.4" -verbose=true
    cyclonedx-gomod mod -licenses -test -output sbom.yml -output-version "1.5" -verbose=true
    cyclonedx-gomod mod -licenses -test -output sbom.yml -output-version "1.6" -verbose=true

Steps to Reproduce

  1. Use a Go project that depends on github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26
  2. Note that this version itself depends on github.com/tedsuo/ifrit@v0.0.0-20230330192023-5cba443a66c4 (older version)
  3. Verify with go mod graph:
    go mod graph | grep -E "tedsuo/ifrit.*tedsuo/ifrit"
    Output:
    github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26 github.com/tedsuo/ifrit@v0.0.0-20230330192023-5cba443a66c4
    
  4. Run cyclonedx-gomod with various output versions (1.4, 1.5, 1.6) - all produce the same issue
  5. Examine the generated SBOM

Expected Behavior

The SBOM should contain:

  1. Two separate component entries:

    • Component for pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26
    • Component for pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230330192023-5cba443a66c4
  2. Correct dependency relationship:

    <dependency ref="pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26?type=module">
      <!-- other dependencies -->
      <dependency ref="pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230330192023-5cba443a66c4?type=module"></dependency>
    </dependency>

Actual Behavior

The SBOM only contains:

  1. One component entry (only the newer version):

    • Component for pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26
    • Missing component for the older version
  2. Circular dependency (self-reference):

    <dependency ref="pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26?type=module">
      <dependency ref="pkg:golang/github.com/onsi/ginkgo/v2@v2.27.3?type=module"></dependency>
      <dependency ref="pkg:golang/github.com/onsi/gomega@v1.38.3?type=module"></dependency>
      <!-- This is a circular reference - should point to older version -->
      <dependency ref="pkg:golang/github.com/tedsuo/ifrit@v0.0.0-20230516164442-7862c310ad26?type=module"></dependency>
      <dependency ref="pkg:golang/golang.org/x/net@v0.48.0?type=module"></dependency>
      <dependency ref="pkg:golang/google.golang.org/grpc@v1.78.0?type=module"></dependency>
    </dependency>

Root Cause

The tool appears to be deduplicating packages by name only, without considering version differences. When building the dependency graph, it then creates a reference to the deduplicated component, resulting in a self-reference rather than a reference to the distinct older version.

Impact

  1. SBOM validation failures: SBOMs with circular dependencies may fail validation in some tools
  2. Inaccurate dependency graphs: Downstream tools cannot accurately analyze the true dependency tree
  3. Security scanning issues: Vulnerability scanners may miss issues in the older version since it's not represented as a separate component

Additional Context

This is valid Go module behavior - a package can legitimately depend on an older version of itself. Go's module system handles this correctly via Minimal Version Selection (MVS), and go mod graph correctly shows both versions as distinct nodes in the dependency graph.

The SBOM generator should preserve this version-specific dependency information rather than collapsing it into a single component.

Workaround

Currently, the only workaround is to manually edit the generated SBOM to:

  1. Add the missing component entry for the older version
  2. Update the dependency reference to point to the correct version

However, this must be redone every time the SBOM is regenerated.

Related Issues

I do not know of any related issues at this time, but this may be a common scenario for Go projects that use older versions of dependencies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions