JuliusBrussee/caveman · error

secretbox: KMS decrypt: %w

Error message

secretbox: KMS decrypt: %w

What it means

Thrown by secretbox.Decrypt when the input is a KMS envelope (kms.IsEnvelope matched) but the KMS Decrypt call fails. The KMS operation runs with a hard 10-second context timeout, so both real KMS failures (bad key id, denied permissions, corrupted envelope) and timeouts surface here wrapped with the 'secretbox: KMS decrypt:' prefix. The underlying %w error carries the KMS-specific detail.

Source

Thrown at shared/platform/secretbox/secretbox.go:110

		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		wrapped, err := kms.EncryptPayload(ctx, plaintext)
		if err != nil {
			return nil, fmt.Errorf("secretbox: payload KMS encrypt: %w", err)
		}
		return wrapped, nil
	}
	return Encrypt(plaintext)
}

// Decrypt reverses Encrypt: it expects nonce(12) || ciphertext+tag.
func Decrypt(envelope []byte) ([]byte, error) {
	if kms.IsEnvelope(envelope) {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		plaintext, err := kms.Decrypt(ctx, envelope)
		if err != nil {
			return nil, fmt.Errorf("secretbox: KMS decrypt: %w", err)
		}
		return plaintext, nil
	}
	if runtimeenv.IsProduction() &&
		!strings.EqualFold(strings.TrimSpace(os.Getenv("CAVE_KMS_ALLOW_LEGACY_LOCAL_DECRYPT")), "true") {
		return nil, fmt.Errorf("secretbox: production refuses legacy local ciphertext")
	}
	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. Inspect the wrapped error chain (errors.Unwrap) to see the true KMS cause: NotFound/Disabled key, AccessDenied, or context deadline exceeded.
  2. If the error is a timeout, verify network egress and KMS endpoint reachability from the service, then retry the decrypt.
  3. If AccessDenied/NotFound, fix the key policy or re-enable the key referenced by the envelope, or re-encrypt the data under an accessible key.
  4. If the envelope bytes are corrupt (invalid base64/structure), restore the correct ciphertext from its source of truth rather than patching bytes.

Example fix

// before
pt, err := secretbox.Decrypt(env)
if err != nil {
    log.Fatalf("decrypt failed: %v", err) // opaque
}

// after
pt, err := secretbox.Decrypt(env)
if err != nil {
    var kerr *kms.Error // or inspect errors.Unwrap chain
    if errors.As(err, &kerr) {
        log.Printf("kms code=%s key=%s", kerr.Code, kerr.KeyID)
    }
    if strings.Contains(err.Error(), "context deadline exceeded") {
        // retry once after verifying egress to the KMS endpoint
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if kms.IsEnvelope(data) {
    // will hit the KMS path; ensure KMS client is configured and reachable first
    if err := kms.HealthCheck(ctx); err != nil {
        return fmt.Errorf("kms not ready: %w", err)
    }
}

Type guard

func isKmsDecryptErr(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), "secretbox: KMS decrypt:")
}

Try / catch

pt, err := secretbox.Decrypt(env)
if err != nil {
    if isKmsDecryptErr(err) {
        if errors.Is(err, context.DeadlineExceeded) {
            // retry once: KMS timeout is often transient
            pt, err = secretbox.Decrypt(env)
        }
    }
    if err != nil {
        return fmt.Errorf("decrypt envelope: %w", err)
    }
}

Prevention

When it happens

Trigger: Calling secretbox.Decrypt on data previously encrypted via secretbox.Encrypt when a KMS envelope was produced, while KMS credentials/key access are missing, the key was rotated/disabled, the envelope bytes were truncated or tampered with, or the KMS API did not answer within 10 seconds.

Common situations: Deploying to an environment where the KMS IAM role/service account differs from the one that encrypted the data; key rotation disabled the old key; a database restore moved ciphertext across KMS regions; network egress to the KMS endpoint blocked so the 10s timeout fires.

Related errors


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