{"record":{"id":"db6242d5ef0e408f","repo":"JuliusBrussee/caveman","slug":"envelope-marshal-metadata-w","errorCode":null,"errorMessage":"envelope: marshal metadata: %w","messagePattern":"envelope: marshal metadata: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/envelope/envelope.go","lineNumber":95,"sourceCode":"\t}\n\tgcm, err := cipher.NewGCM(block)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"envelope: gcm: %w\", err)\n\t}\n\tnonce := make([]byte, gcm.NonceSize())\n\tif _, err := rand.Read(nonce); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"envelope: nonce entropy: %w\", err)\n\t}\n\tciphertext = gcm.Seal(nonce, nonce, plaintext, aad)\n\n\twrapped, err := secretbox.EncryptPayloadKey(dataKey)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"envelope: wrap data key: %w\", err)\n\t}\n\tmeta := Metadata{Scheme: scheme, WrappedDataKey: base64.StdEncoding.EncodeToString(wrapped), ScopeHash: scopeHash}\n\tmetaJSON, err = json.Marshal(meta)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"envelope: marshal metadata: %w\", err)\n\t}\n\treturn ciphertext, metaJSON, nil\n}\n\n// Open reverses Seal: it unwraps the data key from metadata and decrypts the\n// ciphertext. An unknown scheme fails closed.\nfunc Open(ciphertext []byte, metaJSON []byte) ([]byte, error) {\n\tvar meta Metadata\n\tif err := json.Unmarshal(metaJSON, &meta); err != nil {\n\t\treturn nil, fmt.Errorf(\"envelope: parse metadata: %w\", err)\n\t}\n\tif meta.Scheme == schemeV2 {\n\t\treturn nil, fmt.Errorf(\"envelope: tenant scope required for scheme %q\", meta.Scheme)\n\t}\n\tif meta.Scheme != schemeV1 {\n\t\treturn nil, fmt.Errorf(\"envelope: unknown scheme %q\", meta.Scheme)\n\t}\n\treturn open(ciphertext, meta, nil)","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/envelope/envelope.go#L77-L113","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ntype Metadata struct {\n\tScheme        string\n\tWrappedDataKey string\n\tScopeHash     string\n\tNotify        func() // not JSON-marshalable\n}\n// after\ntype Metadata struct {\n\tScheme         string\n\tWrappedDataKey string\n\tScopeHash      string\n}","handlingStrategy":"try-catch","validationCode":"// Metadata contains only string fields, so marshal failures are near-impossible;\n// guard the envelope before storing:\nif _, _, err := envelope.SealForScope(plain, scope); err != nil {\n\treturn fmt.Errorf(\"seal failed before write: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if _, meta, err := envelope.Seal(plain); err != nil {\n\tif strings.Contains(err.Error(), \"marshal metadata\") {\n\t\t// internal invariant issue: inspect Metadata struct changes\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["go","json","envelope-encryption","internal-error"],"backgroundTag":"json-marshal-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}