JuliusBrussee/caveman · error

ciphertext too short

Error message

ciphertext too short

What it means

secretbox.Decrypt rejects an encrypted envelope that is shorter than the AES-GCM nonce size (12 bytes for the standard block). Such input cannot possibly contain a nonce plus ciphertext, so decryption is refused up front rather than letting GCM.Open fail with a confusing low-level error. It indicates the caller passed truncated, empty, or non-secretbox bytes to Decrypt.

Solutions

  1. Verify the env var / byte slice holds the full secretbox envelope: re-encode it with secretbox.Encrypt and compare lengths (must be > 12 bytes).
  2. Check the transport/storage step (DB column size, config loader, HTTP body reader) for truncation of the base64 value.
  3. Confirm you are not passing a plaintext value or a KMS envelope into Decrypt directly; use DecryptPayloadKey which dispatches KMS envelopes.
  4. Re-encrypt the secret at the source with the current key and redeploy the ciphertext.

Example fix

// before
wrapped, _ := base64.StdEncoding.DecodeString(os.Getenv("APP_SECRET"))
key, err := secretbox.Decrypt(wrapped) // ciphertext too short

// after
encoded := strings.TrimSpace(os.Getenv("APP_SECRET"))
if encoded == "" {
    return nil, fmt.Errorf("APP_SECRET is unset")
}
wrapped, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
    return nil, fmt.Errorf("APP_SECRET is not valid base64: %w", err)
}
if len(wrapped) <= 12 {
    return nil, fmt.Errorf("APP_SECRET envelope truncated (%d bytes)", len(wrapped))
}
key, err := secretbox.Decrypt(wrapped)
Defensive patterns

Strategy: validation

Validate before calling

if len(envelope) < 12 { // gcm nonce size
    return fmt.Errorf("secret envelope too short (%d bytes); re-export the secret", len(envelope))
}

Type guard

func looksLikeSecretboxEnvelope(b []byte) bool { return len(b) > 12 }

Try / catch

plain, err := secretbox.Decrypt(envelope)
if err != nil {
    if strings.Contains(err.Error(), "ciphertext too short") {
        return fmt.Errorf("secret envelope truncated or empty; re-provision it: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Decrypt (directly or via DecryptPayloadKey/ResolveEnvironmentSecret) with a zero-length slice, a slice shorter than 12 bytes, a byte slice that was truncated during transport, or a non-secretbox value such as a raw KMS envelope fragment or base64 of an empty/short string.

Common situations: Storing the ciphertext env var truncated by an ops tool that strips padding; copying only part of a base64 blob; a nil/empty value decoded from an unset variable; schema migrations that shortened a column.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/1fe530d43b22a78f. Report an issue: GitHub.

Appendix: source

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

	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)
	}
	ns := gcm.NonceSize()
	if len(envelope) < ns {
		return nil, fmt.Errorf("ciphertext too short")
	}
	nonce, ct := envelope[:ns], envelope[ns:]
	plain, err := gcm.Open(nil, nonce, ct, nil)
	if err != nil {
		return nil, fmt.Errorf("aes-gcm open: %w", err)
	}
	return plain, nil
}

// DecryptPayloadKey unwraps an artifact data-encryption key. KMS envelopes are
// restricted to the configured payload key plus the explicit legacy secrets
// key used before key separation.
func DecryptPayloadKey(envelope []byte) ([]byte, error) {
	if kms.IsEnvelope(envelope) {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		defer cancel()
		plaintext, err := kms.DecryptPayload(ctx, envelope)
		if err != nil {

View on GitHub (pinned to 3ee70a1026)