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
PlayingClick play to listen to audio narration
Table of Contents
- •Show Dev: DevsFTP — An open-source, S/FTP client with dynamic HSL themes, embedded SSH terminals, and AES-256 local vaults.
- •Introduction
- •Why This Matters
- •How It Works
- •Core Concepts
- •AES-256 Local Vault
- •Embedded SSH Terminal
- •Dynamic HSL Theme Engine
- •Examples & Code Walkthrough
- •Vault Key Derivation and Encryption
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:
- UI Layer: Handles rendering, theme application, and user input. It never touches secrets or network sockets.
- Core Engine: Implements the Vault, Session Manager, and Terminal Emulator. This layer runs in a privileged Rust process with direct OS access.
- 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:
- Initialization: The user launches DevsFTP. The UI requests the list of saved connections from the Core Engine.
- 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.
- Connection Establishment: When the user connects,
SessionMgrinitiates an SSH handshake. It authenticates using keys or passwords retrieved from the decrypted vault. - Channel Multiplexing: Once authenticated,
SessionMgropens an SFTP subsystem channel for file operations. The connection remains open. - Terminal Allocation: If the user opens the embedded terminal,
TermEmurequests a new channel from the existing SSH connection. It allocates a PTY via the OS driver and binds the session to the terminal UI. - Theme Application: The
ThemeEnginecalculates 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.- `
Written by Kernel & Systems Software Engineer
Editorial staff persona covering operating system kernels, device drivers, low-level memory management, and runtime environments.