From de308e48e03cc433bebe9f9410701c43f7801325 Mon Sep 17 00:00:00 2001 From: Simon Chrzanowski Date: Fri, 23 Oct 2026 19:29:00 +0200 Subject: [PATCH 1/2] Use purl's ComparisonForm for denylist and NuGet retention keys The per-ecosystem rules for when two spellings name the same package (PEP 503, NuGet case, Composer's leading v, conda channel, OCI paths, Swift registry case) now live in purl. The denylist keeps only the proxy's own pkg:generic/swift-registry spelling and the gem platform match. Keys, cache paths and stored PURLs are unchanged. --- go.mod | 2 +- go.sum | 4 +- internal/denylist/denylist.go | 75 +++++++++-------------------------- internal/handler/nuget.go | 6 ++- 4 files changed, 26 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 7d17bfde..dbbfd49d 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/git-pkgs/gcs v0.1.0 github.com/git-pkgs/integrity v0.2.0 github.com/git-pkgs/magic v0.5.0 - github.com/git-pkgs/purl v0.1.23 + github.com/git-pkgs/purl v0.1.24-0.20261024154844-2a1e0a9803d3 github.com/git-pkgs/registries v0.11.0 github.com/git-pkgs/sbom v0.1.7 github.com/git-pkgs/spdx v0.3.3 diff --git a/go.sum b/go.sum index ee376526..dcf89423 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/git-pkgs/magic v0.5.0 h1:Pm+4fkHR+K6kg9pwJM4hnwWUuBlkYdLmF1yIAI8lU9Q= github.com/git-pkgs/magic v0.5.0/go.mod h1:SXOqcsNmbmpZjJZHEEWnwxprbsFvpwWgTAZrgXp4Jm4= github.com/git-pkgs/pom v0.1.8 h1:mXCuWw6XkrKKZb6IurRZyIznYLPhV5AYg5ZO38Uj/l8= github.com/git-pkgs/pom v0.1.8/go.mod h1:sQBeYRzjCUn77fcABPn6I/a1aBHFxm55jTmkNxYtGtw= -github.com/git-pkgs/purl v0.1.23 h1:RG7F73S4ZnbAnB/posWf2WS4OhdG0xUbyX8UIkEUhPw= -github.com/git-pkgs/purl v0.1.23/go.mod h1:l8rCfvt5iXVrFIcoQ8+vCLOaHhrtEW2id0LENP+IzMw= +github.com/git-pkgs/purl v0.1.24-0.20261024154844-2a1e0a9803d3 h1:Mi8EkmagjRVQ9ee6oBbmyVHOs9gSIuj3Vjq4cBMrwNs= +github.com/git-pkgs/purl v0.1.24-0.20261024154844-2a1e0a9803d3/go.mod h1:l8rCfvt5iXVrFIcoQ8+vCLOaHhrtEW2id0LENP+IzMw= github.com/git-pkgs/registries v0.11.0 h1:kkB80NjO9UncyyxGfKmdPXWqUMNE/CkNw5Z/6gqW7Rs= github.com/git-pkgs/registries v0.11.0/go.mod h1:bKxgdy/PwnzQXL2FnOktmSQHTKk6B85MA4VtvvYItPc= github.com/git-pkgs/sbom v0.1.7 h1:4eboy/Yed/SC9ZcC/xT50iywHPzWbmCU7azDb5T1RbA= diff --git a/internal/denylist/denylist.go b/internal/denylist/denylist.go index 77b076aa..8e84811e 100644 --- a/internal/denylist/denylist.go +++ b/internal/denylist/denylist.go @@ -3,7 +3,6 @@ package denylist import ( "fmt" - "regexp" "strings" "github.com/git-pkgs/purl" @@ -116,65 +115,29 @@ func (id identity) channelKey() string { return id.key + "?channel=" + id.channel } -var pypiSeparators = regexp.MustCompile(`[-_.]+`) - -// swiftRegistryIdentity returns the registry scope and name of a Swift -// package. A source coordinate such as pkg:swift/github.com/apple/swift-nio -// names no registry identity. -func swiftRegistryIdentity(pkg *purl.PURL) (scope, name string, ok bool) { - switch { - case pkg.Type == "swift" && pkg.Namespace != "" && !strings.ContainsAny(pkg.Namespace, "./"): - return pkg.Namespace, pkg.Name, true - case pkg.Type == "generic" && strings.EqualFold(pkg.Namespace, "swift-registry"): - scope, name, found := strings.Cut(pkg.Name, ".") - return scope, name, found && scope != "" && name != "" +// genericSwiftRegistry returns the scope and name of a Swift registry archive, +// which the proxy stores as pkg:generic/swift-registry/{scope}.{name}. +func genericSwiftRegistry(pkg *purl.PURL) (scope, name string, ok bool) { + if pkg.Type != "generic" || !strings.EqualFold(pkg.Namespace, "swift-registry") { + return "", "", false } - return "", "", false + scope, name, found := strings.Cut(pkg.Name, ".") + return scope, name, found && scope != "" && name != "" } +// identify compares PURLs in purl's comparison form. The generic +// swift-registry spelling is the proxy's own, so it is mapped here first. func identify(pkg *purl.PURL) identity { - namespace, name, version := pkg.Namespace, pkg.Name, pkg.Version + if scope, name, ok := genericSwiftRegistry(pkg); ok { + // Registry identities ignore case. + pkg = purl.New("swift", strings.ToLower(scope), strings.ToLower(name), pkg.Version, nil) + } + c := pkg.ComparisonForm() var channel string - switch pkg.Type { - case "pypi": - // PEP 503: case-insensitive, runs of -, _ and . are equivalent. - name = pypiSeparators.ReplaceAllString(strings.ToLower(name), "-") - case "nuget": - // NuGet ids and versions are case-insensitive. - name, version = strings.ToLower(name), strings.ToLower(version) - case "conda": - // The conda handler names packages "channel/name"; entries may give the - // channel as a qualifier or namespace. An entry without one matches - // every channel. - channel = pkg.Qualifier("channel") - if namespace != "" { - channel, namespace = namespace, "" - } - if i := strings.LastIndex(name, "/"); i >= 0 { - channel, name = name[:i], name[i+1:] - } - case "composer": - // Packagist keeps a tag's leading v ("v6.1.7"); Composer treats it - // as the same version. - if len(version) > 1 && (version[0] == 'v' || version[0] == 'V') && version[1] >= '0' && version[1] <= '9' { - version = version[1:] - } - case "oci": - // Handlers name the repository as one name, library%2Falpine; - // entries write it as a path, library/alpine. - if namespace != "" { - name, namespace = namespace+"/"+name, "" - } - case "swift", "generic": - // Swift registry archives are stored as - // pkg:generic/swift-registry/{scope}.{name}; entries may write - // pkg:swift/{scope}/{name}. Registry identities ignore case. - if scope, pkgName, ok := swiftRegistryIdentity(pkg); ok { - namespace, name = strings.ToLower(scope), strings.ToLower(pkgName) - key := purl.New("swift", namespace, name, "", nil).String() - return identity{key: key, version: version} - } + if c.Type == "conda" { + // An entry without a channel matches every channel. + channel = c.Qualifier("channel") } - key := purl.New(pkg.Type, namespace, name, "", nil).String() - return identity{key: key, channel: channel, version: version} + key := purl.New(c.Type, c.Namespace, c.Name, "", nil).String() + return identity{key: key, channel: channel, version: c.Version} } diff --git a/internal/handler/nuget.go b/internal/handler/nuget.go index 3d9f7278..2989298a 100644 --- a/internal/handler/nuget.go +++ b/internal/handler/nuget.go @@ -18,7 +18,8 @@ const ( // Packages are stored under pkg:nuget/{id} with the id lowercased, since // NuGet ids are case-insensitive. purl keeps nuget names as written, so -// package rules are lowercased the same way before the default mapping. +// package rules go through its comparison form, which lowercases them the +// same way, before the default mapping. func init() { retention.Default.Register(retention.Spec{Key: "nuget", CanonicalPackage: nugetRetentionKey}) } @@ -29,7 +30,8 @@ func nugetRetentionKey(p *purl.PURL) (string, bool) { if p == nil { return "", false } - return retention.DefaultCanonical("nuget")(purl.New(p.Type, p.Namespace, strings.ToLower(p.Name), "", nil)) + c := p.ComparisonForm() + return retention.DefaultCanonical("nuget")(purl.New(c.Type, c.Namespace, c.Name, "", nil)) } // NuGetHandler handles NuGet V3 API protocol requests. From 2a4ce1224879630309249c0bfde14f38ab637c12 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sun, 25 Oct 2026 10:14:03 +0000 Subject: [PATCH 2/2] Bump purl to v0.1.24 and drop redundant Swift lowercasing --- go.mod | 2 +- go.sum | 4 ++-- internal/denylist/denylist.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index dbbfd49d..a86e9902 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/git-pkgs/gcs v0.1.0 github.com/git-pkgs/integrity v0.2.0 github.com/git-pkgs/magic v0.5.0 - github.com/git-pkgs/purl v0.1.24-0.20261024154844-2a1e0a9803d3 + github.com/git-pkgs/purl v0.1.24 github.com/git-pkgs/registries v0.11.0 github.com/git-pkgs/sbom v0.1.7 github.com/git-pkgs/spdx v0.3.3 diff --git a/go.sum b/go.sum index dcf89423..a78c08e9 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/git-pkgs/magic v0.5.0 h1:Pm+4fkHR+K6kg9pwJM4hnwWUuBlkYdLmF1yIAI8lU9Q= github.com/git-pkgs/magic v0.5.0/go.mod h1:SXOqcsNmbmpZjJZHEEWnwxprbsFvpwWgTAZrgXp4Jm4= github.com/git-pkgs/pom v0.1.8 h1:mXCuWw6XkrKKZb6IurRZyIznYLPhV5AYg5ZO38Uj/l8= github.com/git-pkgs/pom v0.1.8/go.mod h1:sQBeYRzjCUn77fcABPn6I/a1aBHFxm55jTmkNxYtGtw= -github.com/git-pkgs/purl v0.1.24-0.20261024154844-2a1e0a9803d3 h1:Mi8EkmagjRVQ9ee6oBbmyVHOs9gSIuj3Vjq4cBMrwNs= -github.com/git-pkgs/purl v0.1.24-0.20261024154844-2a1e0a9803d3/go.mod h1:l8rCfvt5iXVrFIcoQ8+vCLOaHhrtEW2id0LENP+IzMw= +github.com/git-pkgs/purl v0.1.24 h1:JMEB1uNWI+baEpl6AeVx+Kfq1c+xkou+THxa5KJ3MrU= +github.com/git-pkgs/purl v0.1.24/go.mod h1:l8rCfvt5iXVrFIcoQ8+vCLOaHhrtEW2id0LENP+IzMw= github.com/git-pkgs/registries v0.11.0 h1:kkB80NjO9UncyyxGfKmdPXWqUMNE/CkNw5Z/6gqW7Rs= github.com/git-pkgs/registries v0.11.0/go.mod h1:bKxgdy/PwnzQXL2FnOktmSQHTKk6B85MA4VtvvYItPc= github.com/git-pkgs/sbom v0.1.7 h1:4eboy/Yed/SC9ZcC/xT50iywHPzWbmCU7azDb5T1RbA= diff --git a/internal/denylist/denylist.go b/internal/denylist/denylist.go index 8e84811e..15bb9e5e 100644 --- a/internal/denylist/denylist.go +++ b/internal/denylist/denylist.go @@ -129,8 +129,7 @@ func genericSwiftRegistry(pkg *purl.PURL) (scope, name string, ok bool) { // swift-registry spelling is the proxy's own, so it is mapped here first. func identify(pkg *purl.PURL) identity { if scope, name, ok := genericSwiftRegistry(pkg); ok { - // Registry identities ignore case. - pkg = purl.New("swift", strings.ToLower(scope), strings.ToLower(name), pkg.Version, nil) + pkg = purl.New("swift", scope, name, pkg.Version, nil) } c := pkg.ComparisonForm() var channel string