VELA is a local-first, zero-knowledge password vault with:
desktopVELA: native GPUI desktop app (the Windows/Linux default), Tauri fallback/macOS app, and browser-extension IPC host.androidVELA: native Android app.iosVELA: native iOS app.serverVELA: sync/auth API server.extension: browser extension and native messaging host.webVELA: ephemeral read-only or read-write web vault (served by the server).libVELA: shared Rust core (vela-core), crypto (vela-crypto), and the Android/iOS/WASM bridges.security/: audit tooling — semgrep rules, static Rust scanner, exploit regression suite.docs/: design documents and the interactive vault atlas.
The app is designed so vault data and root secrets stay on client devices. The server stores encrypted blobs and verifies device authentication, but it must not receive vault plaintext, RMS material, identity private keys, or browser extension secrets.
Important defaults after the hardening pass:
- Desktop browser IPC uses OS-protected IPC only: Windows named pipes or Unix sockets.
- Desktop IPC admits only authenticated browser-native-messaging process trees; there is no bearer capability file to steal.
- Browser extension access is routed through authenticated native messaging, not localhost HTTP.
- Autofill secrets require an active desktop session plus biometric/user approval.
- Server defaults to loopback:
127.0.0.1:8443. - Android blocks cleartext globally except scoped local development hosts listed in
network_security_config.xml. - If
PASETO_SECRET_KEYis unset, the server generates and persists a keypair at{DATA_DIR}/paseto.key(0600), so sessions survive restarts with no manual step. - Production rejects wildcard CORS and enforces HTTPS for auth/recovery endpoints unless
ALLOW_INSECURE_LAN=true.
androidVELA/ Android application (Kotlin)
iosVELA/ iOS application (Swift, XcodeGen)
desktopVELA/ Desktop application (native GPUI default on Windows/Linux; Tauri + React fallback/macOS)
extension/ Browser extension and native messaging host
webVELA/ Ephemeral web vault SPA
libVELA/vela-core/ Shared Rust core (vault, sync, crypto orchestration)
libVELA/vela-crypto/ Rust crypto primitives
libVELA/vela-android-bridge/ Android JNI bridge
libVELA/vela-apple-bridge/ iOS static library bridge
libVELA/vela-wasm-bridge/ WASM bridge for the web vault
serverVELA/ Axum sync/auth server
security/ Audit tooling: semgrep rules, scan.py, exploit suite, formal models
docs/ Design docs (SPEC companion pieces) and docs/vela-atlas.html map
Documentation map:
SPEC.md— protocol specification v2 (identity, enrollment, recovery, sharing).docs/INSTALL.md— per-platform install & first-run guide (server, desktop, mobile, extension, ephemeral web).docs/RESEARCH_POTENTIAL.md— which mechanisms are publishable research, venues, and gaps to close.CHANGELOG.md— release history and security remediations.EPHEMERAL_WEB_ACCESS_DESIGN.md— the web vault design cited throughout the code.SECURITY_AUDIT.md/SECURITY_REDTEAM.md— audit findings;security/exploits/proves the fixes stay fixed in CI.SECURITY.md— vulnerability disclosure policy;.well-known/security.txtis its machine-readable form.ROADMAP.md— what stands between the current tree and a 1.0 release.
Run from serverVELA.
cd serverVELA
cargo runBy default the server binds only to loopback:
127.0.0.1:8443
That is correct for local desktop-only testing, but it is not reachable from Android or other LAN devices.
For Android or another LAN client, bind to all interfaces or the machine LAN IP:
cd serverVELA
$env:LISTEN_ADDR="0.0.0.0:8443"
cargo runThen test from the server machine:
Invoke-WebRequest http://192.168.1.33:8443/health
Test-NetConnection 192.168.1.33 -Port 8443If Test-NetConnection fails while the server is running, Windows Firewall is likely blocking inbound port 8443.
For local LAN HTTP, set clients to:
http://192.168.1.33:8443
The Rust server currently serves plain HTTP. Do not point a client at https://192.168.1.33:8443 unless a TLS terminator is actually running there.
For HTTPS, put Caddy, nginx, Traefik, or another reverse proxy in front of the server and point clients at the proxy origin.
In production, set:
$env:VELA_PRODUCTION="true"
$env:PASETO_SECRET_KEY="<base64-64-byte-ed25519-keypair>"
$env:LISTEN_ADDR="127.0.0.1:8443"
$env:WEBAUTHN_RP_ORIGIN="https://your-domain.example"
$env:CORS_ORIGINS="https://your-domain.example"Terminate TLS in front of the server and forward the standard proxy headers, especially X-Forwarded-Proto: https.
For a reverse proxy or Cloudflare Tunnel running on the same host, also set:
$env:TRUST_PROXY_HEADERS="true"
$env:TRUSTED_PROXY_CIDRS="127.0.0.1/32,::1/128"See serverVELA/DEPLOY_SYSTEMD.md for a systemd + Cloudflare Tunnel deployment guide.
The repo includes a root nixpacks.toml for Coolify. It builds only the Rust
server workspace:
cd serverVELA && cargo build --release -p vela-server
At runtime Nixpacks starts (production-hardened defaults baked in; the
Dockerfile image sets the same via ENV):
VELA_PRODUCTION=true TRUST_PROXY_HEADERS=true TRUSTED_PROXY_CIDRS=127.0.0.1/32,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fd00::/8 LISTEN_ADDR=0.0.0.0:${PORT:-8443} target/release/vela-server serve
Set these Coolify environment variables (domain-specific ones are required; the rest now have secure defaults):
DATA_DIR=/app/data
WEBAUTHN_RP_ID=vault.example.com
WEBAUTHN_RP_ORIGIN=https://vault.example.com
WEBAUTHN_RP_NAME=VELA
CORS_ORIGINS=https://vault.example.com
# Optional — auto-generated and persisted at $DATA_DIR/paseto.key if unset:
PASETO_SECRET_KEY=<base64-64-byte-key>Add persistent storage for /app/data. If Coolify is terminating HTTPS in front
of the app, set TRUSTED_PROXY_CIDRS to the proxy/container network range that
is allowed to send X-Forwarded-Proto: https.
Create encrypted migration bundles when moving a VELA server to another machine:
cargo run -- migrate export `
--out vela-home.vela-migrate `
--env-file .env `
--data-dir ./data `
--include-secrets `
--passphraseRestore on the target server:
cargo run -- migrate import `
--bundle vela-home.vela-migrate `
--target-data-dir /var/lib/vela `
--target-env-file /etc/vela/vela-server.env `
--passphraseCloudflare tunnel files are not included by default. Add them only when wanted:
--include-deployment-config /etc/cloudflared/config.ymlFor trusted LAN-only testing without HTTPS:
$env:ALLOW_INSECURE_LAN="true"
$env:LISTEN_ADDR="0.0.0.0:8443"
$env:CORS_ORIGINS="http://192.168.1.33:1420,http://192.168.1.31"Do not use ALLOW_INSECURE_LAN=true for internet-facing deployments.
The native GPUI frontend is the default on Windows and Linux. Run it from
desktopVELA:
cd desktopVELA
cargo run -p vela-desktop-gpuiBuild the Windows installers:
cargo build --release -p vela-desktop-gpui -p vela-nm-host
cargo install cargo-packager --version 0.11.8 --locked
cd src-gpui
cargo packager --release --formats nsis,wixOutputs:
target/release/*.msi
target/release/*-setup.exe
The Tauri frontend remains available explicitly for macOS and fallback work:
cd desktopVELA
bun install
bun tauri devThe desktop app currently pins:
- Rust
tauri = 2.11.0 - JS
@tauri-apps/api = 2.11.0 - JS
@tauri-apps/cli = 2.11.0 tailwindcss = 3.4.19
Do not upgrade Tailwind to v4 without migrating the config and PostCSS setup. Tailwind v4 builds but drops the app's custom v3 theme utilities such as bg-surface, text-on-surface, and font-body.
If the packaged app has no styling, fonts, or Material Symbols icons, check that the built CSS contains these rules:
Select-String -Path dist\assets\*.css -Pattern "\.bg-surface|\.text-on-surface|\.font-body|material-symbols-outlined"For LAN development, configure the desktop server URL as:
http://192.168.1.33:8443
If you see:
Server authentication failed: Failed to get challenge: error sending request
check:
Test-NetConnection 192.168.1.33 -Port 8443
Invoke-WebRequest http://192.168.1.33:8443/healthCommon causes:
- Server is still bound to
127.0.0.1:8443. - Server is not running.
- Windows Firewall blocks inbound
8443. - Client uses
https://while the server is serving plain HTTP.
Run/build from androidVELA.
cd androidVELA
gradle :app:assembleDebugAndroid blocks cleartext HTTP by default. The app keeps global cleartext disabled and allows only scoped local development hosts in:
androidVELA/app/src/main/res/xml/network_security_config.xml
Currently allowed local cleartext hosts include:
localhost
10.0.2.2
192.168.1.33
vela.local
Use this Android server URL for LAN HTTP:
http://192.168.1.33:8443
If Android reports:
Cleartext HTTP traffic to 192.168.1.33 not permitted
then the IP is missing from network_security_config.xml, or the installed APK was built before the exception was added.
If Android reports a failed HTTPS connection to 192.168.1.33:8443, the usual cause is that no TLS server is listening there. Use HTTP for LAN development, or run a TLS reverse proxy and use its HTTPS URL.
The extension no longer talks to the desktop app over localhost HTTP, and there is no capability file: it uses browser native messaging only. The browser spawns a single self-contained binary (vela-native-messaging-host, built with cargo build --release -p vela-nm-host) over stdio, which relays to a well-known per-user endpoint; the desktop admits only that binary when a browser spawned it.
Build:
cd extension
bun install
bun run buildRegister native messaging for Chromium browsers:
$env:VELA_CHROME_EXTENSION_ID="<your-audited-extension-id>"
.\native-messaging\register-host.batThe registered Chromium host name is com.vela.desktop. Chrome rejects native
messaging host names with hyphens, so stale vela-desktop registrations should
not be used.
For Firefox-family browsers, the host is scoped by extension ID through allowed_extensions.
The Chromium native messaging host manifest must use a concrete origin:
chrome-extension://<extension-id>/
Wildcard origins such as chrome-extension://* are intentionally not allowed.
Shared crypto is in libVELA/vela-crypto.
Current production vault encryption dependencies:
chacha20poly1305 = 0.10.1ml-kem = 0.3.0
Review notes live in:
libVELA/vela-crypto/SECURITY.md
Run tests:
cd libVELA/vela-crypto
cargo testUseful full-stack checks:
# Server
cd serverVELA
cargo check
cargo audit
# Desktop Rust (Windows/Linux default)
cd desktopVELA
cargo check -p vela-desktop-gpui
cargo audit
# Native GPUI frontend
cd desktopVELA
cargo test -p vela-desktop-gpui
cargo build --release -p vela-desktop-gpui -p vela-nm-host
# Tauri fallback frontend
cd desktopVELA
bun audit
bun run build
bun tauri build
# Android
cd androidVELA
gradle :app:assembleDebug
# Extension
cd extension
bun audit
bun run build
# Crypto
cd libVELA/vela-crypto
cargo test
cargo audit
# Android bridge
cd libVELA/vela-android-bridge
cargo check
cargo auditCheck what address the server is listening on:
Get-NetTCPConnection -LocalPort 8443 | Select-Object LocalAddress,LocalPort,State,OwningProcessIf it shows only:
127.0.0.1 8443 Listen
LAN clients cannot connect. Restart the server with:
$env:LISTEN_ADDR="0.0.0.0:8443"
cargo runThe server is plain HTTP unless a reverse proxy terminates TLS. Use:
http://192.168.1.33:8443
or put TLS in front and use the proxy URL.
Add the exact LAN host to:
androidVELA/app/src/main/res/xml/network_security_config.xml
Then rebuild and reinstall the APK.
Keep Tailwind on 3.4.19 unless the config is migrated to v4.
Check the built CSS:
Select-String -Path dist\assets\*.css -Pattern "\.bg-surface|\.text-on-surface|\.font-body|material-symbols-outlined"If those classes are missing, the Tailwind pipeline is wrong.
Keep Rust and JS Tauri packages on the same major/minor line:
tauri = 2.11.0
@tauri-apps/api = 2.11.0
@tauri-apps/cli = 2.11.0
tauri-build is different: 2.6.0 is the currently published build crate and is expected.
Check:
- Desktop app is running.
- Browser native messaging host is registered (the compiled
vela-native-messaging-hostfromdesktopVELA/vela-nm-host). - Chromium registration used
VELA_CHROME_EXTENSION_ID. - The desktop session is active and biometric approval succeeds before secrets are returned.
MIT. See LICENSE.