{"record":{"id":"b3c3d8e38f1ae282","repo":"semaphoreui/semaphore","slug":"invalid-access-key-type","errorCode":null,"errorMessage":"invalid access key type","messagePattern":"invalid access key type","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/server/access_key_serializer_local.go","lineNumber":157,"sourceCode":"\t\t\tvar data []byte\n\t\t\tdata, err = os.ReadFile(filePath)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tres = strings.TrimSuffix(string(data), \"\\n\")\n\t\t\treturn\n\t\t}\n\t}\n\n\tif key.Secret == nil || *key.Secret == \"\" {\n\t\treturn\n\t}\n\n\tsecret := *key.Secret\n\n\tif secret[len(secret)-1] == '\\n' { // not encrypted private key, used for back compatibility\n\t\tif key.Type != db.AccessKeySSH {\n\t\t\terr = fmt.Errorf(\"invalid access key type\")\n\t\t\treturn\n\t\t}\n\n\t\tsshKey := db.SshKey{\n\t\t\tPrivateKey: secret,\n\t\t}\n\n\t\tvar marshaled []byte\n\t\tmarshaled, err = json.Marshal(sshKey)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tres = string(marshaled)\n\n\t\treturn\n\t}\n","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/server/access_key_serializer_local.go#L139-L175","documentation":"As a backward-compatibility path, deserialize treats a stored secret ending in '\\n' as an unencrypted (plaintext) SSH private key from old Semaphore versions. If such a legacy plaintext value is found but the key's Type is not AccessKeySSH, the data is inconsistent and the error 'invalid access key type' is returned.","triggerScenarios":"DeserializeSecret/DeserializeSecret2 on an AccessKey whose stored Secret ends with a newline (detected as legacy plaintext private key) but whose Type is not ssh — e.g. a login/password key whose stored value accidentally ends in '\\n'.","commonSituations":"Legacy data imported from pre-encryption Semaphore; secrets pasted with trailing newlines into non-SSH keys; import scripts not trimming/normalizing key material.","solutions":["Set the access key's Type to ssh if the stored value really is a plaintext private key.","Strip the trailing newline and re-store the secret so it is treated as encrypted data (or re-encrypt it properly).","Re-create the key through the API so the serializer encrypts and types it correctly."],"exampleFix":"// before: login_password key storing \"pass\\n\" (looks like legacy plaintext)\nsecret = strings.TrimRight(secret, \"\\n\"); re-encrypt via SerializeSecret\n// after: properly encrypted secret with matching key type","handlingStrategy":"validation","validationCode":"if strings.HasSuffix(*key.Secret, \"\\n\") && key.Type != db.AccessKeySSH {\n    // legacy plaintext path will reject this: trim/re-encrypt or fix the type\n}","typeGuard":null,"tryCatchPattern":"if _, err := svc.DeserializeSecret(key); err != nil && strings.Contains(err.Error(), \"invalid access key type\") { /* re-type or re-encrypt the key */ }","preventionTips":["Trim trailing newlines when importing legacy key material.","Store plaintext legacy private keys only under ssh-type keys.","Migrate legacy plaintext keys to encrypted storage promptly."],"tags":["go","ssh","backward-compatibility","secrets"],"backgroundTag":"incompatible-source-type","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"}