From 786e2fffd43f7b6f98bddb7e804abc98df98d86c Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Tue, 2 Dec 2025 11:04:38 +1300 Subject: [PATCH 01/10] test: add `transitive.xml` case to `pomlxml` extractor --- .../language/java/pomxml/pomxml_test.go | 44 +++++++++++++++++++ .../java/pomxml/testdata/transitive.xml | 33 ++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 extractor/filesystem/language/java/pomxml/testdata/transitive.xml diff --git a/extractor/filesystem/language/java/pomxml/pomxml_test.go b/extractor/filesystem/language/java/pomxml/pomxml_test.go index e4710dc39..5d6d3ad26 100644 --- a/extractor/filesystem/language/java/pomxml/pomxml_test.go +++ b/extractor/filesystem/language/java/pomxml/pomxml_test.go @@ -336,6 +336,50 @@ func TestExtractor_Extract(t *testing.T) { }, }, }, + { + Name: "transitive dependencies", + InputConfig: extracttest.ScanInputMockConfig{ + Path: "testdata/transitive.xml", + }, + WantPackages: []*extractor.Package{ + { + Name: "org.direct:alice", + Version: "1.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "alice", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.direct:bob", + Version: "2.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "bob", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.direct:chris", + Version: "3.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "chris", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + }, + }, } for _, tt := range tests { diff --git a/extractor/filesystem/language/java/pomxml/testdata/transitive.xml b/extractor/filesystem/language/java/pomxml/testdata/transitive.xml new file mode 100644 index 000000000..52e416a0b --- /dev/null +++ b/extractor/filesystem/language/java/pomxml/testdata/transitive.xml @@ -0,0 +1,33 @@ + + com.mycompany.app + my-app + 1.0 + + + + + org.transitive + frank + 4.4.4 + + + + + + + org.direct + alice + 1.0.0 + + + org.direct + bob + 2.0.0 + + + org.direct + chris + 3.0.0 + + + From 11726766775ea5db0fdd152d3cff5ca9a04e4be9 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:30:33 +1300 Subject: [PATCH 02/10] feat: lets just reimplement the extractor logic in the enricher --- .../transitivedependency/pomxml/pomxml.go | 329 ++++++++++++++++++ .../pomxml/pomxml_test.go | 268 ++++++++++++++ .../pomxml/testdata/transitive.xml | 33 ++ .../testdata/universe/basic-universe.yaml | 60 ++++ .../language/java/javalockfile/metadata.go | 3 + .../language/java/pomxmlnet/pomxmlnet.go | 3 + 6 files changed, 696 insertions(+) create mode 100644 enricher/transitivedependency/pomxml/pomxml.go create mode 100644 enricher/transitivedependency/pomxml/pomxml_test.go create mode 100644 enricher/transitivedependency/pomxml/testdata/transitive.xml create mode 100644 enricher/transitivedependency/pomxml/testdata/universe/basic-universe.yaml diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go new file mode 100644 index 000000000..43f28e4f6 --- /dev/null +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -0,0 +1,329 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package pomxml implements an enricher to perform dependency resolution for Java pom.xml files. +package pomxml + +import ( + "context" + "fmt" + "maps" + "slices" + "strings" + + "deps.dev/util/maven" + "deps.dev/util/resolve" + mavenresolve "deps.dev/util/resolve/maven" + "github.com/google/osv-scalibr/clients/datasource" + "github.com/google/osv-scalibr/clients/resolution" + "github.com/google/osv-scalibr/enricher" + "github.com/google/osv-scalibr/extractor" + "github.com/google/osv-scalibr/extractor/filesystem" + "github.com/google/osv-scalibr/extractor/filesystem/language/java/javalockfile" + "github.com/google/osv-scalibr/extractor/filesystem/language/java/pomxml" + "github.com/google/osv-scalibr/internal/mavenutil" + "github.com/google/osv-scalibr/inventory" + "github.com/google/osv-scalibr/log" + "github.com/google/osv-scalibr/plugin" + "github.com/google/osv-scalibr/purl" +) + +const ( + // Name is the unique name of this enricher. + Name = "transitivedependency/pomxml" +) + +// Enricher performs dependency resolution for pom.xml. +type Enricher struct { + depClient resolve.Client + MavenClient *datasource.MavenRegistryAPIClient +} + +// Name returns the name of the enricher. +func (Enricher) Name() string { + return Name +} + +// Version returns the version of the enricher. +func (Enricher) Version() int { + return 0 +} + +// Requirements returns the requirements of the enricher. +func (Enricher) Requirements() *plugin.Capabilities { + return &plugin.Capabilities{ + Network: plugin.NetworkOnline, + } +} + +// RequiredPlugins returns the names of the plugins required by the enricher. +func (Enricher) RequiredPlugins() []string { + return []string{pomxml.Name} +} + +// Config is the configuration for the pomxmlnet Extractor. +type Config struct { + *datasource.MavenRegistryAPIClient + + DependencyClient resolve.Client +} + +// NewConfig returns the configuration given the URL of the Maven registry to fetch metadata. +func NewConfig(remote, local string, disableGoogleAuth bool) Config { + // No need to check errors since we are using the default Maven Central URL. + mavenClient, _ := datasource.NewMavenRegistryAPIClient(context.Background(), datasource.MavenRegistry{ + URL: remote, + ReleasesEnabled: true, + }, local, disableGoogleAuth) + depClient := resolution.NewMavenRegistryClientWithAPI(mavenClient) + return Config{ + DependencyClient: depClient, + MavenRegistryAPIClient: mavenClient, + } +} + +// DefaultConfig returns the default configuration for the pomxmlnet extractor. +func DefaultConfig() Config { + return NewConfig("", "", false) +} + +// New makes a new pom.xml transitive extractor with the given config. +func New(c Config) *Enricher { + return &Enricher{ + depClient: c.DependencyClient, + MavenClient: c.MavenRegistryAPIClient, + } +} + +// NewDefault returns an extractor with the default config settings. +func NewDefault() *Enricher { return New(DefaultConfig()) } + +// packageWithIndex holds the package with its index in inv.Packages +type packageWithIndex struct { + pkg *extractor.Package + index int +} + +// groupPackages groups packages found in pom.xml files by the first location that they are found +// and returns a map of location -> package name -> package with index. +func groupPackages(pkgs []*extractor.Package) map[string]map[string]packageWithIndex { + result := make(map[string]map[string]packageWithIndex) + for i, pkg := range pkgs { + if !slices.Contains(pkg.Plugins, pomxml.Name) { + continue + } + if len(pkg.Locations) == 0 { + log.Warnf("package %s has no locations", pkg.Name) + continue + } + // Use the path where this package is first found. + path := pkg.Locations[0] + if _, ok := result[path]; !ok { + result[path] = make(map[string]packageWithIndex) + } + result[path][pkg.Name] = packageWithIndex{pkg, i} + } + return result +} + +// Enrich enriches the inventory in pom.xml files with transitive dependencies. +func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *inventory.Inventory) error { + pkgGroups := groupPackages(inv.Packages) + + for path, pkgMap := range pkgGroups { + f, err := input.ScanRoot.FS.Open(path) + + if err != nil { + return err + } + + enrichedInv, err := e.extract(ctx, &filesystem.ScanInput{ + Path: path, + Reader: f, + Info: nil, + FS: input.ScanRoot.FS, + Root: input.ScanRoot.Path, + }) + + if err != nil { + return err + } + + for _, pkg := range enrichedInv.Packages { + indexPkg, ok := pkgMap[pkg.Name] + if ok { + // This dependency is in manifest, update the version and plugins. + i := indexPkg.index + inv.Packages[i].Version = pkg.Version + inv.Packages[i].Plugins = append(inv.Packages[i].Plugins, Name) + } else { + // This dependency is not found in manifest, so it's a transitive dependency. + inv.Packages = append(inv.Packages, pkg) + } + } + } + + return nil +} + +func (e Enricher) extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { + var project maven.Project + if err := datasource.NewMavenDecoder(input.Reader).Decode(&project); err != nil { + return inventory.Inventory{}, fmt.Errorf("could not extract: %w", err) + } + // Empty JDK and ActivationOS indicates merging the default profiles. + if err := project.MergeProfiles("", maven.ActivationOS{}); err != nil { + return inventory.Inventory{}, fmt.Errorf("failed to merge profiles: %w", err) + } + // Interpolate the repositories so that properties are resolved. + if err := project.InterpolateRepositories(); err != nil { + return inventory.Inventory{}, fmt.Errorf("failed to interpolate project: %w", err) + } + // Clear the registries that may be from other extraction. + e.MavenClient = e.MavenClient.WithoutRegistries() + for _, repo := range project.Repositories { + if repo.URL.ContainsProperty() { + continue + } + if err := e.MavenClient.AddRegistry(ctx, datasource.MavenRegistry{ + URL: string(repo.URL), + ID: string(repo.ID), + ReleasesEnabled: repo.Releases.Enabled.Boolean(), + SnapshotsEnabled: repo.Snapshots.Enabled.Boolean(), + }); err != nil { + return inventory.Inventory{}, fmt.Errorf("failed to add registry %s: %w", repo.URL, err) + } + } + // Merging parents data by parsing local parent pom.xml or fetching from upstream. + if err := mavenutil.MergeParents(ctx, project.Parent, &project, mavenutil.Options{ + Input: input, + Client: e.MavenClient, + AddRegistry: true, + AllowLocal: true, + InitialParentIndex: 1, + }); err != nil { + return inventory.Inventory{}, fmt.Errorf("failed to merge parents: %w", err) + } + // Process the dependencies: + // - dedupe dependencies and dependency management + // - import dependency management + // - fill in missing dependency version requirement + project.ProcessDependencies(func(groupID, artifactID, version maven.String) (maven.DependencyManagement, error) { + return mavenutil.GetDependencyManagement(ctx, e.MavenClient, groupID, artifactID, version) + }) + + registries := e.MavenClient.GetRegistries() + + if registries := e.MavenClient.GetRegistries(); len(registries) > 0 { + clientRegs := make([]resolution.Registry, len(registries)) + for i, reg := range registries { + clientRegs[i] = reg + } + if cl, ok := e.depClient.(resolution.ClientWithRegistries); ok { + if err := cl.AddRegistries(ctx, clientRegs); err != nil { + return inventory.Inventory{}, err + } + } + } + + overrideClient := resolution.NewOverrideClient(e.depClient) + resolver := mavenresolve.NewResolver(overrideClient) + + // Resolve the dependencies. + root := resolve.Version{ + VersionKey: resolve.VersionKey{ + PackageKey: resolve.PackageKey{ + System: resolve.Maven, + Name: project.ProjectKey.Name(), + }, + VersionType: resolve.Concrete, + Version: string(project.Version), + }} + reqs := make([]resolve.RequirementVersion, len(project.Dependencies)+len(project.DependencyManagement.Dependencies)) + for i, d := range project.Dependencies { + reqs[i] = resolve.RequirementVersion{ + VersionKey: resolve.VersionKey{ + PackageKey: resolve.PackageKey{ + System: resolve.Maven, + Name: d.Name(), + }, + VersionType: resolve.Requirement, + Version: string(d.Version), + }, + Type: resolve.MavenDepType(d, ""), + } + } + for i, d := range project.DependencyManagement.Dependencies { + reqs[len(project.Dependencies)+i] = resolve.RequirementVersion{ + VersionKey: resolve.VersionKey{ + PackageKey: resolve.PackageKey{ + System: resolve.Maven, + Name: d.Name(), + }, + VersionType: resolve.Requirement, + Version: string(d.Version), + }, + Type: resolve.MavenDepType(d, mavenutil.OriginManagement), + } + } + overrideClient.AddVersion(root, reqs) + + g, err := resolver.Resolve(ctx, root.VersionKey) + if err != nil { + return inventory.Inventory{}, fmt.Errorf("failed resolving %v: %w", root, err) + } + if len(g.Nodes) <= 1 && g.Error != "" { + // Multi-registry error may be appended to the resolved graph so only return error when the graph is empty. + return inventory.Inventory{}, fmt.Errorf("failed resolving %v: %s", root, g.Error) + } + + details := map[string]*extractor.Package{} + for i := 1; i < len(g.Nodes); i++ { + // Ignore the first node which is the root. + node := g.Nodes[i] + depGroups := []string{} + groupID, artifactID, _ := strings.Cut(node.Version.Name, ":") + // We are only able to know dependency groups of direct dependencies but + // not transitive dependencies because the nodes in the resolve graph does + // not have the scope information. + isDirect := false + for _, dep := range project.Dependencies { + if dep.Name() != node.Version.Name { + continue + } + isDirect = true + if dep.Scope != "" && dep.Scope != "compile" { + depGroups = append(depGroups, string(dep.Scope)) + } + break + } + pkg := extractor.Package{ + Name: node.Version.Name, + Version: node.Version.Version, + PURLType: purl.TypeMaven, + Metadata: &javalockfile.Metadata{ + ArtifactID: artifactID, + GroupID: groupID, + DepGroupVals: depGroups, + IsTransitive: !isDirect, + Registries: registries, + }, + // TODO(#408): Add merged paths in here as well + Locations: []string{input.Path}, + } + details[pkg.Name] = &pkg + } + + return inventory.Inventory{Packages: slices.Collect(maps.Values(details))}, nil +} diff --git a/enricher/transitivedependency/pomxml/pomxml_test.go b/enricher/transitivedependency/pomxml/pomxml_test.go new file mode 100644 index 000000000..72f6b9767 --- /dev/null +++ b/enricher/transitivedependency/pomxml/pomxml_test.go @@ -0,0 +1,268 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pomxml_test + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/osv-scalibr/clients/clienttest" + "github.com/google/osv-scalibr/clients/datasource" + "github.com/google/osv-scalibr/enricher" + "github.com/google/osv-scalibr/enricher/transitivedependency/pomxml" + "github.com/google/osv-scalibr/extractor" + "github.com/google/osv-scalibr/extractor/filesystem/language/java/javalockfile" + scalibrfs "github.com/google/osv-scalibr/fs" + "github.com/google/osv-scalibr/inventory" + "github.com/google/osv-scalibr/purl" +) + +func TestEnricher_Enrich(t *testing.T) { + input := enricher.ScanInput{ + ScanRoot: &scalibrfs.ScanRoot{ + Path: "testdata", + FS: scalibrfs.DirFS("."), + }, + } + inv := inventory.Inventory{ + Packages: []*extractor.Package{ + { + // Not a Java package. + Name: "abc", + Version: "1.0.0", + PURLType: purl.TypePyPi, + Locations: []string{"testdata/poetry/poetry.lock"}, + Plugins: []string{"python/poetrylock"}, + }, + { + // Not extracted from a pom.xml + Name: "abc", + Version: "1.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/java/gradle.lockfile"}, + Plugins: []string{"java/gradlelockfile"}, + }, + { + Name: "org.direct:alice", + Version: "1.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"java/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "alice", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.direct:bob", + Version: "2.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"java/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "bob", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.direct:chris", + Version: "3.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"java/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "chris", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + }, + } + + srv := clienttest.NewMockHTTPServer(t) + srv.SetResponse(t, "org/upstream/parent-pom/1.0/parent-pom-1.0.pom", []byte(` + + org.upstream + parent-pom + 1.0 + pom + + + org.eve + eve + 5.0.0 + + + + `)) + srv.SetResponse(t, "org/import/import/1.2.3/import-1.2.3.pom", []byte(` + + org.import + import + 1.2.3 + pom + + + + org.frank + frank + 6.0.0 + + + + + `)) + + apiClient, err := datasource.NewDefaultMavenRegistryAPIClient(t.Context(), srv.URL) + if err != nil { + t.Fatalf("%v", err) + } + + resolutionClient := clienttest.NewMockResolutionClient(t, "testdata/universe/basic-universe.yaml") + + enrichy := pomxml.New(pomxml.Config{ + DependencyClient: resolutionClient, + MavenRegistryAPIClient: apiClient, + }) + + err = enrichy.Enrich(t.Context(), &input, &inv) + if err != nil { + t.Fatalf("failed to enrich: %v", err) + } + + wantInventory := inventory.Inventory{ + Packages: []*extractor.Package{ + { + // Not a Java package. + Name: "abc", + Version: "1.0.0", + PURLType: purl.TypePyPi, + Locations: []string{"testdata/poetry/poetry.lock"}, + Plugins: []string{"python/poetrylock"}, + }, + { + // Not extracted from a pom.xml + Name: "abc", + Version: "1.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/java/gradle.lockfile"}, + Plugins: []string{"java/gradlelockfile"}, + }, + { + Name: "org.direct:alice", + Version: "1.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"java/pomxml", "transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "alice", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.direct:bob", + Version: "2.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"java/pomxml", "transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "bob", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.direct:chris", + Version: "3.0.0", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"java/pomxml", "transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "chris", + GroupID: "org.direct", + IsTransitive: false, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.transitive:chuck", + Version: "1.1.1", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "chuck", + GroupID: "org.transitive", + IsTransitive: true, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.transitive:dave", + Version: "2.2.2", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "dave", + GroupID: "org.transitive", + IsTransitive: true, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.transitive:eve", + Version: "3.3.3", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "eve", + GroupID: "org.transitive", + IsTransitive: true, + DepGroupVals: []string{}, + }, + }, + { + Name: "org.transitive:frank", + Version: "4.4.4", + PURLType: purl.TypeMaven, + Locations: []string{"testdata/transitive.xml"}, + Plugins: []string{"transitivedependency/pomxml"}, + Metadata: &javalockfile.Metadata{ + ArtifactID: "frank", + GroupID: "org.transitive", + IsTransitive: true, + DepGroupVals: []string{}, + }, + }, + }, + } + sort.Slice(inv.Packages, func(i, j int) bool { + return inv.Packages[i].Name < inv.Packages[j].Name + }) + if diff := cmp.Diff(wantInventory, inv); diff != "" { + t.Errorf("%s.Enrich() diff (-want +got):\n%s", enrichy.Name(), diff) + } +} diff --git a/enricher/transitivedependency/pomxml/testdata/transitive.xml b/enricher/transitivedependency/pomxml/testdata/transitive.xml new file mode 100644 index 000000000..52e416a0b --- /dev/null +++ b/enricher/transitivedependency/pomxml/testdata/transitive.xml @@ -0,0 +1,33 @@ + + com.mycompany.app + my-app + 1.0 + + + + + org.transitive + frank + 4.4.4 + + + + + + + org.direct + alice + 1.0.0 + + + org.direct + bob + 2.0.0 + + + org.direct + chris + 3.0.0 + + + diff --git a/enricher/transitivedependency/pomxml/testdata/universe/basic-universe.yaml b/enricher/transitivedependency/pomxml/testdata/universe/basic-universe.yaml new file mode 100644 index 000000000..2bf2b3272 --- /dev/null +++ b/enricher/transitivedependency/pomxml/testdata/universe/basic-universe.yaml @@ -0,0 +1,60 @@ +system: maven +schema: | + com.google.code.findbugs:jsr305 + 3.0.2 + io.netty:netty-all + 4.1.9 + 4.1.42.Final + junit:junit + 4.12 + org.alice:alice + 1.0.0 + org.apache.maven:maven-artifact + 1.0.0 + org.bob:bob + 2.0.0 + org.chuck:chuck + 3.0.0 + org.dave:dave + 4.0.0 + org.direct:alice + 1.0.0 + org.transitive:chuck@1.1.1 + org.transitive:dave@2.2.2 + org.direct:bob + 2.0.0 + org.transitive:eve@3.3.3 + org.direct:chris + 3.0.0 + org.transitive:frank@3.3.3 + org.eve:eve + 5.0.0 + org.frank:frank + 6.0.0 + org.mine:my.package + 2.3.4 + org.mine:mypackage + 1.0.0 + org.mine:ranged-package + 9.4.35 + 9.4.36 + 9.4.37 + 9.5 + org.slf4j:slf4j-log4j12 + 1.7.25 + org.transitive:chuck + 1.1.1 + 2.2.2 + org.transitive:eve@2.2.2 + 3.3.3 + org.transitive:dave + 1.1.1 + 2.2.2 + 3.3.3 + org.transitive:eve + 1.1.1 + 2.2.2 + 3.3.3 + org.transitive:frank + 3.3.3 + 4.4.4 diff --git a/extractor/filesystem/language/java/javalockfile/metadata.go b/extractor/filesystem/language/java/javalockfile/metadata.go index 962d5f2a2..357dd76b6 100644 --- a/extractor/filesystem/language/java/javalockfile/metadata.go +++ b/extractor/filesystem/language/java/javalockfile/metadata.go @@ -15,6 +15,8 @@ // Package javalockfile provides shared structures for Java extractors. package javalockfile +import "github.com/google/osv-scalibr/clients/datasource" + // Metadata holds parsing information for a Java package. type Metadata struct { ArtifactID string @@ -23,6 +25,7 @@ type Metadata struct { Classifier string DepGroupVals []string IsTransitive bool // Only set in pomxmlnet extractor + Registries []datasource.MavenRegistry } // DepGroups returns the dependency groups for the package. diff --git a/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go b/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go index d43cca960..0e8b1004e 100644 --- a/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go +++ b/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go @@ -136,6 +136,8 @@ func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (in return mavenutil.GetDependencyManagement(ctx, e.MavenClient, groupID, artifactID, version) }) + registries := e.MavenClient.GetRegistries() + if registries := e.MavenClient.GetRegistries(); len(registries) > 0 { clientRegs := make([]resolution.Registry, len(registries)) for i, reg := range registries { @@ -228,6 +230,7 @@ func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (in GroupID: groupID, DepGroupVals: depGroups, IsTransitive: !isDirect, + Registries: registries, }, // TODO(#408): Add merged paths in here as well Locations: []string{input.Path}, From 0f072b98d2b1142a70d06fc6236dae4e013fff0d Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:46:26 +1300 Subject: [PATCH 03/10] fix: include the plugin name --- enricher/transitivedependency/pomxml/pomxml.go | 1 + 1 file changed, 1 insertion(+) diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index 43f28e4f6..ca6352503 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -321,6 +321,7 @@ func (e Enricher) extract(ctx context.Context, input *filesystem.ScanInput) (inv }, // TODO(#408): Add merged paths in here as well Locations: []string{input.Path}, + Plugins: []string{Name}, } details[pkg.Name] = &pkg } From a4dbe45a8c38ec7c77deda0e99e02d46ec951eb3 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:48:18 +1300 Subject: [PATCH 04/10] fix: require direct fs --- enricher/transitivedependency/pomxml/pomxml.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index ca6352503..d719eb8ad 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -63,7 +63,8 @@ func (Enricher) Version() int { // Requirements returns the requirements of the enricher. func (Enricher) Requirements() *plugin.Capabilities { return &plugin.Capabilities{ - Network: plugin.NetworkOnline, + Network: plugin.NetworkOnline, + DirectFS: true, } } From 6e8a1dee12eb8082add0435da113eb4560c13c50 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:51:40 +1300 Subject: [PATCH 05/10] fix: register enricher --- enricher/enricherlist/list.go | 2 ++ enricher/transitivedependency/pomxml/pomxml.go | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/enricher/enricherlist/list.go b/enricher/enricherlist/list.go index 8c75f5e4c..91ebd4385 100644 --- a/enricher/enricherlist/list.go +++ b/enricher/enricherlist/list.go @@ -30,6 +30,7 @@ import ( "github.com/google/osv-scalibr/enricher/reachability/java" "github.com/google/osv-scalibr/enricher/secrets/convert" "github.com/google/osv-scalibr/enricher/secrets/hashicorp" + "github.com/google/osv-scalibr/enricher/transitivedependency/pomxml" "github.com/google/osv-scalibr/enricher/transitivedependency/requirements" "github.com/google/osv-scalibr/enricher/vex/filter" "github.com/google/osv-scalibr/enricher/vulnmatch/osvdev" @@ -138,6 +139,7 @@ var ( // TransitiveDependency enrichers. TransitiveDependency = InitMap{ requirements.Name: {requirements.New}, + pomxml.Name: {noCFG(pomxml.NewDefault)}, } // PackageDeprecation enricher. diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index d719eb8ad..6c9642f62 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -108,7 +108,9 @@ func New(c Config) *Enricher { } // NewDefault returns an extractor with the default config settings. -func NewDefault() *Enricher { return New(DefaultConfig()) } +func NewDefault() enricher.Enricher { + return *New(DefaultConfig()) +} // packageWithIndex holds the package with its index in inv.Packages type packageWithIndex struct { From a84898c213e393141205d46aff5149ecb812170d Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:29:43 +1300 Subject: [PATCH 06/10] fix: we don't need to track Maven registries in the metadata --- enricher/transitivedependency/pomxml/pomxml.go | 3 --- extractor/filesystem/language/java/javalockfile/metadata.go | 3 --- extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go | 3 --- 3 files changed, 9 deletions(-) diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index 6c9642f62..ee7467456 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -226,8 +226,6 @@ func (e Enricher) extract(ctx context.Context, input *filesystem.ScanInput) (inv return mavenutil.GetDependencyManagement(ctx, e.MavenClient, groupID, artifactID, version) }) - registries := e.MavenClient.GetRegistries() - if registries := e.MavenClient.GetRegistries(); len(registries) > 0 { clientRegs := make([]resolution.Registry, len(registries)) for i, reg := range registries { @@ -320,7 +318,6 @@ func (e Enricher) extract(ctx context.Context, input *filesystem.ScanInput) (inv GroupID: groupID, DepGroupVals: depGroups, IsTransitive: !isDirect, - Registries: registries, }, // TODO(#408): Add merged paths in here as well Locations: []string{input.Path}, diff --git a/extractor/filesystem/language/java/javalockfile/metadata.go b/extractor/filesystem/language/java/javalockfile/metadata.go index 357dd76b6..962d5f2a2 100644 --- a/extractor/filesystem/language/java/javalockfile/metadata.go +++ b/extractor/filesystem/language/java/javalockfile/metadata.go @@ -15,8 +15,6 @@ // Package javalockfile provides shared structures for Java extractors. package javalockfile -import "github.com/google/osv-scalibr/clients/datasource" - // Metadata holds parsing information for a Java package. type Metadata struct { ArtifactID string @@ -25,7 +23,6 @@ type Metadata struct { Classifier string DepGroupVals []string IsTransitive bool // Only set in pomxmlnet extractor - Registries []datasource.MavenRegistry } // DepGroups returns the dependency groups for the package. diff --git a/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go b/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go index 0e8b1004e..d43cca960 100644 --- a/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go +++ b/extractor/filesystem/language/java/pomxmlnet/pomxmlnet.go @@ -136,8 +136,6 @@ func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (in return mavenutil.GetDependencyManagement(ctx, e.MavenClient, groupID, artifactID, version) }) - registries := e.MavenClient.GetRegistries() - if registries := e.MavenClient.GetRegistries(); len(registries) > 0 { clientRegs := make([]resolution.Registry, len(registries)) for i, reg := range registries { @@ -230,7 +228,6 @@ func (e Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (in GroupID: groupID, DepGroupVals: depGroups, IsTransitive: !isDirect, - Registries: registries, }, // TODO(#408): Add merged paths in here as well Locations: []string{input.Path}, From 7dfdb298deefec984c1c9fa80e53a117cfbdd790 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:41:12 +1300 Subject: [PATCH 07/10] refactor: deduplicate utility functions --- .../transitivedependency/internal/grouping.go | 36 ++++++++++++++++ .../transitivedependency/pomxml/pomxml.go | 34 ++------------- .../requirements/requirements.go | 41 ++++--------------- 3 files changed, 46 insertions(+), 65 deletions(-) create mode 100644 enricher/transitivedependency/internal/grouping.go diff --git a/enricher/transitivedependency/internal/grouping.go b/enricher/transitivedependency/internal/grouping.go new file mode 100644 index 000000000..9c31dce0c --- /dev/null +++ b/enricher/transitivedependency/internal/grouping.go @@ -0,0 +1,36 @@ +package internal + +import ( + "slices" + + "github.com/google/osv-scalibr/extractor" + "github.com/google/osv-scalibr/log" +) + +// PackageWithIndex holds the package with its index in inv.Packages +type PackageWithIndex struct { + Pkg *extractor.Package + Index int +} + +// GroupPackagesFromPlugin groups packages that were added by a particular plugin by the first location +// that they are found and returns a map of location -> package name -> package with index. +func GroupPackagesFromPlugin(pkgs []*extractor.Package, pluginName string) map[string]map[string]PackageWithIndex { + result := make(map[string]map[string]PackageWithIndex) + for i, pkg := range pkgs { + if !slices.Contains(pkg.Plugins, pluginName) { + continue + } + if len(pkg.Locations) == 0 { + log.Warnf("package %s has no locations", pkg.Name) + continue + } + // Use the path where this package is first found. + path := pkg.Locations[0] + if _, ok := result[path]; !ok { + result[path] = make(map[string]PackageWithIndex) + } + result[path][pkg.Name] = PackageWithIndex{pkg, i} + } + return result +} diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index ee7467456..7e5b946a1 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -28,13 +28,13 @@ import ( "github.com/google/osv-scalibr/clients/datasource" "github.com/google/osv-scalibr/clients/resolution" "github.com/google/osv-scalibr/enricher" + "github.com/google/osv-scalibr/enricher/transitivedependency/internal" "github.com/google/osv-scalibr/extractor" "github.com/google/osv-scalibr/extractor/filesystem" "github.com/google/osv-scalibr/extractor/filesystem/language/java/javalockfile" "github.com/google/osv-scalibr/extractor/filesystem/language/java/pomxml" "github.com/google/osv-scalibr/internal/mavenutil" "github.com/google/osv-scalibr/inventory" - "github.com/google/osv-scalibr/log" "github.com/google/osv-scalibr/plugin" "github.com/google/osv-scalibr/purl" ) @@ -112,37 +112,9 @@ func NewDefault() enricher.Enricher { return *New(DefaultConfig()) } -// packageWithIndex holds the package with its index in inv.Packages -type packageWithIndex struct { - pkg *extractor.Package - index int -} - -// groupPackages groups packages found in pom.xml files by the first location that they are found -// and returns a map of location -> package name -> package with index. -func groupPackages(pkgs []*extractor.Package) map[string]map[string]packageWithIndex { - result := make(map[string]map[string]packageWithIndex) - for i, pkg := range pkgs { - if !slices.Contains(pkg.Plugins, pomxml.Name) { - continue - } - if len(pkg.Locations) == 0 { - log.Warnf("package %s has no locations", pkg.Name) - continue - } - // Use the path where this package is first found. - path := pkg.Locations[0] - if _, ok := result[path]; !ok { - result[path] = make(map[string]packageWithIndex) - } - result[path][pkg.Name] = packageWithIndex{pkg, i} - } - return result -} - // Enrich enriches the inventory in pom.xml files with transitive dependencies. func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *inventory.Inventory) error { - pkgGroups := groupPackages(inv.Packages) + pkgGroups := internal.GroupPackagesFromPlugin(inv.Packages, pomxml.Name) for path, pkgMap := range pkgGroups { f, err := input.ScanRoot.FS.Open(path) @@ -167,7 +139,7 @@ func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *in indexPkg, ok := pkgMap[pkg.Name] if ok { // This dependency is in manifest, update the version and plugins. - i := indexPkg.index + i := indexPkg.Index inv.Packages[i].Version = pkg.Version inv.Packages[i].Plugins = append(inv.Packages[i].Plugins, Name) } else { diff --git a/enricher/transitivedependency/requirements/requirements.go b/enricher/transitivedependency/requirements/requirements.go index 814af8c4c..eeb461450 100644 --- a/enricher/transitivedependency/requirements/requirements.go +++ b/enricher/transitivedependency/requirements/requirements.go @@ -27,6 +27,7 @@ import ( cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" "github.com/google/osv-scalibr/clients/resolution" "github.com/google/osv-scalibr/enricher" + "github.com/google/osv-scalibr/enricher/transitivedependency/internal" "github.com/google/osv-scalibr/extractor" "github.com/google/osv-scalibr/extractor/filesystem/language/python/requirements" "github.com/google/osv-scalibr/inventory" @@ -76,19 +77,19 @@ func New(cfg *cpb.PluginConfig) enricher.Enricher { // Enrich enriches the inventory in requirements.txt with transitive dependencies. func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *inventory.Inventory) error { - pkgGroups := groupPackages(inv.Packages) + pkgGroups := internal.GroupPackagesFromPlugin(inv.Packages, requirements.Name) for path, pkgMap := range pkgGroups { - packages := make([]packageWithIndex, 0, len(pkgMap)) + packages := make([]internal.PackageWithIndex, 0, len(pkgMap)) for _, indexPkg := range pkgMap { packages = append(packages, indexPkg) } - slices.SortFunc(packages, func(a, b packageWithIndex) int { - return a.index - b.index + slices.SortFunc(packages, func(a, b internal.PackageWithIndex) int { + return a.Index - b.Index }) list := make([]*extractor.Package, 0, len(packages)) for _, indexPkg := range packages { - list = append(list, indexPkg.pkg) + list = append(list, indexPkg.Pkg) } if len(list) == 0 || len(list[0].Metadata.(*requirements.Metadata).HashCheckingModeValues) > 0 { // Do not perform transitive extraction with hash-checking mode. @@ -109,7 +110,7 @@ func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *in indexPkg, ok := pkgMap[pkg.Name] if ok { // This dependency is in manifest, update the version and plugins. - i := indexPkg.index + i := indexPkg.Index inv.Packages[i].Version = pkg.Version inv.Packages[i].Plugins = append(inv.Packages[i].Plugins, Name) } else { @@ -121,34 +122,6 @@ func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *in return nil } -// packageWithIndex holds the package with its index in inv.Packages -type packageWithIndex struct { - pkg *extractor.Package - index int -} - -// groupPackages groups packages found in requirements.txt by the first location that they are found -// and returns a map of location -> package name -> package with index. -func groupPackages(pkgs []*extractor.Package) map[string]map[string]packageWithIndex { - result := make(map[string]map[string]packageWithIndex) - for i, pkg := range pkgs { - if !slices.Contains(pkg.Plugins, requirements.Name) { - continue - } - if len(pkg.Locations) == 0 { - log.Warnf("package %s has no locations", pkg.Name) - continue - } - // Use the path where this package is first found. - path := pkg.Locations[0] - if _, ok := result[path]; !ok { - result[path] = make(map[string]packageWithIndex) - } - result[path][pkg.Name] = packageWithIndex{pkg, i} - } - return result -} - // resolve performs dependency resolution for packages found in a single requirements.txt. func (e Enricher) resolve(ctx context.Context, path string, list []*extractor.Package) ([]*extractor.Package, error) { overrideClient := resolution.NewOverrideClient(e.Client) From dee8eb56526dfceb6a94a51618f2df363290badc Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 8 Dec 2025 13:57:24 +1300 Subject: [PATCH 08/10] refactor: deduplicate inv package adding logic --- .../transitivedependency/internal/grouping.go | 17 +++++++++++++++++ enricher/transitivedependency/pomxml/pomxml.go | 13 +------------ .../requirements/requirements.go | 13 +------------ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/enricher/transitivedependency/internal/grouping.go b/enricher/transitivedependency/internal/grouping.go index 9c31dce0c..546b7c23b 100644 --- a/enricher/transitivedependency/internal/grouping.go +++ b/enricher/transitivedependency/internal/grouping.go @@ -4,6 +4,7 @@ import ( "slices" "github.com/google/osv-scalibr/extractor" + "github.com/google/osv-scalibr/inventory" "github.com/google/osv-scalibr/log" ) @@ -34,3 +35,19 @@ func GroupPackagesFromPlugin(pkgs []*extractor.Package, pluginName string) map[s } return result } + +// Add handles supplementing an inventory with enriched packages +func Add(enrichedPkgs []*extractor.Package, inv *inventory.Inventory, pluginName string, existingPackages map[string]PackageWithIndex) { + for _, pkg := range enrichedPkgs { + indexPkg, ok := existingPackages[pkg.Name] + if ok { + // This dependency is in manifest, update the version and plugins. + i := indexPkg.Index + inv.Packages[i].Version = pkg.Version + inv.Packages[i].Plugins = append(inv.Packages[i].Plugins, pluginName) + } else { + // This dependency is not found in manifest, so it's a transitive dependency. + inv.Packages = append(inv.Packages, pkg) + } + } +} diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index 7e5b946a1..99fe99576 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -135,18 +135,7 @@ func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *in return err } - for _, pkg := range enrichedInv.Packages { - indexPkg, ok := pkgMap[pkg.Name] - if ok { - // This dependency is in manifest, update the version and plugins. - i := indexPkg.Index - inv.Packages[i].Version = pkg.Version - inv.Packages[i].Plugins = append(inv.Packages[i].Plugins, Name) - } else { - // This dependency is not found in manifest, so it's a transitive dependency. - inv.Packages = append(inv.Packages, pkg) - } - } + internal.Add(enrichedInv.Packages, inv, Name, pkgMap) } return nil diff --git a/enricher/transitivedependency/requirements/requirements.go b/enricher/transitivedependency/requirements/requirements.go index eeb461450..ce8880197 100644 --- a/enricher/transitivedependency/requirements/requirements.go +++ b/enricher/transitivedependency/requirements/requirements.go @@ -106,18 +106,7 @@ func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *in continue } - for _, pkg := range pkgs { - indexPkg, ok := pkgMap[pkg.Name] - if ok { - // This dependency is in manifest, update the version and plugins. - i := indexPkg.Index - inv.Packages[i].Version = pkg.Version - inv.Packages[i].Plugins = append(inv.Packages[i].Plugins, Name) - } else { - // This dependency is not found in manifest, so it's a transitive dependency. - inv.Packages = append(inv.Packages, pkg) - } - } + internal.Add(pkgs, inv, Name, pkgMap) } return nil } From 6076636fd1afee35dfbc4d786b6ee04d462462d6 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:08:05 +1300 Subject: [PATCH 09/10] refactor: use different config setup --- enricher/enricherlist/list.go | 2 +- .../transitivedependency/pomxml/pomxml.go | 42 +++++++------------ .../pomxml/pomxml_test.go | 8 ++-- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/enricher/enricherlist/list.go b/enricher/enricherlist/list.go index 91ebd4385..a4fb57fbb 100644 --- a/enricher/enricherlist/list.go +++ b/enricher/enricherlist/list.go @@ -139,7 +139,7 @@ var ( // TransitiveDependency enrichers. TransitiveDependency = InitMap{ requirements.Name: {requirements.New}, - pomxml.Name: {noCFG(pomxml.NewDefault)}, + pomxml.Name: {pomxml.New}, } // PackageDeprecation enricher. diff --git a/enricher/transitivedependency/pomxml/pomxml.go b/enricher/transitivedependency/pomxml/pomxml.go index 99fe99576..34de4b697 100644 --- a/enricher/transitivedependency/pomxml/pomxml.go +++ b/enricher/transitivedependency/pomxml/pomxml.go @@ -25,6 +25,7 @@ import ( "deps.dev/util/maven" "deps.dev/util/resolve" mavenresolve "deps.dev/util/resolve/maven" + cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" "github.com/google/osv-scalibr/clients/datasource" "github.com/google/osv-scalibr/clients/resolution" "github.com/google/osv-scalibr/enricher" @@ -46,7 +47,7 @@ const ( // Enricher performs dependency resolution for pom.xml. type Enricher struct { - depClient resolve.Client + DepClient resolve.Client MavenClient *datasource.MavenRegistryAPIClient } @@ -80,38 +81,27 @@ type Config struct { DependencyClient resolve.Client } -// NewConfig returns the configuration given the URL of the Maven registry to fetch metadata. -func NewConfig(remote, local string, disableGoogleAuth bool) Config { +// New makes a new pom.xml transitive enricher with the given config. +func New(cfg *cpb.PluginConfig) enricher.Enricher { + upstreamRegistry := "" + specific := plugin.FindConfig(cfg, func(c *cpb.PluginSpecificConfig) *cpb.POMXMLNetConfig { return c.GetPomXmlNet() }) + if specific != nil { + upstreamRegistry = specific.UpstreamRegistry + } + // No need to check errors since we are using the default Maven Central URL. mavenClient, _ := datasource.NewMavenRegistryAPIClient(context.Background(), datasource.MavenRegistry{ - URL: remote, + URL: upstreamRegistry, ReleasesEnabled: true, - }, local, disableGoogleAuth) + }, cfg.LocalRegistry, cfg.DisableGoogleAuth) depClient := resolution.NewMavenRegistryClientWithAPI(mavenClient) - return Config{ - DependencyClient: depClient, - MavenRegistryAPIClient: mavenClient, - } -} -// DefaultConfig returns the default configuration for the pomxmlnet extractor. -func DefaultConfig() Config { - return NewConfig("", "", false) -} - -// New makes a new pom.xml transitive extractor with the given config. -func New(c Config) *Enricher { return &Enricher{ - depClient: c.DependencyClient, - MavenClient: c.MavenRegistryAPIClient, + DepClient: depClient, + MavenClient: mavenClient, } } -// NewDefault returns an extractor with the default config settings. -func NewDefault() enricher.Enricher { - return *New(DefaultConfig()) -} - // Enrich enriches the inventory in pom.xml files with transitive dependencies. func (e Enricher) Enrich(ctx context.Context, input *enricher.ScanInput, inv *inventory.Inventory) error { pkgGroups := internal.GroupPackagesFromPlugin(inv.Packages, pomxml.Name) @@ -192,14 +182,14 @@ func (e Enricher) extract(ctx context.Context, input *filesystem.ScanInput) (inv for i, reg := range registries { clientRegs[i] = reg } - if cl, ok := e.depClient.(resolution.ClientWithRegistries); ok { + if cl, ok := e.DepClient.(resolution.ClientWithRegistries); ok { if err := cl.AddRegistries(ctx, clientRegs); err != nil { return inventory.Inventory{}, err } } } - overrideClient := resolution.NewOverrideClient(e.depClient) + overrideClient := resolution.NewOverrideClient(e.DepClient) resolver := mavenresolve.NewResolver(overrideClient) // Resolve the dependencies. diff --git a/enricher/transitivedependency/pomxml/pomxml_test.go b/enricher/transitivedependency/pomxml/pomxml_test.go index 72f6b9767..aa1ee7e12 100644 --- a/enricher/transitivedependency/pomxml/pomxml_test.go +++ b/enricher/transitivedependency/pomxml/pomxml_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" "github.com/google/osv-scalibr/clients/clienttest" "github.com/google/osv-scalibr/clients/datasource" "github.com/google/osv-scalibr/enricher" @@ -138,10 +139,9 @@ func TestEnricher_Enrich(t *testing.T) { resolutionClient := clienttest.NewMockResolutionClient(t, "testdata/universe/basic-universe.yaml") - enrichy := pomxml.New(pomxml.Config{ - DependencyClient: resolutionClient, - MavenRegistryAPIClient: apiClient, - }) + enrichy := pomxml.New(&cpb.PluginConfig{}) + enrichy.(*pomxml.Enricher).DepClient = resolutionClient + enrichy.(*pomxml.Enricher).MavenClient = apiClient err = enrichy.Enrich(t.Context(), &input, &inv) if err != nil { From ed77a8a3a698c5e10dc9ab0ba15f3bb9af33a7ac Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:09:04 +1300 Subject: [PATCH 10/10] chore: add package copyright and comment --- .../transitivedependency/internal/grouping.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/enricher/transitivedependency/internal/grouping.go b/enricher/transitivedependency/internal/grouping.go index 546b7c23b..9d981c344 100644 --- a/enricher/transitivedependency/internal/grouping.go +++ b/enricher/transitivedependency/internal/grouping.go @@ -1,3 +1,18 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package internal contains miscellaneous functions and objects useful within transitive dependency enrichers package internal import (