{"record":{"id":"65232ffaa546821e","repo":"FiloSottile/age","slug":"invalid-hrp-q","errorCode":null,"errorMessage":"invalid HRP: %q","messagePattern":"invalid HRP: %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/bech32/bech32.go","lineNumber":115,"sourceCode":"\t\t\tret = append(ret, byte(acc<<(tobits-bits))&maxv)\n\t\t}\n\t} else if bits >= frombits {\n\t\treturn nil, fmt.Errorf(\"illegal zero padding\")\n\t} else if byte(acc<<(tobits-bits))&maxv != 0 {\n\t\treturn nil, fmt.Errorf(\"non-zero padding\")\n\t}\n\treturn ret, nil\n}\n\n// Encode encodes the HRP and a bytes slice to Bech32. If the HRP is uppercase,\n// the output will be uppercase.\nfunc Encode(hrp string, data []byte) (string, error) {\n\tvalues, err := convertBits(data, 8, 5, true)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(hrp) < 1 {\n\t\treturn \"\", fmt.Errorf(\"invalid HRP: %q\", hrp)\n\t}\n\tfor p, c := range hrp {\n\t\tif c < 33 || c > 126 {\n\t\t\treturn \"\", fmt.Errorf(\"invalid HRP character: hrp[%d]=%d\", p, c)\n\t\t}\n\t}\n\tif strings.ToUpper(hrp) != hrp && strings.ToLower(hrp) != hrp {\n\t\treturn \"\", fmt.Errorf(\"mixed case HRP: %q\", hrp)\n\t}\n\tlower := strings.ToLower(hrp) == hrp\n\thrp = strings.ToLower(hrp)\n\tvar ret strings.Builder\n\tret.WriteString(hrp)\n\tret.WriteString(\"1\")\n\tfor _, p := range values {\n\t\tret.WriteByte(charset[p])\n\t}\n\tfor _, p := range createChecksum(hrp, values) {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/bech32/bech32.go#L97-L133","documentation":"bech32.Encode validates the human-readable part (HRP) before encoding. A bech32 HRP must be at least one character long; an empty string cannot form the required \"hrp1\" separator structure, so Encode rejects it with this error. It is used by age when serializing SSH identities and recipients.","triggerScenarios":"Calling bech32.Encode (directly, or via agessh encoders like EncodeIdentity/EncodeRecipient) with hrp == \"\" — e.g. an uninitialized constant, a variable that failed lookup, or code that passes an empty prefix when serializing key bytes.","commonSituations":"Refactored code where an HRP constant was deleted or set to empty; config-driven HRP selection where the config key is missing and yields \"\"; tests constructing bech32 strings with an omitted prefix.","solutions":["Pass the intended non-empty HRP (age uses \"AGE-SECRET-KEY-\" for identities and \"age\" for recipients)","Check where the HRP value originates and add a guard: if hrp == \"\" return a descriptive error before calling Encode","If the HRP comes from configuration, provide a sane default and validate at load time"],"exampleFix":"// before\nhrp := cfg.KeyPrefix // \"\" when config key missing\ns, err := bech32.Encode(hrp, data) // \"invalid HRP: \\\"\\\"\"\n\n// after\nhrp := cfg.KeyPrefix\nif hrp == \"\" {\n    hrp = \"age\"\n}\ns, err := bech32.Encode(hrp, data)","handlingStrategy":"validation","validationCode":"func validHRP(hrp string) bool {\n\treturn len(hrp) >= 1\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use age's constants \"age\" and \"AGE-SECRET-KEY-\" rather than dynamic HRPs","Fail fast on missing config values that feed the HRP; never let empty strings reach Encode","Add a unit test covering the empty-HRP path of any custom serialization code","Default empty prefixes to the age standard values at configuration load"],"tags":["bech32","validation","hrp","internal"],"backgroundTag":"invalid-bech32-hrp","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}