{"record":{"id":"c4edaab98c1a6635","repo":"kovidgoyal/kitty","slug":"invalid-totp-secret-w","errorCode":null,"errorMessage":"invalid TOTP secret: %w","messagePattern":"invalid TOTP secret: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kittens/ssh/askpass.go","lineNumber":65,"sourceCode":"}\n\nfunc isOTPPrompt(msg string) bool {\n\tq := strings.ToLower(msg)\n\tif strings.Contains(q, \"passphrase\") {\n\t\treturn false\n\t}\n\tif strings.Contains(q, \"verification code\") || strings.Contains(q, \"one-time password\") || strings.Contains(q, \"one time password\") || strings.Contains(q, \"authenticator code\") || strings.Contains(q, \"authentication code\") || strings.Contains(q, \"two-factor\") || strings.Contains(q, \"2fa\") || strings.Contains(q, \"otp\") || strings.Contains(q, \"passcode\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc generateTOTP(secret string, digits, period int64, t time.Time) (string, error) {\n\ts := strings.ToUpper(strings.TrimSpace(secret))\n\ts = strings.ReplaceAll(s, \" \", \"\")\n\tkey, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(s)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid TOTP secret: %w\", err)\n\t}\n\tcounter := uint64(t.Unix() / period)\n\tvar buf [8]byte\n\tbinary.BigEndian.PutUint64(buf[:], counter)\n\tmac := hmac.New(sha1.New, key)\n\t_, _ = mac.Write(buf[:])\n\tsum := mac.Sum(nil)\n\toff := sum[len(sum)-1] & 0x0f\n\tcode := (uint32(sum[off])&0x7f)<<24 | (uint32(sum[off+1])&0xff)<<16 | (uint32(sum[off+2])&0xff)<<8 | (uint32(sum[off+3]) & 0xff)\n\tmod := uint32(1)\n\tfor range digits {\n\t\tmod *= 10\n\t}\n\tval := code % mod\n\tfmtstr := fmt.Sprintf(\"%%0%dd\", digits)\n\treturn fmt.Sprintf(fmtstr, val), nil\n}\n","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kittens/ssh/askpass.go#L47-L83","documentation":"generateTOTP decodes the configured secret as base32 (uppercase, no padding) before computing the HMAC-based one-time password. If decoding fails, the secret is not valid base32 and this error is returned. Called from RunSSHAskpass when a TOTP field is requested.","triggerScenarios":"Configuring an ssh secret with a password field whose value contains characters outside the base32 alphabet (lowercase after normalization, digits like 0/1/8 in some alphabets, punctuation) or wrong padding.","commonSituations":"Pasting a TOTP seed that includes spaces handled incorrectly, hex-format seeds, or secrets copied with trailing characters/newlines.","solutions":["Provide the secret as standard base32 (A-Z, 2-7), no padding","Strip whitespace and padding; the code already uppercases and removes spaces","Re-copy the seed from the authenticator/QR provisioning URI","Test decode with base32.StdEncoding.WithPadding(base32.NoPadding) locally"],"exampleFix":"// before\npassword: \"hello world!\"\n// after\npassword: \"JBSWY3DPEHPK3PXP\"","handlingStrategy":"validation","validationCode":"func validBase32(s string) bool {\n    s = strings.ToUpper(strings.TrimSpace(strings.ReplaceAll(s, \" \", \"\")))\n    _, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(s)\n    return err == nil\n}","typeGuard":"func isTOTPSecret(s string) bool { return validBase32(s) }","tryCatchPattern":null,"preventionTips":["Copy base32 seeds exactly from the provisioning URI","Avoid hex-format seeds","Strip whitespace before storing"],"tags":["totp","base32","ssh","askpass","secrets"],"backgroundTag":"invalid-secret-encoding","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}