QtPass is actively maintained and security updates are provided for the latest releases. Older versions are not supported.
| Version | Supported |
|---|---|
| 1.8.x | ✅ |
| < 1.8 | ❌ |
If you discover a security vulnerability in QtPass, please report it responsibly:
- Do NOT create a public GitHub issue for security vulnerabilities
- Do email the maintainer directly at:
help@qtpass.org - Alternative: Open a private security advisory via GitHub
- Include in your report:
- Description of the vulnerability
- Steps to reproduce the issue
- Potential impact assessment
- Any suggested fixes (optional)
- Acknowledgment: Within 48 hours
- Initial assessment: Within 7 days
- Fix timeline: Depends on severity; critical issues are prioritized
QtPass is a GUI for pass, the standard Unix password manager. Keep in mind:
- QtPass does not encrypt passwords - encryption is handled by GPG
- Passwords are stored in your local password store (typically
~/.password-store) - QtPass requires GPG to be installed and configured on your system
- The clipboard is cleared after a configurable timeout (default: 45 seconds)
- A password store's
.gitdirectory is not inert data. With Git enabled, QtPass runsgitin the store, and Git runs the hooks in.git/hookswith your rights. A plaingit clonedoes not copy a remote's hooks, but a store received as an archive or an existing checkout, or one on storage others can write to, may carry them. Treat such a.gitas you would treat a script. - A signed
.gpg-idprotects the recipient list against someone who can change the store but does not hold the signing key. QtPass verifies the signature over the exact bytes it then encrypts to, so a file swapped between the check and its use is not a way in. - A signature proves authentic, not current, and not placed. An older list, genuinely signed, that still names a member since removed can be put back by the same someone, or copied into a folder that never had a list of its own.
- A
.gpg-idQtPass writes for a store with a signing key therefore starts with two comment lines the signature covers:# QtPass-GpgId-Generation: Nand# QtPass-GpgId-Folder: <store-relative folder>. - QtPass remembers, per list, the highest generation it has accepted or written on this device, with a SHA-256 of the exact bytes, and refuses a signed list with a lower generation, one of the same generation with other bytes, or one written for another folder.
- Without a readable and writable record nothing is accepted or written; the record is one per user, and a second QtPass process waits for it or is refused.
- A holder of the signing key saving the recipients from QtPass writes the next generation; that is the way back after a deliberate revert. The dialog preselects only a list that verifies (an older but authentic one with a warning; one that does not verify not at all), so what is ticked is never an unsigned edit.
- The limits of that: it detects a rollback only on a device that has already accepted or written a higher generation; a device seeing the store for the first time, or one that was offline for the change, has nothing to compare against.
- A folder moved or renamed carries a list bound to its old place; saving its recipients once binds it anew.
- A sub-folder whose own list was removed or replaced by a link is refused, not encrypted to its parent folder's list instead: a list that was not a regular file, or one this device accepted before and that is now gone, has to be saved again from the Users dialog by a signing-key holder. Without a signing key the parent's list applies, as with
pass. - The generation is monotonic per device, not a distributed sequence number: two devices can both produce the same next generation, and Git synchronisation resolves that.
- The device that wrote or accepted one of two such lists then meets the other as the same generation with other bytes: a conflict, refused and not preselected, until a signing-key holder checks the recipients and saves, writing the next generation.
- Compatibility: the lines are comments to
pass1.7.4 and later and to QtPass 1.8 and later;passup to 1.7.3 and Android Password Store take a comment for a recipient, so a signed store's clients need to be current. Stores without a signing key get plain lists, as before. - A list written by
pass(also through QtPass'spassbackend) or by QtPass before 2.0 carries no generation and reads as 0: once a device has accepted a higher one, such a list is refused, and the Users dialog preselects nothing from it (without a folder line it may be any folder's old pair), until a signing-key holder selects and saves the recipients from QtPass. - At generation 0 nothing is pinned:
passwrites a headerless list for every change (also through QtPass'spassbackend), so different bytes there are the normal course of a store kept withpass, not a conflict. Freshness begins with the first list QtPass writes. - Path checks catch mistakes, not a hostile local writer. Store-boundary checks (
.., symlinks and junctions, absolute paths) run before a file operation, not atomically with it. Another process that can write to your store while QtPass runs can also replace what is in it; that is outside what QtPass can protect. - What QtPass does hold to when it adds, edits, copies or re-encrypts an entry is that
gpgnever opens a path in the store for output: it encrypts into a directory of QtPass's own outside the store, and the bytes go into the store through a file created next to the entry, written by its open handle, and the operating system's rename. - That rename replaces a file or a link under the name by then (a link planted since the check included) as an entry and follows nothing; a directory under the name cannot be replaced by a file, so the write fails and leaves it.
- Afterwards the object under the name is opened without following and compared, by device and inode, with the file that was written: a file swapped under the temporary's name before the rename (a hard link to something of yours, which passes every check by name) is reported instead of taken for the entry.
- What is under the name then is left as it is: removing it by name could take another writer's file that landed there since. Where the system cannot give both identities the write stands rather than reporting a swap it cannot prove: on FAT and exFAT a rename moves the file ID, and neither has hard links to swap in.
- A
.gpg-id(from the Users dialog, a new profile, or a new folder taking its parent's list) and its.gpg-id.sigare written the same way. On Windows a junction planted under one of these names makes the write fail rather than replace it. gpgsigns the bytes QtPass just wrote, fed on its standard input (not whatever is under the file's name by the time it would read it), and writes the signature into a directory of QtPass's own, from where it is placed next to the list.- It signs with
--local-user, so a missing secret key fails the signing instead ofgpgtaking another key, and the new signature is verified as one by a configured signing key before it is placed. - Copying an entry reads the source as the regular file it is at the moment of the open (
O_NOFOLLOW; on Windows the reparse point itself), not as whatever a link planted under the name since the check points to, and writes the copy the same staged way; a co-writer cannot have the bytes of a file only you can read copied into the store and committed. - The
passbackend runspass insert, which writes the entry's path directly; a store kept withpasshaspass's semantics there. - A link inside the store is not part of it. A store is often shared (a team's Git repository, a synced folder), and Git carries symbolic links, so a co-writer can make
git pullcreateBank.gpg -> /elsewhere/secret.gpgon your machine. QtPass treats a symbolic link or NTFS junction found inside the store as neither an entry, a folder nor metadata. - Concretely: every walk skips links (re-encryption, search, staging); every operation on a link, or on anything behind one, is refused (show, edit, add, move, copy, re-key); a linked
.gpg-idor.gpg-id.sigis not a recipient list; deleting a link removes the link. - The configured store root itself may be a link (
~/.password-storepointing at a synced folder is a normal setup): that is your configuration, not something found inside the store. - Nothing is logged that is fed to
gpgorpasson standard input (passwords, passphrases). WithQT_LOGGING_RULES=qtpass.debug=truethe commands and their arguments are logged; values of options such as--passphraseare redacted should they ever appear.
QtPass depends on:
- Qt 6.8+ (use
qmake6) - GUI framework; the 1.8.x releases also build with Qt 5.15 - GPG (gpg2) - encryption
- pass (optional) - password store CLI
- Git (optional) - version control
Ensure your system dependencies are kept up to date for security patches.