VYPR
Medium severityOSV Advisory· Published Dec 30, 2025· Updated Apr 15, 2026

CVE-2025-14987

CVE-2025-14987

Description

When system.enableCrossNamespaceCommands is enabled (on by default), the Temporal server permits certain workflow task commands (e.g. StartChildWorkflowExecution, SignalExternalWorkflowExecution, RequestCancelExternalWorkflowExecution) to target a different namespace than the namespace authorized at the gRPC boundary. The frontend authorizes RespondWorkflowTaskCompleted based on the outer request namespace, but the history service later resolves and executes the command using the namespace embedded in command attributes without authorizing the caller for that target namespace. This can allow a worker authorized for one namespace to create, signal, or cancel workflows in another namespace. This issue affects Temporal: through 1.29.1. Fixed in 1.27.4, 1.28.2, 1.29.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
go.temporal.io/serverGo
< 1.27.41.27.4
go.temporal.io/serverGo
>= 1.28.0, < 1.28.21.28.2
go.temporal.io/serverGo
>= 1.29.0, < 1.29.21.29.2
go.temporal.io/serverGo
>= 1.29.0-0, < 1.29.0-135.0.0.20251218190115-b292a32bacdf1.29.0-135.0.0.20251218190115-b292a32bacdf

Affected products

1

Patches

4
b292a32bacdf

Authorize cross namespace commands (#8858)

https://github.com/temporalio/temporalPrathyush PVDec 18, 2025via ghsa
4 files changed · +434 24
  • common/authorization/interceptor.go+101 18 modified
    @@ -7,7 +7,10 @@ import (
     	"crypto/x509/pkix"
     	"time"
     
    +	enumspb "go.temporal.io/api/enums/v1"
     	"go.temporal.io/api/serviceerror"
    +	"go.temporal.io/api/workflowservice/v1"
    +	"go.temporal.io/server/common/api"
     	"go.temporal.io/server/common/dynamicconfig"
     	"go.temporal.io/server/common/headers"
     	"go.temporal.io/server/common/log"
    @@ -77,15 +80,16 @@ func PeerCert(tlsInfo *credentials.TLSInfo) *x509.Certificate {
     }
     
     type Interceptor struct {
    -	claimMapper            ClaimMapper
    -	authorizer             Authorizer
    -	metricsHandler         metrics.Handler
    -	logger                 log.Logger
    -	namespaceChecker       NamespaceChecker
    -	audienceGetter         JWTAudienceMapper
    -	authHeaderName         string
    -	authExtraHeaderName    string
    -	exposeAuthorizerErrors dynamicconfig.BoolPropertyFn
    +	claimMapper                  ClaimMapper
    +	authorizer                   Authorizer
    +	metricsHandler               metrics.Handler
    +	logger                       log.Logger
    +	namespaceChecker             NamespaceChecker
    +	audienceGetter               JWTAudienceMapper
    +	authHeaderName               string
    +	authExtraHeaderName          string
    +	exposeAuthorizerErrors       dynamicconfig.BoolPropertyFn
    +	enableCrossNamespaceCommands dynamicconfig.BoolPropertyFn
     }
     
     // NewInterceptor creates an authorization interceptor.
    @@ -99,17 +103,19 @@ func NewInterceptor(
     	authHeaderName string,
     	authExtraHeaderName string,
     	exposeAuthorizerErrors dynamicconfig.BoolPropertyFn,
    +	enableCrossNamespaceCommands dynamicconfig.BoolPropertyFn,
     ) *Interceptor {
     	return &Interceptor{
    -		claimMapper:            claimMapper,
    -		authorizer:             authorizer,
    -		logger:                 logger,
    -		namespaceChecker:       namespaceChecker,
    -		metricsHandler:         metricsHandler,
    -		authHeaderName:         cmp.Or(authHeaderName, defaultAuthHeaderName),
    -		authExtraHeaderName:    cmp.Or(authExtraHeaderName, defaultAuthExtraHeaderName),
    -		audienceGetter:         audienceGetter,
    -		exposeAuthorizerErrors: exposeAuthorizerErrors,
    +		claimMapper:                  claimMapper,
    +		authorizer:                   authorizer,
    +		logger:                       logger,
    +		namespaceChecker:             namespaceChecker,
    +		metricsHandler:               metricsHandler,
    +		authHeaderName:               cmp.Or(authHeaderName, defaultAuthHeaderName),
    +		authExtraHeaderName:          cmp.Or(authExtraHeaderName, defaultAuthExtraHeaderName),
    +		audienceGetter:               audienceGetter,
    +		exposeAuthorizerErrors:       exposeAuthorizerErrors,
    +		enableCrossNamespaceCommands: enableCrossNamespaceCommands,
     	}
     }
     
    @@ -154,6 +160,11 @@ func (a *Interceptor) Intercept(
     		if err := a.Authorize(ctx, claims, ct); err != nil {
     			return nil, err
     		}
    +
    +		// Authorize target namespaces in cross-namespace commands
    +		if err := a.authorizeTargetNamespaces(ctx, claims, namespace, req); err != nil {
    +			return nil, err
    +		}
     	}
     	return handler(ctx, req)
     }
    @@ -255,3 +266,75 @@ func (a *Interceptor) getMetricsHandler(nsName string) metrics.Handler {
     	}
     	return a.metricsHandler.WithTags(metrics.OperationTag(metrics.AuthorizationScope), nsTag)
     }
    +
    +// authorizeTargetNamespaces authorizes cross-namespace commands in RespondWorkflowTaskCompleted.
    +// Commands like SignalExternalWorkflow, StartChildWorkflow, and CancelExternalWorkflow can target
    +// workflows in different namespaces. This method ensures the caller has permission in those target
    +// namespaces as well.
    +func (a *Interceptor) authorizeTargetNamespaces(
    +	ctx context.Context,
    +	claims *Claims,
    +	sourceNamespace string,
    +	req interface{},
    +) error {
    +	// Skip if cross-namespace commands are not enabled
    +	if !a.enableCrossNamespaceCommands() {
    +		return nil
    +	}
    +
    +	// Cross-namespace commands can only be initiated via RespondWorkflowTaskCompletedRequest.
    +	// Here we handle authorization for all such commands: SignalExternalWorkflow,
    +	// StartChildWorkflow, and RequestCancelExternalWorkflow targeting a different namespace.
    +	wftRequest, ok := req.(*workflowservice.RespondWorkflowTaskCompletedRequest)
    +	if !ok {
    +		return nil
    +	}
    +
    +	// Track namespace+API combinations we've already authorized to avoid duplicate checks
    +	authorizedNamespaceAPIs := make(map[string]struct{})
    +
    +	for _, cmd := range wftRequest.GetCommands() {
    +		var targetNamespace string
    +		var apiName string
    +
    +		switch cmd.GetCommandType() {
    +		case enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION:
    +			if attr := cmd.GetSignalExternalWorkflowExecutionCommandAttributes(); attr != nil {
    +				targetNamespace = attr.GetNamespace()
    +				apiName = "SignalWorkflowExecution"
    +			}
    +		case enumspb.COMMAND_TYPE_START_CHILD_WORKFLOW_EXECUTION:
    +			if attr := cmd.GetStartChildWorkflowExecutionCommandAttributes(); attr != nil {
    +				targetNamespace = attr.GetNamespace()
    +				apiName = "StartWorkflowExecution"
    +			}
    +		case enumspb.COMMAND_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION:
    +			if attr := cmd.GetRequestCancelExternalWorkflowExecutionCommandAttributes(); attr != nil {
    +				targetNamespace = attr.GetNamespace()
    +				apiName = "RequestCancelWorkflowExecution"
    +			}
    +		default:
    +			// Other command types don't target external namespaces
    +		}
    +
    +		// Skip if empty, same as source, or already authorized
    +		if targetNamespace == "" || targetNamespace == sourceNamespace {
    +			continue
    +		}
    +		key := targetNamespace + ":" + apiName
    +		if _, ok := authorizedNamespaceAPIs[key]; ok {
    +			continue
    +		}
    +
    +		// Authorize access to target namespace for this specific API
    +		if err := a.Authorize(ctx, claims, &CallTarget{
    +			APIName:   api.WorkflowServicePrefix + apiName,
    +			Namespace: targetNamespace,
    +			Request:   req,
    +		}); err != nil {
    +			return err
    +		}
    +		authorizedNamespaceAPIs[key] = struct{}{}
    +	}
    +	return nil
    +}
    
  • common/authorization/interceptor_test.go+319 5 modified
    @@ -7,8 +7,11 @@ import (
     
     	"github.com/stretchr/testify/require"
     	"github.com/stretchr/testify/suite"
    +	commandpb "go.temporal.io/api/command/v1"
    +	enumspb "go.temporal.io/api/enums/v1"
     	"go.temporal.io/api/serviceerror"
     	"go.temporal.io/api/workflowservice/v1"
    +	"go.temporal.io/server/common/api"
     	"go.temporal.io/server/common/dynamicconfig"
     	"go.temporal.io/server/common/log"
     	"go.temporal.io/server/common/metrics"
    @@ -19,7 +22,9 @@ import (
     )
     
     const (
    -	testNamespace string = "test-namespace"
    +	testNamespace    string = "test-namespace"
    +	targetNamespace  string = "target-namespace"
    +	anotherNamespace string = "another-namespace"
     )
     
     var (
    @@ -30,6 +35,8 @@ var (
     	startWorkflowExecutionRequest = &workflowservice.StartWorkflowExecutionRequest{Namespace: testNamespace}
     	startWorkflowExecutionTarget  = &CallTarget{Namespace: testNamespace, Request: startWorkflowExecutionRequest, APIName: "/temporal.api.workflowservice.v1.WorkflowService/StartWorkflowExecution"}
     	startWorkflowExecutionInfo    = &grpc.UnaryServerInfo{FullMethod: "/temporal.api.workflowservice.v1.WorkflowService/StartWorkflowExecution"}
    +
    +	respondWorkflowTaskCompletedInfo = &grpc.UnaryServerInfo{FullMethod: api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted"}
     )
     
     type (
    @@ -75,7 +82,8 @@ func (s *authorizerInterceptorSuite) SetupTest() {
     		nil,
     		"",
     		"",
    -		dynamicconfig.GetBoolPropertyFn(false),
    +		dynamicconfig.GetBoolPropertyFn(false), // exposeAuthorizerErrors
    +		dynamicconfig.GetBoolPropertyFn(false), // enableCrossNamespaceCommands
     	)
     	s.handler = func(ctx context.Context, req interface{}) (interface{}, error) { return true, nil }
     }
    @@ -149,7 +157,8 @@ func (s *authorizerInterceptorSuite) TestAuthorizationFailedExposed() {
     		nil,
     		"",
     		"",
    -		dynamicconfig.GetBoolPropertyFn(true),
    +		dynamicconfig.GetBoolPropertyFn(true),  // exposeAuthorizerErrors
    +		dynamicconfig.GetBoolPropertyFn(false), // enableCrossNamespaceCommands
     	)
     
     	authErr := serviceerror.NewInternal("intentional test failure")
    @@ -181,7 +190,8 @@ func (s *authorizerInterceptorSuite) TestNoopClaimMapperWithoutTLS() {
     		nil,
     		"",
     		"",
    -		dynamicconfig.GetBoolPropertyFn(false),
    +		dynamicconfig.GetBoolPropertyFn(false), // exposeAuthorizerErrors
    +		dynamicconfig.GetBoolPropertyFn(false), // enableCrossNamespaceCommands
     	)
     	_, err := interceptor.Intercept(ctx, describeNamespaceRequest, describeNamespaceInfo, s.handler)
     	s.NoError(err)
    @@ -197,7 +207,8 @@ func (s *authorizerInterceptorSuite) TestAlternateHeaders() {
     		nil,
     		"custom-header",
     		"custom-extra-header",
    -		dynamicconfig.GetBoolPropertyFn(false),
    +		dynamicconfig.GetBoolPropertyFn(false), // exposeAuthorizerErrors
    +		dynamicconfig.GetBoolPropertyFn(false), // enableCrossNamespaceCommands
     	)
     
     	cases := []struct {
    @@ -246,3 +257,306 @@ func (n mockNamespaceChecker) Exists(name namespace.Name) error {
     	}
     	return errors.New("doesn't exist")
     }
    +
    +// multiNamespaceChecker is a mock that recognizes multiple namespaces
    +type multiNamespaceChecker []string
    +
    +func (m multiNamespaceChecker) Exists(name namespace.Name) error {
    +	for _, ns := range m {
    +		if ns == string(name) {
    +			return nil
    +		}
    +	}
    +	return errors.New("doesn't exist")
    +}
    +
    +// Helper to create a cross-namespace command
    +func makeCrossNamespaceCommand(commandType enumspb.CommandType, targetNs string) *commandpb.Command {
    +	switch commandType {
    +	case enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION:
    +		return &commandpb.Command{
    +			CommandType: commandType,
    +			Attributes: &commandpb.Command_SignalExternalWorkflowExecutionCommandAttributes{
    +				SignalExternalWorkflowExecutionCommandAttributes: &commandpb.SignalExternalWorkflowExecutionCommandAttributes{
    +					Namespace: targetNs,
    +				},
    +			},
    +		}
    +	case enumspb.COMMAND_TYPE_START_CHILD_WORKFLOW_EXECUTION:
    +		return &commandpb.Command{
    +			CommandType: commandType,
    +			Attributes: &commandpb.Command_StartChildWorkflowExecutionCommandAttributes{
    +				StartChildWorkflowExecutionCommandAttributes: &commandpb.StartChildWorkflowExecutionCommandAttributes{
    +					Namespace: targetNs,
    +				},
    +			},
    +		}
    +	case enumspb.COMMAND_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION:
    +		return &commandpb.Command{
    +			CommandType: commandType,
    +			Attributes: &commandpb.Command_RequestCancelExternalWorkflowExecutionCommandAttributes{
    +				RequestCancelExternalWorkflowExecutionCommandAttributes: &commandpb.RequestCancelExternalWorkflowExecutionCommandAttributes{
    +					Namespace: targetNs,
    +				},
    +			},
    +		}
    +	default:
    +		return nil
    +	}
    +}
    +
    +// Helper to create interceptor with cross-namespace commands enabled
    +func (s *authorizerInterceptorSuite) newCrossNamespaceInterceptor(namespaces ...string) *Interceptor {
    +	return NewInterceptor(
    +		s.mockClaimMapper,
    +		s.mockAuthorizer,
    +		s.mockMetricsHandler,
    +		log.NewNoopLogger(),
    +		multiNamespaceChecker(namespaces),
    +		nil,
    +		"",
    +		"",
    +		dynamicconfig.GetBoolPropertyFn(false), // exposeAuthorizerErrors
    +		dynamicconfig.GetBoolPropertyFn(true),  // enableCrossNamespaceCommands
    +	)
    +}
    +
    +func (s *authorizerInterceptorSuite) TestCrossNamespaceCommands_Authorized() {
    +	testCases := []struct {
    +		name        string
    +		commandType enumspb.CommandType
    +		expectedAPI string
    +	}{
    +		{
    +			name:        "SignalExternalWorkflow",
    +			commandType: enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION,
    +			expectedAPI: "SignalWorkflowExecution",
    +		},
    +		{
    +			name:        "StartChildWorkflow",
    +			commandType: enumspb.COMMAND_TYPE_START_CHILD_WORKFLOW_EXECUTION,
    +			expectedAPI: "StartWorkflowExecution",
    +		},
    +		{
    +			name:        "CancelExternalWorkflow",
    +			commandType: enumspb.COMMAND_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION,
    +			expectedAPI: "RequestCancelWorkflowExecution",
    +		},
    +	}
    +
    +	for _, tc := range testCases {
    +		s.Run(tc.name, func() {
    +			request := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +				Namespace: testNamespace,
    +				Commands:  []*commandpb.Command{makeCrossNamespaceCommand(tc.commandType, targetNamespace)},
    +			}
    +
    +			sourceTarget := &CallTarget{
    +				Namespace: testNamespace,
    +				Request:   request,
    +				APIName:   api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted",
    +			}
    +			crossNsTarget := &CallTarget{
    +				Namespace: targetNamespace,
    +				Request:   request,
    +				APIName:   api.WorkflowServicePrefix + tc.expectedAPI,
    +			}
    +
    +			interceptor := s.newCrossNamespaceInterceptor(testNamespace, targetNamespace)
    +
    +			s.mockAuthorizer.EXPECT().Authorize(ctx, nil, sourceTarget).
    +				Return(Result{Decision: DecisionAllow}, nil)
    +			s.mockMetricsHandler.EXPECT().WithTags(
    +				metrics.OperationTag(metrics.AuthorizationScope),
    +				metrics.NamespaceTag(targetNamespace),
    +			).Return(s.mockMetricsHandler)
    +			s.mockAuthorizer.EXPECT().Authorize(ctx, nil, crossNsTarget).
    +				Return(Result{Decision: DecisionAllow}, nil)
    +
    +			res, err := interceptor.Intercept(ctx, request, respondWorkflowTaskCompletedInfo, s.handler)
    +			s.True(res.(bool))
    +			s.NoError(err)
    +		})
    +	}
    +}
    +
    +func (s *authorizerInterceptorSuite) TestCrossNamespaceCommand_Unauthorized() {
    +	request := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +		Namespace: testNamespace,
    +		Commands:  []*commandpb.Command{makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION, targetNamespace)},
    +	}
    +
    +	sourceTarget := &CallTarget{
    +		Namespace: testNamespace,
    +		Request:   request,
    +		APIName:   api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted",
    +	}
    +	crossNsTarget := &CallTarget{
    +		Namespace: targetNamespace,
    +		Request:   request,
    +		APIName:   api.WorkflowServicePrefix + "SignalWorkflowExecution",
    +	}
    +
    +	interceptor := s.newCrossNamespaceInterceptor(testNamespace, targetNamespace)
    +
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, sourceTarget).
    +		Return(Result{Decision: DecisionAllow}, nil)
    +	s.mockMetricsHandler.EXPECT().WithTags(
    +		metrics.OperationTag(metrics.AuthorizationScope),
    +		metrics.NamespaceTag(targetNamespace),
    +	).Return(s.mockMetricsHandler)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, crossNsTarget).
    +		Return(Result{Decision: DecisionDeny}, nil)
    +	s.mockMetricsHandler.EXPECT().Counter(metrics.ServiceErrUnauthorizedCounter.Name()).Return(metrics.NoopCounterMetricFunc)
    +
    +	res, err := interceptor.Intercept(ctx, request, respondWorkflowTaskCompletedInfo, s.handler)
    +	s.Nil(res)
    +	s.Error(err)
    +}
    +
    +func (s *authorizerInterceptorSuite) TestNoExtraAuthCheck() {
    +	testCases := []struct {
    +		name        string
    +		targetNs    string
    +		description string
    +	}{
    +		{
    +			name:        "SameNamespace",
    +			targetNs:    testNamespace, // Same as source
    +			description: "command targeting same namespace should not trigger extra auth",
    +		},
    +		{
    +			name:        "EmptyNamespace",
    +			targetNs:    "", // Empty defaults to source
    +			description: "command with empty namespace should not trigger extra auth",
    +		},
    +	}
    +
    +	for _, tc := range testCases {
    +		s.Run(tc.name, func() {
    +			request := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +				Namespace: testNamespace,
    +				Commands:  []*commandpb.Command{makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION, tc.targetNs)},
    +			}
    +
    +			sourceTarget := &CallTarget{
    +				Namespace: testNamespace,
    +				Request:   request,
    +				APIName:   api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted",
    +			}
    +
    +			// Only expect authorization for source namespace
    +			s.mockAuthorizer.EXPECT().Authorize(ctx, nil, sourceTarget).
    +				Return(Result{Decision: DecisionAllow}, nil)
    +
    +			res, err := s.interceptor.Intercept(ctx, request, respondWorkflowTaskCompletedInfo, s.handler)
    +			s.True(res.(bool))
    +			s.NoError(err)
    +		})
    +	}
    +}
    +
    +func (s *authorizerInterceptorSuite) TestCrossNamespaceCommand_DisabledFeature() {
    +	// When cross-namespace commands are disabled, no extra auth check should happen
    +	request := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +		Namespace: testNamespace,
    +		Commands:  []*commandpb.Command{makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION, targetNamespace)},
    +	}
    +
    +	sourceTarget := &CallTarget{
    +		Namespace: testNamespace,
    +		Request:   request,
    +		APIName:   api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted",
    +	}
    +
    +	// Interceptor with cross-namespace commands DISABLED (uses default s.interceptor which has it disabled)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, sourceTarget).
    +		Return(Result{Decision: DecisionAllow}, nil)
    +
    +	res, err := s.interceptor.Intercept(ctx, request, respondWorkflowTaskCompletedInfo, s.handler)
    +	s.True(res.(bool))
    +	s.NoError(err)
    +}
    +
    +func (s *authorizerInterceptorSuite) TestMultipleCommands_AuthDeduplication() {
    +	// Test that authorization is deduplicated per namespace+API combination
    +	request := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +		Namespace: testNamespace,
    +		Commands: []*commandpb.Command{
    +			makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION, targetNamespace),
    +			makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_START_CHILD_WORKFLOW_EXECUTION, targetNamespace),
    +			makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION, targetNamespace),
    +			// Duplicate signal to same namespace - should not trigger extra auth
    +			makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION, targetNamespace),
    +		},
    +	}
    +
    +	sourceTarget := &CallTarget{
    +		Namespace: testNamespace,
    +		Request:   request,
    +		APIName:   api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted",
    +	}
    +
    +	interceptor := s.newCrossNamespaceInterceptor(testNamespace, targetNamespace)
    +
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, sourceTarget).
    +		Return(Result{Decision: DecisionAllow}, nil)
    +	// Expect 3 auth checks (one per unique API type), not 4
    +	s.mockMetricsHandler.EXPECT().WithTags(
    +		metrics.OperationTag(metrics.AuthorizationScope),
    +		metrics.NamespaceTag(targetNamespace),
    +	).Return(s.mockMetricsHandler).Times(3)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, &CallTarget{
    +		Namespace: targetNamespace, Request: request, APIName: api.WorkflowServicePrefix + "SignalWorkflowExecution",
    +	}).Return(Result{Decision: DecisionAllow}, nil)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, &CallTarget{
    +		Namespace: targetNamespace, Request: request, APIName: api.WorkflowServicePrefix + "StartWorkflowExecution",
    +	}).Return(Result{Decision: DecisionAllow}, nil)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, &CallTarget{
    +		Namespace: targetNamespace, Request: request, APIName: api.WorkflowServicePrefix + "RequestCancelWorkflowExecution",
    +	}).Return(Result{Decision: DecisionAllow}, nil)
    +
    +	res, err := interceptor.Intercept(ctx, request, respondWorkflowTaskCompletedInfo, s.handler)
    +	s.True(res.(bool))
    +	s.NoError(err)
    +}
    +
    +func (s *authorizerInterceptorSuite) TestMultipleTargetNamespaces() {
    +	// Test commands targeting different namespaces
    +	request := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +		Namespace: testNamespace,
    +		Commands: []*commandpb.Command{
    +			makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION, targetNamespace),
    +			makeCrossNamespaceCommand(enumspb.COMMAND_TYPE_START_CHILD_WORKFLOW_EXECUTION, anotherNamespace),
    +		},
    +	}
    +
    +	sourceTarget := &CallTarget{
    +		Namespace: testNamespace,
    +		Request:   request,
    +		APIName:   api.WorkflowServicePrefix + "RespondWorkflowTaskCompleted",
    +	}
    +
    +	interceptor := s.newCrossNamespaceInterceptor(testNamespace, targetNamespace, anotherNamespace)
    +
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, sourceTarget).
    +		Return(Result{Decision: DecisionAllow}, nil)
    +	s.mockMetricsHandler.EXPECT().WithTags(
    +		metrics.OperationTag(metrics.AuthorizationScope),
    +		metrics.NamespaceTag(targetNamespace),
    +	).Return(s.mockMetricsHandler)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, &CallTarget{
    +		Namespace: targetNamespace, Request: request, APIName: api.WorkflowServicePrefix + "SignalWorkflowExecution",
    +	}).Return(Result{Decision: DecisionAllow}, nil)
    +	s.mockMetricsHandler.EXPECT().WithTags(
    +		metrics.OperationTag(metrics.AuthorizationScope),
    +		metrics.NamespaceTag(anotherNamespace),
    +	).Return(s.mockMetricsHandler)
    +	s.mockAuthorizer.EXPECT().Authorize(ctx, nil, &CallTarget{
    +		Namespace: anotherNamespace, Request: request, APIName: api.WorkflowServicePrefix + "StartWorkflowExecution",
    +	}).Return(Result{Decision: DecisionAllow}, nil)
    +
    +	res, err := interceptor.Intercept(ctx, request, respondWorkflowTaskCompletedInfo, s.handler)
    +	s.True(res.(bool))
    +	s.NoError(err)
    +}
    
  • service/frontend/fx.go+2 0 modified
    @@ -163,6 +163,7 @@ func AuthorizationInterceptorProvider(
     	authorizer authorization.Authorizer,
     	claimMapper authorization.ClaimMapper,
     	audienceGetter authorization.JWTAudienceMapper,
    +	dc *dynamicconfig.Collection,
     ) *authorization.Interceptor {
     	return authorization.NewInterceptor(
     		claimMapper,
    @@ -174,6 +175,7 @@ func AuthorizationInterceptorProvider(
     		cfg.Global.Authorization.AuthHeaderName,
     		cfg.Global.Authorization.AuthExtraHeaderName,
     		serviceConfig.ExposeAuthorizerErrors,
    +		dynamicconfig.EnableCrossNamespaceCommands.Get(dc),
     	)
     }
     
    
  • service/frontend/nexus_handler_test.go+12 1 modified
    @@ -117,7 +117,18 @@ func newOperationContext(options contextOptions) *operationContext {
     	)
     
     	checker := mockNamespaceChecker(oc.namespace.Name())
    -	oc.auth = authorization.NewInterceptor(nil, mockAuthorizer{}, oc.metricsHandler, oc.logger, checker, nil, "", "", dynamicconfig.GetBoolPropertyFn(false))
    +	oc.auth = authorization.NewInterceptor(
    +		nil,
    +		mockAuthorizer{},
    +		oc.metricsHandler,
    +		oc.logger,
    +		checker,
    +		nil,
    +		"",
    +		"",
    +		dynamicconfig.GetBoolPropertyFn(false), // exposeAuthorizerErrors
    +		dynamicconfig.GetBoolPropertyFn(false), // enableCrossNamespaceCommands
    +	)
     	oc.namespaceConcurrencyLimitInterceptor = interceptor.NewConcurrentRequestLimitInterceptor(
     		nil,
     		nil,
    

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

6

News mentions

0

No linked articles in our index yet.