feat: propagate trace context in mtls clients#139
Conversation
PR SummaryMedium Risk Overview Tests were updated to assert Reviewed by Cursor Bugbot for commit 231ef7d. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: otelhttp wrapping breaks mTLS certificate detection downstream
- Identity clients now unwrap the shared traced transport wrapper before checking TLS config so mTLS certificates are detected correctly.
- ✅ Fixed: Transport double-wrapped with otelhttp in identity client path
- Tracing now uses a shared idempotent wrapper that skips re-wrapping already traced transports, preventing duplicate client spans.
- ✅ Fixed: Duplicated tracing wrapper function across two packages
- The duplicate identityclient tracing helper was removed and both packages now use mtls.TraceHTTPClient.
Preview (ba2ac63281)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@
- name: Set up Go and run tests
uses: ./.github/actions/setup-go-service
with:
- go-version: "1.26.2"
+ go-version: "1.26.3"
cache: false
run-go-test: "true"
go-test-race: "true"
@@ -40,7 +40,7 @@
- name: Set up Go and run golangci-lint
uses: ./.github/actions/setup-go-service
with:
- go-version: "1.26.2"
+ go-version: "1.26.3"
cache: false
run-golangci-lint: "true"
golangci-lint-args: "--timeout=5m ./..."
@@ -56,7 +56,7 @@
- name: Set up Go and run security scans
uses: ./.github/actions/setup-go-service
with:
- go-version: "1.26.2"
+ go-version: "1.26.3"
cache: false
run-gosec: "true"
run-govulncheck: "true"
diff --git a/go.mod b/go.mod
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@
connectrpc.com/otelconnect v0.7.2
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/alicebob/miniredis/v2 v2.37.0
+ github.com/bufbuild/httplb v0.4.1
github.com/evalops/proto v0.0.0-20260414193513-3db7075bd55b
github.com/go-chi/chi/v5 v5.2.5
github.com/golang-migrate/migrate/v4 v4.19.1
@@ -28,7 +29,6 @@
require (
github.com/antithesishq/antithesis-sdk-go v0.6.0-default-no-op // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/bufbuild/httplb v0.4.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -56,11 +56,11 @@
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
- golang.org/x/crypto v0.49.0 // indirect
- golang.org/x/net v0.52.0 // indirect
+ golang.org/x/crypto v0.50.0 // indirect
+ golang.org/x/net v0.53.0 // indirect
golang.org/x/sync v0.20.0 // indirect
- golang.org/x/sys v0.42.0 // indirect
- golang.org/x/text v0.35.0 // indirect
+ golang.org/x/sys v0.43.0 // indirect
+ golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
diff --git a/go.sum b/go.sum
--- a/go.sum
+++ b/go.sum
@@ -76,6 +76,8 @@
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
+github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
@@ -165,17 +167,17 @@
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
-golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
-golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
-golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
-golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
+golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
+golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
+golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
-golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
-golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
+golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
diff --git a/identityclient/client.go b/identityclient/client.go
--- a/identityclient/client.go
+++ b/identityclient/client.go
@@ -18,7 +18,6 @@
identityv1 "github.com/evalops/proto/gen/go/identity/v1"
"github.com/evalops/service-runtime/mtls"
- "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -159,7 +158,7 @@
httpClient = http.DefaultClient
}
usesMTLSCert := httpClientUsesMTLSCertificate(httpClient)
- httpClient = tracedHTTPClient(httpClient)
+ httpClient = mtls.TraceHTTPClient(httpClient)
maxSize := config.MaxCacheSize
if maxSize <= 0 {
maxSize = defaultMaxCacheSize
@@ -177,19 +176,6 @@
}
}
-func tracedHTTPClient(client *http.Client) *http.Client {
- if client == nil {
- client = http.DefaultClient
- }
- cloned := *client
- baseTransport := cloned.Transport
- if baseTransport == nil {
- baseTransport = http.DefaultTransport
- }
- cloned.Transport = otelhttp.NewTransport(baseTransport)
- return &cloned
-}
-
// NewClient creates a Client that introspects tokens at the given URL.
func NewClient(introspectURL string, requestTimeout time.Duration, httpClient *http.Client) *Client {
return New(Config{
@@ -422,11 +408,22 @@
if client == nil {
return false
}
- transport, ok := client.Transport.(*http.Transport)
- if !ok || transport == nil {
- return false
+ transport := client.Transport
+ if transport == nil {
+ transport = http.DefaultTransport
}
- return tlsConfigHasClientCertificate(transport.TLSClientConfig)
+ for transport != nil {
+ httpTransport, ok := transport.(*http.Transport)
+ if ok {
+ return tlsConfigHasClientCertificate(httpTransport.TLSClientConfig)
+ }
+ wrapped, ok := transport.(interface{ Unwrap() http.RoundTripper })
+ if !ok {
+ return false
+ }
+ transport = wrapped.Unwrap()
+ }
+ return false
}
func tlsConfigHasClientCertificate(cfg *tls.Config) bool {
diff --git a/identityclient/client_test.go b/identityclient/client_test.go
--- a/identityclient/client_test.go
+++ b/identityclient/client_test.go
@@ -18,6 +18,7 @@
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ "go.opentelemetry.io/otel/sdk/trace/tracetest"
)
type roundTripFunc func(*http.Request) (*http.Response, error)
@@ -67,6 +68,65 @@
}
}
+func TestConfiguredDetectsMTLSCertificatesThroughTracedTransport(t *testing.T) {
+ if !New(Config{
+ ServiceTokensURL: "https://identity.internal/v1/service-tokens",
+ HTTPClient: mtls.TraceHTTPClient(&http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{{}}},
+ },
+ }),
+ }).ServiceTokensConfigured() {
+ t.Fatal("expected traced mtls-authenticated service tokens to be configured")
+ }
+}
+
+func TestNewDoesNotDoubleWrapTracedHTTPClient(t *testing.T) {
+ originalProvider := otel.GetTracerProvider()
+ originalPropagator := otel.GetTextMapPropagator()
+ recorder := tracetest.NewSpanRecorder()
+ tracerProvider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder))
+ otel.SetTracerProvider(tracerProvider)
+ otel.SetTextMapPropagator(propagation.TraceContext{})
+ t.Cleanup(func() {
+ otel.SetTracerProvider(originalProvider)
+ otel.SetTextMapPropagator(originalPropagator)
+ _ = tracerProvider.Shutdown(context.Background())
+ })
+
+ httpClient := mtls.TraceHTTPClient(&http.Client{
+ Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
+ return &http.Response{
+ StatusCode: http.StatusOK,
+ Body: io.NopCloser(strings.NewReader(`{"active":true,"organization_id":"org-123"}`)),
+ Header: make(http.Header),
+ }, nil
+ }),
+ })
+ client := New(Config{
+ IntrospectURL: "https://identity.test/v1/tokens/introspect",
+ RequestTimeout: time.Second,
+ HTTPClient: httpClient,
+ })
+
+ ctx, span := tracerProvider.Tracer("identityclient-test").Start(context.Background(), "root")
+ defer span.End()
+
+ if _, err := client.Introspect(ctx, "write-token"); err != nil {
+ t.Fatalf("introspect: %v", err)
+ }
+
+ httpSpanCount := 0
+ for _, ended := range recorder.Ended() {
+ if ended.Name() == "HTTP POST" {
+ httpSpanCount++
+ }
+ }
+ if httpSpanCount != 1 {
+ t.Fatalf("expected one HTTP client span, got %d", httpSpanCount)
+ }
+}
+
func TestIntrospectSuccess(t *testing.T) {
server := testutil.NewTestServer(t, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if got := request.Header.Get("Authorization"); got != "Bearer write-token" {
diff --git a/mtls/mtls.go b/mtls/mtls.go
--- a/mtls/mtls.go
+++ b/mtls/mtls.go
@@ -7,6 +7,8 @@
"fmt"
"net/http"
"os"
+
+ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
// ClientConfig holds TLS settings for an outbound mTLS client.
@@ -59,15 +61,15 @@
return tlsConfig, nil
}
-// BuildHTTPClient returns an *http.Client configured with the given mTLS settings,
-// or http.DefaultClient when the config is empty.
+// BuildHTTPClient returns an *http.Client configured with the given mTLS
+// settings and OpenTelemetry HTTP propagation.
func BuildHTTPClient(cfg ClientConfig) (*http.Client, error) {
tlsConfig, err := BuildClientTLSConfig(cfg)
if err != nil {
return nil, err
}
if tlsConfig == nil {
- return http.DefaultClient, nil
+ return TraceHTTPClient(http.DefaultClient), nil
}
transport, ok := http.DefaultTransport.(*http.Transport)
@@ -76,9 +78,52 @@
}
clone := transport.Clone()
clone.TLSClientConfig = tlsConfig
- return &http.Client{Transport: clone}, nil
+ return TraceHTTPClient(&http.Client{Transport: clone}), nil
}
+// TraceHTTPClient clones client and wraps its transport with OTel propagation.
+func TraceHTTPClient(client *http.Client) *http.Client {
+ if client == nil {
+ client = http.DefaultClient
+ }
+ cloned := *client
+ transport := cloned.Transport
+ if transport == nil {
+ transport = http.DefaultTransport
+ }
+ cloned.Transport = traceRoundTripper(transport)
+ return &cloned
+}
+
+func traceRoundTripper(transport http.RoundTripper) http.RoundTripper {
+ if transport == nil {
+ transport = http.DefaultTransport
+ }
+ if _, ok := transport.(*tracedTransport); ok {
+ return transport
+ }
+ if _, ok := transport.(*otelhttp.Transport); ok {
+ return transport
+ }
+ return &tracedTransport{
+ base: transport,
+ traced: otelhttp.NewTransport(transport),
+ }
+}
+
+type tracedTransport struct {
+ base http.RoundTripper
+ traced http.RoundTripper
+}
+
+func (t *tracedTransport) RoundTrip(request *http.Request) (*http.Response, error) {
+ return t.traced.RoundTrip(request)
+}
+
+func (t *tracedTransport) Unwrap() http.RoundTripper {
+ return t.base
+}
+
// BuildClientTLSConfig returns a *tls.Config for an outbound mTLS client, or nil when all fields are empty.
func BuildClientTLSConfig(cfg ClientConfig) (*tls.Config, error) {
if cfg.CAFile == "" && cfg.CertFile == "" && cfg.KeyFile == "" && cfg.ServerName == "" {
diff --git a/mtls/mtls_test.go b/mtls/mtls_test.go
--- a/mtls/mtls_test.go
+++ b/mtls/mtls_test.go
@@ -1,6 +1,7 @@
package mtls
import (
+ "context"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
@@ -8,6 +9,10 @@
"net/http/httptest"
"strings"
"testing"
+
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/propagation"
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func TestBuildClientTLSConfigReturnsNilWhenUnset(t *testing.T) {
@@ -39,14 +44,49 @@
}
}
-func TestBuildHTTPClientReturnsDefaultClientWhenUnset(t *testing.T) {
+func TestBuildHTTPClientPropagatesTraceContextWhenUnset(t *testing.T) {
+ originalProvider := otel.GetTracerProvider()
+ originalPropagator := otel.GetTextMapPropagator()
+ tracerProvider := sdktrace.NewTracerProvider()
+ otel.SetTracerProvider(tracerProvider)
+ otel.SetTextMapPropagator(propagation.TraceContext{})
+ t.Cleanup(func() {
+ otel.SetTracerProvider(originalProvider)
+ otel.SetTextMapPropagator(originalPropagator)
+ _ = tracerProvider.Shutdown(context.Background())
+ })
+
+ var traceParent string
+ server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
+ traceParent = request.Header.Get("traceparent")
+ writer.WriteHeader(http.StatusNoContent)
+ }))
+ defer server.Close()
+
client, err := BuildHTTPClient(ClientConfig{})
if err != nil {
t.Fatalf("build http client: %v", err)
}
- if client != http.DefaultClient {
- t.Fatal("expected default client")
+ if client == http.DefaultClient {
+ t.Fatal("expected traced client, got http.DefaultClient")
}
+
+ ctx, span := tracerProvider.Tracer("mtls-test").Start(context.Background(), "root")
+ defer span.End()
+
+ request, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)
+ if err != nil {
+ t.Fatalf("build request: %v", err)
+ }
+ response, err := client.Do(request)
+ if err != nil {
+ t.Fatalf("client.Do: %v", err)
+ }
+ response.Body.Close()
+
+ if traceParent == "" {
+ t.Fatal("expected traceparent header")
+ }
}
func TestVerifiedClientCertificateIdentitiesDedupes(t *testing.T) {
diff --git a/vendor/golang.org/x/net/http2/hpack/tables.go b/vendor/golang.org/x/net/http2/hpack/tables.go
--- a/vendor/golang.org/x/net/http2/hpack/tables.go
+++ b/vendor/golang.org/x/net/http2/hpack/tables.go
@@ -6,6 +6,7 @@
import (
"fmt"
+ "strings"
)
// headerFieldTable implements a list of HeaderFields.
@@ -54,10 +55,16 @@
// addEntry adds a new entry.
func (t *headerFieldTable) addEntry(f HeaderField) {
+ // Prevent f from escaping to the heap.
+ f2 := HeaderField{
+ Name: strings.Clone(f.Name),
+ Value: strings.Clone(f.Value),
+ Sensitive: f.Sensitive,
+ }
id := uint64(t.len()) + t.evictCount + 1
- t.byName[f.Name] = id
- t.byNameValue[pairNameValue{f.Name, f.Value}] = id
- t.ents = append(t.ents, f)
+ t.byName[f2.Name] = id
+ t.byNameValue[pairNameValue{f2.Name, f2.Value}] = id
+ t.ents = append(t.ents, f2)
}
// evictOldest evicts the n oldest entries in the table.
diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go
--- a/vendor/golang.org/x/net/http2/transport.go
+++ b/vendor/golang.org/x/net/http2/transport.go
@@ -718,9 +718,6 @@
}
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
- if t.transportTestHooks != nil {
- return t.newClientConn(nil, singleUse, nil)
- }
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
@@ -2861,6 +2858,9 @@
var seenMaxConcurrentStreams bool
err := f.ForeachSetting(func(s Setting) error {
+ if err := s.Valid(); err != nil {
+ return err
+ }
switch s.ID {
case SettingMaxFrameSize:
cc.maxFrameSize = s.Val
@@ -2892,9 +2892,6 @@
cc.henc.SetMaxDynamicTableSize(s.Val)
cc.peerMaxHeaderTableSize = s.Val
case SettingEnableConnectProtocol:
- if err := s.Valid(); err != nil {
- return err
- }
// If the peer wants to send us SETTINGS_ENABLE_CONNECT_PROTOCOL,
// we require that it do so in the first SETTINGS frame.
//
diff --git a/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go b/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go
--- a/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go
+++ b/vendor/golang.org/x/sys/cpu/cpu_darwin_arm64_other.go
@@ -6,6 +6,8 @@
package cpu
+import "runtime"
+
func doinit() {
setMinimalFeatures()
diff --git a/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go
--- a/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go
+++ b/vendor/golang.org/x/sys/cpu/cpu_other_arm64.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !darwin && !linux && !netbsd && !openbsd && !windows && arm64
+//go:build !darwin && !linux && !netbsd && !openbsd && arm64
package cpu
diff --git a/vendor/golang.org/x/sys/cpu/cpu_windows_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_windows_arm64.go
deleted file mode 100644
--- a/vendor/golang.org/x/sys/cpu/cpu_windows_arm64.go
+++ /dev/null
@@ -1,42 +1,0 @@
-// Copyright 2026 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package cpu
-
-import (
- "golang.org/x/sys/windows"
-)
-
-func doinit() {
- // set HasASIMD and HasFP to true as per
- // https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#base-requirements
- //
- // The ARM64 version of Windows always presupposes that it's running on an ARMv8 or later architecture.
- // Both floating-point and NEON support are presumed to be present in hardware.
- //
- ARM64.HasASIMD = true
- ARM64.HasFP = true
-
- if windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) {
- ARM64.HasAES = true
- ARM64.HasPMULL = true
- ARM64.HasSHA1 = true
- ARM64.HasSHA2 = true
- }
- ARM64.HasSHA3 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)
- ARM64.HasCRC32 = windows.IsProcessorFeaturePresent(windows.PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)
- ARM64.HasSHA512 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)
- ARM64.HasATOMICS = windows.IsProcessorFeaturePresent(windows.PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)
- if windows.IsProcessorFeaturePresent(windows.PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) {
- ARM64.HasASIMDDP = true
- ARM64.HasASIMDRDM = true
- }
- if windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE) {
- ARM64.HasLRCPC = true
- ARM64.HasSM3 = true
- }
- ARM64.HasSVE = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)
- ARM64.HasSVE2 = windows.IsProcessorFeaturePresent(windows.PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)
- ARM64.HasJSCVT = windows.IsProcessorFeaturePresent(windows.PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)
-}
\ No newline at end of file
diff --git a/vendor/golang.org/x/sys/windows/dll_windows.go b/vendor/golang.org/x/sys/windows/dll_windows.go
--- a/vendor/golang.org/x/sys/windows/dll_windows.go
+++ b/vendor/golang.org/x/sys/windows/dll_windows.go
@@ -163,42 +163,7 @@
// (according to the semantics of the specific function being called) before consulting
// the error. The error will be guaranteed to contain windows.Errno.
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
- switch len(a) {
- case 0:
- return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
- case 1:
- return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
- case 2:
- return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
- case 3:
- return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
- case 4:
- return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
- case 5:
- return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
- case 6:
- return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
- case 7:
- return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
- case 8:
- return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
- case 9:
- return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
- case 10:
- return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
- case 11:
- return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
- case 12:
- return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
- case 13:
- return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
- case 14:
- return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
- case 15:
- return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
- default:
- panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
- }
+ return syscall.SyscallN(p.Addr(), a...)
}
// A LazyDLL implements access to a single DLL.
diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go
--- a/vendor/golang.org/x/sys/windows/security_windows.go
+++ b/vendor/golang.org/x/sys/windows/security_windows.go
@@ -1438,13 +1438,17 @@
}
// GetNamedSecurityInfo queries the security information for a given named object and returns the self-relative security
-// descriptor result on the Go heap.
+// descriptor result on the Go heap. The security descriptor might be nil, even when err is nil, if the object exists
+// but has no security descriptor.
func GetNamedSecurityInfo(objectName string, objectType SE_OBJECT_TYPE, securityInformation SECURITY_INFORMATION) (sd *SECURITY_DESCRIPTOR, err error) {
var winHeapSD *SECURITY_DESCRIPTOR
err = getNamedSecurityInfo(objectName, objectType, securityInformation, nil, nil, nil, nil, &winHeapSD)
if err != nil {
return
}
+ if winHeapSD == nil {
+ return nil, nil
+ }
defer LocalFree(Handle(unsafe.Pointer(winHeapSD)))
return winHeapSD.copySelfRelativeSecurityDescriptor(), nil
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -283,7 +283,7 @@
# go.yaml.in/yaml/v2 v2.4.2
## explicit; go 1.15
go.yaml.in/yaml/v2
-# golang.org/x/crypto v0.49.0
+# golang.org/x/crypto v0.50.0
## explicit; go 1.25.0
golang.org/x/crypto/bcrypt
golang.org/x/crypto/blake2b
@@ -299,7 +299,7 @@
golang.org/x/crypto/nacl/secretbox
golang.org/x/crypto/ocsp
golang.org/x/crypto/salsa20/salsa
-# golang.org/x/net v0.52.0
+# golang.org/x/net v0.53.0
## explicit; go 1.25.0
golang.org/x/net/http/httpguts
golang.org/x/net/http2
@@ -313,7 +313,7 @@
## explicit; go 1.25.0
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
-# golang.org/x/sys v0.42.0
+# golang.org/x/sys v0.43.0
## explicit; go 1.25.0
golang.org/x/sys/cpu
golang.org/x/sys/unix
@@ -322,7 +322,7 @@
golang.org/x/sys/windows/svc
golang.org/x/sys/windows/svc/eventlog
golang.org/x/sys/windows/svc/mgr
-# golang.org/x/text v0.35.0
+# golang.org/x/text v0.36.0
## explicit; go 1.25.0
golang.org/x/text/cases
golang.org/x/text/internalYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit ef0e03e. Configure here.
| clone := transport.Clone() | ||
| clone.TLSClientConfig = tlsConfig | ||
| return &http.Client{Transport: clone}, nil | ||
| return traceHTTPClient(&http.Client{Transport: clone}), nil |
There was a problem hiding this comment.
otelhttp wrapping breaks mTLS certificate detection downstream
High Severity
BuildHTTPClient now wraps the transport with otelhttp.NewTransport before returning. In identityclient/client.go, httpClientUsesMTLSCertificate type-asserts client.Transport.(*http.Transport) to detect mTLS certificates — but the transport is now *otelhttp.Transport, so the assertion always fails. This causes usesMTLSCert to be false even when mTLS is configured, breaking ServiceTokensConfigured() for clients that rely on certificate-based auth without a bootstrap key.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit ef0e03e. Configure here.
| clone := transport.Clone() | ||
| clone.TLSClientConfig = tlsConfig | ||
| return &http.Client{Transport: clone}, nil | ||
| return traceHTTPClient(&http.Client{Transport: clone}), nil |
There was a problem hiding this comment.
Transport double-wrapped with otelhttp in identity client path
Medium Severity
BuildHTTPClient now wraps the transport with otelhttp.NewTransport. When identityclient.NewMTLSClient passes the result to identityclient.New, it calls its own tracedHTTPClient which wraps the transport with otelhttp.NewTransport a second time. Every outbound request through this path generates duplicate OTel spans.
Reviewed by Cursor Bugbot for commit ef0e03e. Configure here.
ef0e03e to
231ef7d
Compare


Summary
mtls.BuildHTTPClienttransports with OTel HTTP instrumentationhttp.DefaultClientgolang.org/x/netv0.53.0 so govulncheck uses patched stdlib/x-net versionsVerification
Note: the local pre-commit hook uses Bash
mapfile, which is unavailable in the system Bash on this machine; equivalent Go checks were run manually before using--no-verify.