VYPR
Moderate severityOSV Advisory· Published Jan 22, 2026· Updated Jan 23, 2026

Rekor affected by Server-Side Request Forgery (SSRF) via provided public key URL

CVE-2026-24117

Description

Rekor is a software supply chain transparency log. In versions 1.4.3 and below, attackers can trigger SSRF to arbitrary internal services because /api/v1/index/retrieve supports retrieving a public key via user-provided URL. Since the SSRF only can trigger GET requests, the request cannot mutate state. The response from the GET request is not returned to the caller so data exfiltration is not possible. A malicious actor could attempt to probe an internal network through Blind SSRF. The issue has been fixed in version 1.5.0. To workaround this issue, disable the search endpoint with --enable_retrieve_api=false.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/sigstore/rekorGo
< 1.5.01.5.0

Affected products

1

Patches

1
60ef2bceba19

Drop support for fetching public keys by URL in the search index (#2731)

https://github.com/sigstore/rekorHaydenJan 22, 2026via ghsa
8 files changed · +26 53
  • cmd/rekor-cli/app/search.go+13 5 modified
    @@ -115,13 +115,12 @@ var searchCmd = &cobra.Command{
     			hasher := sha256.New()
     			var tee io.Reader
     			if isURL(artifactStr) {
    -				/* #nosec G107 */
    -				resp, err := http.Get(artifactStr)
    +				r, err := util.FileOrURLReadCloser(cmd.Context(), artifactStr, nil)
     				if err != nil {
     					return nil, fmt.Errorf("error fetching '%v': %w", artifactStr, err)
     				}
    -				defer resp.Body.Close()
    -				tee = io.TeeReader(resp.Body, hasher)
    +				defer r.Close()
    +				tee = io.TeeReader(r, hasher)
     			} else {
     				file, err := os.Open(filepath.Clean(artifactStr))
     				if err != nil {
    @@ -167,7 +166,16 @@ var searchCmd = &cobra.Command{
     			splitPubKeyString := strings.Split(publicKeyStr, ",")
     			if len(splitPubKeyString) == 1 {
     				if isURL(splitPubKeyString[0]) {
    -					params.Query.PublicKey.URL = strfmt.URI(splitPubKeyString[0])
    +					r, err := util.FileOrURLReadCloser(cmd.Context(), splitPubKeyString[0], nil)
    +					if err != nil {
    +						return nil, fmt.Errorf("error fetching '%v': %w", splitPubKeyString[0], err)
    +					}
    +					defer r.Close()
    +					c, err := io.ReadAll(r)
    +					if err != nil {
    +						return nil, fmt.Errorf("error reading public key from '%v': %w", splitPubKeyString[0], err)
    +					}
    +					params.Query.PublicKey.Content = c
     				} else {
     					keyBytes, err := os.ReadFile(filepath.Clean(splitPubKeyString[0]))
     					if err != nil {
    
  • openapi.yaml+0 4 modified
    @@ -140,7 +140,6 @@ paths:
           summary: Creates an entry in the transparency log
           description: >
             Creates an entry in the transparency log for a detached signature, public key, and content.
    -        Items can be included in the request or fetched by the server when URLs are specified.
           operationId: createLogEntry
           tags:
             - entries
    @@ -496,9 +495,6 @@ definitions:
               content:
                 type: string
                 format: byte
    -          url:
    -            type: string
    -            format: uri
             required:
               - "format"
           hash:
    
  • pkg/api/index.go+2 5 modified
    @@ -16,6 +16,7 @@
     package api
     
     import (
    +	"bytes"
     	"context"
     	"crypto/sha256"
     	"encoding/hex"
    @@ -62,11 +63,7 @@ func SearchIndexHandler(params index.SearchIndexParams) middleware.Responder {
     		if err != nil {
     			return handleRekorAPIError(params, http.StatusBadRequest, err, unsupportedPKIFormat)
     		}
    -		keyReader, err := util.FileOrURLReadCloser(httpReqCtx, params.Query.PublicKey.URL.String(), params.Query.PublicKey.Content)
    -		if err != nil {
    -			return handleRekorAPIError(params, http.StatusBadRequest, err, malformedPublicKey)
    -		}
    -		defer keyReader.Close()
    +		keyReader := bytes.NewReader(params.Query.PublicKey.Content)
     
     		key, err := af.NewPublicKey(keyReader)
     		if err != nil {
    
  • pkg/generated/client/entries/entries_client.go+1 1 modified
    @@ -84,7 +84,7 @@ type ClientService interface {
     /*
     CreateLogEntry creates an entry in the transparency log
     
    -Creates an entry in the transparency log for a detached signature, public key, and content. Items can be included in the request or fetched by the server when URLs are specified.
    +Creates an entry in the transparency log for a detached signature, public key, and content.
     */
     func (a *Client) CreateLogEntry(params *CreateLogEntryParams, opts ...ClientOption) (*CreateLogEntryCreated, error) {
     	// NOTE: parameters are not validated before sending
    
  • pkg/generated/models/search_index.go+0 20 modified
    @@ -238,10 +238,6 @@ type SearchIndexPublicKey struct {
     	// Required: true
     	// Enum: ["pgp","x509","minisign","ssh","tuf"]
     	Format *string `json:"format"`
    -
    -	// url
    -	// Format: uri
    -	URL strfmt.URI `json:"url,omitempty"`
     }
     
     // Validate validates this search index public key
    @@ -252,10 +248,6 @@ func (m *SearchIndexPublicKey) Validate(formats strfmt.Registry) error {
     		res = append(res, err)
     	}
     
    -	if err := m.validateURL(formats); err != nil {
    -		res = append(res, err)
    -	}
    -
     	if len(res) > 0 {
     		return errors.CompositeValidationError(res...)
     	}
    @@ -314,18 +306,6 @@ func (m *SearchIndexPublicKey) validateFormat(formats strfmt.Registry) error {
     	return nil
     }
     
    -func (m *SearchIndexPublicKey) validateURL(formats strfmt.Registry) error {
    -	if swag.IsZero(m.URL) { // not required
    -		return nil
    -	}
    -
    -	if err := validate.FormatOf("publicKey"+"."+"url", "body", "uri", m.URL.String(), formats); err != nil {
    -		return err
    -	}
    -
    -	return nil
    -}
    -
     // ContextValidate validates this search index public key based on context it is used
     func (m *SearchIndexPublicKey) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
     	return nil
    
  • pkg/generated/restapi/embedded_spec.go+2 14 modified
    @@ -144,7 +144,7 @@ func init() {
             }
           },
           "post": {
    -        "description": "Creates an entry in the transparency log for a detached signature, public key, and content. Items can be included in the request or fetched by the server when URLs are specified.\n",
    +        "description": "Creates an entry in the transparency log for a detached signature, public key, and content.\n",
             "tags": [
               "entries"
             ],
    @@ -589,10 +589,6 @@ func init() {
                     "ssh",
                     "tuf"
                   ]
    -            },
    -            "url": {
    -              "type": "string",
    -              "format": "uri"
                 }
               }
             }
    @@ -1074,7 +1070,7 @@ func init() {
             }
           },
           "post": {
    -        "description": "Creates an entry in the transparency log for a detached signature, public key, and content. Items can be included in the request or fetched by the server when URLs are specified.\n",
    +        "description": "Creates an entry in the transparency log for a detached signature, public key, and content.\n",
             "tags": [
               "entries"
             ],
    @@ -2719,10 +2715,6 @@ func init() {
                     "ssh",
                     "tuf"
                   ]
    -            },
    -            "url": {
    -              "type": "string",
    -              "format": "uri"
                 }
               }
             }
    @@ -2747,10 +2739,6 @@ func init() {
                 "ssh",
                 "tuf"
               ]
    -        },
    -        "url": {
    -          "type": "string",
    -          "format": "uri"
             }
           }
         },
    
  • pkg/generated/restapi/operations/entries/create_log_entry.go+1 1 modified
    @@ -50,7 +50,7 @@ func NewCreateLogEntry(ctx *middleware.Context, handler CreateLogEntryHandler) *
     
     # Creates an entry in the transparency log
     
    -Creates an entry in the transparency log for a detached signature, public key, and content. Items can be included in the request or fetched by the server when URLs are specified.
    +Creates an entry in the transparency log for a detached signature, public key, and content.
     */
     type CreateLogEntry struct {
     	Context *middleware.Context
    
  • pkg/util/fetch.go+7 3 modified
    @@ -21,14 +21,18 @@ import (
     	"fmt"
     	"io"
     	"net/http"
    +	"time"
     )
     
    -// FileOrURLReadCloser Note: caller is responsible for closing ReadCloser returned from method!
    +// FileOrURLReadCloser reads content either from a URL or a byte slice
    +// Note: Caller is responsible for closing the returned ReadCloser
    +// Note: This must never be called from any server codepath to prevent SSRF
     func FileOrURLReadCloser(ctx context.Context, url string, content []byte) (io.ReadCloser, error) {
     	var dataReader io.ReadCloser
     	if url != "" {
    -		//TODO: set timeout here, SSL settings?
    -		client := &http.Client{}
    +		client := &http.Client{
    +			Timeout: 30 * time.Second,
    +		}
     		req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
     		if err != nil {
     			return nil, err
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.