JuliusBrussee/caveman · error
envelope: ciphertext too short
Error message
envelope: ciphertext too short
What it means
The ciphertext blob passed to envelope decryption is shorter than the GCM nonce size (12 bytes for AES-GCM), so there is not even a nonce present. The library rejects it before attempting authenticated decryption. This almost always means the input is truncated, empty, corrupted, or not actually an envelope ciphertext.
Solutions
- Check the length of the ciphertext before calling Open — it must be at least 12 bytes (nonce) plus 16 bytes (tag) plus plaintext
- Re-fetch or restore the original encrypted blob; the stored copy is corrupted or truncated
- Verify the write path that persisted the ciphertext completed without truncation (column size, streaming cutoffs)
- Confirm you are passing the ciphertext, not plaintext or an encoded ID, into Open/OpenForScope
Example fix
// before
plaintext, err := envelope.Open(ctx, keyRef, blobFromCache, nil)
// after
if len(blob) < 12+16 {
return fmt.Errorf("stored blob truncated: %d bytes", len(blob))
}
plaintext, err := envelope.Open(ctx, keyRef, blob, nil) Defensive patterns
Strategy: validation
Validate before calling
if len(ciphertext) < 12+16 {
return fmt.Errorf("ciphertext too short (%d bytes): blob is truncated or not envelope data", len(ciphertext))
} Type guard
func isPlausibleCiphertext(b []byte) bool { return len(b) >= 12+16 } Try / catch
pt, err := envelope.Open(ctx, keyRef, ct, aad)
if err != nil {
if strings.Contains(err.Error(), "ciphertext too short") {
// treat as corrupted/unrecoverable record; re-seal from source or surface 404
}
return err
} Prevention
- Use large-enough DB columns / no truncating encodings for ciphertext blobs
- Validate base64 decodes round-trip before storing
- Never write plaintext into fields expected to hold ciphertext
When it happens
Trigger: Calling envelope.Open or envelope.OpenForScope with a ciphertext byte slice of length less than the GCM nonce size (12) — e.g. an empty slice, a truncated stored blob, or plaintext handed in by mistake.
Common situations: A database row or object-store blob was truncated by a bad write; a column sized too small silently cut the value; decoding a base64 blob produced empty/partial bytes; passing a hash or ID string instead of the ciphertext.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e26a4922140e22ac.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/envelope/envelope.go:158
wrapped, err := base64.StdEncoding.DecodeString(meta.WrappedDataKey)
if err != nil {
return nil, fmt.Errorf("envelope: decode wrapped key: %w", err)
}
dataKey, err := secretbox.DecryptPayloadKey(wrapped)
if err != nil {
return nil, fmt.Errorf("envelope: unwrap data key: %w", err)
}
block, err := aes.NewCipher(dataKey)
if err != nil {
return nil, fmt.Errorf("envelope: aes: %w", err)
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("envelope: gcm: %w", err)
}
ns := gcm.NonceSize()
if len(ciphertext) < ns {
return nil, fmt.Errorf("envelope: ciphertext too short")
}
nonce, ct := ciphertext[:ns], ciphertext[ns:]
plaintext, err := gcm.Open(nil, nonce, ct, aad)
if err != nil {
return nil, fmt.Errorf("envelope: open: %w", err)
}
return plaintext, nil
}
func scopeAAD(scope Scope) ([]byte, string, error) {
scope.OrganizationID = strings.TrimSpace(scope.OrganizationID)
scope.ProjectID = strings.TrimSpace(scope.ProjectID)
scope.Kind = strings.TrimSpace(scope.Kind)
if scope.OrganizationID == "" {
return nil, "", fmt.Errorf("envelope: organization scope is required")
}
if scope.Kind == "" {
return nil, "", fmt.Errorf("envelope: object kind is required")View on GitHub (pinned to 3ee70a1026)