{"record":{"id":"0e79526eac2ef257","repo":"JuliusBrussee/caveman","slug":"secretbox-payload-kms-encrypt-w","errorCode":null,"errorMessage":"secretbox: payload KMS encrypt: %w","messagePattern":"secretbox: payload KMS encrypt: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"shared/platform/secretbox/secretbox.go","lineNumber":96,"sourceCode":"\tnonce := make([]byte, gcm.NonceSize())\n\tif _, err := rand.Read(nonce); err != nil {\n\t\treturn nil, fmt.Errorf(\"nonce entropy: %w\", err)\n\t}\n\t// Seal appends the ciphertext+tag to nonce, so the returned slice is the\n\t// full nonce||ciphertext envelope.\n\treturn gcm.Seal(nonce, nonce, plaintext, nil), nil\n}\n\n// EncryptPayloadKey wraps an artifact data-encryption key. Production uses the\n// dedicated payload KEK; local development retains the same AES-GCM envelope as\n// other local secrets.\nfunc EncryptPayloadKey(plaintext []byte) ([]byte, error) {\n\tif useKMS() {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\tdefer cancel()\n\t\twrapped, err := kms.EncryptPayload(ctx, plaintext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"secretbox: payload KMS encrypt: %w\", err)\n\t\t}\n\t\treturn wrapped, nil\n\t}\n\treturn Encrypt(plaintext)\n}\n\n// Decrypt reverses Encrypt: it expects nonce(12) || ciphertext+tag.\nfunc Decrypt(envelope []byte) ([]byte, error) {\n\tif kms.IsEnvelope(envelope) {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\tdefer cancel()\n\t\tplaintext, err := kms.Decrypt(ctx, envelope)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"secretbox: KMS decrypt: %w\", err)\n\t\t}\n\t\treturn plaintext, nil\n\t}\n\tif runtimeenv.IsProduction() &&","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/secretbox/secretbox.go#L78-L114","documentation":"EncryptPayloadKey encrypts data-encryption keys. When useKMS() is true it delegates to kms.EncryptPayload with a 10s timeout and wraps any KMS failure as 'secretbox: payload KMS encrypt'. This is distinct from the master Encrypt path (kms.Encrypt) and means the payload-key envelope could not be produced. Note it wraps the error rather than falling back to local encryption, so a KMS outage fails closed.","triggerScenarios":"Calling EncryptPayloadKey while CAVE_KMS_PROVIDER is set and kms.EncryptPayload fails: bad or rotated Scaleway payload-key credentials, wrong key ID/region, KMS endpoint unreachable, or the 10s context deadline exceeded.","commonSituations":"KMS outage or rate limiting during a deploy stampede; payload-key rotated in the Scaleway console while running processes still hold old credentials; region mismatch between the configured key and the API endpoint; egress blocked in a private subnet without NAT to the KMS API.","solutions":["Inspect the wrapped error (errors.Unwrap) to identify auth vs network vs timeout cause.","Verify the payload-key-specific credentials and key ID/region env vars are current and match the Scaleway console.","Check KMS service status and retry after transient outages; the errors are intentionally not masked by a local fallback.","Confirm network egress from the workload to the Scaleway KMS endpoint; add NAT/proxy rules if missing.","For requests that hit the 10s deadline, check KMS latency and consider the configured timeout budget."],"exampleFix":"// before: rotated key not redeployed -> secretbox: payload KMS encrypt: 401 unauthorized\n// process started before key rotation\n\n// after: reload credentials on rotation and restart/re-auth\nif err := kms.RefreshCredentials(ctx); err != nil { log.Fatal(err) }\nwrapped, err := secretbox.EncryptPayloadKey(dek)","handlingStrategy":"try-catch","validationCode":"if os.Getenv(\"CAVE_KMS_PROVIDER\") == \"scaleway\" {\n    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n    defer cancel()\n    if err := kms.Ping(ctx); err != nil { // or a tiny EncryptPayload round trip\n        return fmt.Errorf(\"payload KMS not reachable at startup: %w\", err)\n    }\n}","typeGuard":null,"tryCatchPattern":"wrapped, err := secretbox.EncryptPayloadKey(dek)\nif err != nil {\n    if kmsErr := (*kms.Error)(nil); errors.As(err, &kmsErr) {\n        // fail closed: never fall back to local key encryption for DEKs\n        return fmt.Errorf(\"payload key wrapping failed, refusing local fallback: %w\", err)\n    }\n    if errors.Is(err, context.DeadlineExceeded) {\n        // retry with backoff for transient latency\n        return retryWithBackoff(dek)\n    }\n    return err\n}","preventionTips":["Health-check the payload-key KMS path at startup with a real wrap/unwrap of a test DEK.","Redeploy or hot-reload credentials after any key rotation.","Alert on KMS error-rate spikes; they will fail closed by design.","Keep key ID/region env config consistent across producers and consumers of payload keys."],"tags":["kms","encryption","key-management","network","go"],"backgroundTag":"api-request-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}