JuliusBrussee/caveman · error
envelope: marshal metadata
Error message
envelope: marshal metadata: %w
What it means
Seal generates a random data key, encrypts the payload, then serializes the Metadata struct (scheme, base64 wrapped key, scope hash) to JSON. This error wraps a json.Marshal failure on that Metadata struct. In practice it is nearly impossible to hit because Metadata contains only marshalable types, but the guard keeps Seal fail-closed instead of returning corrupted envelopes.
Solutions
- Inspect the wrapped underlying error; it names the exact json.Marshal failure and offending value
- Check any recent change to the Metadata struct for fields that are not JSON-marshalable or have an erroring MarshalJSON method
- Re-run Seal on a known-good plaintext/key pair to confirm it is data-independent (i.e. a build/code problem, not input)
- Report upstream if Metadata is unmodified — this indicates an internal invariant violation
Example fix
// before
type Metadata struct {
Scheme string
WrappedDataKey string
ScopeHash string
Notify func() // not JSON-marshalable
}
// after
type Metadata struct {
Scheme string
WrappedDataKey string
ScopeHash string
} Defensive patterns
Strategy: try-catch
Validate before calling
// Metadata contains only string fields, so marshal failures are near-impossible;
// guard the envelope before storing:
if _, _, err := envelope.SealForScope(plain, scope); err != nil {
return fmt.Errorf("seal failed before write: %w", err)
} Try / catch
if _, meta, err := envelope.Seal(plain); err != nil {
if strings.Contains(err.Error(), "marshal metadata") {
// internal invariant issue: inspect Metadata struct changes
}
return err
} Prevention
- Keep Metadata limited to JSON-marshalable primitive fields
- Add a round-trip test that Seals and immediately json.Validates the metadata
- Never add func/chan fields to Metadata
When it happens
Trigger: Calling Seal or SealForScope with a scheme/scope whose scopeHash or wrapped-key string somehow yields an unmarshalable Metadata value; in practice only a custom json.Marshaler injected via a modified Metadata type or a corrupted build.
Common situations: Barely seen in production; typically only during code changes where Metadata gains a field of a non-marshalable type (chan, func, cyclic pointer) or a MarshalJSON method that errors.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- envelope: encode scope
- envelope: parse metadata
- envelope: decode wrapped key
- envelope: tenant scope mismatch
- envelope: tenant scope required for scheme
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/db6242d5ef0e408f.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/envelope/envelope.go:95
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, fmt.Errorf("envelope: gcm: %w", err)
}
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, nil, fmt.Errorf("envelope: nonce entropy: %w", err)
}
ciphertext = gcm.Seal(nonce, nonce, plaintext, aad)
wrapped, err := secretbox.EncryptPayloadKey(dataKey)
if err != nil {
return nil, nil, fmt.Errorf("envelope: wrap data key: %w", err)
}
meta := Metadata{Scheme: scheme, WrappedDataKey: base64.StdEncoding.EncodeToString(wrapped), ScopeHash: scopeHash}
metaJSON, err = json.Marshal(meta)
if err != nil {
return nil, nil, fmt.Errorf("envelope: marshal metadata: %w", err)
}
return ciphertext, metaJSON, nil
}
// Open reverses Seal: it unwraps the data key from metadata and decrypts the
// ciphertext. An unknown scheme fails closed.
func Open(ciphertext []byte, metaJSON []byte) ([]byte, error) {
var meta Metadata
if err := json.Unmarshal(metaJSON, &meta); err != nil {
return nil, fmt.Errorf("envelope: parse metadata: %w", err)
}
if meta.Scheme == schemeV2 {
return nil, fmt.Errorf("envelope: tenant scope required for scheme %q", meta.Scheme)
}
if meta.Scheme != schemeV1 {
return nil, fmt.Errorf("envelope: unknown scheme %q", meta.Scheme)
}
return open(ciphertext, meta, nil)View on GitHub (pinned to 3ee70a1026)