{"record":{"id":"11bc25c5e3a6c6c8","repo":"JuliusBrussee/caveman","slug":"kms-plaintext-exceeds-d-bytes","errorCode":null,"errorMessage":"kms: plaintext exceeds %d bytes","messagePattern":"kms: plaintext exceeds (.+?) bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/kms/kms.go","lineNumber":180,"sourceCode":"\treturn client.Encrypt(ctx, plaintext)\n}\n\n// EncryptPayload wraps an artifact data key with the dedicated payload KEK.\nfunc EncryptPayload(ctx context.Context, plaintext []byte) ([]byte, error) {\n\tclient, err := FromPayloadEnvironment()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn client.Encrypt(ctx, plaintext)\n}\n\n// Encrypt delegates encryption to key manager.\nfunc (c *Client) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error) {\n\tif len(plaintext) == 0 {\n\t\treturn nil, errors.New(\"kms: plaintext is empty\")\n\t}\n\tif len(plaintext) > maxPlaintextBytes {\n\t\treturn nil, fmt.Errorf(\"kms: plaintext exceeds %d bytes\", maxPlaintextBytes)\n\t}\n\tvar response struct {\n\t\tKeyID      string `json:\"key_id\"`\n\t\tCiphertext string `json:\"ciphertext\"`\n\t}\n\tif err := c.call(ctx, c.region, c.keyID, \"encrypt\", map[string]string{\n\t\t\"plaintext\": base64.StdEncoding.EncodeToString(plaintext),\n\t}, &response); err != nil {\n\t\treturn nil, err\n\t}\n\tif response.KeyID != c.keyID || strings.TrimSpace(response.Ciphertext) == \"\" {\n\t\treturn nil, errors.New(\"kms: invalid encrypt response\")\n\t}\n\tenvelope, err := json.Marshal(Envelope{Provider: c.provider, Region: c.region, KeyID: response.KeyID, Ciphertext: response.Ciphertext})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"kms: encode envelope: %w\", err)\n\t}\n\treturn append([]byte(prefix), envelope...), nil","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/kms/kms.go#L162-L198","documentation":"Client.Encrypt rejects plaintexts larger than maxPlaintextBytes (65535 bytes, 64 KiB). This is a client-side pre-check before the Scaleway key-manager call, bounding envelope size and request body. Empty plaintext is rejected separately (error 'kms: plaintext is empty'), so this error is purely about the upper bound.","triggerScenarios":"Encrypting a document, image, or large config blob directly instead of a data key; passing an unbounded io.ReadAll result into Encrypt; encrypting a full API response body of hundreds of KB.","commonSituations":"Using envelope encryption incorrectly — encrypting the payload itself rather than a symmetric key; log/backup pipelines that grew past 64 KiB per record over time; merging multiple secrets into one blob before encryption.","solutions":["Switch to envelope encryption: generate a 32-byte data key, encrypt payload with AES-GCM locally, and Encrypt only the data key","If the payload must be single-blob, chunk it under 65535 bytes and track chunk order yourself","Add a length check in the calling code so oversized input fails with your own clearer error","For secrets like tokens and PEM keys (well under 64 KiB), no change is needed"],"exampleFix":"// before\nciphertext, err := client.Encrypt(ctx, largeSecret) // 500KB\n\n// after\ndek := make([]byte, 32)\nrand.Read(dek)\nnonce := make([]byte, 12)\nrand.Read(nonce)\nblock, _ := aes.NewCipher(dek)\ngcm, _ := cipher.NewGCM(block)\nbody := gcm.Seal(nil, nonce, largeSecret, nil)\nwrappedDEK, err := client.Encrypt(ctx, dek)\n_ = body; _ = nonce; _ = wrappedDEK // persist together","handlingStrategy":"validation","validationCode":"const maxPT = 65535\nfunc plaintextWithinLimit(b []byte) bool { return len(b) > 0 && len(b) <= maxPT }","typeGuard":null,"tryCatchPattern":"if _, err := client.Encrypt(ctx, secret); err != nil { if strings.Contains(err.Error(), \"plaintext exceeds\") { return encryptEnvelope(secret) /* DEK pattern */ } }","preventionTips":["Use envelope encryption (encrypt a 32-byte DEK, AES-GCM the payload)","Assert len(plaintext) <= 65535 at the API boundary that accepts secrets","Never pass unbounded user input straight to Encrypt"],"tags":["go","kms","encryption","size-limit"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}