JuliusBrussee/caveman · critical
secretbox: KMS encrypt
Error message
secretbox: KMS encrypt: %w
What it means
secretbox.Encrypt detected that KMS-based encryption is enabled (CAVE_KMS_PROVIDER is set) and delegated the encryption to the configured KMS provider, but the kms.Encrypt call failed. The underlying KMS error (network failure, bad credentials, misconfigured key) is wrapped with %w so the cause is preserved. This error means the envelope could not be produced at all.
Solutions
- Check the wrapped cause: print err with %v or errors.Unwrap to see the underlying KMS failure (auth vs network vs timeout).
- Verify CAVE_KMS_PROVIDER and the provider credential env vars (e.g. Scaleway access key, secret key, region) are present and current.
- Test network egress to the KMS endpoint from the host/container; confirm no firewall or VPC rule blocks it.
- If credentials were rotated, redeploy with the new values and re-run the failing operation.
- For local development where KMS is unnecessary, unset CAVE_KMS_PROVIDER so secretbox falls back to local AES-GCM (not allowed in production).
Example fix
// before (missing/rotated credentials)
os.Setenv("CAVE_KMS_PROVIDER", "scaleway")
// encrypted, err := secretbox.Encrypt(plaintext) -> secretbox: KMS encrypt: ...
// after (valid creds exported, or local mode outside production)
os.Setenv("CAVE_KMS_ACCESS_KEY", scwAccessKey)
os.Setenv("CAVE_KMS_SECRET_KEY", scwSecretKey)
// or, in dev only: os.Unsetenv("CAVE_KMS_PROVIDER") Defensive patterns
Strategy: try-catch
Validate before calling
if os.Getenv("CAVE_KMS_PROVIDER") != "" {
for _, v := range []string{"CAVE_KMS_ACCESS_KEY", "CAVE_KMS_SECRET_KEY"} {
if os.Getenv(v) == "" {
return fmt.Errorf("%s must be set when CAVE_KMS_PROVIDER is configured", v)
}
}
} Try / catch
wrapped, err := secretbox.Encrypt(plaintext)
if err != nil {
var kmsErr *kms.Error
if errors.As(err, &kmsErr) {
// alert: KMS unavailable; fail closed, do not fall back to local keys
return fmt.Errorf("encrypt unavailable: %w", err)
}
return err
} Prevention
- Run a startup health check that performs a tiny KMS encrypt/decrypt round trip before serving traffic.
- Inject KMS credentials via a secret manager with rotation-aware restart, never hardcode them.
- Monitor KMS API latency and error rates; alert before the 10s timeout is routinely hit.
- Keep dev and prod provider config separate so local runs never touch production KMS.
When it happens
Trigger: Calling secretbox.Encrypt (directly or via EncryptPayloadKey falling through) while useKMS() is true and kms.Encrypt returns a non-nil error, e.g. expired Scaleway credentials, unreachable KMS endpoint, or an invalid CAVE_KMS_* key configuration. The call has a hard 10s context timeout, so KMS latency over 10 seconds also produces this.
Common situations: Deployed environment has CAVE_KMS_PROVIDER=scaleway but the access/secret key env vars are wrong or rotated; KMS API outage or network egress blocked from the container; KMS request exceeding the 10-second timeout; wrong KMS region/key ID configured.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
- secretbox: payload KMS encrypt
- kms: plaintext exceeds
- kms: probe encrypt
- kms: read response
- kms: request failed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/5a86c7edc0f22ba4.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/secretbox/secretbox.go:59
keyBytes, err := base64.StdEncoding.DecodeString(keyB64)
if err != nil {
return nil, fmt.Errorf("%s is not valid base64: %w", envKey, err)
}
if len(keyBytes) != 32 {
return nil, fmt.Errorf("%s must decode to exactly 32 bytes, got %d", envKey, len(keyBytes))
}
return keyBytes, nil
}
// Encrypt seals plaintext with AES-256-GCM and a fresh random nonce, returning
// nonce(12) || ciphertext+tag as raw bytes.
func Encrypt(plaintext []byte) ([]byte, error) {
if useKMS() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
wrapped, err := kms.Encrypt(ctx, plaintext)
if err != nil {
return nil, fmt.Errorf("secretbox: KMS encrypt: %w", err)
}
return wrapped, nil
}
if runtimeenv.IsProduction() {
return nil, fmt.Errorf("secretbox: production requires CAVE_KMS_PROVIDER=scaleway")
}
keyBytes, err := loadKey()
if err != nil {
return nil, err
}
block, err := aes.NewCipher(keyBytes)
if err != nil {
return nil, fmt.Errorf("aes cipher: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("aes-gcm: %w", err)
}View on GitHub (pinned to 3ee70a1026)