VYPR
Moderate severityOSV Advisory· Published Jan 12, 2026· Updated Jan 12, 2026

Fulcio vulnerable to Server-Side Request Forgery (SSRF) via MetaIssuer Regex Bypass

CVE-2026-22772

Description

Fulcio is a certificate authority for issuing code signing certificates for an OpenID Connect (OIDC) identity. Prior to 1.8.5, Fulcio's metaRegex() function uses unanchored regex, allowing attackers to bypass MetaIssuer URL validation and trigger SSRF to arbitrary internal services. 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. This vulnerability is fixed in 1.8.5.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/sigstore/fulcioGo
< 1.8.51.8.5

Affected products

1

Patches

1
eaae2f2be56d

Add anchors when matching meta issuer regexp (GHSA-59jp-pj84-45mr) (#2263)

https://github.com/sigstore/fulcioHaydenJan 12, 2026via ghsa
4 files changed · +70 73
  • pkg/config/config.go+8 2 modified
    @@ -140,7 +140,7 @@ type OIDCIssuer struct {
     	SkipEmailVerification bool `json:"SkipEmailVerification,omitempty" yaml:"skip-email-verification,omitempty"`
     }
     
    -func metaRegex(issuer string) (*regexp.Regexp, error) {
    +func MetaRegex(issuer string) (*regexp.Regexp, error) {
     	// Quote all of the "meta" characters like `.` to avoid
     	// those literal characters in the URL matching any character.
     	// This will ALSO quote `*`, so we replace the quoted version.
    @@ -151,6 +151,12 @@ func metaRegex(issuer string) (*regexp.Regexp, error) {
     	// "special" characters.
     	replaced := strings.ReplaceAll(quoted, regexp.QuoteMeta("*"), "[-_a-zA-Z0-9]+")
     
    +	// Add anchors to the beginning and end of the regular expression
    +	// to prevent matching URLs where the issuer is not the host of the URL,
    +	// e.g. http://localhost:3000?https://meta-url-issuer.com/*
    +	// Resolves GHSA-59jp-pj84-45mr
    +	replaced = "^" + replaced + "$"
    +
     	// Compile into a regular expression.
     	return regexp.Compile(replaced)
     }
    @@ -165,7 +171,7 @@ func (fc *FulcioConfig) GetIssuer(issuerURL string) (OIDCIssuer, bool) {
     	}
     
     	for meta, iss := range fc.MetaIssuers {
    -		re, err := metaRegex(meta)
    +		re, err := MetaRegex(meta)
     		if err != nil {
     			continue // Shouldn't happen, we check parsing the config
     		}
    
  • pkg/config/config_test.go+61 1 modified
    @@ -114,6 +114,9 @@ func TestMetaURLs(t *testing.T) {
     			"https://oidc.eks.us.west.2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB",
     			// Extra slashes
     			"https://oidc.eks.us-west/2.amazonaws.com/id/B02C93B6A2D3/0341AD01E1B6D48164CB",
    +			// Issuer is not the URL host (GHSA-59jp-pj84-45mr)
    +			"http://localhost?issuer=https://oidc.eks.us-west-2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB",
    +			"http://localhost/redirect/https://oidc.eks.us-west-2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB",
     		},
     	}, {
     		name:   "GKE meta URL",
    @@ -124,12 +127,69 @@ func TestMetaURLs(t *testing.T) {
     		misses: []string{
     			// Extra dots
     			"https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us.west1.b/clusters/tenant-cluster",
    +			// Issuer is not the URL host (GHSA-59jp-pj84-45mr)
    +			"http://localhost?issuer=https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster",
    +			"http://localhost/redirect/https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster",
    +		},
    +	}, {
    +		name:   "Azure meta URL",
    +		issuer: "https://*.oic.prod-aks.azure.com/*",
    +		matches: []string{
    +			"https://eastus.oic.prod-aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +		},
    +		misses: []string{
    +			// Extra dots
    +			"https://eastus.oic.prod.aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +			// Extra slashes
    +			"https://eastus/oic.prod.aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +			// Issuer is not the URL host (GHSA-59jp-pj84-45mr)
    +			"http://localhost?issuer=https://eastus.oic.prod-aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +			"http://localhost/redirect/https://eastus.oic.prod-aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +		},
    +	}, {
    +		name:   "CircleCI meta URL",
    +		issuer: "https://oidc.circleci.com/org/*",
    +		matches: []string{
    +			"https://oidc.circleci.com/org/my-org",
    +			"https://oidc.circleci.com/org/12345",
    +		},
    +		misses: []string{
    +			// Extra slashes
    +			"https://oidc.circleci.com/org/my-org/extra",
    +			// Missing org
    +			"https://oidc.circleci.com/org/",
    +			// Issuer is not the URL host (GHSA-59jp-pj84-45mr)
    +			"http://localhost?issuer=https://oidc.circleci.com/org/my-org",
    +		},
    +	}, {
    +		name:   "Azure prod-aks meta URL",
    +		issuer: "https://oidc.prod-aks.azure.com/*",
    +		matches: []string{
    +			"https://oidc.prod-aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +		},
    +		misses: []string{
    +			// Extra slashes
    +			"https://oidc.prod-aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0/extra",
    +			// Issuer is not the URL host (GHSA-59jp-pj84-45mr)
    +			"http://localhost?issuer=https://oidc.prod-aks.azure.com/ffffffff-eeee-dddd-cccc-bbbbbbbbbbb0",
    +		},
    +	}, {
    +		name:   "GitHub Actions meta URL",
    +		issuer: "https://token.actions.githubusercontent.com/*",
    +		matches: []string{
    +			"https://token.actions.githubusercontent.com/some-enterprise",
    +		},
    +		misses: []string{
    +			// Extra slashes
    +			"https://token.actions.githubusercontent.com/some-enterprise/extra",
    +			// Issuer is not the URL host (GHSA-59jp-pj84-45mr)
    +			"http://localhost?issuer=https://token.actions.githubusercontent.com/some-enterprise",
     		},
     	}}
     
     	for _, test := range tests {
     		t.Run(test.name, func(t *testing.T) {
    -			re, err := metaRegex(test.issuer)
    +			re, err := MetaRegex(test.issuer)
     			if err != nil {
     				t.Errorf("metaRegex() = %v", err)
     			}
    
  • pkg/identity/base/issuer.go+1 18 modified
    @@ -17,8 +17,6 @@ package base
     import (
     	"context"
     	"fmt"
    -	"regexp"
    -	"strings"
     
     	"github.com/google/go-cmp/cmp/cmpopts"
     	"github.com/sigstore/fulcio/pkg/config"
    @@ -50,24 +48,9 @@ func (e *baseIssuer) Match(_ context.Context, url string) bool {
     	}
     	// If this is a MetaIssuer the issuer URL could be a regex
     	// Check if the regex is valid against the provided url
    -	re, err := metaRegex(e.issuerURL)
    +	re, err := config.MetaRegex(e.issuerURL)
     	if err != nil {
     		return false
     	}
     	return re.MatchString(url)
     }
    -
    -func metaRegex(issuer string) (*regexp.Regexp, error) {
    -	// Quote all of the "meta" characters like `.` to avoid
    -	// those literal characters in the URL matching any character.
    -	// This will ALSO quote `*`, so we replace the quoted version.
    -	quoted := regexp.QuoteMeta(issuer)
    -
    -	// Replace the quoted `*` with a regular expression that
    -	// will match alpha-numeric parts with common additional
    -	// "special" characters.
    -	replaced := strings.ReplaceAll(quoted, regexp.QuoteMeta("*"), "[-_a-zA-Z0-9]+")
    -
    -	// Compile into a regular expression.
    -	return regexp.Compile(replaced)
    -}
    
  • pkg/identity/base/issuer_test.go+0 52 modified
    @@ -19,58 +19,6 @@ import (
     	"testing"
     )
     
    -func TestMetaURLs(t *testing.T) {
    -	tests := []struct {
    -		name    string
    -		issuer  string
    -		matches []string
    -		misses  []string
    -	}{{
    -		name:   "AWS meta URL",
    -		issuer: "https://oidc.eks.*.amazonaws.com/id/*",
    -		matches: []string{
    -			"https://oidc.eks.us-west-2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB",
    -		},
    -		misses: []string{
    -			// Extra dots
    -			"https://oidc.eks.us.west.2.amazonaws.com/id/B02C93B6A2D30341AD01E1B6D48164CB",
    -			// Extra slashes
    -			"https://oidc.eks.us-west/2.amazonaws.com/id/B02C93B6A2D3/0341AD01E1B6D48164CB",
    -		},
    -	}, {
    -		name:   "GKE meta URL",
    -		issuer: "https://container.googleapis.com/v1/projects/*/locations/*/clusters/*",
    -		matches: []string{
    -			"https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us-west1-b/clusters/tenant-cluster",
    -		},
    -		misses: []string{
    -			// Extra dots
    -			"https://container.googleapis.com/v1/projects/mattmoor-credit/locations/us.west1.b/clusters/tenant-cluster",
    -		},
    -	}}
    -
    -	for _, test := range tests {
    -		t.Run(test.name, func(t *testing.T) {
    -			re, err := metaRegex(test.issuer)
    -			if err != nil {
    -				t.Errorf("metaRegex() = %v", err)
    -			}
    -
    -			for _, match := range test.matches {
    -				if !re.MatchString(match) {
    -					t.Errorf("MatchString(%q) = false, wanted true", match)
    -				}
    -			}
    -
    -			for _, miss := range test.misses {
    -				if re.MatchString(miss) {
    -					t.Errorf("MatchString(%q) = true, wanted false", miss)
    -				}
    -			}
    -		})
    -	}
    -}
    -
     func TestMatch(t *testing.T) {
     	tests := []struct {
     		description string
    

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

4

News mentions

0

No linked articles in our index yet.