From 99ae605359309ee0b62db444832ad13601debfd3 Mon Sep 17 00:00:00 2001
From: Easwar Swaminathan <easwars@google.com>
Date: Wed, 11 Mar 2026 23:34:57 +0000
Subject: [PATCH 1/3] grpc: enforce strict path checking for incoming requests
 on the server

---
 internal/envconfig/envconfig.go |  16 +++
 server.go                       |  57 +++++++---
 2 files changed, 234 insertions(+), 16 deletions(-)

diff --git a/internal/envconfig/envconfig.go b/internal/envconfig/envconfig.go
index e8dc791299ea..7ad6fb44ca85 100644
--- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go
+++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go
@@ -35,4 +35,29 @@ var (
 	Retry = strings.EqualFold(os.Getenv(retryStr), "on")
 	// TXTErrIgnore is set if TXT errors should be ignored ("GRPC_GO_IGNORE_TXT_ERRORS" is not "false").
 	TXTErrIgnore = !strings.EqualFold(os.Getenv(retryStr), "false")
+
+	// DisableStrictPathChecking indicates whether strict path checking is
+	// disabled. This feature can be disabled by setting the environment
+	// variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to "true".
+	//
+	// When strict path checking is enabled, gRPC will reject requests with
+	// paths that do not conform to the gRPC over HTTP/2 specification found at
+	// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
+	//
+	// When disabled, gRPC will allow paths that do not contain a leading slash.
+	// Enabling strict path checking is recommended for security reasons, as it
+	// prevents potential path traversal vulnerabilities.
+	//
+	// A future release will remove this environment variable, enabling strict
+	// path checking behavior unconditionally.
+	DisableStrictPathChecking = boolFromEnv("GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING", false)
 )
+
+func boolFromEnv(envVar string, def bool) bool {
+	if def {
+		// The default is true; return true unless the variable is "false".
+		return !strings.EqualFold(os.Getenv(envVar), "false")
+	}
+	// The default is false; return false unless the variable is "true".
+	return strings.EqualFold(os.Getenv(envVar), "true")
+}
diff --git a/server.go b/server.go
index 1b5cefe81715..8efb29a7b95c 100644
--- a/vendor/google.golang.org/grpc/server.go
+++ b/vendor/google.golang.org/grpc/server.go
@@ -42,6 +42,7 @@ import (
 	"google.golang.org/grpc/grpclog"
 	"google.golang.org/grpc/internal/binarylog"
 	"google.golang.org/grpc/internal/channelz"
+	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/grpcsync"
 	"google.golang.org/grpc/internal/transport"
 	"google.golang.org/grpc/keepalive"
@@ -107,6 +108,8 @@ type Server struct {
 
 	channelzID int64 // channelz unique identification number
 	czData     *channelzData
+
+	strictPathCheckingLogEmitted atomic.Bool
 }
 
 type serverOptions struct {
@@ -1372,28 +1375,50 @@ func (s *Server) processStreamingRPC(ctx context.Context, stream *transport.Serv
 	return err
 }
 
+func (s *Server) handleMalformedMethodName(t transport.ServerTransport, stream *transport.Stream, ti *traceInfo) {
+	if ti != nil {
+		ti.tr.LazyLog(&fmtStringer{"Malformed method name %q", []interface{}{stream.Method()}}, true)
+		ti.tr.SetError()
+	}
+	errDesc := fmt.Sprintf("malformed method name: %q", stream.Method())
+	if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
+		if ti != nil {
+			ti.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true)
+			ti.tr.SetError()
+		}
+		channelz.Warningf(s.channelzID, "grpc: Server.handleStream failed to write status: %v", err)
+	}
+	if ti != nil {
+		ti.tr.Finish()
+	}
+}
+
 func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) {
 	sm := stream.Method()
-	if sm != "" && sm[0] == '/' {
+	if sm == "" {
+		s.handleMalformedMethodName(t, stream, trInfo)
+		return
+	}
+	if sm[0] != '/' {
+		// TODO(easwars): Add a link to the CVE in the below log messages once
+		// published.
+		if envconfig.DisableStrictPathChecking {
+			if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
+				channelz.Warningf(s.channelzID, "grpc: Server.handleStream received malformed method name %q. Allowing it because the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING is set to true, but this option will be removed in a future release.", sm)
+			}
+		} else {
+			if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
+				channelz.Warningf(s.channelzID, "grpc: Server.handleStream rejected malformed method name %q. To temporarily allow such requests, set the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to true. Note that this is not recommended as it may allow requests to bypass security policies.", sm)
+			}
+			s.handleMalformedMethodName(t, stream, trInfo)
+			return
+		}
+	} else {
 		sm = sm[1:]
 	}
 	pos := strings.LastIndex(sm, "/")
 	if pos == -1 {
-		if trInfo != nil {
-			trInfo.tr.LazyLog(&fmtStringer{"Malformed method name %q", []interface{}{sm}}, true)
-			trInfo.tr.SetError()
-		}
-		errDesc := fmt.Sprintf("malformed method name: %q", stream.Method())
-		if err := t.WriteStatus(stream, status.New(codes.ResourceExhausted, errDesc)); err != nil {
-			if trInfo != nil {
-				trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true)
-				trInfo.tr.SetError()
-			}
-			channelz.Warningf(s.channelzID, "grpc: Server.handleStream failed to write status: %v", err)
-		}
-		if trInfo != nil {
-			trInfo.tr.Finish()
-		}
+		s.handleMalformedMethodName(t, stream, trInfo)
 		return
 	}
 	service := sm[:pos]
