{"record":{"id":"3d9f3e8189ebd0c6","repo":"Eugeny/tabby","slug":"no-private-keys-in-profile","errorCode":null,"errorMessage":"No private keys in profile","messagePattern":"No private keys in profile","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"tabby-ssh/src/services/ssh.service.ts","lineNumber":84,"sourceCode":"        }\n        let tmpFile: tmp.FileResult|null = null\n        if (profile.options.jumpHost) {\n            const jumpHostProfile = this.config.store.profiles.find(x => x.id === profile.options.jumpHost) ?? null\n            const xTunnelParams = await this.generateWinSCPXTunnelURI(jumpHostProfile)\n            uri += xTunnelParams.uri ?? ''\n            tmpFile = xTunnelParams.privateKeyFile ?? null\n        }\n        if (profile.options.host.includes(':')) {\n            uri += `@[${profile.options.host}]:${profile.options.port}${cwd ?? '/'}`\n        }else {\n            uri += `@${profile.options.host}:${profile.options.port}${cwd ?? '/'}`\n        }\n        return { uri, privateKeyFile: tmpFile?? null }\n    }\n\n    async convertPrivateKeyFileToPuTTYFormat (profile: SSHProfile): Promise<{ passphrase: string|null, privateKeyFile: tmp.FileResult|null }> {\n        if (profile.options.privateKeys.length === 0) {\n            throw new Error('No private keys in profile')\n        }\n        const path = this.getWinSCPPath()\n        if (!path) {\n            throw new Error('WinSCP not found')\n        }\n        let tmpPrivateKeyFile: tmp.FileResult|null = null\n        let passphrase: string|null = null\n        const tmpFile: tmp.FileResult = await tmp.file()\n        for (const pk of profile.options.privateKeys) {\n            let privateKeyContent: string|null = null\n            const buffer = await this.fileProviders.retrieveFile(pk)\n            privateKeyContent = buffer.toString()\n            await fs.writeFile(tmpFile.path, privateKeyContent)\n            const keyHash = crypto.createHash('sha512').update(privateKeyContent).digest('hex')\n            // need to pass an default passphrase, otherwise it might get stuck at the passphrase input\n            const curPassphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash) ?? 'tabby'\n            const winSCPcom = path.slice(0, -3) + 'com'\n            try {","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-ssh/src/services/ssh.service.ts#L66-L102","documentation":"Thrown by `SSHService.convertPrivateKeyFileToPuTTYFormat` when the given SSH profile's `options.privateKeys` array is empty. The function exists to convert an existing private key into PuTTY format for use with WinSCP; with no keys there is nothing to convert, so it fails fast rather than producing a meaningless empty file.","triggerScenarios":"Calling `convertPrivateKeyFileToPuTTYFormat(profile)` on a profile where `profile.options.privateKeys.length === 0`. Reachable when a profile uses password auth (no keys) but a code path assumes key auth, or when keys were removed from the profile.","commonSituations":"Profile configured for password-only auth but a PuTTY/WinSCP integration tries to convert keys; UI button enabled for a keyless profile; race where keys were cleared mid-session.","solutions":["Guard the caller: only invoke the conversion when `profile.options.privateKeys.length > 0`.","If keys are expected, add one to the profile via the key picker before calling.","Disable/hide the PuTTY-conversion UI affordance for profiles without keys.","Refactor the check upstream so the function is unreachable for keyless profiles."],"exampleFix":"// before\nasync convertPrivateKeyFileToPuTTYFormat (profile: SSHProfile) {\n    if (profile.options.privateKeys.length === 0) throw new Error('No private keys in profile')\n    ...\n}\n\n// caller guard\nif (profile.options.privateKeys.length > 0) {\n    result = await this.ssh.convertPrivateKeyFileToPuTTYFormat(profile)\n}","handlingStrategy":"validation","validationCode":"function profileHasKeys (profile: SSHProfile): boolean {\n    return Array.isArray(profile.options.privateKeys) && profile.options.privateKeys.length > 0\n}\n\nif (!profileHasKeys(profile)) {\n    throw new Error('No private keys in profile; add a key or use password auth')\n}","typeGuard":"function hasPrivateKey (profile: SSHProfile): profile is SSHProfile & { options: { privateKeys: string[] } } {\n    return Array.isArray(profile.options.privateKeys) && profile.options.privateKeys.length > 0\n}","tryCatchPattern":null,"preventionTips":["Guard the caller with a length check before invoking the conversion.","Disable PuTTY/WinSCP conversion UI for keyless profiles.","Add a key via the picker before offering conversion.","Keep the check upstream so the function is unreachable for keyless profiles."],"tags":["ssh","private-keys","putty","winscp","validation"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}