securepayload

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 11, 2026 License: MIT Imports: 15 Imported by: 0

README

temporal-secure-payload

Selective payload encryption for Temporal SDK (Go).

Features

  • Encrypts only payloads marked with metadata key temporal-sensitive=1.
  • Leaves other payloads unchanged.
  • Works as a Temporal DataConverter you can pass to client.Dial(...) and workers.
  • Exposes interface-based public contracts (PayloadCodec).

How Selective Encryption Works

You do not need to set metadata manually.

When you wrap a value with securepayload.Sensitive(...), the package marks the payload internally with temporal-sensitive=1. The codec checks this marker and encrypts only marked payloads.

Installation

go get github.com/efeligne/temporal-secure-payload

Quick Start

Use the same DataConverter on both sides:

  • workflow starter/client
  • worker that executes/decode payloads
import (
    "os"

    securepayload "github.com/efeligne/temporal-secure-payload"
    "go.temporal.io/sdk/client"
)

dc, err := securepayload.NewDataConverter(os.Getenv("TEMPORAL_ENCRYPTION_KEY"))
if err != nil {
    return err
}

c, err := client.Dial(client.Options{
    HostPort:      hostPort,
    Namespace:     namespace,
    DataConverter: dc,
})

Mark sensitive values using securepayload.Sensitive(...):

memo := map[string]interface{}{
    "requestId": "abc-123",
    "auth":      securepayload.Sensitive(map[string]string{"jwt": "..."}),
}

End-to-End Example: Client -> Worker (JWT)

  1. Client side: configure converter and start workflow with sensitive JWT in Memo["auth"].
  2. Worker side: configure the same converter and decode Memo["auth"] into JWT.
// client side
dc, err := securepayload.NewDataConverter(os.Getenv("TEMPORAL_ENCRYPTION_KEY"))
if err != nil {
    return err
}

c, err := client.Dial(client.Options{
    HostPort:      hostPort,
    Namespace:     namespace,
    DataConverter: dc,
})
if err != nil {
    return err
}

_, err = c.ExecuteWorkflow(
    context.Background(),
    client.StartWorkflowOptions{
        ID:        "wf-jwt-example",
        TaskQueue: "default",
        Memo: map[string]interface{}{
            "auth": securepayload.Sensitive(map[string]string{
                "jwt": "...",
            }),
        },
    },
    "MyWorkflow",
)
if err != nil {
    return err
}
// worker side
dc, err := securepayload.NewDataConverter(os.Getenv("TEMPORAL_ENCRYPTION_KEY"))
if err != nil {
    return err
}

c, err := client.Dial(client.Options{
    HostPort:      hostPort,
    Namespace:     namespace,
    DataConverter: dc,
})
if err != nil {
    return err
}

w := worker.New(c, "default", worker.Options{})
w.RegisterWorkflow(func(ctx workflow.Context) error {
    memoPayload := workflow.GetInfo(ctx).Memo.Fields["auth"]
    var auth map[string]string
    if err := dc.FromPayload(memoPayload, &auth); err != nil {
        return err
    }

    jwt := auth["jwt"] // already decrypted by converter
    _ = jwt
    return nil
})

Public API

  • type PayloadCodec interface
  • func NewCodec(key string) (PayloadCodec, error)
  • func NewDataConverter(key string) (converter.DataConverter, error)
  • func NewDataConverterWithCodec(payloadCodec PayloadCodec) converter.DataConverter
  • func Sensitive(v any) any
  • func ParseKey(key string) ([]byte, error)

Dependency-injection example:

payloadCodec, err := securepayload.NewCodec(key)
if err != nil {
    return err
}

dataConverter := securepayload.NewDataConverterWithCodec(payloadCodec)

Public Methods: Purpose and Examples

ParseKey

Purpose: validate and normalize encryption key into 32 raw bytes.

keyBytes, err := securepayload.ParseKey(os.Getenv("TEMPORAL_ENCRYPTION_KEY"))
if err != nil {
    return err
}
_ = keyBytes
NewCodec

Purpose: create selective payload codec that encrypts only marked payloads.

payloadCodec, err := securepayload.NewCodec(os.Getenv("TEMPORAL_ENCRYPTION_KEY"))
if err != nil {
    return err
}
_ = payloadCodec
NewDataConverter

Purpose: create ready-to-use Temporal DataConverter from key.

dc, err := securepayload.NewDataConverter(os.Getenv("TEMPORAL_ENCRYPTION_KEY"))
if err != nil {
    return err
}

client, err := client.Dial(client.Options{
    HostPort:      hostPort,
    Namespace:     namespace,
    DataConverter: dc,
})
NewDataConverterWithCodec

Purpose: build DataConverter with injected codec (DI-friendly).

payloadCodec, err := securepayload.NewCodec(key)
if err != nil {
    return err
}

dc := securepayload.NewDataConverterWithCodec(payloadCodec)
Sensitive

Purpose: mark a value as sensitive so package encrypts only this value.

memo := map[string]interface{}{
    "requestId": "abc-123",
    "auth":      securepayload.Sensitive(map[string]string{"jwt": "..."}),
}

Key Formats

NewDataConverter / NewCodec accept:

  • 32-byte raw string
  • base64-encoded 32-byte key
  • hex-encoded 32-byte key

Important Notes

  • Do not put secrets in WorkflowID, logs, or Search Attributes.
  • If a value is not wrapped with securepayload.Sensitive(...), it is not encrypted by this package.
  • If Temporal UI is configured with a codec endpoint that can decrypt payloads, users with access to that endpoint can read decrypted values.

Documentation

Overview

Package securepayload provides selective payload encryption helpers for the Temporal Go SDK.

The package encrypts only values explicitly wrapped with Sensitive(...). All other payloads remain unchanged.

To work correctly, the same DataConverter must be configured on both the Temporal client (starter/BFF) and worker sides.

Limitations:

  • Search Attributes, WorkflowID, and logs are not protected by this package.
  • If a codec endpoint can decrypt payloads, users with access to that endpoint can read decrypted values.

Index

Constants

View Source
const (
	// MetadataSensitive marks payloads that should be encrypted by the codec.
	MetadataSensitive = "temporal-sensitive"

	// EncryptedEncoding is used as payload encoding for encrypted blobs.
	EncryptedEncoding = "binary/temporal-secure-payload-encrypted"
)

Variables

This section is empty.

Functions

func NewDataConverter

func NewDataConverter(key string) (converter.DataConverter, error)

NewDataConverter creates Temporal DataConverter with selective encryption codec.

func NewDataConverterWithCodec

func NewDataConverterWithCodec(payloadCodec PayloadCodec) converter.DataConverter

NewDataConverterWithCodec creates Temporal DataConverter with injected codec.

func ParseKey

func ParseKey(key string) ([]byte, error)

ParseKey supports 32-byte raw, base64 and hex key formats.

func Sensitive

func Sensitive(v any) any

Sensitive wraps a value so converter marks it as encryptable payload.

Types

type PayloadCodec

type PayloadCodec interface {
	Encode(payloads []*pb.Payload) ([]*pb.Payload, error)
	Decode(payloads []*pb.Payload) ([]*pb.Payload, error)
}

PayloadCodec describes selective payload encryption/decryption behavior.

func NewCodec

func NewCodec(key string) (PayloadCodec, error)

NewCodec creates selective payload codec.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL