#2479·vuls

fix: merge go.sum into go.mod for Go <1.17 projects

Author: kotakanbeCreated Mar 19, 2026Updated Mar 19, 2026

Summary

For Go projects using Go <1.17, `go.mod` does not include indirect dependencies. Trivy's gomod analyzer is designed to merge adjacent `go.sum` into `go.mod` results to capture these indirect deps. However, this merge has never worked in Vuls because `scanLibraries()` processes each lockfile individually — the fanal PostAnalyzer's filesystem walk cannot find `go.sum` when only `go.mod` is passed.

This is a pre-existing bug (present in master), not a regression from #2476.

Impact

  • Go <1.17 projects: indirect dependencies from `go.sum` are missing from scan results, potentially causing missed vulnerability detections
  • Go 1.17+: no impact (indirect deps are listed in `go.mod`)

Root cause

`scanLibraries()` calls `AnalyzeLibrary()` per file. The fanal gomod PostAnalyzer expects both `go.mod` and `go.sum` to be present in its virtual filesystem, but only the single file being analyzed is copied there.

Fix plan

After #2476 is merged, modify `scanLibraries()` in `scanner/base.go` (and similarly in `scanner/pseudo.go` and `scanner/windows.go`).

Step 1: Buffer go.sum in scanLibraries

In the file processing loop, when a `go.sum` is encountered, save its contents keyed by directory instead of passing to `AnalyzeLibrary`:

```go goSumContents := map[string][]byte{} // dir -> contents

for _, path := range detectFiles { // ... existing path resolution and file reading ...

if filepath.Base(abspath) == "go.sum" {
    dir := filepath.Dir(abspath)
    goSumContents[dir] = contents
    continue
}

// ... existing AnalyzeLibrary call ...

} ```

Step 2: After loop, merge go.sum into go.mod results

```go for i, scanner := range l.LibraryScanners { if scanner.Type != ftypes.GoModule { continue } if !lessThanGo117(scanner) { continue } dir := filepath.Dir(scanner.LockfilePath) sumContents, ok := goSumContents[dir] if !ok { continue } mergeGoSum(&l.LibraryScanners[i], sumContents) } ```

Step 3: Implement lessThanGo117

Reference: `pkg/fanal/analyzer/language/golang/mod/mod.go` line 268-276

```go func lessThanGo117(scanner models.LibraryScanner) bool { // In Go 1.17+, go.mod includes "// indirect" comments which Trivy // parses into Relationship=RelationshipIndirect. If none are found, // the project is likely Go <1.17. for _, lib := range scanner.Libs { if lib.Indirect { return false } } return true } ```

Note: `models.Library` currently does not have an `Indirect` field. This needs to be added, or the check needs to use the raw `ftypes.Package.Relationship` before conversion to `models.Library`. The simpler approach is to check at the `ftypes.Application` level before `convertLibWithScanner`.

Step 4: Implement mergeGoSum

Reference: `pkg/fanal/analyzer/language/golang/mod/mod.go` line 279-302

```go func mergeGoSum(scanner *models.LibraryScanner, sumContents []byte) { sumParser := sum.NewParser() r := xio.NopCloser(bytes.NewReader(sumContents)) sumPkgs, _, err := sumParser.Parse(context.Background(), r) if err != nil { return // best effort }

existing := map[string]bool{}
for _, lib := range scanner.Libs {
    existing[lib.Name] = true
}

for _, pkg := range sumPkgs {
    if existing[pkg.Name] {
        continue
    }
    scanner.Libs = append(scanner.Libs, models.Library{
        Name:    pkg.Name,
        Version: pkg.Version,
        PURL:    newPURL(ftypes.GoModule, types.Metadata{}, pkg),
    })
}

} ```

Step 5: Handle file discovery order

go.sum may appear before or after go.mod in the `detectFiles` list. The approach above handles this by:

  1. Buffering ALL go.sum files first (they are skipped in the main loop)
  2. Processing go.mod normally through `AnalyzeLibrary`
  3. After the loop, merging buffered go.sum into matching go.mod results

This requires the dispatch to recognize go.sum (currently returns `parserNone`). Change `dispatch.go` to still return `parserNone` for go.sum, but the `scanLibraries` loop needs to check for go.sum BEFORE calling `detectParserType`.

Reference code locations

  • Trivy `lessThanGo117`: `~/go/pkg/mod/github.com/aquasecurity/[email protected]/pkg/fanal/analyzer/language/golang/mod/mod.go` line 268
  • Trivy `mergeGoSum`: same file, line 279
  • Trivy `sum.NewParser`: `~/go/pkg/mod/github.com/aquasecurity/[email protected]/pkg/dependency/parser/golang/sum/parse.go`
  • Vuls `scanLibraries`: `scanner/base.go` (after #2476 merge)
  • Vuls `dispatch.go`: go.sum case at line ~98
  • Vuls `models/library.go`: `Library` struct (may need `Indirect` field)

Testing

  1. Create a Go 1.16 style go.mod fixture (no `// indirect` comments) + matching go.sum
  2. Verify: with fix, indirect deps from go.sum appear in scan results
  3. Verify: Go 1.17+ go.mod is unaffected (no merge needed)
  4. Add golden test fixtures for both cases
  5. Run `make compare-lockfile` to verify no regressions

Blocked by

  • #2476 (deps: remove fanal framework) — must merge first to avoid conflicts

Discovered during

PR #2476 review — confirmed via `make compare-lockfile` A/B comparison that master has the same behavior.