{"record":{"id":"498bf20c88ec60f2","repo":"caddyserver/caddy","slug":"marshaling-ech-private-key-v","errorCode":null,"errorMessage":"marshaling ECH private key: %v","messagePattern":"marshaling ECH private key: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/ech.go","lineNumber":665,"sourceCode":"\t\t\t\tAEADID: hpke.AEAD_AES128GCM,\n\t\t\t},\n\t\t\t{\n\t\t\t\tKDFID:  hpke.KDF_HKDF_SHA256,\n\t\t\t\tAEADID: hpke.AEAD_AES256GCM,\n\t\t\t},\n\t\t\t{\n\t\t\t\tKDFID:  hpke.KDF_HKDF_SHA256,\n\t\t\t\tAEADID: hpke.AEAD_ChaCha20Poly1305,\n\t\t\t},\n\t\t},\n\t}\n\tmeta := echConfigMeta{\n\t\tCreated: time.Now(),\n\t}\n\n\tprivKeyBytes, err := privateKey.MarshalBinary()\n\tif err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"marshaling ECH private key: %v\", err)\n\t}\n\techConfigBytes, err := echCfg.MarshalBinary()\n\tif err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"marshaling ECH config: %v\", err)\n\t}\n\tmetaBytes, err := json.Marshal(meta)\n\tif err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"marshaling ECH config metadata: %v\", err)\n\t}\n\n\tparentKey := path.Join(echConfigsKey, strconv.Itoa(int(configID)))\n\tkeyKey := path.Join(parentKey, \"key.bin\")\n\tconfigKey := path.Join(parentKey, \"config.bin\")\n\tmetaKey := path.Join(parentKey, \"meta.json\")\n\n\tif err := ctx.Storage().Store(ctx, keyKey, privKeyBytes); err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"storing ECH private key: %v\", err)\n\t}","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/ech.go#L647-L683","documentation":"When Caddy creates a new ECH config, it generates an X25519 HPKE key pair and serializes the private half with MarshalBinary before persisting it to storage under ech/configs/<id>/key.bin. For a freshly generated, in-memory X25519 key this call is effectively infallible in the crypto/ecdh-backed hpke library; the check is purely defensive. If it ever fires, it signals an internal invariant break or a library regression, not a user config problem.","triggerScenarios":"Only reachable inside the internal routine that mints a new ECH config (privateKey just returned by hpke KEM_X25519_HKDF_SHA256.Scheme().GenerateKeyPair()). No user input influences it;MarshalBinary on a generated key does not fail in practice.","commonSituations":"Essentially never seen in the wild. Could appear after a Go or hpke dependency upgrade that changed key serialization semantics, or memory corruption.","solutions":["Treat as an internal error: report it upstream to Caddy with the Go and Caddy versions.","Retry config load/start once to rule out a transient environment issue.","Pin/rollback the Go toolchain or caddy version if the error appeared right after an upgrade."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"marshaling ECH private key\") {\n    // internal invariant break: report, do not retry — same result guaranteed\n    return fmt.Errorf(\"internal ECH key error, please report upstream: %w\", err)\n}","preventionTips":["Pin known-good Caddy/Go versions in deployment images.","Report occurrences upstream with version info rather than working around."],"tags":["tls","ech","crypto","internal"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}