- Python 97.1%
- Shell 2.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| assets | ||
| docs/screenshots | ||
| kpassword_store | ||
| share | ||
| tests | ||
| appimage_runtime.sh | ||
| CHANGELOG.md | ||
| e2e_test.py | ||
| LICENSE | ||
| make_appimage.sh | ||
| make_demo_store.py | ||
| make_icon.py | ||
| pyproject.toml | ||
| README.md | ||
| README_ru.md | ||
| requirements.txt | ||
| run.sh | ||
KPassword-Store
A Qt6 desktop front end for pass — the standard
unix password manager. The store, the encryption and the file format are exactly
the ones pass uses: GPG-encrypted files in $PASSWORD_STORE_DIR. This
application never invents a vault of its own; it reads and writes your existing
store through the pass command, so anything you do here is immediately usable
from the terminal and vice versa.
- Version: 0.5.2
- Author: Orfik
- Project: https://git.orfik-dmn.ru/orf/KPassword-Store
- License: GNU General Public License v3 or later — see LICENSE
- Written with the assistance of the MiniMax AI model (agent Mavis). The author reviewed the result and is responsible for every line.
Requirements
Required: pass and GnuPG
KPassword-Store is a front end, not a password manager of its own. Before the
first launch you need a working, initialised pass store:
# Debian / Ubuntu
sudo apt install pass gnupg
# Arch
sudo pacman -S pass gnupg
# Fedora
sudo dnf install pass gnupg2
# macOS
brew install pass gnupg pinentry-mac
Then create the GPG key and initialise the store:
gpg --full-generate-key # or: gpg --quick-generate-key
pass init your@mail.com # creates ~/.password-store with a .gpg-id
The application refuses to pretend everything is fine when the store is not
ready: Tools → Check Environment reports the pass binary, the gpg binary,
the store path, the GPG recipients taken from .gpg-id, whether the store is a
git repository, and the number of entries. The same check runs at start-up and
raises a notification if something is missing.
Required for two-factor codes: pass-otp
One-time passwords are stored in the pass-otp
format, as a separate GPG-encrypted file next to the entry: example.com.otp.gpg
next to example.com.gpg. To create and manage those files from the command
line you need pass-otp:
# Debian / Ubuntu
sudo apt install pass-otp
# Arch
sudo pacman -S pass-otp
# Fedora
sudo dnf install pass-otp
# macOS
brew install pass-otp
pass otp add example.com # prompts for the otpauth:// URI
pass otp show example.com # prints a code
What this application does and does not need it for:
- Reading a code only requires
gpg. The app decrypts<name>.otp.gpgitself and computes HOTP/TOTP locally in pure Python (RFC 4226 / RFC 6238). It does not shell out topass otp. - Creating an OTP file is also possible without
pass-otp: paste anotpauth://link into the notes of an entry and use Promote to OTP file, or edit the secret directly in the OTP details dialog. pass-otpis what you want on the command line — it makespass ls,pass mvand friends treat the.otpfiles consistently, and it is the standard way to add a secret in the first place.
One-time codes are never sent anywhere — they are computed on your machine, and the application makes no network requests on its own. The single exception is the git synchronisation you can ask for explicitly (see below); there is no telemetry of any kind.
Installation
AppImage (recommended)
Grab the release, mark it executable, run it:
chmod +x KPassword-Store-0.5.2-x86_64.AppImage
./KPassword-Store-0.5.2-x86_64.AppImage
One self-contained file. The interpreter, the standard library, PyQt6 with Qt 6
and the whole X11/xcb stack are inside; only pass and gpg come from the host,
which is exactly what the application needs them for.
- Compatibility: x86_64 Linux with glibc 2.17 or newer — Ubuntu 20.04+, Debian 10+, Fedora 32+.
- If FUSE is unavailable (containers, some hardened distributions), run
./KPassword-Store-0.5.2-x86_64.AppImage --appimage-extract-and-run. - If Qt still cannot reach the display, the launcher says so and prints the exact
packages to install instead of dying silently.
KPASS_DEBUG_PLATFORM=1additionally lists the available platform plugins and the libraries that could not be resolved.
From source
git clone https://git.orfik-dmn.ru/orf/KPassword-Store
cd KPassword-Store
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
./run.sh
To build the AppImage yourself you need appimagetool:
APPIMAGETOOL=/path/to/appimagetool ./make_appimage.sh
# if the interpreter with PyQt6 is not in .venv
KPASS_PYTHON=/usr/bin/python3 ./make_appimage.sh
make_appimage.sh assembles the AppDir and verifies it; appimage_runtime.sh
puts a relocatable CPython and the X11/xcb stack inside it. Everything
downloaded is cached in dist/cache, so a rebuild needs no network.
Desktop entry
mkdir -p ~/.local/share/applications
cp share/kpassword-store.desktop ~/.local/share/applications/
cp assets/kpassword-store-256.png ~/.local/share/icons/hicolor/256x256/apps/kpassword-store.png
Features
Working with entries
- Create, edit, rename, duplicate and delete entries.
- Application trash. Deleting moves the encrypted files to a trash folder with metadata instead of removing them. Restore brings an entry back with its OTP file; purge deletes it for good; the trash can be emptied wholesale.
- Duplicate protection: the app refuses to overwrite an existing entry silently.
- Path validation: empty names,
.., hidden components and a manual.gpgsuffix are rejected with a readable message instead of a GPG error. - Site / username layout. With the usual
site/usernameconvention the login is taken from the last path component and the site from the rest, so the form fills itself in. Write them into the notes when that guess is wrong.
Two-factor codes
- Codes are read from existing
.otp.gpgfiles and from validotpauth://links found in the notes of an entry. - Entries with no password at all work: when the file opens with an
otpauth://link, that link is the two-factor secret, and it is shown as a live code instead of being taken for a password. Such an entry says so in the panel instead of showing an empty password field. - An invalid link stays visible as ordinary text — it is never swallowed or mangled into a "valid-looking" card.
- Promote to OTP file moves an inline secret into a real
.otp.gpgfile and removes the link from the notes, leaving everything else intact. - The OTP details dialog shows the issuer, account, algorithm, digits and period, and lets you correct the stored secret.
- The app is read-only about OTP: it shows and copies codes, it does not generate or change secrets beyond an explicit edit you confirm yourself.
Synchronising the store with git
If the store is a git repository — the usual arrangement for pass-git — two entries under Tools work with the remote:
- Update from Repository runs
pass git pull. - Send to Repository runs
pass git push.
Both run on a background thread: a remote can be slow, and a GPG passphrase prompt has no terminal to type into. After a successful pull the entry list is reloaded, so what you see is what the remote actually has.
Two things about these commands are worth knowing, because pass hides them:
- The first push sets the upstream. A branch that tracks nothing is exactly what
pass git initleaves behind, and a plaingit pushthere refuses to do anything. The app notices and sendsorigin <branch>with--set-upstreaminstead, so the first push just works. After that, plainpass git pushis used. - A refusal is never silent.
passdoes not pass git's exit code through, so a failed push or pull returns success with an empty result. The app reads git's own messages and shows them, together with what to do about the usual causes: no remote configured, no upstream yet, diverged history, or a rejected connection. - Uncommitted changes stop the push.
passcommits after every write, but it cannot do that without a git identity. Whenuser.name/user.emailare not configured, the entries land in the working tree and stay there — a push would succeed and carry stale history while the newest passwords never leave the machine. The app checks for that and says so instead of pretending it worked.
The entries are offered but greyed out when the store is not a git repository, with
the reason in the tooltip — making the store one is your decision, not the app's. Run
pass git init once if you want it, and add a remote the usual way.
Settings → Git Settings shows what pass-git actually works with: the remotes it
knows, the current branch and what it tracks. pass-git keeps no settings of its own —
they live in the git config of the store — so that dialog reads exactly the same
config, and writes changes back through pass git, which is what the sync entries
use. When no remote is configured, the same dialog takes the name and the address and
creates it, so a store that is ready for pass git init can be finished without
opening a terminal. Addresses are checked before they are saved: git@host:user/repo.git
and https://host/repo.git are accepted, anything that cannot be an address is
refused with a sentence saying what a correct one looks like.
If the local and remote histories have diverged, git refuses to merge and the app
shows its message plus a hint: resolve it with pass git pull --rebase from a
terminal.
This is the only part of the application that touches the network, and it does so only when you ask it to.
When ssh refuses, the error comes with an explanation rather than just
Permission denied (publickey). The app reads ~/.ssh/config the same way ssh
does and reports the HostName, user and identity file it will actually use,
whether that key exists on disk, and whether an agent is running. It also flags
the trap that catches people: OpenSSH resolves ~ through the password database,
not through $HOME, so in a sandbox or container the config you are editing is
not the config ssh reads — and the app says so when it sees one.
A push takes the working tree with it. pass git init stages its
.gitattributes and never commits it, and a commit that failed for lack of a git
identity is silent — so a store could hold changes forever while push refused to
send anything. The app makes the commit pass would have made, reports how many
changes went in, and only complains when committing is genuinely impossible — in
which case the error names user.name and user.email.
Password generation
A generator dialog with length, character classes and an entropy readout, plus a live preview. Passwords are the only thing the app ever generates — it will not invent OTP secrets.
Appearance
- Dark, light and follow the system themes.
- Colour scheme: own palette or Plasma. The Plasma option takes the background, the panels, three surface steps, the text, both muted text steps, the borders and the semantic colours (success / error / warning) from the active Plasma colour scheme rather than picking them by eye. Whatever the scheme does not define is listed in the settings dialog, so nothing is silently substituted.
- The accent comes from the Plasma
AccentColor, falling back toDecorationFocus. The accent is expanded into a full ramp (hover, press, soft tone) and the label colour is chosen by WCAG contrast, so a pale accent never ends up with white text on white. - Interface and monospace font pickers with a live preview inside the list.
- Radio buttons and checkboxes show a real checkmark instead of an accent-filled blob, so they stay readable with any accent colour.
Interface
- Menus follow the KDE menu guidelines: File, Edit, View, Tools, Settings, Help — in that order, and only the entries the application can actually perform.
- English and Russian, switchable live from Help → Switch Application Language or in the settings dialog. The language follows the system locale by default; an unsupported locale falls back to English rather than to a half-translated UI.
- System tray with optional minimise-to-tray and a tray menu that keeps the same preferences the window has.
- Auto-lock after a configurable idle time, clipboard clearing after a configurable delay, optional confirmation before deleting.
Keyboard shortcuts
Ctrl+N |
New entry |
Ctrl+E |
Edit the selected entry |
F2 |
Rename |
Ctrl+D |
Duplicate |
Delete |
Delete (moves to the application trash) |
Ctrl+C |
Copy the password |
Ctrl+Shift+C |
Copy all fields |
Ctrl+Shift+P |
Show or hide the password |
Ctrl+F |
Focus the search field |
Ctrl+A |
Select all entries |
F5 / Ctrl+R |
Reload the tree |
Ctrl+L |
Lock now |
Ctrl+, |
Settings |
Ctrl+M |
Show or hide the menubar |
Ctrl+Q |
Quit |
Store layout and entry format
~/.password-store/
├── .gpg-id
├── github.com/
│ └── octocat.gpg
└── mail.ru/
└── ivan_petrov.gpg
An entry is a plain text file, exactly as pass defines it:
the-password
login: octocat
website: https://github.com
Everything after the first line is free-form. Known key: value lines
(login:, website:, email:, url:, autofill:, …) are surfaced as fields;
unknown ones and otpauth:// links stay in the notes and are preserved on save.
A two-factor secret lives next to the entry:
github.com/
├── octocat.gpg
└── octocat.otp.gpg # the pass-otp secret
Development
./tests/run_all.sh
Six suites, 528 checks, no external test runner required:
| suite | what it covers |
|---|---|
tests/test_otp.py |
RFC 4226 / RFC 6238 vectors, otpauth:// parsing, edge cases |
tests/test_core.py |
pass/gpg integration, CRUD, trash, layout detection, validation |
tests/test_kde.py |
kdeglobals parsing, Plasma 5 and 6, mapping the scheme onto the palette, contrast of the results |
tests/test_i18n.py |
catalogue completeness, placeholder integrity, locale detection and the English fallback |
tests/test_settings_gui.py |
every settings control applied live, including the font pickers |
e2e_test.py |
the whole window end to end, including the menu structure |
tests/run_all.sh builds a throwaway demo store when one is not supplied, and
rebuilds it if a previous run died halfway and left an empty shell behind.
Screenshots
![]() |
![]() |
| Main window with the Plasma colour scheme | The same store under the light Plasma scheme |
![]() |
![]() |
| Settings: language, colour scheme, fonts | About: GPL-3.0, author, project page |
Changelog
Every release is described in CHANGELOG.md.
Deleting your data
Deleting an entry never touches the encrypted file immediately: it is moved to the
application trash, where it can be restored. Purging removes the file. Emptying
the trash calls pass rm --recursive --force, which is the same thing pass rm
does — a deleted entry is gone for good, and the trash exists to undo a mistake,
not to be a second backup. Keep your own backups.
License
GNU General Public License v3 or later (GPL-3.0-or-later). The full text is in LICENSE.
GPL was chosen deliberately. This application works with your secrets and runs
external pass/gpg processes, so anyone distributing a modified build has to
publish their changes. That is the guarantee that nobody ends up with a "closed"
version silently doing something with your keys.



