JuliusBrussee/caveman · error
envelope: open
Error message
envelope: open: %w
What it means
GCM authenticated decryption failed in envelope.Open/OpenForScope: gcm.Open returned an error because the ciphertext, nonce, or additional authenticated data (AAD) does not match what was sealed. GCM is an authenticated mode, so any tampering, key mismatch, or scope mismatch surfaces as this wrapped error. The original plaintext is never exposed.
Solutions
- Confirm the same data key / key reference used at seal time is being used at open time (check key version, keyring, env)
- For OpenForScope, ensure the Scope (OrganizationID, ProjectID, Kind) exactly matches the one passed to SealForScope — AAD is bound via SHA-256
- Re-seal the data with the current key/scope if the original credentials are unrecoverable
- Check whether the stored ciphertext was altered (encoding round-trips, whitespace trimming, JSON escaping)
Example fix
// before: mismatched scope on open
pt, err := envelope.OpenForScope(ctx, key, ct, envelope.Scope{OrganizationID: orgID, Kind: "note"})
// after: reuse the exact scope recorded at seal time
pt, err := envelope.OpenForScope(ctx, key, ct, envelope.Scope{OrganizationID: orgID, ProjectID: projID, Kind: "document"}) Defensive patterns
Strategy: try-catch
Validate before calling
// cannot pre-validate integrity; ensure key ref and scope match what was used at seal time // store the key version and scope hash alongside the ciphertext
Try / catch
pt, err := envelope.OpenForScope(ctx, key, ct, scope)
if err != nil && strings.Contains(err.Error(), "envelope: open:") {
// wrong key or tampered/mismatched AAD: do not retry blindly;
// check key rotation history and scope equality before failing
return ErrDecryptionMismatch
} Prevention
- Persist the key version / keyring ref next to each ciphertext
- Reuse the exact same Scope struct value for seal and open (single constructor helper)
- Treat any modification of encrypted blobs (trim, re-encode) as forbidden
When it happens
Trigger: Calling envelope.Open with the wrong data key (different keyring/key version), or envelope.OpenForScope with a Scope whose OrganizationID/ProjectID/Kind differ from those used at SealForScope time, or with ciphertext that was modified or corrupted in storage.
Common situations: Rotating keys without re-encrypting old rows; changing the scope kind string (e.g. "doc" vs "document") between seal and open; a migration edited encrypted bytes; an env pointed at a different KMS key than the one that sealed the data.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- aes-gcm
- envelope: ciphertext too short
- aes-gcm open
- ciphertext too short
- envelope: object kind is required
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/0820862275b8091b.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/envelope/envelope.go:163
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")
}
aad, err := json.Marshal(struct {
Version int `json:"version"`
OrganizationID string `json:"organization_id"`
ProjectID string `json:"project_id"`View on GitHub (pinned to 3ee70a1026)