Security model
Security boundary
The intended design protects confidential vault records at rest using established authenticated encryption, with key-unwrapping material protected by an explicitly supported OS credential store. The Linux creation/unlock path is implemented but has not been independently reviewed. Linux unlocking requires a separate Latch passphrase. The core wrapping implementation uses Argon2id v19 with that passphrase and OS-held random material as its standard secret input, then XChaCha20-Poly1305 to wrap the vault key. Its fixed profile uses 64 MiB, three passes, and four lanes. SQLite stores the wrapped key, encrypted project records, and allowlisted structural/audit metadata. Device material is stored in the explicitly selected GNOME login collection. Unsupported providers and unprotected collection formats fail closed.
If an attacker obtains both a wrapped key and device material, offline passphrase guessing remains possible. Losing either unlock factor prevents normal unlock; recovery is not implemented. Application-owned sensitive buffers are wiped on drop, but this does not guarantee removal of compiler copies, library or OS buffers, swap, or crash dumps.
Approval controls which values Latch delivers to a selected process. It cannot prevent that process from reading or leaking them. It does not contain malicious software running as the same user, compromised operating systems, administrators, or approved code. Locking cannot retract a value already delivered.
Agent labels are self-reported claims, not authenticated identities. Process-request audit records store that label separately from the peer UID and PID observed on the local socket. Audit archives will contain allowlisted metadata only and will not be tamper-proof records or password-encrypted vault backups.
The desktop exposes vault, project, and one-record secret commands only to the management window. A separate request window has only status, unlock, request review/decision/cancellation, and explicit open-manager commands; it cannot list or reveal stored secrets. Rust validates window labels as well as Tauri capabilities. Closing a request denies it. Closing the manager hides it and locks the vault; the tray offers Open Latch, Lock Vault, and Quit Latch. Background startup never unlocks the vault. Request auditing begins after unlocking and successful request preparation; cancellations during the pre-unlock wait do not yet produce per-request audit events. The folder picker uses the Rust dialog API. Its native acceptance flow has been exercised locally on Linux with a disposable vault. The webview receives no general dialog, clipboard, filesystem, SQL, or shell permission. Passphrases and deliberately revealed values necessarily exist briefly in the webview and IPC; vault keys do not. Copy stays in Rust and returns only its expiry. Lock epochs reject requests submitted before a subsequent lock, including requests delayed in the desktop task queue. One worker serializes SQLite, credential-store access, and memory-hard derivation. Manual locks cancel queued work and drop the live key; a metadata mutation already inside its short SQLite commit section finishes first. Lock metadata is flushed when the worker can write. A crash or disk failure can lose a final lock event. Audit failure blocks normal mutations and further unlocking. The five-minute timer is implemented; OS-session lock and suspend integration are still pending and are not claimed by this build.
Clipboard copy records scoped metadata before handing one value to the operating-system adapter. Latch retains one zeroizing comparison value until lock or the selected 15, 30, or 60 second expiry, then clears only if the clipboard still matches the active Latch copy. Linux uses the common password-manager exclusion MIME type, macOS uses the concealed pasteboard convention, and Windows requests exclusion from monitoring, history, and cloud clipboard. These hints and timed clearing cannot prevent same-user software from reading or retaining a copied value. macOS and Windows behavior has not been qualified.
Project names, canonical directory paths, and environment kinds/identities share one authenticated encrypted record per project. The record is bound to its vault, key, project identity, and revision. Updates reserve a fresh random nonce durably before encryption; failed writes keep that reservation. Encrypted mutations and their audit events commit together. Names and paths never enter SQLite as plaintext. Deletion removes live records but does not promise forensic erasure from database free pages, old copies, or storage media.
Directory-selection tokens are single-use, expire after five minutes, and are invalidated on lock or successful unlock. An unlock advances the session epoch so earlier requests cannot apply to the new session. Listing responses contain at most 20 projects and no secret values. Mutations check uniqueness in Rust across at most 100 decrypted metadata records, releasing the session lock between records. A returned page or native picker may already have shown metadata before a lock; locking cannot erase what a person or compromised renderer has observed. The GUI discards its project state on lock and ignores late results.
Canonical directory checks reject traversal and lossy path decoding. The Linux broker revalidates the encrypted project binding, environment identity, project and secret revisions, executable identity, and canonical directory before launch. This narrows review-to-use replacement but cannot prevent changes to executable content that preserve the checked filesystem metadata, runtime library or interpreter changes, or changes after process creation. Weekly audit rotation, portable recovery, and authenticated agent identity are still pending.
Agent request and process boundary
The Linux CLI and desktop exchange bounded, versioned, names-only JSON frames through $XDG_RUNTIME_DIR/latch-development.sock. The runtime directory and socket must be owned by the current user with no group or world permissions, and the desktop accepts only peers whose kernel-reported effective UID matches its own. Same-user malware can still submit requests, impersonate an agent label, manipulate the user interface, observe process state, or steal values from an approved process.
Only one request can await approval. The popup shows no existing values and defaults keyboard focus to Deny. A missing requested value crosses the webview IPC once, is validated and encrypted in Rust, and is committed with the consumed approval before launch. The command receives the small documented baseline environment and the exact selected names. Latch invokes the canonical executable directly with literal arguments, an explicit working directory, null standard streams, and no implicit shell. --shell records and displays that interpreter semantics are intentional; it does not parse or join arguments.
Approval is consumed before the operating system spawn call so a failed or ambiguous launch cannot be retried with the same decision. A spawn failure leaves an auditable failed job. Latch observes exit status but does not capture command output, arguments, executable paths, secret values, or environment contents in its database. Request, decision, per-secret dispatch, and exit events contain opaque IDs and allowlisted scope/caller metadata. Running jobs become unknown after broker restart; this build cannot stop a child or retract a value already delivered.
Dependencies and releases
Dependencies and toolchains are pinned in manifests and lockfiles. CI includes dependency audits and secret scanning with full value redaction. Tauri's Linux dependency graph currently includes unmaintained GTK3 bindings and a GLib soundness advisory. These findings remain visible and require review before release.
No signed release or updater exists. Public release requires qualified platform key-store behavior, native IPC testing, recovery testing, signing and artifact verification, and external review of the cryptographic and approval design. Automated checks alone do not establish those properties.
Configuration import
Only the native Rust file picker supplies import paths. Rust rejects final-component symlinks, non-regular files, parent traversal, invalid UTF-8, NUL bytes, and input above 1 MiB or 256 assignments. Reads are bounded and do not invoke a shell or evaluate expansions. The documented literal parser rejects ambiguous or unsupported import syntax with fixed errors. Example comparison discards right-hand sides without constructing credential values.
Import preview returns names, existing-name conflicts, skipped-empty names, and a random single-use token. Candidates are authenticated ciphertext encrypted under the vault key with durably reserved nonces. One pending review is bound to the project, environment identity, lock epoch, and a five-minute deadline. Manual/automatic locking, cancellation, expiry, and replacement invalidate it. Commit consumes the review, rechecks current names and capacity, and saves the batch plus per-secret create events and an import event atomically. Comparison records a scoped metadata-only audit event. Neither operation includes source contents or values in its response or audit records.
The picker grants access to the selected file, not a project-directory sandbox. Parent symlinks and concurrent file edits before/during the single read are not excluded. The preview binds the exact parsed bytes retained as ciphertext, not a future file read. Source plaintext remains on disk; this feature cannot protect it from same-user software or guarantee filesystem erasure. Native file-picker acceptance still requires manual platform qualification.