Skip to main content
Glama
ssh-encrypted-key-bug-reproduction.test.ts7.93 kB
/** * Test to reproduce the critical encrypted SSH key passphrase bug * * Bug: Passphrase parameter is not properly passed from MCP tool to ssh2 library * This causes encrypted SSH keys to fail with "Encrypted private OpenSSH key detected, but no passphrase given" */ import * as fs from 'fs/promises'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { SSHConnectionManager } from '../src/ssh-connection-manager.js'; import { SSHConnectionConfig } from '../src/types.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.resolve(__dirname, '..'); describe('Encrypted SSH Key Passphrase Bug Fix Verification', () => { let connectionManager: SSHConnectionManager; // Test key paths and passphrases from ssh2 fixtures const RSA_ENCRYPTED_KEY_PATH = path.join(projectRoot, 'node_modules/ssh2/test/fixtures/id_rsa_enc'); const OPENSSH_NEW_RSA_ENCRYPTED_KEY_PATH = path.join(projectRoot, 'node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc'); const TEST_PASSPHRASE = 'foobarbaz'; beforeEach(() => { connectionManager = new SSHConnectionManager(); }); afterEach(() => { connectionManager.cleanup(); }); describe('Traditional RSA encrypted key format', () => { test('should successfully decrypt RSA encrypted key with correct passphrase', async () => { // Read the encrypted key to confirm it exists const keyContent = await fs.readFile(RSA_ENCRYPTED_KEY_PATH, 'utf8'); expect(keyContent).toContain('Proc-Type: 4,ENCRYPTED'); expect(keyContent).toContain('DEK-Info:'); const config: SSHConnectionConfig = { name: 'test-encrypted-rsa-key', host: '127.0.0.1', // Fake host - will fail at connection but key should decrypt username: 'testuser', keyFilePath: RSA_ENCRYPTED_KEY_PATH, passphrase: TEST_PASSPHRASE, }; // The key should decrypt successfully, but connection will fail since no SSH server // We expect "All configured authentication methods failed" NOT "no passphrase given" await expect(connectionManager.createConnection(config)).rejects.toThrow(); await expect(connectionManager.createConnection(config)).rejects.toThrow( /All configured authentication methods failed|Connection timeout|ECONNREFUSED/i ); }, 30000); test('should successfully decrypt RSA key when using privateKey parameter directly', async () => { // Test direct privateKey parameter with encrypted content const keyContent = await fs.readFile(RSA_ENCRYPTED_KEY_PATH, 'utf8'); const config: SSHConnectionConfig = { name: 'test-encrypted-rsa-direct', host: '127.0.0.1', username: 'testuser', privateKey: keyContent, passphrase: TEST_PASSPHRASE, }; // Key should decrypt successfully - connection failure is expected await expect(connectionManager.createConnection(config)).rejects.toThrow(); await expect(connectionManager.createConnection(config)).rejects.toThrow( /All configured authentication methods failed|Connection timeout|ECONNREFUSED/i ); }, 30000); test('should fail with "no passphrase given" when passphrase is missing for RSA key', async () => { const config: SSHConnectionConfig = { name: 'test-rsa-no-passphrase', host: '127.0.0.1', username: 'testuser', keyFilePath: RSA_ENCRYPTED_KEY_PATH, // No passphrase provided }; // Should get an error indicating passphrase is required await expect(connectionManager.createConnection(config)).rejects.toThrow(); await expect(connectionManager.createConnection(config)).rejects.toThrow( /no passphrase|passphrase.*required|Key is encrypted but no passphrase provided/i ); }, 30000); }); describe('OpenSSH new format encrypted key', () => { test('should handle OpenSSH new format encrypted keys with passphrase (wrong passphrase test)', async () => { // Read the OpenSSH new format encrypted key const keyContent = await fs.readFile(OPENSSH_NEW_RSA_ENCRYPTED_KEY_PATH, 'utf8'); expect(keyContent).toContain('-----BEGIN OPENSSH PRIVATE KEY-----'); const config: SSHConnectionConfig = { name: 'test-encrypted-openssh-key', host: '127.0.0.1', username: 'testuser', keyFilePath: OPENSSH_NEW_RSA_ENCRYPTED_KEY_PATH, passphrase: TEST_PASSPHRASE, // This passphrase might be wrong for OpenSSH new format }; // With wrong passphrase, should get "bad passphrase" error, not "no passphrase given" await expect(connectionManager.createConnection(config)).rejects.toThrow(); await expect(connectionManager.createConnection(config)).rejects.toThrow( /bad passphrase|integrity check failed/i ); }, 30000); test('should fail with "no passphrase given" when passphrase is missing for OpenSSH key', async () => { const config: SSHConnectionConfig = { name: 'test-openssh-no-passphrase', host: '127.0.0.1', username: 'testuser', keyFilePath: OPENSSH_NEW_RSA_ENCRYPTED_KEY_PATH, // No passphrase provided }; // Should get an error indicating passphrase is required await expect(connectionManager.createConnection(config)).rejects.toThrow(); await expect(connectionManager.createConnection(config)).rejects.toThrow( /no passphrase|passphrase.*required|Key is encrypted but no passphrase provided/i ); }, 30000); }); describe('Key encryption detection', () => { test('should correctly detect traditional RSA encrypted keys', async () => { const keyContent = await fs.readFile(RSA_ENCRYPTED_KEY_PATH, 'utf8'); // Access the private method to test encryption detection const isEncrypted = (connectionManager as any).isKeyEncrypted(keyContent); expect(isEncrypted).toBe(true); }); test('should correctly detect OpenSSH new format encrypted keys', async () => { const keyContent = await fs.readFile(OPENSSH_NEW_RSA_ENCRYPTED_KEY_PATH, 'utf8'); // The current implementation may not detect OpenSSH new format encrypted keys correctly // This test documents the current behavior const isEncrypted = (connectionManager as any).isKeyEncrypted(keyContent); // This might fail if the isKeyEncrypted method doesn't handle OpenSSH new format // which could be part of the bug expect(isEncrypted).toBe(true); }); }); describe('Bug fix verification', () => { test('should prove that passphrase is now being passed to ssh2 library correctly', async () => { // This test verifies that the bug has been fixed by confirming the passphrase // is being passed through to ssh2 library correctly const config: SSHConnectionConfig = { name: 'test-passphrase-flow', host: '127.0.0.1', username: 'testuser', keyFilePath: RSA_ENCRYPTED_KEY_PATH, passphrase: TEST_PASSPHRASE, }; try { await connectionManager.createConnection(config); fail('Expected connection to fail due to no SSH server running'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Document the exact error message we get after fix console.log('Fixed implementation error message:', errorMessage); // The error should NOT mention "no passphrase given" anymore // Instead it should be a connection failure since the key decrypted successfully expect(errorMessage).toMatch(/All configured authentication methods failed|Connection timeout|ECONNREFUSED/i); expect(errorMessage).not.toMatch(/no passphrase given/i); } }, 30000); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LightspeedDMS/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server