{"record":{"id":"e6aca2f600121c02","repo":"AlistGo/alist","slug":"public-key-refused","errorCode":null,"errorMessage":"public key refused","messagePattern":"public key refused","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/sftp.go","lineNumber":130,"sourceCode":"\t\treturn nil, errors.New(\"user is not allowed to access via SFTP\")\n\t}\n\tkeys, _, err := op.GetSSHPublicKeyByUserId(userObj.ID, 1, -1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmarshal := string(key.Marshal())\n\tfor _, sk := range keys {\n\t\tif marshal != sk.KeyStr {\n\t\t\tpubKey, _, _, _, e := ssh.ParseAuthorizedKey([]byte(sk.KeyStr))\n\t\t\tif e != nil || marshal != string(pubKey.Marshal()) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tsk.LastUsedTime = time.Now()\n\t\t_ = op.UpdateSSHPublicKey(&sk)\n\t\treturn nil, nil\n\t}\n\treturn nil, errors.New(\"public key refused\")\n}\n\nfunc (d *SftpDriver) AuthLogCallback(conn ssh.ConnMetadata, method string, err error) {\n\tip := conn.RemoteAddr().String()\n\tif err == nil {\n\t\tutils.Log.Infof(\"[SFTP] %s(%s) logged in via %s\", conn.User(), ip, method)\n\t} else if method != \"none\" {\n\t\tutils.Log.Infof(\"[SFTP] %s(%s) tries logging in via %s but with error: %s\", conn.User(), ip, method, err)\n\t}\n}\n\nfunc (d *SftpDriver) GetBanner(_ ssh.ConnMetadata) string {\n\treturn setting.GetStr(conf.Announcement)\n}\n","sourceCodeStart":112,"sourceCodeEnd":145,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/sftp.go#L112-L145","documentation":"Returned at the end of PublicKeyAuth after none of the SSH public keys stored for the user matches the key presented by the client. The driver fetches all keys via op.GetSSHPublicKeyByUserId and compares the marshaled key bytes; a mismatch (or unparsable stored key) causes the loop to skip the candidate, and the final statement returns 'public key refused'. This is an authentication failure, distinct from the permission check that precedes it.","triggerScenarios":"Client offers an SSH key that is not registered for the account; the stored KeyStr is in a different format/comment so string comparison fails AND ssh.ParseAuthorizedKey fails or the re-marshaled bytes differ; all user keys were deleted from the admin panel.","commonSituations":"New machine / new SSH keypair not added in the AList SSH public keys settings; key was re-saved with a trailing comment or whitespace so neither raw-string nor parsed comparison matches; user uploaded a private key instead of the public key; key registered under a different user account.","solutions":["Add the client's public key (~/.ssh/id_*.pub output) under the user's SSH public keys in the AList admin panel and reconnect.","Confirm the key belongs to the same username being used to connect (keys are scoped per user ID).","Re-copy the stored key exactly (single line, key-type base64-comment) so ssh.ParseAuthorizedKey can parse it and the marshaled form matches.","If the client agent offers many keys, ensure the correct one is offered (ssh -i) so a later refusal is not caused by wrong-key ordering."],"exampleFix":"# before: client key not registered, server logs 'public key refused'\nssh alist@example.com\n\n# after: register the exact public key, then connect\n# admin UI -> Users -> SSH Public Keys -> paste contents of:\ncat ~/.ssh/id_ed25519.pub\nssh -i ~/.ssh/id_ed25519 alist@example.com","handlingStrategy":"validation","validationCode":"# Before connecting, confirm the key you will offer is registered:\nssh-keygen -lf ~/.ssh/id_ed25519.pub   # fingerprint of client key\n# compare with the fingerprint shown for the stored key in the AList admin panel","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Register every client public key under the exact connecting username.","Store keys as single-line OpenSSH format (type base64 comment).","Use ssh -i to pin the offered key when the agent holds many keys."],"tags":["sftp","ssh","public-key","auth","golang"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}