{"record":{"id":"ae5f410eaa2645df","repo":"FiloSottile/age","slug":"malformed-ssh-recipient-q-v","errorCode":null,"errorMessage":"malformed SSH recipient: %q: %v","messagePattern":"malformed SSH recipient: %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agessh/agessh.go","lineNumber":176,"sourceCode":"\tepk, ok := cpk.CryptoPublicKey().(ed25519.PublicKey)\n\tif !ok {\n\t\treturn nil, errors.New(\"unexpected public key type\")\n\t}\n\tmpk, err := ed25519PublicKeyToCurve25519(epk)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid Ed25519 public key: %v\", err)\n\t}\n\n\treturn &Ed25519Recipient{\n\t\tsshKey:         pk,\n\t\ttheirPublicKey: mpk,\n\t}, nil\n}\n\nfunc ParseRecipient(s string) (age.Recipient, error) {\n\tpubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"malformed SSH recipient: %q: %v\", s, err)\n\t}\n\n\tvar r age.Recipient\n\tswitch t := pubKey.Type(); t {\n\tcase \"ssh-rsa\":\n\t\tr, err = NewRSARecipient(pubKey)\n\tcase \"ssh-ed25519\":\n\t\tr, err = NewEd25519Recipient(pubKey)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown SSH recipient type: %q\", t)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"malformed SSH recipient: %q: %v\", s, err)\n\t}\n\n\treturn r, nil\n}\n","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/agessh/agessh.go#L158-L194","documentation":"ParseRecipient parses an SSH recipient string with golang.org/x/crypto/ssh.ParseAuthorizedKey. If the string is not a valid SSH authorized_keys line (bad base64, wrong format, unsupported content), it wraps the underlying error in \"malformed SSH recipient\". The %q shows the offending string and %v the parse failure reason.","triggerScenarios":"Calling agessh.ParseRecipient with a string that ssh.ParseAuthorizedKey rejects: not in 'type base64 [comment]' format, invalid base64, truncated key, or extra invalid fields.","commonSituations":"Passing a private key file contents instead of the public key; quoting or whitespace corruption in config files; passing an age native recipient (age1...) to the SSH parser; copy-paste that dropped characters.","solutions":["Pass the public key in authorized_keys format, e.g. 'ssh-ed25519 AAAA... user@host'.","Verify with `ssh-keygen -lf keyfile.pub` that the key parses before giving it to age.","Ensure you are not passing a private key or an age1... recipient where an SSH recipient is expected."],"exampleFix":"// before\nr, err := agessh.ParseRecipient(string(privKeyBytes)) // private key passed\n// after\npubBytes, _ := os.ReadFile(\"id_ed25519.pub\")\nr, err := agessh.ParseRecipient(strings.TrimSpace(string(pubBytes)))","handlingStrategy":"validation","validationCode":"s := strings.TrimSpace(recipientStr)\nfields := strings.Fields(s)\nif len(fields) < 2 || !strings.HasPrefix(fields[0], \"ssh-\") {\n    return errors.New(\"recipient must be an authorized_keys-format SSH public key\")\n}\nif _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(s)); err != nil {\n    return fmt.Errorf(\"invalid SSH public key: %w\", err)\n}","typeGuard":"func isSSHPublicKey(s string) bool {\n    _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(strings.TrimSpace(s)))\n    return err == nil\n}","tryCatchPattern":"r, err := agessh.ParseRecipient(s)\nif err != nil {\n    return fmt.Errorf(\"check recipient format (type base64 comment): %w\", err)\n}","preventionTips":["Pass the .pub public key file contents, never the private key.","Trim whitespace and newlines from config-sourced recipient strings.","Sanity-check keys with `ssh-keygen -lf` before adding to configs."],"tags":["ssh","parsing","recipient"],"backgroundTag":"malformed-ssh-recipient","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}