Skip to content

fix(go): distinguish direct from indirect dependencies#449

Merged
ruromero merged 3 commits intoguacsec:mainfrom
ruromero:TC-4300
Apr 30, 2026
Merged

fix(go): distinguish direct from indirect dependencies#449
ruromero merged 3 commits intoguacsec:mainfrom
ruromero:TC-4300

Conversation

@ruromero
Copy link
Copy Markdown
Collaborator

@ruromero ruromero commented Apr 29, 2026

Summary

  • Fix Go provider to correctly distinguish direct from indirect dependencies using go mod edit -json parsing, matching the JS client behavior
  • Since Go 1.17, go mod tidy records all transitively-imported modules in go.mod as require entries with // indirect, and go mod graph emits root-level edges for all of them — the Java client was treating all root-level edges as direct dependencies
  • Match JS client empty SBOM structure: ensure "components": [] is always present and "dependencies": [] is empty when all components are filtered out

Changes

GoModulesProvider.java

  • Add getDirectDependencyPaths() — runs go mod edit -json, parses the Require array, returns the set of non-indirect module paths
  • Filter root-level edges in both buildSbomFromGraph() and buildSbomFromList() to only include direct dependencies

CycloneDXSbom.java

  • Clear dependencies list when all components are filtered out (matches JS client)
  • Post-process JSON to ensure "components" key is always present (CycloneDX library omits empty collections)

Test plan

  • Updated expected SBOM fixtures for go_mod_light_no_ignore, go_mod_no_ignore, go_mod_with_ignore, go_mod_with_all_ignore, and msc/golang/mvs_logic
  • Verified fixture parity with JS client test expectations
  • Unit tests (blocked by pre-existing NoClassDefFoundError in cyclonedx-core-java 12.1.0 JPMS module setup — exists on main)

Closes TC-4300
Related to TC-3818

🤖 Generated with Claude Code

Summary by Sourcery

Correct Go module SBOM generation to only include direct dependencies and align CycloneDX JSON output structure with other clients, while updating automation config and documentation.

Bug Fixes:

  • Ensure Go SBOMs treat only non-indirect Go modules as direct dependencies when building both list and graph-based dependency views.
  • Make generated CycloneDX JSON SBOMs always include an empty components array and clear dependencies when all components are filtered out, matching expected consumer behavior.

Enhancements:

  • Add parsing of go mod edit -json output to derive direct dependency module paths for Go projects.
  • Introduce helper utilities to normalize Go module graph entries by stripping version suffixes before filtering.

Build:

  • Reconfigure Dependabot ecosystems and explicitly scope Maven test fixture directories to prevent unintended dependency updates while keeping production Maven dependencies updatable.

Documentation:

  • Add a CONVENTIONS.md documenting project-specific coding standards, structure, testing practices, and dependency management guidelines.

Tests:

  • Update Go SBOM JSON fixtures to reflect the new handling of direct dependencies and empty component sets in generated SBOMs.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 29, 2026

Reviewer's Guide

Adjusts Go SBOM generation to treat only non-indirect go.mod entries as direct dependencies, aligns CycloneDX JSON structure with the JS client, tightens Dependabot configuration, and adds project coding conventions plus updated Go SBOM fixtures.

Sequence diagram for GoModulesProvider SBOM generation with direct dependency detection

sequenceDiagram
    actor Caller
    participant GoModulesProvider
    participant Operations

    Caller->>GoModulesProvider: getDependenciesSbom(manifestPath, buildTree)
    GoModulesProvider->>GoModulesProvider: determineMainModuleVersion(manifestPath.parent)
    GoModulesProvider->>GoModulesProvider: getIgnoredDeps(manifestPath)

    GoModulesProvider->>Operations: runProcessGetOutput(manifestPath.parent, "go mod edit -json")
    Operations-->>GoModulesProvider: goModEditOutput
    GoModulesProvider->>GoModulesProvider: getDirectDependencyPaths(manifestPath)
    GoModulesProvider-->>GoModulesProvider: directDepPaths

    alt matchManifestVersions
        GoModulesProvider->>Operations: runProcessGetOutput(manifestPath.parent, "go mod graph")
        Operations-->>GoModulesProvider: goModulesResult
        GoModulesProvider->>GoModulesProvider: performManifestVersionsCheck(goModulesResult, manifestPath)
    else no_match_manifest_versions
        GoModulesProvider->>Operations: runProcessGetOutput(manifestPath.parent, "go list -m all")
        Operations-->>GoModulesProvider: goModulesResult
    end

    alt buildTree == false
        GoModulesProvider->>GoModulesProvider: buildSbomFromList(goModulesResult, ignoredDeps, directDepPaths)
    else buildTree == true
        GoModulesProvider->>GoModulesProvider: buildSbomFromGraph(goModulesResult, ignoredDeps, manifestPath, directDepPaths)
    end

    GoModulesProvider-->>Caller: Sbom
Loading

Updated class diagram for GoModulesProvider and CycloneDXSbom

classDiagram
    class GoModulesProvider {
      +Sbom getDependenciesSbom(Path manifestPath, boolean buildTree)
      -Sbom buildSbomFromGraph(String goModulesResult, List ignoredDeps, Path manifestPath, Set directDepPaths)
      -Sbom buildSbomFromList(String golangDeps, List ignoredDeps, Set directDepPaths)
      -Set getDirectDependencyPaths(Path manifestPath)
      -static String getModulePath(String graphEntry)
      -void determineMainModuleVersion(Path directory)
      -List getIgnoredDeps(Path manifestPath)
    }

    class CycloneDXSbom {
      -Bom bom
      +Sbom removeIgnoredDepsFromSbom(List refsToIgnore)
      +Sbom addDependency(PackageURL sourceRef, PackageURL targetRef, String scope)
      +String getAsJsonString()
      -String ensureComponentsField(String json)
    }

    class Sbom {
      +void addRoot(PackageURL root, String license)
      +void addDependency(PackageURL sourceRef, PackageURL targetRef, String scope)
      +String getAsJsonString()
    }

    GoModulesProvider --> Sbom : builds
    CycloneDXSbom ..|> Sbom
Loading

File-Level Changes

Change Details Files
Distinguish Go direct vs indirect dependencies using go mod edit -json and filter SBOM edges accordingly.
  • Invoke go mod edit -json from getDependenciesSbom to obtain module metadata and compute a set of direct dependency paths.
  • Parse the Require array from go mod edit -json and collect module paths where Indirect is absent or false.
  • Introduce helper getModulePath() to strip versions from go mod graph entries for path comparison.
  • Update buildSbomFromGraph to skip root-level edges whose child module paths are not in the direct dependency set, while still excluding Go toolchain entries.
  • Update buildSbomFromList to only include dependencies whose module paths are in the direct dependency set, while still respecting ignored dependencies.
src/main/java/io/github/guacsec/trustifyda/providers/GoModulesProvider.java
Normalize generated CycloneDX SBOM JSON to always include components and clear dependencies when all components are filtered out.
  • When all components are removed by ignore rules, reset the BOM dependencies list to an empty list so no dependency references remain.
  • Post-process the JSON returned from BomGeneratorFactory.createJson(...) to ensure a components field is always present, defaulting to an empty array if missing.
  • Fix a typo in the exception message when JSON generation fails.
src/main/java/io/github/guacsec/trustifyda/sbom/CycloneDXSbom.java
Rework Dependabot configuration to correctly map ecosystems and explicitly suppress updates for test fixtures, especially Maven.
  • Adjust the root-level Dependabot ecosystem entries so uv, gomod, gradle, and cargo are correctly assigned to the root directory.
  • Add open-pull-requests-limit: 0 for the root-level cargo entry while still ignoring all dependencies.
  • Replace broad multi-ecosystem fixture suppression blocks with a Maven-only block that lists each Maven test fixture directory explicitly to avoid unintended updates while allowing production Maven deps to be managed.
.github/dependabot.yml
Document repository-specific coding conventions and project structure.
  • Introduce CONVENTIONS.md describing language level, build tooling, formatting rules, naming conventions, file layout, error handling, testing patterns, and dependency management.
  • Clarify how Dependabot suppression for test fixtures is intended to work and when to update .github/dependabot.yml when adding new fixtures.
CONVENTIONS.md
Align Go SBOM fixture expectations with the new direct-dependency semantics and JSON structure.
  • Update expected SBOM JSON fixtures for multiple Go test manifests to reflect filtering out indirect dependencies and the normalized components/dependencies structure.
  • Update the msc/golang/mvs_logic expected SBOM to match JS client parity for dependency graph semantics.
src/test/resources/msc/golang/mvs_logic/expected_sbom_stack_analysis.json
src/test/resources/tst_manifests/golang/go_mod_light_no_ignore/expected_sbom_component_analysis.json
src/test/resources/tst_manifests/golang/go_mod_light_no_ignore/expected_sbom_stack_analysis.json
src/test/resources/tst_manifests/golang/go_mod_no_ignore/expected_sbom_component_analysis.json
src/test/resources/tst_manifests/golang/go_mod_no_ignore/expected_sbom_stack_analysis.json
src/test/resources/tst_manifests/golang/go_mod_with_all_ignore/expected_sbom_component_analysis.json
src/test/resources/tst_manifests/golang/go_mod_with_ignore/expected_sbom_component_analysis.json
src/test/resources/tst_manifests/golang/go_mod_with_ignore/expected_sbom_stack_analysis.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In getDirectDependencyPaths, consider hardening the JSON parsing by checking for missing/null Path or non-boolean Indirect fields and logging/ignoring malformed entries instead of assuming they exist, to avoid NullPointerException/unexpected failures on slightly different go mod edit -json outputs.
  • The ensureComponentsField method re-parses and re-serializes the full JSON string; if possible, it would be cleaner and more efficient to enforce an empty components list directly on the Bom model (or via CycloneDX generator configuration) rather than doing a post-processing pass on the serialized JSON.
  • In ensureComponentsField, errors are silently swallowed by returning the original JSON; consider at least logging the exception at debug level so that failures in this post-processing step can be diagnosed if the components field is unexpectedly missing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `getDirectDependencyPaths`, consider hardening the JSON parsing by checking for missing/null `Path` or non-boolean `Indirect` fields and logging/ignoring malformed entries instead of assuming they exist, to avoid `NullPointerException`/unexpected failures on slightly different `go mod edit -json` outputs.
- The `ensureComponentsField` method re-parses and re-serializes the full JSON string; if possible, it would be cleaner and more efficient to enforce an empty `components` list directly on the `Bom` model (or via CycloneDX generator configuration) rather than doing a post-processing pass on the serialized JSON.
- In `ensureComponentsField`, errors are silently swallowed by returning the original JSON; consider at least logging the exception at debug level so that failures in this post-processing step can be diagnosed if the `components` field is unexpectedly missing.

## Individual Comments

### Comment 1
<location path="CONVENTIONS.md" line_range="22" />
<code_context>
+- **Charset**: UTF-8
+- **License header**: Apache 2.0, automatically injected by Spotless
+- **EditorConfig**: `.editorconfig` enforces formatting rules
+- **Code coverage**: 81% unit test threshold (JaCoCo), 50 mutation threshold (PIT)
+
+## Naming Conventions
</code_context>
<issue_to_address>
**suggestion (typo):** Clarify "50 mutation threshold" (likely intended as "50% mutation threshold").

The phrase "50 mutation threshold (PIT)" is unclear. If you mean mutation coverage, consider "50% mutation threshold (PIT)" or "50% mutation coverage threshold (PIT)" instead.

```suggestion
- **Code coverage**: 81% unit test threshold (JaCoCo), 50% mutation coverage threshold (PIT)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread CONVENTIONS.md Outdated
@ruromero ruromero force-pushed the TC-4300 branch 4 times, most recently from e1853f2 to 075da61 Compare April 30, 2026 09:00
ruromero and others added 2 commits April 30, 2026 11:01
Add go mod edit -json parsing to GoModulesProvider to identify which
dependencies in go.mod are direct vs indirect. Since Go 1.17, go mod
tidy records all transitively-imported modules as require entries with
an // indirect marker, causing go mod graph to emit root-level edges
for all of them. Previously the Java client treated all root-level
edges as direct dependencies, inflating component analysis SBOMs.

The fix adds getDirectDependencyPaths() which runs go mod edit -json
and builds a Set of module paths where Indirect is false/absent. Both
buildSbomFromGraph (stack analysis) and buildSbomFromList (component
analysis) now filter root-level edges to only include direct deps.
Also adds getModulePath() helper to strip version suffixes from go
mod graph entries for comparison with go mod edit -json output.

Implements TC-4300

Assisted-by: Claude Code
When all dependencies are filtered out, the Java client now produces
the same SBOM structure as the JS client: "components": [] is always
present and "dependencies": [] is empty (no orphan root dep entry).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ruben Romero Montes <rromerom@redhat.com>
Comment thread src/main/java/io/github/guacsec/trustifyda/sbom/CycloneDXSbom.java
@ruromero ruromero merged commit e8921c8 into guacsec:main Apr 30, 2026
44 checks passed
@ruromero ruromero deleted the TC-4300 branch April 30, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants