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

  1. Check the wrapped error's cause first — status code 401/403 means credentials, 404 means wrong key ID, timeouts mean network/latency.
  2. Verify the Scaleway credentials and key ID configured for the KMS package, and that the endpoint is reachable from the deploy network.
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/5a86c7edc0f22ba4. Report an issue: GitHub.