{"record":{"id":"e9df8f1bb3af0514","repo":"FiloSottile/age","slug":"invalid-ssh-rsa-recipient-block","errorCode":null,"errorMessage":"invalid ssh-rsa recipient block","messagePattern":"invalid ssh-rsa recipient block","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agessh/agessh.go","lineNumber":125,"sourceCode":"}\n\nfunc (i *RSAIdentity) Recipient() *RSARecipient {\n\treturn &RSARecipient{\n\t\tsshKey: i.sshKey,\n\t\tpubKey: &i.k.PublicKey,\n\t}\n}\n\nfunc (i *RSAIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {\n\treturn multiUnwrap(i.unwrap, stanzas)\n}\n\nfunc (i *RSAIdentity) unwrap(block *age.Stanza) ([]byte, error) {\n\tif block.Type != \"ssh-rsa\" {\n\t\treturn nil, age.ErrIncorrectIdentity\n\t}\n\tif len(block.Args) != 1 {\n\t\treturn nil, errors.New(\"invalid ssh-rsa recipient block\")\n\t}\n\n\tif block.Args[0] != sshFingerprint(i.sshKey) {\n\t\treturn nil, age.ErrIncorrectIdentity\n\t}\n\n\tfileKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, i.k,\n\t\tblock.Body, []byte(oaepLabel))\n\tif err != nil {\n\t\t// The fingerprint is only a short hint, and might collide with the\n\t\t// fingerprint of a different recipient.\n\t\treturn nil, age.ErrIncorrectIdentity\n\t}\n\treturn fileKey, nil\n}\n\ntype Ed25519Recipient struct {\n\tsshKey         ssh.PublicKey","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/agessh/agessh.go#L107-L143","documentation":"RSAIdentity.unwrap only accepts stanzas of type \"ssh-rsa\" carrying exactly one argument (the key fingerprint). This error means an \"ssh-rsa\"-typed stanza arrived with a different number of arguments, so the stanza is malformed rather than merely belonging to another identity (that case returns age.ErrIncorrectIdentity). It indicates a corrupted, hand-crafted, or future/mutated age header.","triggerScenarios":"Calling Unwrap on a recipient stanza whose Type == \"ssh-rsa\" but len(Args) != 1 — e.g. an age file whose header stanza was edited, truncated, or generated by a non-conforming writer.","commonSituations":"Manually edited age files; files transferred through lossy pipelines that mangled the header; testing malicious/malformed headers against the parser; a producer bug emitting extra stanza args.","solutions":["Obtain the file from a trusted source or re-encrypt it; the stanza structure is invalid.","Verify the header with 'age-inspect' or by printing stanzas before unwrapping.","If a tool you wrote builds stanzas, emit exactly one arg (the sshFingerprint) for ssh-rsa recipients.","Treat this as a fatal parse error — do not retry with the same identity."],"exampleFix":"// before (producer)\nst := &age.Stanza{Type: \"ssh-rsa\", Args: []string{fp, extra}}\n// after\nst := &age.Stanza{Type: \"ssh-rsa\", Args: []string{fp}}","handlingStrategy":"try-catch","validationCode":"// inspect stanza before unwrapping\nif block.Type == \"ssh-rsa\" && len(block.Args) != 1 {\n    return fmt.Errorf(\"malformed ssh-rsa stanza: %d args\", len(block.Args))\n}","typeGuard":"func isWellFormedRSABlock(b *age.Stanza) bool {\n    return b != nil && b.Type == \"ssh-rsa\" && len(b.Args) == 1\n}","tryCatchPattern":"fileKey, err := identity.Unwrap(stanza)\nif err != nil {\n    if !errors.Is(err, age.ErrIncorrectIdentity) {\n        // malformed stanza: log and abort; retrying with other identities won't help\n        return fmt.Errorf(\"corrupt age header: %w\", err)\n    }\n    return err // try next identity\n}","preventionTips":["Never hand-edit age file headers; treat them as binary-opaque.","Transfer .age files in binary-safe channels (no truncation/mangling).","If you emit stanzas, emit exactly one arg (fingerprint) for ssh-rsa.","Distinguish ErrIncorrectIdentity (skippable) from structural errors (fatal)."],"tags":["age","stanza","malformed-input","ssh-rsa"],"backgroundTag":"malformed-age-stanza","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}