JuliusBrussee/caveman · error
secretbox: KMS encrypt: %w
Error message
secretbox: KMS encrypt: %w
What it means
secretbox.Encrypt delegated to the KMS (useKMS() true, i.e. CAVE_KMS_PROVIDER configured) and kms.Encrypt returned an error within its 10-second context deadline; the error is wrapped with the 'secretbox: KMS encrypt' prefix so the KMS root cause survives for errors.Is/As. Failures originate in the KMS client: auth, network, throttling, or the deadline itself.
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 27d5a3981a)
Solutions
- Check the wrapped error's cause first — status code 401/403 means credentials, 404 means wrong key ID, timeouts mean network/latency.
- Verify the Scaleway credentials and key ID configured for the KMS package, and that the endpoint is reachable from the deploy network.
- Retry transient failures (throttle/network) with backoff at the caller; if 10s is consistently exceeded, address latency rather than removing the deadline.
Example fix
// before
ct, err := secretbox.Encrypt(secret)
if err != nil { return err } // surfaces as "secretbox: KMS encrypt: ..."
// after
var ct []byte
err := retry(3, time.Second, func() error {
var e error
ct, e = secretbox.Encrypt(secret)
return e
})
if err != nil { return fmt.Errorf("seal secret: %w", err) } Defensive patterns
Strategy: retry
Validate before calling
// Preflight the KMS path at startup so config errors surface before traffic:
func probeKMS(ctx context.Context) error {
_, err := kms.Encrypt(ctx, []byte("healthcheck"))
return err // 401/403/404 => config; timeout => network
} Try / catch
var out []byte
err := retry(3, 500*time.Millisecond, func() error {
var e error
out, e = secretbox.Encrypt(pt)
return e
})
if err != nil {
return fmt.Errorf("secretbox encrypt: %w", err) // inspect cause; only retry transient (network/429)
} Prevention
- Probe KMS reachability and credentials at startup with a cheap encrypt call.
- Alert on wrapped 4xx causes — they are configuration drift, not transient failures.
- Keep the 10s deadline; if it trips regularly, fix latency instead of removing the bound.
When it happens
Trigger: Encrypting a secret while the Scaleway KMS endpoint is unreachable (DNS, firewall, outage), the API credentials are wrong/expired, the key reference points to a deleted/disabled KMS key, or the call exceeded the hard-coded 10s timeout.
Common situations: Production deploy with missing/rotated SCW credentials; VPC/firewall blocking the KMS endpoint; the KMS key was deleted after a cleanup but the config still references it; latency spikes making 10s too tight.
Related errors
- secretbox: payload KMS encrypt: %w
- kms: %s request failed: %w
- kms: read %s response: %w
- %s is not set; cannot encrypt/decrypt secrets
- %s must decode to exactly 32 bytes, got %d
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/5a86c7edc0f22ba4.
Report an issue: GitHub.