Changelog
All notable changes to ZVault are documented here.
[0.4.0] — 2026-04-09
Security fixes
- Sensitive variable edit bypass closed (
src/components/variables/VariableModal.tsx,src-tauri/src/commands/variables.rs)- Opening the Edit Variable modal for a sensitive variable previously called
reveal_variableunconditionally, exposing the plaintext value in the textarea with no password check — completely bypassing the re-auth protection - Fixed: sensitive variables no longer have their value loaded into the edit form. The value field shows an amber-bordered lock panel with a Reveal button
- Clicking Reveal prompts for the master password inline (same amber ShieldAlert UI as the row-level reveal). Wrong password → error toast; correct password → value appears in the editable textarea
- Turning off the "Require re-auth" toggle on a sensitive variable also now requires the master password (if the value is not already unlocked, the same prompt is shown and also reveals the value)
- On save: if the value was never unlocked or edited,
nullis passed to the backend and the existingvalue_encis kept unchanged (no re-encryption, no plaintext ever leaves Rust for untouched sensitive values) - Rust
update_variablecommand updated to acceptvalue: Option<String>—Nonereuses the existingvalue_encfrom the database
- Opening the Edit Variable modal for a sensitive variable previously called
UI
- Variable row checkbox spacing (
src/components/variables/VariableRow.tsx,src/components/variables/VariableTable.tsx)- Selection checkbox was overflowing its 28 px column and sitting flush against the key text
- First grid column widened from 28 px to 44 px; inner flex now uses
justifyContent: centerand a 6 px gap between the drag grip and checkbox — both elements are now properly centered and visually separated from the key column
Features
-
Variable groups (
src/components/variables/VariableTable.tsx,src-tauri/src/db/migrations.rs)- Variables within a tier can be tagged with a
group_name— shown as collapsible section headers in the table - Groups are sorted alphabetically; ungrouped variables appear last
- Group name field added to the variable editor with a
<datalist>autocomplete for existing group names
- Variables within a tier can be tagged with a
-
Pinned variables (
src/components/variables/VariableRow.tsx,src/components/variables/VariableTable.tsx)- New
pinnedboolean column on variables; pinned rows float above all other groups regardless of sort order - Pin/unpin toggle button (Pin / PinOff icon) in the row action bar and context menu
- Small pin indicator icon shown in the key column when a variable is pinned
- New
-
Undo delete (
src/components/variables/VariableRow.tsx,src/components/layout/Sidebar.tsx)- Deleting a variable or project now uses a soft-delete pattern (
deleted_atcolumn) - A toast with an Undo button appears for 5.5 seconds — clicking it calls
restore_variable/restore_projectand puts the row back immediately - After 5.5 s with no undo,
hard_delete_variable/hard_delete_projectis called to permanently remove the record - Startup migration cleans up any soft-deleted rows older than 1 hour
- Deleting a variable or project now uses a soft-delete pattern (
-
Environment clone (
src/components/variables/CloneEnvModal.tsx,src-tauri/src/commands/tiers.rs)- "Clone" button in the env tabs toolbar opens a modal to pick source project → source tier → target project → new env name
- Calls the existing
clone_tierRust command, then updates the store and selects the new tier
-
Export formats (
src/components/import-export/ExportDialog.tsx,src-tauri/src/commands/import_export.rs)- Export dialog now has a format picker: .env (KEY=VALUE), JSON (
{"KEY":"VALUE"}), YAML (KEY: VALUE), Docker (--env-fileformat) - Non-.env formats call the new
export_as_formatTauri command
- Export dialog now has a format picker: .env (KEY=VALUE), JSON (
-
Sensitive variables / per-variable re-auth (
src/components/variables/VariableModal.tsx,src-tauri/src/commands/variables.rs)- Variables can be marked Sensitive — revealing their value requires re-entering the master password even when the vault is unlocked
reveal_sensitive_variableTauri command validates vault is unlocked before decrypting- Sensitive toggle shown in the variable editor
-
Variable value type hints (
src/lib/typeValidators.ts,src/components/variables/VariableRow.tsx,src/components/variables/VariableModal.tsx)- Optional type annotation per variable:
url,jwt,hex,uuid,port,boolean - Shown as a colored pill badge in the variable row
- On save, value is validated against the type pattern; a warning toast fires if the format looks wrong
<select>in the variable editor; no external dependency
- Optional type annotation per variable:
-
Password strength meter (
src/lib/passwordStrength.ts,src/components/ui/PasswordStrengthMeter.tsx)- 4-segment colored bar + label (Weak / Fair / Good / Strong) shown on the setup screen and the change-password form in Settings
- Pure client-side heuristic scorer — length, character class diversity, pattern penalties — no external library
-
Light theme (
src/styles/tokens.css,src/store/configStore.ts,src/App.tsx)body.light {}block with ~20 CSS variable overrides for a clean light palette- Theme toggle (Dark / Light) in Settings → Security → Appearance, persisted to config
document.body.classList.toggle('light', theme === 'light')applied reactively inApp.tsx
-
System tray (
src-tauri/src/lib.rs)- Tray icon built with
TrayIconBuilder; left-click toggles show/hide the window - Tray menu: Show ZVault, Lock Vault, separator, Quit
- "Lock Vault" from the tray emits
vault-lockedevent and zeroes the DEK in-process - Minimize to tray on close: when enabled in Settings, the
CloseRequestedwindow event is intercepted and the window is hidden instead of closed; the process keeps running
- Tray icon built with
-
Drag & drop reordering (
src/components/variables/VariableTable.tsx,src/components/layout/Sidebar.tsx)- Variables can be reordered by dragging the grip handle (visible on row hover) — active only in Custom sort with no search or filter applied; persisted via
reorder_variables - Projects in the sidebar can be dragged to reorder; persisted via
reorder_projects - Uses
@dnd-kit/core+@dnd-kit/sortable(no jQuery dependency)
- Variables can be reordered by dragging the grip handle (visible on row hover) — active only in Custom sort with no search or filter applied; persisted via
-
Auto-backup (
src-tauri/src/commands/backup.rs,src/components/settings/SettingsModal.tsx)- New Backup section in Settings: enable toggle, interval picker (1 / 3 / 7 / 14 / 30 days), folder picker, and "Back up now" button
backup_vaultTauri command copies the encrypted SQLite file to the configured folder with azvault_backup_YYYYMMDD_HHmmss.dbtimestamp- Background task runs every hour and triggers an automatic backup if the interval has elapsed
- Last-backup timestamp persisted to
last_backup.txtin the app data dir - Backup files remain fully encrypted — safe to store anywhere
-
Linux CI pipeline (
.github/workflows/linux-build.yml)- Builds
.debandAppImageon Ubuntu 22.04 on every push to main and on PRs - Caches Rust build artifacts with
Swatinem/rust-cache - Automatically creates a GitHub Release and attaches the bundles when a
v*tag is pushed
- Builds
UI
-
Login & setup screen redesign (
src/components/auth/LockScreen.tsx,src/components/auth/SetupScreen.tsx,src/styles/animations.css)- Both screens now use a floating card layout on a dot-grid background with a soft accent halo
- Staggered spring-eased enter animation (background fade → content rise), floating logo idle loop
- Show/hide toggle on all password inputs
- Wrong password: input shakes horizontally with a red border glow
- Correct password: button turns green with checkmark + ripple ring, screen blurs out before app loads
- Logout re-lock fades the screen in cleanly every time it appears
-
Audit log tab redesign (
src/components/settings/SettingsModal.tsx)- Filter chips: All / Reads / Writes / Auth (client-side, no re-fetch)
- Icon badges per action type (Plus, Pencil, Trash, Eye, Copy, Lock/Unlock, Key, Download/Upload)
- Date group separators: Today / Yesterday / weekday+date
- Row hover highlight; timestamp with full datetime on hover (
titleattribute) - Spinner + two-line empty state (disabled vs no-data vs filtered-empty)
- Event count shown in toolbar
-
Toast notifications (
src/components/ui/Toast.tsx)- 2 px progress bar at the bottom depletes over 3.5 s, color-matched per type
- Icon now uses a square badge (consistent with audit log style)
-
Empty states (
src/components/variables/EmptyState.tsx)- "No environments":
Layersicon in a bordered box, proper heading + styled code examples - "No variables": accent-colored
KeyRoundicon (was grey), hover states on action buttons
- "No environments":
-
MainPanel toolbar (
src/components/layout/MainPanel.tsx)- Filter
<select>replaced withAll | Secrets | Non-secretspill group - Sort
<select>restyled withappearance: none+ custom chevron (no more native OS control) - "No project selected" splash: ZLogo + heading +
Encrypted · Offline · Organizedbadges + "New project" CTA
- Filter
-
Sidebar (
src/components/layout/Sidebar.tsx)- Projects now show edit (pencil) and delete (trash) buttons on hover — trash turns red, confirms before deleting
- Footer redesigned: Lock button is icon-only (32 px), turns red on hover; Settings button is icon + label
- Vertical separator between Lock and Settings
-
Variable table header (
src/components/variables/VariableTable.tsx)- "Key" column header is now clickable — toggles A→Z / Z→A sort with
▲/▼indicator in accent color - Subtle
↕hint shown on hover when not actively sorted
- "Key" column header is now clickable — toggles A→Z / Z→A sort with
-
Status bar auto-lock countdown (
src/components/layout/StatusBar.tsx,src/store/configStore.ts,src/types/index.ts)- Status bar shows remaining time before auto-lock (e.g.
4m 32s) with a lock icon - Color shifts to amber at ≤5 min and red at ≤60 s remaining
- Countdown is optional — toggle in Settings → Security → "Show lock countdown in status bar"
- Defaults to on; hidden automatically when auto-lock is set to Never
- Status bar shows remaining time before auto-lock (e.g.
-
Command palette redesign (
src/components/search/CommandPalette.tsx)- Each action now has an icon badge (Plus, FolderOpen, Download, Upload, Settings, Lock)
- "Lock vault" action added — locks immediately
- Keyboard shortcut hints shown next to each action (
N,P,,,L) - Variable results show a Key icon badge and
↵hint; clicking or pressing Enter navigates to the correct project + env - Navigation hint bar at the bottom (↑↓ navigate, ↵ select, ESC close)
-
Settings modal — new sections & controls (
src/components/settings/SettingsModal.tsx)- Appearance sub-section in Security: Dark / Light theme toggle with Moon / Sun icons
- Minimize to tray on close toggle in Appearance
- Backup section (new tab): enable toggle, interval selector, folder picker, "Back up now" button
- Password strength meter shown below the new-password field in the change-password form
-
Setup screen (
src/components/auth/SetupScreen.tsx)- Replaced inline strength bar with the shared
PasswordStrengthMetercomponent
- Replaced inline strength bar with the shared
-
Version bump (
src/components/settings/SettingsModal.tsx,src/components/layout/StatusBar.tsx)
Hardcoded version strings updated tov0.4.0.
Security
-
Argon2id time cost raised from 3 → 5 (
src-tauri/src/crypto/argon2.rs)
The previous value of 3 was below the OWASP-recommended minimum of 4 for
Argon2id. New vaults now uset=5(64 MB memory, 5 iterations, 4 threads).
Existing vaults are unaffected — the t_cost used at setup is persisted in the
database (argon2_t_costconfig key) and read back at unlock time, so old
vaults remain openable with their original parameters. The upgrade applies
automatically the next time a user changes their master password. -
KDF parameters stored per-vault (
src-tauri/src/commands/auth.rs)
argon2_t_costis now written to the database at setup and after every
password change. This decouples vault files from the application binary,
making future parameter upgrades safe and backward-compatible. -
Recovery code entropy raised from ~100 → ~150 bits (
src-tauri/src/crypto/random.rs)
Recovery codes now use 6 groups of 5 characters (format:XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)
instead of 4 groups. Both formats use the same 32-symbol unambiguous alphabet
(ABCDEFGHJKLMNPQRSTUVWXYZ23456789). Old recovery codes stored in existing
vaults continue to work — the minimum-length check inreset_password_with_recovery
accepts any normalized code ≥ 16 characters. -
Decrypted secret values are now zeroized in memory (
src-tauri/src/crypto/aes_gcm.rs,src-tauri/src/commands/auth.rs)
A newdecrypt_str_zeroizingfunction returnsZeroizing<String>instead of
a plainString. It is used in all code paths where a decrypted variable
value is a transient intermediate — specifically the re-encryption loops
inchange_master_passwordandreset_password_with_recovery, and the
unseal_dekhelper (which decrypts a hex-encoded copy of the DEK).
Thesecrecycrate was already a dependency; this closes the gap where
the DEK itself was zeroized but the plaintext values it decrypted were not. -
Constant-time comparison in
verify_key(src-tauri/src/crypto/aes_gcm.rs)
The equality check after decrypting the verification blob now uses
subtle::ConstantTimeEqinstead of==. While the risk in this specific
context is low (AES-GCM authentication already provides the primary
security guarantee), constant-time comparison is the correct practice for
any comparison involving key-derived material.
Backend
-
Soft-delete pattern (
src-tauri/src/db/migrations.rs,src-tauri/src/db/queries.rs,src-tauri/src/commands/variables.rs,src-tauri/src/commands/projects.rs)deleted_at TEXTcolumn added to bothvariablesandprojectsvia additiveALTER TABLEmigrations- All list queries filter
WHERE deleted_at IS NULL; soft-deleted rows are invisible instantly - New commands:
restore_variable,hard_delete_variable,restore_project,hard_delete_project - Startup migration hard-deletes any soft-deleted rows older than 1 hour
-
New variable fields (
src-tauri/src/db/models.rs,src-tauri/src/db/queries.rs)pinned INTEGER NOT NULL DEFAULT 0sensitive INTEGER NOT NULL DEFAULT 0group_name TEXTvalue_type TEXT- All added via additive migrations; existing rows get safe defaults
-
AppConfig new fields (
src-tauri/src/db/models.rs,src-tauri/src/db/queries.rs)theme(dark/light),minimize_to_tray,backup_enabled,backup_interval_days,backup_folder- All stored as key-value rows in
app_configtable; default values applied if row is missing
-
export_as_formatcommand (src-tauri/src/commands/import_export.rs)- Supports
"json","yaml","docker"output in addition to the existing.envwriter
- Supports
-
pin_variablecommand (src-tauri/src/commands/variables.rs)- Flips the
pinnedflag; no re-encryption needed
- Flips the
-
reveal_sensitive_variablecommand (src-tauri/src/commands/variables.rs)- Decrypts and returns a sensitive variable's value; requires vault to be unlocked (DEK in memory)
-
backup_vaultcommand (src-tauri/src/commands/backup.rs)- Copies the encrypted SQLite file with a timestamp suffix to the configured backup folder
Dependencies
- Added
subtle = "2"for constant-time comparisons. - Added
@dnd-kit/core@6,@dnd-kit/sortable@10,@dnd-kit/utilitiesfor drag & drop reordering.