{"record":{"id":"873f4a3422f5fc85","repo":"caddyserver/caddy","slug":"supported-version-must-be-d-got-d","errorCode":null,"errorMessage":"supported version must be %d: got %d","messagePattern":"supported version must be (.+?): got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/ech.go","lineNumber":976,"sourceCode":"\tif err := echCfg.marshalBinary(&b); err != nil {\n\t\treturn nil, err\n\t}\n\treturn b.Bytes()\n}\n\n// UnmarshalBinary decodes the data back into an ECH config.\n//\n// Borrowed from github.com/OmarTariq612/goech with modifications.\n// Original code: Copyright (c) 2023 Omar Tariq AbdEl-Raziq\nfunc (echCfg *echConfig) UnmarshalBinary(data []byte) error {\n\tvar content cryptobyte.String\n\tb := cryptobyte.String(data)\n\n\tif !b.ReadUint16(&echCfg.Version) {\n\t\treturn errInvalidLen\n\t}\n\tif echCfg.Version != draftTLSESNI25 {\n\t\treturn fmt.Errorf(\"supported version must be %d: got %d\", draftTLSESNI25, echCfg.Version)\n\t}\n\n\tif !b.ReadUint16LengthPrefixed(&content) || !b.Empty() {\n\t\treturn errInvalidLen\n\t}\n\n\tvar t cryptobyte.String\n\tvar pk []byte\n\n\tif !content.ReadUint8(&echCfg.ConfigID) ||\n\t\t!content.ReadUint16((*uint16)(&echCfg.KEMID)) ||\n\t\t!content.ReadUint16LengthPrefixed(&t) ||\n\t\t!t.ReadBytes(&pk, len(t)) ||\n\t\t!content.ReadUint16LengthPrefixed(&t) ||\n\t\tlen(t)%4 != 0 /* the length of (KDFs and AEADs) must be divisible by 4 */ {\n\t\treturn errInvalidLen\n\t}\n","sourceCodeStart":958,"sourceCodeEnd":994,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/ech.go#L958-L994","documentation":"When decoding a stored ECH config (UnmarshalBinary, code borrowed from goech), the first two bytes must equal draftTLSESNI25 (0xfe0d, the draft-13 ECH version). If the stored config.bin holds a different version — corrupt bytes, a different ECH draft generation, or unrelated data placed at ech/configs/<id>/config.bin — decoding aborts with this message naming expected vs actual version.","triggerScenarios":"Loading ECH configs from storage where the binary was written by a Caddy build supporting a different ECH draft version, or the file was corrupted/truncated/replaced; also reading a config list with only the 2-byte version present and mismatched.","commonSituations":"Upgrading/downgrading Caddy across ECH draft-version changes while reusing the same storage; manual tampering or partial writes in ech/configs/*; storage backend returning garbage due to a bug.","solutions":["Compare the reported 'got' value: 0xfe0d expected; anything else means foreign data.","Delete the offending ech/configs/* entries in storage so Caddy regenerates fresh configs (ECH keys are cheap to rotate).","If it recurs, verify storage integrity and avoid downgrading Caddy below the version that wrote the configs."],"exampleFix":"# before\n$ caddy start\n error: supported version must be 65292: got 123\n\n# after: wipe ECH state and let Caddy re-create\n$ rm -rf ~/.local/share/caddy/ech/configs/*\n$ caddy start","handlingStrategy":"validation","validationCode":"// Before passing stored bytes to the loader, check the version field.\nfunc isKnownECHVersion(b []byte) bool {\n    return len(b) >= 2 && binary.BigEndian.Uint16(b[:2]) == 0xfe0d\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"supported version must be\") {\n    // stale/foreign stored config: delete ech/configs/* and let Caddy regenerate\n}","preventionTips":["Don't downgrade Caddy across ECH draft-version boundaries while reusing storage.","Wipe ech/configs/ when moving storage between incompatible builds.","Treat unexpected 'got' values as corruption: prune and regenerate."],"tags":["tls","ech","serialization","version-mismatch","storage"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}