{"record":{"id":"1eccb03d6c00994a","repo":"semaphoreui/semaphore","slug":"invalid-ssh-key","errorCode":null,"errorMessage":"invalid ssh key","messagePattern":"invalid ssh key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/server/access_key_serializer_local.go","lineNumber":41,"sourceCode":"\t// No-op for local deserializer\n\treturn nil\n}\n\nfunc (d *LocalAccessKeyDeserializer) SerializeSecret(key *db.AccessKey) error {\n\tvar plaintext []byte\n\tvar err error\n\n\tswitch key.Type {\n\tcase db.AccessKeyString:\n\t\tif key.String == \"\" {\n\t\t\tkey.Secret = nil\n\t\t\treturn nil\n\t\t}\n\t\tplaintext = []byte(key.String)\n\tcase db.AccessKeySSH:\n\t\tif key.SshKey.PrivateKey == \"\" {\n\t\t\tif key.SshKey.Login != \"\" || key.SshKey.Passphrase != \"\" {\n\t\t\t\treturn fmt.Errorf(\"invalid ssh key\")\n\t\t\t}\n\t\t\tkey.Secret = nil\n\t\t\treturn nil\n\t\t}\n\n\t\tplaintext, err = json.Marshal(key.SshKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\tcase db.AccessKeyLoginPassword:\n\t\tif key.LoginPassword.Password == \"\" {\n\t\t\tif key.LoginPassword.Login != \"\" {\n\t\t\t\treturn fmt.Errorf(\"invalid password key\")\n\t\t\t}\n\t\t\tkey.Secret = nil\n\t\t\treturn nil\n\t\t}\n","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/server/access_key_serializer_local.go#L23-L59","documentation":"SerializeSecret for a local-stored SSH-type access key requires consistency: if there is no private key, the key may not carry a login or passphrase either — such a key is meaningless. Instead of silently saving a hollow SSH key, SerializeSecret returns 'invalid ssh key' so RekeyAccessKeys fails loudly.","triggerScenarios":"Calling SerializeSecret (typically during RekeyAccessKeys re-encryption) on an AccessKey of type AccessKeySSH whose SshKey.PrivateKey is empty while SshKey.Login or SshKey.Passphrase is non-empty.","commonSituations":"Partial key creation where the private key upload failed but login/passphrase fields were saved; API clients sending SSH keys with only login/passphrase; data corrupted by partial updates.","solutions":["Provide the SshKey.PrivateKey value for the access key before serializing.","Clear SshKey.Login and SshKey.Passphrase if the key is intentionally key-less, or change the key type to LoginPassword/None.","Fix the client code that constructs the AccessKey so a complete SshKey is always submitted."],"exampleFix":"// before\nkey.SshKey = db.SshKey{Login: \"deploy\"}\n// after\nkey.SshKey = db.SshKey{PrivateKey: privPEM, Login: \"deploy\", Passphrase: \"...\"}","handlingStrategy":"validation","validationCode":"if key.Type == db.AccessKeySSH && key.SshKey.PrivateKey == \"\" && (key.SshKey.Login != \"\" || key.SshKey.Passphrase != \"\") {\n    // invalid: supply PrivateKey or clear Login/Passphrase\n}","typeGuard":null,"tryCatchPattern":"if err := svc.SerializeSecret(key); err != nil && err.Error() == \"invalid ssh key\" { /* fix key fields before retry */ }","preventionTips":["Require the private key field in SSH key creation forms and API clients.","Never persist partial SSH keys; validate before saving.","Run RekeyAccessKeys only after a data-integrity pass on SSH keys."],"tags":["go","ssh","validation","secrets"],"backgroundTag":"empty-required-field","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}