From bcc73ae63c877f5bf2e128b4c9a52f9fbf889998 Mon Sep 17 00:00:00 2001 From: Prashant Yadav Date: Fri, 27 Mar 2026 12:53:02 -0700 Subject: [PATCH] vault: validate encrypted value size in request validator --- core/capabilities/vault/capability.go | 6 +- core/capabilities/vault/validator.go | 26 ++++- core/capabilities/vault/validator_test.go | 96 +++++++++++++++++++ .../gateway/handlers/vault/handler.go | 6 +- 4 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 core/capabilities/vault/validator_test.go diff --git a/core/capabilities/vault/capability.go b/core/capabilities/vault/capability.go index 74430246c6b..d4901025460 100644 --- a/core/capabilities/vault/capability.go +++ b/core/capabilities/vault/capability.go @@ -269,6 +269,10 @@ func NewCapability( if err != nil { return nil, fmt.Errorf("could not create request batch size limiter: %w", err) } + ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.VaultCiphertextSizeLimit) + if err != nil { + return nil, fmt.Errorf("could not create ciphertext size limiter: %w", err) + } return &Capability{ lggr: logger.Named(lggr, "VaultCapability"), clock: clock, @@ -276,6 +280,6 @@ func NewCapability( handler: handler, capabilitiesRegistry: capabilitiesRegistry, publicKey: publicKey, - RequestValidator: NewRequestValidator(limiter), + RequestValidator: NewRequestValidator(limiter, ciphertextLimiter), }, nil } diff --git a/core/capabilities/vault/validator.go b/core/capabilities/vault/validator.go index 7309ecd07fb..877a34a3bb7 100644 --- a/core/capabilities/vault/validator.go +++ b/core/capabilities/vault/validator.go @@ -11,12 +11,14 @@ import ( "github.com/smartcontractkit/tdh2/go/tdh2/tdh2easy" vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" + pkgconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" ) type RequestValidator struct { MaxRequestBatchSizeLimiter limits.BoundLimiter[int] + MaxCiphertextLengthLimiter limits.BoundLimiter[pkgconfig.Size] } func (r *RequestValidator) ValidateCreateSecretsRequest(publicKey *tdh2easy.PublicKey, request *vaultcommon.CreateSecretsRequest) error { @@ -60,6 +62,9 @@ func (r *RequestValidator) validateWriteRequest(publicKey *tdh2easy.PublicKey, i if req.EncryptedValue == "" { return errors.New("secret must have encrypted value set at index " + strconv.Itoa(idx) + ":" + req.Id.String()) } + if err := r.validateCiphertextSize(req.EncryptedValue); err != nil { + return fmt.Errorf("secret encrypted value at index %d is invalid: %w", idx, err) + } err := EnsureRightLabelOnSecret(publicKey, req.EncryptedValue, req.Id.Owner) if err != nil { return errors.New("Encrypted Secret at index [" + strconv.Itoa(idx) + "] doesn't have owner as the label. Error: " + err.Error()) @@ -75,6 +80,21 @@ func (r *RequestValidator) validateWriteRequest(publicKey *tdh2easy.PublicKey, i return nil } +func (r *RequestValidator) validateCiphertextSize(encryptedValue string) error { + rawCiphertext, err := hex.DecodeString(encryptedValue) + if err != nil { + return fmt.Errorf("failed to decode encrypted value: %w", err) + } + if err := r.MaxCiphertextLengthLimiter.Check(context.Background(), pkgconfig.Size(len(rawCiphertext))*pkgconfig.Byte); err != nil { + var errBoundLimited limits.ErrorBoundLimited[pkgconfig.Size] + if errors.As(err, &errBoundLimited) { + return fmt.Errorf("ciphertext size exceeds maximum allowed size: %s", errBoundLimited.Limit) + } + return fmt.Errorf("failed to check ciphertext size limit: %w", err) + } + return nil +} + func (r *RequestValidator) ValidateGetSecretsRequest(request *vaultcommon.GetSecretsRequest) error { if len(request.Requests) == 0 { return errors.New("no GetSecret request specified in request") @@ -129,9 +149,13 @@ func (r *RequestValidator) ValidateDeleteSecretsRequest(request *vaultcommon.Del return nil } -func NewRequestValidator(maxRequestBatchSizeLimiter limits.BoundLimiter[int]) *RequestValidator { +func NewRequestValidator( + maxRequestBatchSizeLimiter limits.BoundLimiter[int], + maxCiphertextLengthLimiter limits.BoundLimiter[pkgconfig.Size], +) *RequestValidator { return &RequestValidator{ MaxRequestBatchSizeLimiter: maxRequestBatchSizeLimiter, + MaxCiphertextLengthLimiter: maxCiphertextLengthLimiter, } } diff --git a/core/capabilities/vault/validator_test.go b/core/capabilities/vault/validator_test.go new file mode 100644 index 00000000000..2a4745a4aaa --- /dev/null +++ b/core/capabilities/vault/validator_test.go @@ -0,0 +1,96 @@ +package vault + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + + vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" + pkgconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" +) + +func TestRequestValidator_CiphertextSizeLimit(t *testing.T) { + validator := NewRequestValidator( + limits.NewUpperBoundLimiter(10), + limits.NewUpperBoundLimiter[pkgconfig.Size](10*pkgconfig.Byte), + ) + + id := &vaultcommon.SecretIdentifier{ + Key: "key", + Namespace: "namespace", + Owner: "0x1111111111111111111111111111111111111111", + } + + tests := []struct { + name string + call func(*testing.T, *RequestValidator, string) error + value string + errSubstr string + }{ + { + name: "create accepts ciphertext at the limit", + call: func(t *testing.T, validator *RequestValidator, value string) error { + return validator.ValidateCreateSecretsRequest(nil, &vaultcommon.CreateSecretsRequest{ + RequestId: "request-id", + EncryptedSecrets: []*vaultcommon.EncryptedSecret{ + {Id: id, EncryptedValue: value}, + }, + }) + }, + value: hex.EncodeToString(make([]byte, 10)), + }, + { + name: "create rejects ciphertext above the limit", + call: func(t *testing.T, validator *RequestValidator, value string) error { + return validator.ValidateCreateSecretsRequest(nil, &vaultcommon.CreateSecretsRequest{ + RequestId: "request-id", + EncryptedSecrets: []*vaultcommon.EncryptedSecret{ + {Id: id, EncryptedValue: value}, + }, + }) + }, + value: hex.EncodeToString(make([]byte, 11)), + errSubstr: "ciphertext size exceeds maximum allowed size", + }, + { + name: "update accepts ciphertext at the limit", + call: func(t *testing.T, validator *RequestValidator, value string) error { + return validator.ValidateUpdateSecretsRequest(nil, &vaultcommon.UpdateSecretsRequest{ + RequestId: "request-id", + EncryptedSecrets: []*vaultcommon.EncryptedSecret{ + {Id: id, EncryptedValue: value}, + }, + }) + }, + value: hex.EncodeToString(make([]byte, 10)), + }, + { + name: "update rejects ciphertext above the limit", + call: func(t *testing.T, validator *RequestValidator, value string) error { + return validator.ValidateUpdateSecretsRequest(nil, &vaultcommon.UpdateSecretsRequest{ + RequestId: "request-id", + EncryptedSecrets: []*vaultcommon.EncryptedSecret{ + {Id: id, EncryptedValue: value}, + }, + }) + }, + value: hex.EncodeToString(make([]byte, 11)), + errSubstr: "ciphertext size exceeds maximum allowed size", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.call(t, validator, tt.value) + if tt.errSubstr == "" { + require.NoError(t, err) + return + } + + require.Error(t, err) + require.ErrorContains(t, err, tt.errSubstr) + }) + } +} diff --git a/core/services/gateway/handlers/vault/handler.go b/core/services/gateway/handlers/vault/handler.go index 1168c27a2ef..ceb95f92a49 100644 --- a/core/services/gateway/handlers/vault/handler.go +++ b/core/services/gateway/handlers/vault/handler.go @@ -197,6 +197,10 @@ func NewHandler(methodConfig json.RawMessage, donConfig *config.DONConfig, don g if err != nil { return nil, fmt.Errorf("could not create request batch size limiter: %w", err) } + ciphertextLimiter, err := limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.VaultCiphertextSizeLimit) + if err != nil { + return nil, fmt.Errorf("could not create ciphertext size limiter: %w", err) + } writeMethodsEnabled, err := limits.MakeGateLimiter(limitsFactory, cresettings.Default.GatewayVaultManagementEnabled) if err != nil { @@ -218,7 +222,7 @@ func NewHandler(methodConfig json.RawMessage, donConfig *config.DONConfig, don g metrics: metrics, aggregator: &baseAggregator{capabilitiesRegistry: capabilitiesRegistry}, clock: clock, - RequestValidator: vaultcap.NewRequestValidator(limiter), + RequestValidator: vaultcap.NewRequestValidator(limiter, ciphertextLimiter), }, nil }