VYPR
Low severityNVD Advisory· Published Feb 12, 2025· Updated Apr 15, 2026

CVE-2025-1243

CVE-2025-1243

Description

The Temporal api-go library prior to version 1.44.1 did not send update response information to Data Converter when the proxy package within the api-go module was used in a gRPC proxy prior to transmission. This resulted in information contained within the update response field not having Data Converter transformations (e.g. encryption) applied. This is an issue only when using the UpdateWorkflowExecution APIs (released on 13th January 2025) with a proxy leveraging the api-go library before version 1.44.1.

Other data fields were correctly sent to Data Converter. This issue does not impact the Data Converter server. Data was encrypted in transit. Temporal Cloud services are not impacted.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
go.temporal.io/apiGo
< 1.44.11.44.1

Patches

1
dad8b169ada9

Fix update.Response getting skipped by the proxy (#209)

https://github.com/temporalio/api-goQuinn KlassenFeb 10, 2025via ghsa
3 files changed · +243 8
  • cmd/proxygenerator/interceptor.go+54 6 modified
    @@ -122,6 +122,9 @@ type VisitFailuresOptions struct {
     	// Context is the same for every call of a visit, callers should not store it.
     	// Visitor is free to mutate the passed failure struct.
     	Visitor func(*VisitFailuresContext, *failure.Failure) (error)
    +	// Will be called for each Any encountered. If not set, the default is to recurse into the Any
    +	// object, unmarshal it, visit, and re-marshal it always (even if there are no changes).
    +	WellKnownAnyVisitor func(*VisitFailuresContext, *anypb.Any) error
     }
     
     // VisitFailures calls the options.Visitor function for every Failure proto within msg.
    @@ -162,6 +165,25 @@ func NewFailureVisitorInterceptor(options FailureVisitorInterceptorOptions) (grp
     	}, nil
     }
     
    +func (o *VisitFailuresOptions) defaultWellKnownAnyVisitor(ctx *VisitFailuresContext, p *anypb.Any) error {
    +	child, err := p.UnmarshalNew()
    +	if err != nil {
    +		return fmt.Errorf("failed to unmarshal any: %w", err)
    +	}
    +	// We choose to visit and re-marshal always instead of cloning, visiting,
    +	// and checking if anything changed before re-marshaling. It is assumed the
    +	// clone + equality check is not much cheaper than re-marshal.
    +	if err := visitFailures(ctx, o, child); err != nil {
    +		return err
    +	}
    +	// Confirmed this replaces both Any fields on non-error, there is nothing
    +	// left over
    +	if err := p.MarshalFrom(child); err != nil {
    +		return fmt.Errorf("failed to marshal any: %w", err)
    +	}
    +	return nil
    +}
    +
     func (o *VisitPayloadsOptions) defaultWellKnownAnyVisitor(ctx *VisitPayloadsContext, p *anypb.Any) error {
     	child, err := p.UnmarshalNew()
     	if err != nil {
    @@ -299,6 +321,20 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     				if o == nil { continue }
     				if err := options.Visitor(ctx, o); err != nil { return err }
     				if err := visitFailures(ctx, options, o.GetCause()); err != nil { return err }
    +			case *anypb.Any:
    +				if o == nil {
    +					continue
    +				}
    +				visitor := options.WellKnownAnyVisitor
    +				if visitor == nil {
    +					visitor = options.defaultWellKnownAnyVisitor
    +				}
    +				ctx.Parent = o
    +				err := visitor(ctx, o)
    +				ctx.Parent = nil
    +				if err != nil {
    +					return err
    +				}
     {{range $type, $record := .FailureTypes}}
     		{{if $record.Slice}}
     			case []{{$type}}:
    @@ -508,17 +544,19 @@ func generateInterceptor(cfg config) error {
     	if err != nil {
     		return err
     	}
    -	// For the purposes of payloads, we also consider the Any well known type as
    +
    +	failureTypes, err := lookupTypes("go.temporal.io/api/failure/v1", []string{"Failure"})
    +	if err != nil {
    +		return err
    +	}
    +
    +	// For the purposes of payloads and failures, we also consider the Any well known type as
     	// possible
     	if anyTypes, err := lookupTypes("google.golang.org/protobuf/types/known/anypb", []string{"Any"}); err != nil {
     		return err
     	} else {
     		payloadTypes = append(payloadTypes, anyTypes...)
    -	}
    -
    -	failureTypes, err := lookupTypes("go.temporal.io/api/failure/v1", []string{"Failure"})
    -	if err != nil {
    -		return err
    +		failureTypes = append(failureTypes, anyTypes...)
     	}
     
     	// UnimplementedWorkflowServiceServer is auto-generated via our API package
    @@ -542,6 +580,11 @@ func generateInterceptor(cfg config) error {
     	}
     	workflowExecutions := types.NewPointer(exportTypes[0])
     
    +	updateTypes, err := lookupTypes("go.temporal.io/api/update/v1", []string{"Acceptance", "Rejection", "Response"})
    +	if err != nil {
    +		return err
    +	}
    +
     	payloadRecords := map[string]*TypeRecord{}
     	failureRecords := map[string]*TypeRecord{}
     
    @@ -572,6 +615,11 @@ func generateInterceptor(cfg config) error {
     	walk(payloadTypes, workflowExecutions, &payloadRecords, true)
     	walk(failureTypes, workflowExecutions, &failureRecords, false)
     
    +	for _, ut := range updateTypes {
    +		walk(payloadTypes, types.NewPointer(ut), &payloadRecords, true)
    +		walk(failureTypes, types.NewPointer(ut), &failureRecords, false)
    +	}
    +
     	payloadRecords = pruneRecords(payloadRecords)
     	failureRecords = pruneRecords(failureRecords)
     
    
  • proxy/interceptor.go+131 0 modified
    @@ -120,6 +120,9 @@ type VisitFailuresOptions struct {
     	// Context is the same for every call of a visit, callers should not store it.
     	// Visitor is free to mutate the passed failure struct.
     	Visitor func(*VisitFailuresContext, *failure.Failure) error
    +	// Will be called for each Any encountered. If not set, the default is to recurse into the Any
    +	// object, unmarshal it, visit, and re-marshal it always (even if there are no changes).
    +	WellKnownAnyVisitor func(*VisitFailuresContext, *anypb.Any) error
     }
     
     // VisitFailures calls the options.Visitor function for every Failure proto within msg.
    @@ -160,6 +163,25 @@ func NewFailureVisitorInterceptor(options FailureVisitorInterceptorOptions) (grp
     	}, nil
     }
     
    +func (o *VisitFailuresOptions) defaultWellKnownAnyVisitor(ctx *VisitFailuresContext, p *anypb.Any) error {
    +	child, err := p.UnmarshalNew()
    +	if err != nil {
    +		return fmt.Errorf("failed to unmarshal any: %w", err)
    +	}
    +	// We choose to visit and re-marshal always instead of cloning, visiting,
    +	// and checking if anything changed before re-marshaling. It is assumed the
    +	// clone + equality check is not much cheaper than re-marshal.
    +	if err := visitFailures(ctx, o, child); err != nil {
    +		return err
    +	}
    +	// Confirmed this replaces both Any fields on non-error, there is nothing
    +	// left over
    +	if err := p.MarshalFrom(child); err != nil {
    +		return fmt.Errorf("failed to marshal any: %w", err)
    +	}
    +	return nil
    +}
    +
     func (o *VisitPayloadsOptions) defaultWellKnownAnyVisitor(ctx *VisitPayloadsContext, p *anypb.Any) error {
     	child, err := p.UnmarshalNew()
     	if err != nil {
    @@ -1644,6 +1666,21 @@ func visitPayloads(
     				o.Summary = no
     			}
     
    +		case *update.Acceptance:
    +
    +			if o == nil {
    +				continue
    +			}
    +
    +			if err := visitPayloads(
    +				ctx,
    +				options,
    +				o,
    +				o.GetAcceptedRequest(),
    +			); err != nil {
    +				return err
    +			}
    +
     		case *update.Input:
     
     			if o == nil {
    @@ -1676,6 +1713,22 @@ func visitPayloads(
     				return err
     			}
     
    +		case *update.Rejection:
    +
    +			if o == nil {
    +				continue
    +			}
    +
    +			if err := visitPayloads(
    +				ctx,
    +				options,
    +				o,
    +				o.GetFailure(),
    +				o.GetRejectedRequest(),
    +			); err != nil {
    +				return err
    +			}
    +
     		case *update.Request:
     
     			if o == nil {
    @@ -1691,6 +1744,21 @@ func visitPayloads(
     				return err
     			}
     
    +		case *update.Response:
    +
    +			if o == nil {
    +				continue
    +			}
    +
    +			if err := visitPayloads(
    +				ctx,
    +				options,
    +				o,
    +				o.GetOutcome(),
    +			); err != nil {
    +				return err
    +			}
    +
     		case []*workflow.CallbackInfo:
     			for _, x := range o {
     				if err := visitPayloads(ctx, options, parent, x); err != nil {
    @@ -2740,6 +2808,20 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     			if err := visitFailures(ctx, options, o.GetCause()); err != nil {
     				return err
     			}
    +		case *anypb.Any:
    +			if o == nil {
    +				continue
    +			}
    +			visitor := options.WellKnownAnyVisitor
    +			if visitor == nil {
    +				visitor = options.defaultWellKnownAnyVisitor
    +			}
    +			ctx.Parent = o
    +			err := visitor(ctx, o)
    +			ctx.Parent = nil
    +			if err != nil {
    +				return err
    +			}
     
     		case []*command.Command:
     			for _, x := range o {
    @@ -3063,6 +3145,26 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     				return err
     			}
     
    +		case []*protocol.Message:
    +			for _, x := range o {
    +				if err := visitFailures(ctx, options, x); err != nil {
    +					return err
    +				}
    +			}
    +
    +		case *protocol.Message:
    +			if o == nil {
    +				continue
    +			}
    +			ctx.Parent = o
    +			if err := visitFailures(
    +				ctx,
    +				options,
    +				o.GetBody(),
    +			); err != nil {
    +				return err
    +			}
    +
     		case map[string]*query.WorkflowQueryResult:
     			for _, x := range o {
     				if err := visitFailures(ctx, options, x); err != nil {
    @@ -3096,6 +3198,32 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     				return err
     			}
     
    +		case *update.Rejection:
    +			if o == nil {
    +				continue
    +			}
    +			ctx.Parent = o
    +			if err := visitFailures(
    +				ctx,
    +				options,
    +				o.GetFailure(),
    +			); err != nil {
    +				return err
    +			}
    +
    +		case *update.Response:
    +			if o == nil {
    +				continue
    +			}
    +			ctx.Parent = o
    +			if err := visitFailures(
    +				ctx,
    +				options,
    +				o.GetOutcome(),
    +			); err != nil {
    +				return err
    +			}
    +
     		case []*workflow.CallbackInfo:
     			for _, x := range o {
     				if err := visitFailures(ctx, options, x); err != nil {
    @@ -3300,6 +3428,7 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     				ctx,
     				options,
     				o.GetHistory(),
    +				o.GetMessages(),
     			); err != nil {
     				return err
     			}
    @@ -3378,6 +3507,7 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     				ctx,
     				options,
     				o.GetCommands(),
    +				o.GetMessages(),
     				o.GetQueryResults(),
     			); err != nil {
     				return err
    @@ -3405,6 +3535,7 @@ func visitFailures(ctx *VisitFailuresContext, options *VisitFailuresOptions, obj
     				ctx,
     				options,
     				o.GetFailure(),
    +				o.GetMessages(),
     			); err != nil {
     				return err
     			}
    
  • proxy/interceptor_test.go+58 2 modified
    @@ -183,8 +183,14 @@ func TestVisitPayloads_Any(t *testing.T) {
     		Payloads: []*common.Payload{{Data: []byte("orig-val-don't-touch")}},
     	}}})
     	require.NoError(t, err)
    +	msg3, err := anypb.New(&update.Response{Outcome: &update.Outcome{Value: &update.Outcome_Success{
    +		Success: &common.Payloads{
    +			Payloads: []*common.Payload{{Data: []byte("orig-val")}},
    +		},
    +	}}})
    +	require.NoError(t, err)
     	root := &workflowservice.PollWorkflowTaskQueueResponse{
    -		Messages: []*protocol.Message{{Body: msg1}, {Body: msg2}},
    +		Messages: []*protocol.Message{{Body: msg1}, {Body: msg2}, {Body: msg3}},
     	}
     
     	// Visit with any recursion enabled and only change orig-val
    @@ -204,6 +210,9 @@ func TestVisitPayloads_Any(t *testing.T) {
     	update2, err := root.Messages[1].Body.UnmarshalNew()
     	require.NoError(t, err)
     	require.Equal(t, "orig-val-don't-touch", string(update2.(*update.Request).Input.Args.Payloads[0].Data))
    +	update3, err := root.Messages[2].Body.UnmarshalNew()
    +	require.NoError(t, err)
    +	require.Equal(t, "new-val", string(update3.(*update.Response).GetOutcome().GetSuccess().Payloads[0].Data))
     
     	// Do the same test but with a do-nothing visitor and confirm unchanged
     	msg1, err = anypb.New(&update.Request{Input: &update.Input{Args: &common.Payloads{
    @@ -214,8 +223,14 @@ func TestVisitPayloads_Any(t *testing.T) {
     		Payloads: []*common.Payload{{Data: []byte("orig-val-don't-touch")}},
     	}}})
     	require.NoError(t, err)
    +	msg3, err = anypb.New(&update.Response{Outcome: &update.Outcome{Value: &update.Outcome_Success{
    +		Success: &common.Payloads{
    +			Payloads: []*common.Payload{{Data: []byte("orig-val")}},
    +		},
    +	}}})
    +	require.NoError(t, err)
     	root = &workflowservice.PollWorkflowTaskQueueResponse{
    -		Messages: []*protocol.Message{{Body: msg1}, {Body: msg2}},
    +		Messages: []*protocol.Message{{Body: msg1}, {Body: msg2}, {Body: msg3}},
     	}
     	err = VisitPayloads(context.Background(), root, VisitPayloadsOptions{
     		Visitor: func(ctx *VisitPayloadsContext, p []*common.Payload) ([]*common.Payload, error) {
    @@ -234,6 +249,9 @@ func TestVisitPayloads_Any(t *testing.T) {
     	update2, err = root.Messages[1].Body.UnmarshalNew()
     	require.NoError(t, err)
     	require.Equal(t, "orig-val-don't-touch", string(update2.(*update.Request).Input.Args.Payloads[0].Data))
    +	update3, err = root.Messages[2].Body.UnmarshalNew()
    +	require.NoError(t, err)
    +	require.Equal(t, "orig-val", string(update3.(*update.Response).GetOutcome().GetSuccess().Payloads[0].Data))
     }
     
     func TestVisitFailures(t *testing.T) {
    @@ -274,6 +292,44 @@ func TestVisitFailures(t *testing.T) {
     	require.Equal(2, failureCount)
     }
     
    +func TestVisitFailuresAny(t *testing.T) {
    +	require := require.New(t)
    +
    +	fail := &failure.Failure{
    +		Message: "test failure",
    +	}
    +
    +	msg, err := anypb.New(&update.Response{Outcome: &update.Outcome{Value: &update.Outcome_Failure{
    +		Failure: fail,
    +	}}})
    +	require.NoError(err)
    +
    +	req := &workflowservice.RespondWorkflowTaskCompletedRequest{
    +		Messages: []*protocol.Message{{Body: msg}},
    +	}
    +	failureCount := 0
    +	err = VisitFailures(
    +		context.Background(),
    +		req,
    +		VisitFailuresOptions{
    +			Visitor: func(vfc *VisitFailuresContext, f *failure.Failure) error {
    +				failureCount += 1
    +				require.Equal("test failure", f.Message)
    +				f.EncodedAttributes = &common.Payload{Data: []byte("test failure")}
    +				f.Message = "encoded failure"
    +				return nil
    +			},
    +		},
    +	)
    +	require.NoError(err)
    +	require.Equal(1, failureCount)
    +	updateMsg, err := req.GetMessages()[0].GetBody().UnmarshalNew()
    +	require.NoError(err)
    +	require.Equal("encoded failure", updateMsg.(*update.Response).GetOutcome().GetFailure().GetMessage())
    +	require.Equal("test failure", string(updateMsg.(*update.Response).GetOutcome().GetFailure().EncodedAttributes.Data))
    +
    +}
    +
     func TestClientInterceptor(t *testing.T) {
     	require := require.New(t)
     
    

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

5

News mentions

0

No linked articles in our index yet.