The enrichment library provides a Client interface for package metadata lookups, but both git-pkgs and the proxy reimplement vulnerability checking, license categorization, and outdated detection independently. This causes the same logic to exist in three places with slightly different types and behavior.
What the proxy reimplements
The proxy has internal/enrichment/enrichment.go with a Service struct that wraps registries.Client and vulns.Source directly, bypassing the shared Client interface almost entirely. It reimplements:
EnrichPackage / EnrichVersion -- calls registries.FetchPackageFromPURL directly instead of going through the shared Client
CheckVulnerabilities -- wraps vulns/osv to query OSV, extract severity/CVSS/fixed versions, return []VulnInfo
BulkCheckVulnerabilities -- batch version of the above
IsOutdated -- vers.Compare(current, latest) < 0
CategorizeLicense -- spdx.HasCopyleft / spdx.IsFullyPermissive to classify as permissive/copyleft/unknown
GetLatestVersion -- fetches latest from registry
EnrichFull -- parallel package + version + vulns enrichment
It defines its own types (PackageInfo, VersionInfo, VulnInfo) that overlap with the shared library's types but aren't the same. The proxy's VersionInfo adds a Yanked field and its VulnInfo has no equivalent in the shared library at all.
What git-pkgs reimplements
git-pkgs uses the shared enrichment Client for package lookups but handles everything else separately:
cmd/vulns.go has scanLive() which creates an osv.Source directly, calls QueryBatch, and maps results to a VulnResult struct. The mapping logic (severity level, CVSS score, fixed version, references) is nearly identical to the proxy's CheckVulnerabilities.
cmd/outdated.go, cmd/licenses.go, and cmd/sbom.go each have their own cache-then-fetch function (getPackageData, getLicenseData, getSBOMLicenseData) that all follow the same pattern: check DB cache with 24h TTL, call client.BulkLookup for misses, save results. They differ only in which fields they extract from the response.
- License normalization uses
spdx.Normalize() while the proxy uses spdx.NormalizeExpressionLax() for the same purpose.
Data source flexibility
The library already has multiple Client implementations (EcosystemsClient for ecosyste.ms, RegistriesClient for direct registry access, DepsDevClient for deps.dev, HybridClient that routes based on PURL qualifiers). Any new functionality should preserve this pattern. Consumers need to be able to:
- Use ecosyste.ms or deps.dev for public packages and fall back to direct registry queries for private registries
- Skip ecosyste.ms/deps.dev entirely and always go to registries directly (firewalled environments, air-gapped installs)
- Mix strategies per-package based on PURL qualifiers (the HybridClient approach)
Not everything is available from every source. Vulnerability data (OSV) and security scorecards are external services that registries don't provide. License strings come from registries but categorization (permissive vs copyleft) is local logic using the spdx library. Version lists and latest version come from registries or aggregators. The enrichment library should make it clear which data comes from where and degrade gracefully when a source is unavailable -- a firewalled install can still categorize licenses and compare versions, it just can't query OSV for vulnerabilities or ecosyste.ms for aggregated metadata.
This also matters for future consumers beyond git-pkgs and the proxy. A forge like Forgejo could import this module to show license info, vulnerability counts, or outdated badges on package pages. A Forgejo instance on a private network with no access to ecosyste.ms or deps.dev should still get license categorization and version comparison. One with full internet access gets the complete enrichment pipeline. The Client interface already supports this, and the new functions should too.
What could move into this library
Vulnerability checking. A CheckVulnerabilities(ctx, ecosystem, name, version) function and a VulnInfo type. Both consumers do the same thing: build a PURL, query OSV, extract ID/summary/severity/CVSS/fixed version/references. The proxy and git-pkgs could both import this instead of wrapping vulns/osv themselves.
License categorization. A CategorizeLicense(license string) function returning permissive/copyleft/unknown. Three copies of the spdx.HasCopyleft / spdx.IsFullyPermissive pattern exist across both projects.
Outdated detection. An IsOutdated(current, latest string) bool function. Both projects do vers.Compare(current, latest) < 0.
Unified types. The VulnInfo struct exists in three shapes. A single definition here would let the proxy's API layer and git-pkgs' output formatting both work from the same type. The VersionInfo type should gain a Yanked field to match what the proxy needs.
Changes needed in consumers
Proxy: Replace internal/enrichment/enrichment.go with a thin wrapper around the shared library. The Service struct would compose the shared Client with the new vulnerability and license functions instead of reimplementing them. The proxy-specific EnrichFull (parallel package + version + vulns) could stay internal or move here if useful.
git-pkgs: Replace direct osv.Source usage in vulns.go with the shared vulnerability function. Collapse the three cache-then-fetch helpers into one, parameterized by which fields to extract, using the shared types.
The enrichment library provides a Client interface for package metadata lookups, but both git-pkgs and the proxy reimplement vulnerability checking, license categorization, and outdated detection independently. This causes the same logic to exist in three places with slightly different types and behavior.
What the proxy reimplements
The proxy has
internal/enrichment/enrichment.gowith aServicestruct that wrapsregistries.Clientandvulns.Sourcedirectly, bypassing the shared Client interface almost entirely. It reimplements:EnrichPackage/EnrichVersion-- callsregistries.FetchPackageFromPURLdirectly instead of going through the shared ClientCheckVulnerabilities-- wrapsvulns/osvto query OSV, extract severity/CVSS/fixed versions, return[]VulnInfoBulkCheckVulnerabilities-- batch version of the aboveIsOutdated--vers.Compare(current, latest) < 0CategorizeLicense--spdx.HasCopyleft/spdx.IsFullyPermissiveto classify as permissive/copyleft/unknownGetLatestVersion-- fetches latest from registryEnrichFull-- parallel package + version + vulns enrichmentIt defines its own types (
PackageInfo,VersionInfo,VulnInfo) that overlap with the shared library's types but aren't the same. The proxy'sVersionInfoadds aYankedfield and itsVulnInfohas no equivalent in the shared library at all.What git-pkgs reimplements
git-pkgs uses the shared enrichment Client for package lookups but handles everything else separately:
cmd/vulns.gohasscanLive()which creates anosv.Sourcedirectly, callsQueryBatch, and maps results to aVulnResultstruct. The mapping logic (severity level, CVSS score, fixed version, references) is nearly identical to the proxy'sCheckVulnerabilities.cmd/outdated.go,cmd/licenses.go, andcmd/sbom.goeach have their own cache-then-fetch function (getPackageData,getLicenseData,getSBOMLicenseData) that all follow the same pattern: check DB cache with 24h TTL, callclient.BulkLookupfor misses, save results. They differ only in which fields they extract from the response.spdx.Normalize()while the proxy usesspdx.NormalizeExpressionLax()for the same purpose.Data source flexibility
The library already has multiple Client implementations (EcosystemsClient for ecosyste.ms, RegistriesClient for direct registry access, DepsDevClient for deps.dev, HybridClient that routes based on PURL qualifiers). Any new functionality should preserve this pattern. Consumers need to be able to:
Not everything is available from every source. Vulnerability data (OSV) and security scorecards are external services that registries don't provide. License strings come from registries but categorization (permissive vs copyleft) is local logic using the spdx library. Version lists and latest version come from registries or aggregators. The enrichment library should make it clear which data comes from where and degrade gracefully when a source is unavailable -- a firewalled install can still categorize licenses and compare versions, it just can't query OSV for vulnerabilities or ecosyste.ms for aggregated metadata.
This also matters for future consumers beyond git-pkgs and the proxy. A forge like Forgejo could import this module to show license info, vulnerability counts, or outdated badges on package pages. A Forgejo instance on a private network with no access to ecosyste.ms or deps.dev should still get license categorization and version comparison. One with full internet access gets the complete enrichment pipeline. The Client interface already supports this, and the new functions should too.
What could move into this library
Vulnerability checking. A
CheckVulnerabilities(ctx, ecosystem, name, version)function and aVulnInfotype. Both consumers do the same thing: build a PURL, query OSV, extract ID/summary/severity/CVSS/fixed version/references. The proxy and git-pkgs could both import this instead of wrappingvulns/osvthemselves.License categorization. A
CategorizeLicense(license string)function returning permissive/copyleft/unknown. Three copies of thespdx.HasCopyleft/spdx.IsFullyPermissivepattern exist across both projects.Outdated detection. An
IsOutdated(current, latest string) boolfunction. Both projects dovers.Compare(current, latest) < 0.Unified types. The
VulnInfostruct exists in three shapes. A single definition here would let the proxy's API layer and git-pkgs' output formatting both work from the same type. TheVersionInfotype should gain aYankedfield to match what the proxy needs.Changes needed in consumers
Proxy: Replace
internal/enrichment/enrichment.gowith a thin wrapper around the shared library. TheServicestruct would compose the sharedClientwith the new vulnerability and license functions instead of reimplementing them. The proxy-specificEnrichFull(parallel package + version + vulns) could stay internal or move here if useful.git-pkgs: Replace direct
osv.Sourceusage invulns.gowith the shared vulnerability function. Collapse the three cache-then-fetch helpers into one, parameterized by which fields to extract, using the shared types.