# How Tabby Handles SSH Password and Private Key Storage Securely

> Discover how Tabby securely stores SSH passwords and private keys using AES-256-CBC encryption or OS native keychain, safeguarding your credentials.

- Repository: [Eugene/tabby](https://github.com/Eugeny/tabby)
- Tags: deep-dive
- Published: 2026-03-03

---

**Tabby encrypts SSH credentials using either an AES-256-CBC encrypted vault file or the OS native keychain, ensuring passwords and private-key passphrases are never stored in plain text.**

The open-source terminal emulator [Eugeny/tabby](https://github.com/Eugeny/tabby) routes all sensitive credential handling through a centralized abstraction layer. This article examines how Tabby securely stores SSH passwords and private key passphrases by analyzing the `PasswordStorageService` implementation and its dual-backend architecture.

## The PasswordStorageService Architecture

Tabby’s credential system is implemented in [`tabby-ssh/src/services/passwordStorage.service.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-ssh/src/services/passwordStorage.service.ts). The service provides a unified API for saving and loading secrets while abstracting two distinct storage backends: an encrypted JSON vault and the operating system’s native credential store.

### VaultService (Encrypted Vault)

When enabled, `VaultService` (located in [`tabby-core/src/services/vault.service.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-core/src/services/vault.service.ts)) stores secrets in a JSON vault file encrypted with **AES-256-CBC**. The user supplies a passphrase during vault initialization, which is derived into a cryptographic key via `deriveVaultKey` and never persisted to disk. The passphrase lives only in memory (cached in the module-private variable `_rememberedPassphrase`) while the vault is unlocked.

Secrets are stored as typed objects with the following structure:
- **`ssh:password`** – Plain SSH passwords (`VAULT_SECRET_TYPE_PASSWORD`)
- **`ssh:key-passphrase`** – Private-key passphrases (`VAULT_SECRET_TYPE_PASSPHRASE`)

### Keytar (OS Keychain Integration)

If the vault is disabled, Tabby falls back to the **`keytar`** npm module, which proxies to the host OS secure storage:
- **macOS**: Keychain Services
- **Windows**: Credential Manager
- **Linux**: Secret Service (via libsecret)

Keytar encrypts data using OS-level facilities, ensuring secrets remain outside Tabby’s JavaScript runtime when at rest.

## How Credentials Are Saved and Retrieved

The `PasswordStorageService` exposes four primary methods that SSH sessions and UI components consume. The implementation checks `this.vault.isEnabled()` to determine which backend to use.

### Storing SSH Passwords

Passwords are saved via `savePassword()` after a successful authentication or through the settings UI component ([`sshProfileSettings.component.ts`](https://github.com/Eugeny/tabby/blob/main/sshProfileSettings.component.ts)):

```typescript
// passwordStorage.service.ts
async savePassword (profile: SSHProfile, password: string, username?: string) {
    const account = username ?? profile.options.user;
    if (this.vault.isEnabled()) {
        const key = this.getVaultKeyForConnection(profile, account);
        this.vault.addSecret({ 
            type: VAULT_SECRET_TYPE_PASSWORD, 
            key, 
            value: password 
        });
    } else {
        const key = this.getKeytarKeyForConnection(profile);
        return keytar.setPassword(key, account, password);
    }
}

```

For vault storage, the key is a JSON object containing `{ user, host, port }`. For keytar, the service constructs a deterministic service string: `ssh@host` or `ssh@host:port`.

### Loading SSH Passwords on Session Start

When an SSH session initializes (in [`tabby-ssh/src/session/ssh.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-ssh/src/session/ssh.ts)), it attempts to auto-populate authentication methods:

```typescript
// ssh.ts
const storedPassword = await this.passwordStorage.loadPassword(
    this.profile, 
    this.authUsername
);

```

The `loadPassword()` method mirrors the save logic, checking the vault first, then falling back to `keytar.getPassword()`.

### Handling Private-Key Passphrases

Encrypted private keys require passphrase caching. Tabby handles these separately using `savePrivateKeyPassword()` and `loadPrivateKeyPassword()`:

```typescript
// passwordStorage.service.ts
async savePrivateKeyPassword (id: string, password: string) {
    if (this.vault.isEnabled()) {
        const key = this.getVaultKeyForPrivateKey(id);
        this.vault.addSecret({ 
            type: VAULT_SECRET_TYPE_PASSPHRASE, 
            key, 
            value: password 
        });
    } else {
        const key = this.getKeytarKeyForPrivateKey(id);
        return keytar.setPassword(key, 'user', password);
    }
}

```

The `id` parameter represents a hash of the private-key file content, ensuring unique storage per key file.

## Vault Encryption Implementation

The vault file resides at `$HOME/.config/tabby/vault.json` by default. Encryption utilizes Node.js’s `crypto` module:

```typescript
// vault.service.ts
const cipher = crypto.createCipheriv(CRYPT_ALG, key, iv);
const ciphertext = Buffer.concat([
    cipher.update(JSON.stringify(content)), 
    cipher.final()
]);

```

- **Algorithm**: AES-256-CBC (`crypt` constant)
- **Key derivation**: User passphrase processed through `deriveVaultKey`
- **IV**: Randomly generated for each encryption operation
- **Storage format**: Encrypted JSON containing secret objects with `type`, `key`, and `value` fields

## OS Keychain Fallback Mechanism

When the vault is disabled, Tabby constructs deterministic service keys for keytar lookups:

```typescript
// passwordStorage.service.ts
private getKeytarKeyForConnection (profile: SSHProfile): string {
    let key = `ssh@${profile.options.host}`;
    if (profile.options.port) {
        key = `ssh@${profile.options.host}:${profile.options.port}`;
    }
    return key;
}

```

The account name corresponds to the SSH username. This scheme allows Tabby to retrieve the correct password for a specific user@host combination without maintaining a local database.

## Summary

- **Tabby never stores raw SSH passwords or private-key passphrases in plain text**; all credentials route through `PasswordStorageService`.
- **Dual-backend architecture** supports either an AES-256-CBC encrypted vault file or the OS native keychain (via keytar).
- **Vault passphrase protection** keeps the encryption key only in memory (`_rememberedPassphrase`) and never writes it to disk.
- **Deterministic key naming** ensures consistent retrieval of credentials based on connection profiles (user, host, port) or private-key hashes.
- **Source implementation** spans [`tabby-ssh/src/services/passwordStorage.service.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-ssh/src/services/passwordStorage.service.ts), [`tabby-core/src/services/vault.service.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-core/src/services/vault.service.ts), and [`tabby-ssh/src/session/ssh.ts`](https://github.com/Eugeny/tabby/blob/main/tabby-ssh/src/session/ssh.ts).

## Frequently Asked Questions

### Does Tabby store SSH passwords in plain text?

No. According to the source code in [`passwordStorage.service.ts`](https://github.com/Eugeny/tabby/blob/main/passwordStorage.service.ts), Tabby encrypts all credentials using either the AES-256-CBC vault or the OS keychain. Raw passwords are never written to configuration files or logs.

### What encryption algorithm does Tabby's vault use?

The vault implements **AES-256-CBC** encryption via Node.js `crypto.createCipheriv`, using a key derived from the user’s vault passphrase. The encrypted data is stored in a JSON file at `$HOME/.config/tabby/vault.json`.

### Where is the vault file located on disk?

By default, the vault file is located at `$HOME/.config/tabby/vault.json` on Linux, with equivalent paths for macOS and Windows. The file contains encrypted JSON and is unreadable without the vault passphrase.

### How does Tabby identify which password belongs to which SSH connection?

For vault storage, Tabby constructs a key object containing `{ user, host, port }`. For OS keychain storage, it creates a service string like `ssh@hostname:port` and uses the username as the account name. This deterministic naming scheme ensures credentials map uniquely to specific connection profiles.