From f01589d98b42ce2f597ada0cd8973332970e8c44 Mon Sep 17 00:00:00 2001 From: localai-org-maint-bot Date: Fri, 31 Jul 2026 09:20:26 +0200 Subject: [PATCH] fix(oci): identify signature verification requests (#11244) Signed backend verification performs separate registry requests for manifests and referrers. Reuse LocalAI's version-aware User-Agent there so the full install flow is attributable to LocalAI. Assisted-by: Codex:gpt-5 Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com> --- internal/version.go | 8 +++ .../cosignverify/useragent_internal_test.go | 56 +++++++++++++++++++ pkg/oci/cosignverify/verify.go | 2 + pkg/oci/useragent.go | 11 +--- 4 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 pkg/oci/cosignverify/useragent_internal_test.go diff --git a/internal/version.go b/internal/version.go index 86588b407..018c07d90 100644 --- a/internal/version.go +++ b/internal/version.go @@ -8,3 +8,11 @@ var Commit = "" func PrintableVersion() string { return fmt.Sprintf("%s (%s)", Version, Commit) } + +// UserAgent returns the version-aware client identity used for outbound requests. +func UserAgent() string { + if Version == "" { + return "LocalAI" + } + return fmt.Sprintf("LocalAI/%s", Version) +} diff --git a/pkg/oci/cosignverify/useragent_internal_test.go b/pkg/oci/cosignverify/useragent_internal_test.go new file mode 100644 index 000000000..38137bea3 --- /dev/null +++ b/pkg/oci/cosignverify/useragent_internal_test.go @@ -0,0 +1,56 @@ +package cosignverify + +import ( + "context" + "io" + "net/http" + "strings" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/mudler/LocalAI/internal" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + +var _ = Describe("registry User-Agent", func() { + It("identifies LocalAI during signature verification requests", func() { + originalVersion := internal.Version + internal.Version = "v-test" + DeferCleanup(func() { + internal.Version = originalVersion + }) + + var userAgent string + transport := roundTripFunc(func(req *http.Request) (*http.Response, error) { + userAgent = req.Header.Get("User-Agent") + return &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{ + "Content-Type": []string{"application/vnd.oci.image.manifest.v1+json"}, + "Docker-Content-Digest": []string{"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, + }, + Body: io.NopCloser(strings.NewReader("")), + Request: req, + }, nil + }) + + verifier, err := NewVerifier(Policy{ + Issuer: "https://token.actions.githubusercontent.com", + IdentityRegex: `^https://github\.com/example/.*`, + }, nil, transport) + Expect(err).NotTo(HaveOccurred()) + + ref, err := name.ParseReference("registry.example.com/localai/backend:latest") + Expect(err).NotTo(HaveOccurred()) + _, err = remote.Head(ref, verifier.remoteOptions(context.Background())...) + Expect(err).NotTo(HaveOccurred()) + Expect(userAgent).To(ContainSubstring("LocalAI/v-test")) + }) +}) diff --git a/pkg/oci/cosignverify/verify.go b/pkg/oci/cosignverify/verify.go index a644a9ded..579b0d8c6 100644 --- a/pkg/oci/cosignverify/verify.go +++ b/pkg/oci/cosignverify/verify.go @@ -30,6 +30,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" + "github.com/mudler/LocalAI/internal" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/tuf" "github.com/sigstore/sigstore-go/pkg/verify" @@ -297,6 +298,7 @@ func (v *Verifier) remoteOptions(ctx context.Context) []remote.Option { opts := []remote.Option{ remote.WithContext(ctx), remote.WithTransport(t), + remote.WithUserAgent(internal.UserAgent()), } if v.auth != nil { opts = append(opts, remote.WithAuth(staticAuth{auth: v.auth})) diff --git a/pkg/oci/useragent.go b/pkg/oci/useragent.go index 82277c70d..7164c6b30 100644 --- a/pkg/oci/useragent.go +++ b/pkg/oci/useragent.go @@ -1,10 +1,6 @@ package oci -import ( - "fmt" - - "github.com/mudler/LocalAI/internal" -) +import "github.com/mudler/LocalAI/internal" // UserAgent returns the User-Agent string LocalAI sends on outbound registry // requests (OCI registries and Ollama). It identifies the client as LocalAI @@ -12,8 +8,5 @@ import ( // can attribute client-side usage to LocalAI rather than to the generic // User-Agent of the underlying transport library. func UserAgent() string { - if internal.Version == "" { - return "LocalAI" - } - return fmt.Sprintf("LocalAI/%s", internal.Version) + return internal.UserAgent() }