Operating Systems11 min read

Show Dev: DevsFTP — An open-source, S/FTP client with dynamic HSL themes, embedded SSH terminals, and AES-256 local vaults.

We built DevsFTP to address a gap in the developer toolkit: a secure, performant, and deeply integrated file transfer client that respects modern OS boundaries....

Listen to Article

Click play to listen to audio narration

Show Dev: DevsFTP — An open-source, S/FTP client with dynamic HSL themes, embedded SSH terminals, and AES-256 local vaults.

Introduction

We built DevsFTP to address a gap in the developer toolkit: a secure, performant, and deeply integrated file transfer client that respects modern OS boundaries. Existing solutions often rely on legacy codebases, store credentials in plaintext, or suffer from the memory overhead of heavy web-view runtimes without leveraging native security primitives.

DevsFTP is a cross-platform client written in Rust, wrapped in a lightweight Tauri shell. It provides SFTP and FTP access with a focus on security and developer workflow. The core differentiators are an AES-256 encrypted local vault for connection profiles, an embedded SSH terminal that shares the underlying transport session, and a dynamic HSL theme engine that allows granular UI customization without performance penalties.

This architecture prioritizes memory safety, zero-cost abstractions, and direct integration with OS-level credential managers and pseudo-terminal drivers.

Why This Matters

Software engineers interact with remote servers constantly. Storing SSH keys, passwords, and connection metadata in unencrypted configuration files poses a significant risk, especially on shared workstations or laptops. We observed that many popular clients persist secrets in base64-encoded JSON or plist files, offering a false sense of security.

DevsFTP solves this by implementing a local vault that derives encryption keys from a user master password using Argon2id, ensuring that stored secrets are never readable on disk without authentication. Furthermore, by integrating the SSH terminal directly into the SFTP session, we eliminate the latency and configuration drift of managing separate terminal windows. The client reuses the established SSH connection for both file transfer and shell access, reducing handshake overhead and simplifying state management.

For operating system engineers and architects, DevsFTP demonstrates effective use of OS primitives: leveraging native keychains where available, handling PTY allocation correctly, and managing IPC between a secure backend and a reactive frontend.

How It Works

DevsFTP follows a secure backend model. The user interface runs in a sandboxed WebView, while all cryptographic operations, network I/O, and terminal emulation occur in the Rust core. Communication happens via a typed IPC bridge.

The architecture separates concerns into three layers:

  1. UI Layer: Handles rendering, theme application, and user input. It never touches secrets or network sockets.
  2. Core Engine: Implements the Vault, Session Manager, and Terminal Emulator. This layer runs in a privileged Rust process with direct OS access.
  3. OS Integration Layer: Interfaces with the OS Keychain, File System, and PTY driver.
flowchart TD
    subgraph UI_Layer [UI Layer]
        WebView[WebView Renderer]
        ThemeEngine[HSL Theme Engine]
    end

    subgraph Core_Engine [Rust Core Engine]
        IPC_Bridge[IPC Bridge]
        Vault[AES-256 Vault Service]
        SessionMgr[SSH / SFTP Manager]
        TermEmu[PTY Terminal Emulator]
    end

    subgraph OS_Layer [OS Integration Layer]
        Keychain[Native Keychain / Credential Store]
        PTY_Driver[OS Pseudo-Terminal Driver]
        FS_Watcher[File System Watcher]
    end

    WebView <--> IPC_Bridge
    ThemeEngine <--> WebView

    IPC_Bridge --> Vault
    IPC_Bridge --> SessionMgr
    IPC_Bridge --> TermEmu

    Vault <--> Keychain
    SessionMgr --> FS_Watcher
    TermEmu <--> PTY_Driver
    SessionMgr -. Reuses Connection .-> TermEmu

Step-by-Step Flow:

  1. Initialization: The user launches DevsFTP. The UI requests the list of saved connections from the Core Engine.
  2. Vault Decryption: The Core Engine checks for a master password. If provided, it derives the AES-256 key using Argon2id and decrypts the vault blob. If the OS keychain is available and trusted, it may retrieve the master key directly.
  3. Connection Establishment: When the user connects, SessionMgr initiates an SSH handshake. It authenticates using keys or passwords retrieved from the decrypted vault.
  4. Channel Multiplexing: Once authenticated, SessionMgr opens an SFTP subsystem channel for file operations. The connection remains open.
  5. Terminal Allocation: If the user opens the embedded terminal, TermEmu requests a new channel from the existing SSH connection. It allocates a PTY via the OS driver and binds the session to the terminal UI.
  6. Theme Application: The ThemeEngine calculates HSL color values and pushes CSS variables to the WebView. This happens asynchronously and does not block the main thread.

Core Concepts

AES-256 Local Vault

The vault stores connection profiles, including hostnames, ports, usernames, passwords, and private keys. Data is encrypted using AES-256-GCM, which provides authenticated encryption. The encryption key is derived from the user’s master password using Argon2id, a memory-hard key derivation function resistant to GPU and ASIC attacks.

We implement strict zeroization of sensitive buffers. Passwords and derived keys are stored in Zeroize-aware containers that overwrite memory upon drop, mitigating cold boot attacks and memory dumps.

Embedded SSH Terminal

The terminal is not a separate SSH client. It leverages the existing SSH connection established by the SFTP manager. When a terminal is requested, the core engine opens a new SSH channel on the same TCP connection and requests a PTY. This approach reduces latency and ensures that environment variables and session state remain consistent between file transfer and shell access.

PTY allocation is handled via tokio-pty, which interacts with the OS-specific pseudo-terminal driver. The terminal emulator manages resize signals, ensuring the remote shell adjusts to window changes in real-time.

Dynamic HSL Theme Engine

The theme engine uses the HSL color space to allow intuitive color manipulation. Users can adjust hue, saturation, and lightness values to create custom themes. The engine calculates contrast ratios to ensure accessibility and exports the final palette as CSS custom properties.

Themes are stored as JSON configurations and can be hot-reloaded. The engine applies themes asynchronously, preventing UI jank during color transitions.

Examples & Code Walkthrough

Vault Key Derivation and Encryption

The following Rust code demonstrates how we derive the encryption key and encrypt vault data. We use argonaut2 for key derivation and aes-gcm for encryption.

use argonaut2::{Argon2, PasswordHash};
use aes_gcm::{
    aead::{Aead, KeyInit},
    Aes256Gcm, Key, Nonce,
};
use rand::rngs::OsRng;
use zeroize::{Zeroize, Zeroizing};

/// Derives a 32-byte key from the master password using Argon2id.
/// Returns the derived key and the salt for future verification.
pub fn derive_vault_key(
    password: &str,
    salt: Option<[u8; 16]>,
) -> Result<(Zeroizing<Vec<u8>>, [u8; 16]), VaultError> {
    let salt = salt.unwrap_or_else(|| {
        let mut s = [0u8; 16];
        OsRng.fill_bytes(&mut s);
        s
    });

    let argon2 = Argon2::new(
        argonaut2::Algorithm::Argon2id,
        argonaut2::Version::V0x13,
        argonaut2::Params::new(64 * 1024, 3, 1, Some(32)).unwrap(),
    );

    let derived_key = argon2
        .hash_password_into(password.as_bytes(), &salt, &mut [0u8; 32])
        .map_err(|e| VaultError::KeyDerivationFailed(e))?;

    Ok((Zeroizing::new(derived_key.to_vec()), salt))
}

/// Encrypts the vault payload using AES-256-GCM.
/// Returns the ciphertext and the nonce.
pub fn encrypt_vault_payload(
    key: &[u8; 32],
    plaintext: &[u8],
) -> Result<(Vec<u8>, [u8; 12]), VaultError> {
    let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(key));
    let nonce = Nonce::generate(&mut OsRng);
    
    let ciphertext = cipher
        .encrypt(&nonce, plaintext)
        .map_err(|e| VaultError::EncryptionFailed(e))?;

    Ok((ciphertext, nonce.to_bytes()))
}

Code Commentary:

  • Zeroizing<Vec<u8>>: Ensures the derived key is wiped from memory when the variable goes out of scope.
  • `
Tags:#show#devsftp#operating systems#open
K

Written by Kernel & Systems Software Engineer

Editorial staff persona covering operating system kernels, device drivers, low-level memory management, and runtime environments.

View Profile
Recommended For You

Related Articles

Quick:
Navigate Select
Loading search index...