{"record":{"id":"87bd55db2c80e694","repo":"FiloSottile/age","slug":"invalid-character-human-readable-part-s-d-d","errorCode":null,"errorMessage":"invalid character human-readable part: s[%d]=%d","messagePattern":"invalid character human-readable part: s\\[(.+?)\\]=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bech32/bech32.go","lineNumber":154,"sourceCode":"\tif lower {\n\t\treturn ret.String(), nil\n\t}\n\treturn strings.ToUpper(ret.String()), nil\n}\n\n// Decode decodes a Bech32 string. If the string is uppercase, the HRP will be uppercase.\nfunc Decode(s string) (hrp string, data []byte, err error) {\n\tif strings.ToLower(s) != s && strings.ToUpper(s) != s {\n\t\treturn \"\", nil, fmt.Errorf(\"mixed case\")\n\t}\n\tpos := strings.LastIndex(s, \"1\")\n\tif pos < 1 || pos+7 > len(s) {\n\t\treturn \"\", nil, fmt.Errorf(\"separator '1' at invalid position: pos=%d, len=%d\", pos, len(s))\n\t}\n\thrp = s[:pos]\n\tfor p, c := range hrp {\n\t\tif c < 33 || c > 126 {\n\t\t\treturn \"\", nil, fmt.Errorf(\"invalid character human-readable part: s[%d]=%d\", p, c)\n\t\t}\n\t}\n\tfor p, c := range s[pos+1:] {\n\t\t// Fold ASCII explicitly. Unicode case folding can turn a non-ASCII\n\t\t// rune into a shorter valid charset member.\n\t\tif c >= 'A' && c <= 'Z' {\n\t\t\tc += 'a' - 'A'\n\t\t}\n\t\td := strings.IndexRune(charset, c)\n\t\tif d == -1 {\n\t\t\treturn \"\", nil, fmt.Errorf(\"invalid character data part: s[%d]=%v\", p, c)\n\t\t}\n\t\tdata = append(data, byte(d))\n\t}\n\tif len(data) < 6 {\n\t\treturn \"\", nil, fmt.Errorf(\"data part too short\")\n\t}\n\tif !verifyChecksum(hrp, data) {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/bech32/bech32.go#L136-L172","documentation":"After splitting on the last '1', Decode validates every rune of the human-readable part is in the printable ASCII range 33-126. A rune outside that range (spaces are ok at 32? no - 32 is excluded, so space fails; control chars, and non-ASCII unicode fail) makes the HRP invalid. The library enforces the bech32 spec's printable-ASCII HRP requirement.","triggerScenarios":"Decode with a string whose text before the last '1' contains a space, control character, or any byte/rune outside 33-126, e.g. \"my key1qqqqqq\" or \"café1qqqq\".","commonSituations":"Keys pasted from documents with smart quotes or trailing whitespace, config files with BOM or invisible control characters, terminal copy including ANSI codes.","solutions":["Trim and clean the input of whitespace and control characters before decoding","Re-copy the key as plain ASCII from the original source","Strip BOM/zero-width characters from config-sourced values"],"exampleFix":"// before\ncallersBech32.Decode(strings.TrimSpace(cfg.Recipient)) // may still contain unicode\n// after\nhrp1qq := cfg.Recipient\nhrp1qq = strings.TrimSpace(hrp1qq)\nhrp1qq = strings.Map(func(r rune) rune { if r >= 33 && r <= 126 { return r }; return -1 }, hrp1qq)","handlingStrategy":"validation","validationCode":"func printableASCII(s string) bool {\n    for _, r := range s {\n        if r < 33 || r > 126 { return false }\n    }\n    return true\n}\nif !printableASCII(input) { return errors.New(\"input contains non-printable or non-ASCII characters\") }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Trim and sanitize values read from config/env before use","Strip BOM and zero-width characters from copied text","Treat key material as opaque ASCII-only strings"],"tags":["bech32","decoding","ascii"],"backgroundTag":"bech32-invalid-character","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}