Skip to content

Kiefer-Networks/sshvault

Repository files navigation

SSHVault

SSHVault

Zero-Knowledge Encrypted SSH Client
Secure, self-hosted, cross-platform terminal and SFTP manager

Release License: AGPL-3.0 CI Issues Stars

Google Play F-Droid Repo Kiefer Networks Donate via Liberapay


SSHVault is a cross-platform SSH terminal and SFTP file manager that encrypts all data client-side before syncing. The server never sees your plaintext credentials, keys, or session data.

Screenshots

Host List Navigation Add Server SSH Keys

SSH Terminal SFTP File Manager Settings

Features

Feature Description
SSH Terminal Split view, tabs, multiple simultaneous sessions, xterm-256color emulation
SFTP File Manager Browse, transfer, rename, chmod, symlinks, archive extraction, bookmarks
Zero-Knowledge Encryption AES-256-GCM with Argon2id key derivation
Host Key Verification Trust On First Use (TOFU) with known hosts management
SSH Key Management Ed25519, RSA, ECDSA key generation and import
SSH Config Import Import hosts and keys from ~/.ssh/config on desktop
Jump Hosts ProxyJump support for multi-hop connections
Proxy Support SOCKS5 and HTTP CONNECT, global or per-server configuration
Code Snippets Save and organize frequently used commands
Server Organization Folders, tags, color codes, icons, search and filtering
Post-Connect Commands Auto-run commands after connection
Biometric Lock Fingerprint/Face ID with PIN fallback and duress PIN
Cross-Device Sync End-to-end encrypted via self-hosted backend
Keep-Alive & Timeouts Configurable keep-alive interval and connection timeout
SSH Compression Optional compression toggle for slow connections
Export & Import Full backup and restore of all data
No Tracking No analytics, no telemetry, no ads, no in-app purchases

Platforms

Platform Status
Android Supported
iOS / iPadOS Supported
macOS Supported
Linux Supported (Flatpak)
Windows Supported

iPad input modes

SSHVault is fully usable on iPad with every supported input device. Flutter treats stylus events as touch by default, and the iPad-specific desktop-class integrations (pointer + keyboard) are wired up natively.

Input Support
Touch Full support — taps, drags, swipe-to-reveal row actions, pinch in the terminal
Magic Keyboard Full support — every ⌘ shortcut and the on-screen-keyboard toolbar (item 12)
Trackpad Full support — hover, scroll, right-click, and contextual cursors via UIPointerInteraction (item 11)
Apple Pencil Works as a touch input — every tap target accepts stylus events. No Pencil-only features (low value for an SSH client)
External Bluetooth keyboard Full support — all ⌘ shortcuts, arrow-key navigation, and terminal modifier passthrough

Multi-window on iPadOS

SSHVault opts into iPadOS multi-scene support, so every SSH session can live in its own window:

Mode How to use
Stage Manager Swipe a server row to reveal New window — a fresh SSHVault window opens, pre-routed to that session
Split View Drag the SSHVault icon from the Dock onto the side of another app — iPadOS will host two SSHVault scenes side-by-side, each with its own terminal
Slide Over Same gesture as Split View, but drop on top of the running app — iPadOS mounts SSHVault as a slide-over panel with one SSH session active

UIApplicationSupportsMultipleScenes=true is declared in Info.plist, so Slide Over and Split View are available system-wide without any per-session opt-in. The New window action only appears on iPad-sized layouts (width > 600); it is hidden on iPhone, where iOS rejects requestSceneSessionActivation.

Install via F-Droid Repo

Add the Kiefer Networks F-Droid repository to your F-Droid client:

https://fdroid.kiefer-networks.de/fdroid/repo/

Install via Obtainium

You can install SSHVault directly from GitHub releases using Obtainium:

  1. Open Obtainium and tap Add App
  2. Enter the source URL:
    https://github.com/Kiefer-Networks/sshvault
    
  3. Set Release asset filter to arm64-v8a (or your architecture)
  4. Tap Add — Obtainium will track new releases and notify you of updates

Security

Layer Implementation
Encryption AES-256-GCM, 12-byte counter nonces
Key Derivation Argon2id (256 MiB, 3 iterations, p=1)
SSH Transport CSPRNG padding, SHA-256 fingerprints, constant-time MAC
Server Attestation Ed25519 TOFU key pinning (official + self-hosted)
DNS DNS-over-HTTPS with multi-provider cross-verification
Storage Platform keychain (Keystore / Keychain / libsecret / DPAPI)
PIN Argon2id hashed, brute-force lockout, duress wipe
Server Response padding, timing equalization, PoW challenges

Weak algorithms (DH-group1, CBC ciphers, HMAC-MD5/SHA1, ssh-rsa) are excluded from default negotiation.

ssh-agent integration (Linux + macOS)

SSHVault talks directly to the OpenSSH agent over the unix-domain socket referenced by $SSH_AUTH_SOCK, using a pure-Dart implementation of the agent wire protocol (no native bindings, no shelling out to ssh-add). This unlocks two flows:

  • Read from the running agent — when adding a host, SSHVault offers every key currently held by the agent so you can pick one without copying private material into the vault.
  • Write SSHVault keys to the running agent — each key in the vault has Add to ssh-agent / Remove from agent buttons, so other Linux apps (git, scp, ansible, vscode-remote-ssh) can use SSHVault-managed keys without ever touching the underlying private file. Lifetime is configurable in Settings → Security → ssh-agent integration (default 1 h, 0 = no expiry).

Agent-loaded keys are surfaced with an agent chip on the key list so you can tell at a glance which material is live in the running session.

The integration is environment-aware: on platforms or sessions where $SSH_AUTH_SOCK is unset (Windows, headless CI, mobile) the feature gracefully degrades — the buttons remain hidden, the host-form "Use key from ssh-agent" option is suppressed, and SSHVault falls back to its own key vault as if the agent integration weren't there.

Supported SSH algorithms

The bundled hardened dartssh2 fork advertises only modern algorithms. Connections to servers that require something not on this list will fail during the SSH transport handshake (the connect dialog reports which class — KEX, cipher, MAC, host key — was rejected).

Layer Supported (in negotiation order)
Key exchange mlkem768x25519-sha256, sntrup761x25519-sha512@openssh.com, curve25519-sha256@libssh.org, ecdh-sha2-nistp521, ecdh-sha2-nistp384, ecdh-sha2-nistp256, diffie-hellman-group-exchange-sha256, diffie-hellman-group14-sha256
Host key ssh-ed25519, rsa-sha2-512, rsa-sha2-256, ecdsa-sha2-nistp521, ecdsa-sha2-nistp384, ecdsa-sha2-nistp256
Cipher chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes128-ctr
MAC hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, hmac-sha2-256, hmac-sha2-512, hmac-sha2-256-96, hmac-sha2-512-96 (ignored when an AEAD cipher is selected)

The two hybrid post-quantum KEX algorithms (mlkem768x25519-sha256, sntrup761x25519-sha512@openssh.com) match the OpenSSH 9.9+ default order and are advertised first. The KEMs come from the Open Quantum Safe liboqs library bundled per platform via Dart FFI; on builds where liboqs is not present (e.g. Flutter web) the names are stripped from the advertised list at runtime and the client falls back to classical KEX without any error.

PuTTY .ppk import

SSHVault imports PuTTY private keys (.ppk) directly — no puttygen conversion required.

  • Detected formats: PPK v2 (PuTTY-User-Key-File-2:) and PPK v3 (PuTTY-User-Key-File-3:).
  • Encryption: unencrypted keys load instantly; encrypted keys are decrypted with AES-256-CBC after the KDF derives the AES key — PPK v2 uses PuTTY's SHA-1 KDF and PPK v3 uses Argon2id with the parameters embedded in the file. Both MAC variants (HMAC-SHA1 for v2, HMAC-SHA-256 for v3) are verified before the key is accepted; a wrong passphrase or tampered file is rejected with a PpkParseException.
  • Supported algorithms: RSA, Ed25519, and ECDSA P-256 / P-384 / P-521.
  • How it lands in the vault: the parser re-serializes the key into the standard OpenSSH private-key format (-----BEGIN OPENSSH PRIVATE KEY-----) so downstream code (dartssh2, ssh-agent forwarding, exports) treats it identically to a key generated inside SSHVault.
  • Importing: open Add SSH key → Import, paste the .ppk text or use Import from file; for encrypted keys provide the passphrase. On Windows, double-clicking a .ppk opens SSHVault directly because the installer registers the file association under HKCU\Software\Classes\.ppk.

Architecture

  • Client: Flutter 3.11+ / Dart 3.11+
  • Backend: sshvault-server — Go 1.26+, PostgreSQL 16+, chi router
  • State Management: Riverpod (no setState)
  • Local Database: Drift (SQLite) + Platform Secure Storage
  • Routing: go_router (declarative)
  • SSH: dartssh2 (hardened fork)
  • Design: Material 3 on all platforms

Clean Architecture with feature-based folder structure. Structured logging only.

Building from Source

Prerequisites

  • Flutter SDK 3.11+ (install guide)
  • Android SDK with minSdk 33 (Android 13+)
  • Java 17+ (for Android builds)
  • For Linux: sudo dnf install libsecret-devel (Fedora) or sudo apt install libsecret-1-dev (Debian/Ubuntu)

Clone

git clone https://github.com/Kiefer-Networks/sshvault.git
cd sshvault

Install dependencies and generate code

flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter gen-l10n

Build Android APK

# Per-ABI builds (~30 MB each, recommended)
flutter build apk --release --split-per-abi

# Universal build (~80 MB, all architectures)
flutter build apk --release

Per-ABI APKs will be at build/app/outputs/flutter-apk/app-{arm64-v8a,armeabi-v7a,x86_64}-release.apk.

Build Android App Bundle (AAB)

flutter build appbundle --release

The release AAB is written to:

build/app/outputs/bundle/release/app-release.aab

The bundle is configured (in android/app/build.gradle.kts) to enable per-language, per-density, and per-ABI splits. When uploaded to the Play Store, end-users receive a tailored install of roughly 10–15 MB per ABI (compared with the ~80 MB universal APK), because each device downloads only its own native code, screen-density resources, and locale.

To validate the splits locally with bundletool:

# 1. Generate the universal+split APK set from the AAB
bundletool build-apks \
  --bundle=build/app/outputs/bundle/release/app-release.aab \
  --output=build/app/outputs/bundle/release/app-release.apks \
  --mode=default

# 2. Inspect what splits were produced
bundletool dump manifest --bundle build/app/outputs/bundle/release/app-release.aab
unzip -l build/app/outputs/bundle/release/app-release.apks

# 3. Install the device-specific subset on a connected device
bundletool install-apks \
  --apks=build/app/outputs/bundle/release/app-release.apks

R8/ProGuard rules for the bundled native libraries (Drift, SQLCipher, BouncyCastle, liboqs FFI, flutter_local_notifications) live in android/app/proguard-rules.pro and are applied automatically because isMinifyEnabled = true and isShrinkResources = true are set on the release build type.

Other platforms

flutter build ipa --release         # iOS
flutter build linux --release       # Linux
flutter build macos --release       # macOS
flutter build windows --release     # Windows

Windows Credential Manager (master vault key)

On Windows the master vault key is persisted to the Windows Credential Vault (advapi32.dll!CredWriteW) under the target name de.kiefer_networks.SSHVault.MasterKey. Each entry is encrypted with DPAPI under the user's logon credential, follows them across machines when roaming profiles are enabled, and is auditable from control.exe /name Microsoft.CredentialManager (or cmdkey /list:de.kiefer_networks.SSHVault.MasterKey).

Detail Value
Target name de.kiefer_networks.SSHVault.MasterKey
Type CRED_TYPE_GENERIC
Persist CRED_PERSIST_LOCAL_MACHINE (survives logoff, dropped on OS reinstall)
Backed by DPAPI under the user's logon credential

Older installs that pre-date this change persisted the key through flutter_secure_storage's default Windows backend (also DPAPI, but stored in the app's data directory rather than as a first-class credential). On the first launch after upgrading, SSHVault transparently copies that value into the Credential Vault and removes the legacy entry.

Windows Hello pre-unlock

When Settings → Security → Biometric unlock is on, every read of the master key is gated by Windows.Security.Credentials.UI.UserConsentVerifier.RequestVerificationAsync via the local_auth package (supported on Windows 10 1809+). This means a user who walks away from their machine cannot have the vault re-opened without a fresh fingerprint / face / PIN prompt, even if the OS session is still active. The toggle is a no-op on machines without a Windows Hello provisioning (for example, a desktop without a compatible camera or fingerprint reader and no PIN).

To remove the credential out-of-band — for example, when migrating a machine — open Credential Manager → Windows Credentials and delete the entry under de.kiefer_networks.SSHVault.MasterKey. SSHVault will prompt for the master passphrase again on the next launch.

iOS Keychain access group + App Transport Security

On iOS / iPadOS the master vault key is stored in the system Keychain as a generic-password item under a shared access group, declared in ios/Runner/Runner.entitlements:

<key>keychain-access-groups</key>
<array>
  <string>$(AppIdentifierPrefix)de.kiefer_networks.SSHVault</string>
</array>

The $(AppIdentifierPrefix) token is replaced at sign time by the Team ID prefix from your provisioning profile. The host app and any companion extension targets (Live Activity, WidgetKit complications, Quick Look previews) embed the same group string in their own entitlements so they can dereference the master key directly via SecItemCopyMatching without copying it across process boundaries.

Detail Value
Service de.kiefer_networks.SSHVault
Access group $(AppIdentifierPrefix)de.kiefer_networks.SSHVault
Class kSecClassGenericPassword
Accessibility kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly (device-only) or kSecAttrAccessibleWhenUnlocked (when iCloud Keychain syncing is on)

Apple Developer portal step. Before signing a build that uses this entitlement, the keychain access group must be registered against the App ID:

  1. Sign in to https://developer.apple.com/account/resources/identifiers/list.
  2. Select the SSHVault App ID (de.kiefer-networks.sshvault).
  3. Under Capabilities → Keychain Sharing, enable the capability and add de.kiefer_networks.SSHVault to the keychain group list.
  4. Regenerate the provisioning profiles for every target (host app + extensions) so they pick up the new entitlement, then download them in Xcode (Settings → Accounts → Download Manual Profiles).

Without this portal step, code-signing succeeds but SecItemAdd returns errSecMissingEntitlement (-34018) at runtime.

App Transport Security. SSH and SFTP traffic does not flow through ATS — that policy only governs URLSession / CFNetwork HTTPS. The vault-sync HTTPS endpoint (when the optional self-hosted backend is configured) is the only network surface ATS sees, and it uses TLS 1.2+ with default ATS settings — NSAllowsArbitraryLoads is false in ios/Runner/Info.plist. The single exception domain is localhost, which keeps flutter run debug hot-reload working over plain HTTP without weakening any production traffic.

iOS Home Screen + Lock Screen widgets

SSHVault ships a WidgetKit extension target, ios/SshvaultWidget/, that exposes two widgets:

  • QuickConnectWidget (Home Screen, iOS 14+). Three sizes: systemSmall (1 host), systemMedium (2×2 grid), systemLarge (4×2 grid). Each tile is a Link(URL("sshvault://host/<id>")) — tapping it routes through AppDelegate and opens the host inside the app.
  • LockScreenAccessoryWidget (Lock Screen, iOS 16+). Three families: .accessoryCircular (single terminal glyph that deep-links to the most recent host), .accessoryRectangular (host name + small icon), and .accessoryInline ("Last connected: <name>" text rendered in the lock-screen status area above the clock).

Both widgets read their data from a shared App Group, group.de.kiefer_networks.sshvault, which is declared in both entitlement files:

<!-- ios/Runner/Runner.entitlements
     ios/SshvaultWidget/SshvaultWidget.entitlements -->
<key>com.apple.security.application-groups</key>
<array>
  <string>group.de.kiefer_networks.sshvault</string>
</array>

The Flutter side (lib/core/services/ios_widget_service.dart) listens to favoriteServersProvider + recentServersProvider. When either changes, it pushes a JSON payload over the de.kiefer_networks.sshvault/ios_widget method channel; the native side (AppDelegate.swift) writes that JSON into the shared App Group UserDefaults (key qc_widget_payload) and calls WidgetCenter.shared.reloadAllTimelines() so every active widget instance refreshes.

A user-facing Show widgets toggle (iosWidgetsEnabledProvider) defaults to true — flipping it to false pushes an empty payload so the widget renders its placeholder. The toggle is a no-op on every non-iOS platform.

Apple Developer portal step. Before signing a build that uses this entitlement, the App Group must be registered against both App IDs:

  1. Sign in to https://developer.apple.com/account/resources/identifiers/list.
  2. Select the SSHVault App ID (de.kiefer-networks.sshvault) and the widget extension App ID (de.kiefer-networks.sshvault.SshvaultWidget).
  3. Under Capabilities → App Groups, enable the capability on both and add group.de.kiefer_networks.sshvault to each.
  4. Regenerate the provisioning profiles for both targets so they pick up the new entitlement, then download them in Xcode (Settings → Accounts → Download Manual Profiles).

Without this portal step, code-signing succeeds but UserDefaults(suiteName:) returns nil at runtime and the widget silently shows the placeholder.

Windows native toast notifications

While at least one SSH session is active, SSHVault surfaces a rolling notification through the native Windows.UI.Notifications.ToastNotificationManager API (via the local_notifier package). This replaces the legacy balloon-style fallback that flutter_local_notifications uses on Win32 so the toast looks and behaves like every other Windows 11 notification:

  • It renders with the SSHVault icon + display name resolved from the registered AppUserModelID de.kiefer_networks.SSHVault.
  • It persists in Action Center after dismissal — you can re-open it, click an action button, or clear it like any first-party Windows toast.
  • Two action buttons are attached when the toggle is on: Disconnect closes the most recently surfaced session and Show brings the SSHVault window to the foreground and routes to the terminal branch.
  • Successive updates use replace-by-id semantics, so connecting to a new host updates the existing entry rather than stacking duplicates.

The AUMID is registered in two places that must stay in sync:

  1. The Inno Setup installer writes the descriptor under HKCU\Software\Classes\AppUserModelId\de.kiefer_networks.SSHVault (DisplayName, IconUri, IconBackgroundColor).
  2. lib/main.dart calls windowManager.setAppUserModelId(...) early during boot so toasts produced by a portable / zip-installed copy still resolve correctly.

To silence Windows toasts entirely, open Settings → Appearance → Notifications and turn off Show action buttons (default on). The toggle is Windows-only — Linux and macOS continue to use flutter_local_notifications and are not affected.

Linux clipboard persistence on Wayland

Under a Wayland compositor the clipboard offer is owned by the process that put the data there: as soon as the owning process exits, the offer is gone. That breaks the very common pattern "open SSHVault → copy a private key / password → close SSHVault → paste into a terminal".

To work around this, SSHVault detects Wayland sessions (WAYLAND_DISPLAY set on Linux) and shells out to wl-copy (from the wl-clipboard package) as a detached helper. The helper keeps the clipboard offer alive after the SSHVault window is closed, until either the user pastes or the 30 s auto-clear timer fires (which then runs wl-copy --clear). On X11, macOS, Windows, and mobile, the standard in-process clipboard is used because those platforms don't have this limitation.

wl-clipboard ships in the default install of every mainstream Wayland distro (Fedora, Ubuntu, Debian, Arch, openSUSE) and is bundled in the SSHVault Flatpak runtime, so no manual setup is needed in the typical case. On a minimal/headless Wayland install you can opt in via your distro's package manager:

sudo dnf install wl-clipboard          # Fedora
sudo apt install wl-clipboard          # Debian / Ubuntu
sudo pacman -S wl-clipboard            # Arch
sudo zypper install wl-clipboard       # openSUSE

If wl-copy is missing the app falls back to the in-process clipboard silently — copy still works, it just doesn't survive the app exiting.

Linux DBus integration

SSHVault on Linux owns the well-known session-bus name de.kiefer_networks.SSHVault and exports /de/kiefer_networks/SSHVault implementing the de.kiefer_networks.SSHVault interface. This gives you:

  • Single-instance enforcement: a second sshvault invocation forwards its argv (e.g. ssh://host) to the running instance and exits.
  • External triggers from KRunner, Rofi, Polybar etc.:
    • Connect(s host_id) — open a session for a stored host id.
    • Disconnect(s session_id) — close an active session.
    • ListHosts() -> a(ssss) / ListSessions() -> a(ssss).
    • OpenUrl(s url) — handles ssh://... URLs.
    • Activate() — raise the window.
  • Subscribable signals: SessionStarted(host_id, session_id), SessionEnded(session_id), Notified(message).

Distros packaging SSHVault should install linux/de.kiefer_networks.SSHVault.service to /usr/share/dbus-1/services/ so that DBus can autostart the binary on first method call:

[D-BUS Service]
Name=de.kiefer_networks.SSHVault
Exec=/usr/bin/sshvault

For Flatpak, the manifest needs --talk-name=de.kiefer_networks.SSHVault — this is the app's own well-known name, so it is normally granted by default.

Global keyboard shortcut (Quick connect)

SSHVault registers a system-wide hotkey (Super+Shift+S by default) that opens the Quick connect overlay from anywhere on the desktop. The binding goes through org.freedesktop.portal.Desktop → GlobalShortcuts, which is supported on:

  • GNOME 45+ (xdg-desktop-portal-gnome 1.16+)
  • KDE Plasma 5.27+ / 6.x (xdg-desktop-portal-kde)

The first time the app starts you'll see a portal dialog asking you to confirm or rebind the trigger. You can change it later from Settings → Network → Desktop integration → Global shortcut → Re-bind.

On XFCE, LXQt and other desktops without a GlobalShortcuts portal backend, bind a hotkey in your desktop settings (XFCE: Settings → Keyboard → Application Shortcuts) to this exact dbus-send line:

dbus-send --session --type=method_call \
  --dest=de.kiefer_networks.SSHVault \
  /de/kiefer_networks/SSHVault \
  de.kiefer_networks.SSHVault.Activate

The Flatpak manifest already grants --talk-name=org.freedesktop.portal.Desktop, so no extra permission is required.

XDG desktop portals (Flatpak)

SSHVault is sandbox-clean: every host interaction that could leak filesystem access or punch out of the bubblewrap goes through an XDG desktop portal. The Flatpak manifest only needs a single --talk-name=org.freedesktop.portal.Desktop — that one bus name covers the whole portal surface.

Portal Used by Replaces
org.freedesktop.portal.FileChooser file_picker 10.x — SFTP upload/download, SSH key import, settings export, vault import/export, ~/.ssh/config import direct GtkFileChooserNative
org.freedesktop.portal.OpenURI url_launcher 6.3.x — About screen links, error-message links Process.start('xdg-open', …)
org.freedesktop.portal.Settings DesktopAppearanceService — live prefers-color-scheme + accent color direct GSettings reads
org.freedesktop.portal.Secret KeyringService — fallback for the vault master key when org.freedesktop.secrets is unreachable direct libsecret only

Read-only access to the host's SSH client configuration and key material is granted via --filesystem=xdg-config/ssh:ro and --filesystem=~/.ssh:ro — the picker portal handles ad-hoc choices, but importing well-known files (~/.ssh/config, ~/.ssh/id_*) is a direct read. The legacy --talk-name=org.freedesktop.secrets is kept for backwards-compatibility with existing installs but is no longer strictly required: distributors may drop it for a tighter sandbox and SSHVault will transparently use org.freedesktop.portal.Secret instead.

Linux AppArmor profile (native packages only)

When SSHVault is installed from a native package on Debian, Ubuntu or OpenSUSE — i.e. outside the Flatpak sandbox — it ships with an AppArmor profile that confines what the binary can read, write and connect to.

Flatpak users: you do not need this profile. The Flatpak sandbox (bubblewrap + XDG portals) is already strictly tighter; running both at once only complicates debugging.

The profile lives at linux/apparmor/de.kiefer_networks.sshvault and is copied to /etc/apparmor.d/de.kiefer_networks.sshvault by the package's post-install hook (linux/apparmor/postinst.sh), which then runs apparmor_parser -r to load it without requiring a reboot.

What it allows:

Resource Access
~/.ssh/config, ~/.ssh/known_hosts, ~/.ssh/id_*, ~/.ssh/id_*.pub read-only
~/.local/share/sshvault/, ~/.config/sshvault/ read/write
~/.config/autostart/de.kiefer_networks.sshvault.desktop read/write
Bundled flutter_assets/, libapp.so, libflutter_linux_gtk.so, libliboqs.so* read
TCP/UDP network (IPv4 + IPv6) outbound
ssh-agent socket ($SSH_AUTH_SOCK), ssh-keysign helper unix peer / exec
DBus: own well-known name de.kiefer_networks.SSHVault bind
DBus: org.freedesktop.secrets (libsecret), org.freedesktop.portal.*, org.freedesktop.login1.Manager, org.freedesktop.Notifications, org.kde.StatusNotifierWatcher talk

What it explicitly denies (even though ~ would otherwise be readable):

  • ~/.gnupg/, ~/.mozilla/
  • Browser profiles + cookies (Chrome, Chromium, Edge, Brave, Vivaldi, Firefox cache)
  • Password stores (~/.password-store, *.kdbx, KeePassXC, Bitwarden, 1Password, the legacy ~/.local/share/keyrings/)
  • Shell history (.bash_history, .zsh_history, .python_history)
  • Raw block devices (/dev/sd*, /dev/nvme*) and kernel introspection (/proc/kcore, /proc/kallsyms)

Disable for debugging:

# Temporary — log violations but do not enforce them
sudo aa-complain /etc/apparmor.d/de.kiefer_networks.sshvault

# Full disable (until you re-enable or reload)
sudo aa-disable /etc/apparmor.d/de.kiefer_networks.sshvault

# Re-enable
sudo aa-enforce /etc/apparmor.d/de.kiefer_networks.sshvault

# Watch live denials while reproducing the bug
sudo journalctl -kf | grep -i apparmor

Add custom paths (e.g. you keep your vault on an external disk, or you import keys from ~/Projects/secrets/) without editing the shipped profile — drop a snippet into /etc/apparmor.d/local/de.kiefer_networks.sshvault, which the main profile already includes if it exists. The file survives package upgrades.

# /etc/apparmor.d/local/de.kiefer_networks.sshvault
owner /mnt/encrypted-disk/sshvault/** rwk,
owner @{HOME}/Projects/secrets/**     r,

Then reload:

sudo apparmor_parser -r /etc/apparmor.d/de.kiefer_networks.sshvault

Validate the profile before shipping a release:

./linux/apparmor/test_profile.sh

This runs apparmor_parser -Q (parse only, no kernel load) so it works in CI containers that don't have an AppArmor-enabled kernel.

Run tests

flutter test

Backend

The self-hosted backend handles encrypted vault sync and authentication. It never processes plaintext user data.

See sshvault-server for setup instructions.

Localization

Available in 28 languages:

Arabic, Chinese, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Norwegian, Polish, Portuguese, Romanian, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese

Translation files are in lib/l10n/.

Privacy

SSHVault is built with a privacy-first philosophy:

  • Zero telemetry - no analytics, crash reporting, or usage metrics are collected
  • Zero tracking - no advertising identifiers, no third-party tracking domains
  • All data local - SSH credentials, vault contents, settings, and host history live only on your device (encrypted at rest)
  • No accounts, no cloud - SSHVault never phones home; the only network traffic is the SSH/SFTP connections you initiate

The Apple Privacy Manifest is published at macos/Runner/PrivacyInfo.xcprivacy (macOS) and ios/Runner/PrivacyInfo.xcprivacy (iOS). It declares NSPrivacyTracking=false and an empty NSPrivacyCollectedDataTypes array, plus the required reason codes for the small set of platform APIs SSHVault actually uses (file timestamps for vault export metadata, NSUserDefaults for the settings DataStore, and system boot time).

Donate

If you find SSHVault useful, consider supporting development:

Donate via Liberapay

License

Copyright (C) 2024-2026 Kiefer Networks

This program is licensed under the GNU Affero General Public License v3.0.

The bundled dartssh2 fork (packages/dartssh2/) is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors