getsops/sops · error
malformed SSH identity in %q: %w
Error message
malformed SSH identity in %q: %w
What it means
sops parses the SSH private key file with an SSH decoder to obtain its public key. This error means the file could not be parsed as a valid SSH private key at all, so no identity could be derived from it.
Source
Thrown at age/ssh_parse.go:81
if err != nil {
return nil, err
}
}
passphrasePrompt := func() ([]byte, error) {
pass, err := pluginTerminalUI.RequestValue("", fmt.Sprintf("Enter passphrase for %q:", keyPath), true)
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", keyPath, err)
}
return []byte(pass), nil
}
i, err := agessh.NewEncryptedSSHIdentity(pubKey, contents, passphrasePrompt)
if err != nil {
return nil, fmt.Errorf("could not create encrypted SSH identity: %w", err)
}
return i, nil
}
if err != nil {
return nil, fmt.Errorf("malformed SSH identity in %q: %w", keyPath, err)
}
return id, nil
}
View on GitHub (pinned to 13442bb981)
Solutions
- Verify the path points to a valid SSH PRIVATE key (not .pub): run ssh-keygen -y -f <path>
- Check the file exists and is readable: ls -l / cat the file
- Decrypt/convert legacy PEM keys: ssh-keygen -p -f <keyfile> to re-save in the new OpenSSH format
- Regenerate the key if the file is corrupted: ssh-keygen -t ed25519
Example fix
// before export SOPS_AGE_SSH_PRIVATE_KEY_FILE=~/.ssh/id_ed25519.pub # public key, unparseable // after export SOPS_AGE_SSH_PRIVATE_KEY_FILE=~/.ssh/id_ed25519 # private key file
Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Stat(keyPath)
if err != nil || info.IsDir() { return fmt.Errorf("SSH key path missing: %s", keyPath) }
head, _ := os.ReadFile(keyPath)
if !strings.Contains(string(head), "PRIVATE KEY") {
return fmt.Errorf("%s does not look like a private key", keyPath)
}
if err := exec.Command("ssh-keygen", "-y", "-f", keyPath).Run(); err != nil {
return fmt.Errorf("unparseable SSH private key: %w", err)
} Type guard
func isSSHPrivateKeyFile(path string) bool {
b, err := os.ReadFile(path)
return err == nil && strings.Contains(string(b), "PRIVATE KEY")
} Try / catch
id, err := parseSSHIdentityFromPrivateKeyFile(keyPath)
if err != nil {
if strings.Contains(err.Error(), "malformed SSH identity") {
return fmt.Errorf("check SOPS_AGE_SSH_PRIVATE_KEY_FILE points to a valid private key: %w", err)
}
return err
} Prevention
- Never point config at .pub files
- Run ssh-keygen -y -f <key> as a preflight check
- Migrate legacy PEM keys to the new OpenSSH format
When it happens
Trigger: parseSSHIdentityFromPrivateKeyFile reads keyPath, and the SSH decoder (x/ssh.ParseRawPrivateKey family) returns an error on the file contents — the file is not a valid PEM/OpenSSH private key, is encrypted in a way the parser rejects without a passphrase, or is truncated.
Common situations: Pointing SOPS_AGE_SSH_PRIVATE_KEY_FILE at a public key file, a config file, or a path that doesn't exist; file contains an old PEM-encrypted RSA key; corrupted download; wrong file (e.g. known_hosts) passed in.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- could not create encrypted SSH identity: %w
- failed to parse input as age-ssh public key: %w
- incorrect passphrase
- failed to decrypt identity file: %v
- could not read passphrase: %v
AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01).
Data as JSON: /api/errors/da9f6cd816fa8811.
Report an issue: GitHub.