Page: https://lkraider.github.io/keywise/
A terminal UI, a macOS app and a Windows app show the saved logins in a local Firefox profile. All three read them through one Zig core. The released binaries are Apple Silicon macOS, Linux x86_64, Linux ARM64, Windows x86_64 and Windows ARM64.
brew tap lkraider/keywise https://github.com/lkraider/keywise
brew install lkraider/keywise/keywise # the terminal UI
brew install --cask lkraider/keywise/keywise-app # the macOS app
The app is ad-hoc signed. This project has no Apple Developer ID, so it is not notarized. Gatekeeper blocks a first launch as coming from an unidentified developer. Right-click the app in Finder and choose Open. Clearing the quarantine flag works too:
xattr -cr /Applications/Keywise.app
An extended attribute sits outside the code signature, so
codesign -v --deep --strict still passes after that command.
On Linux, download keywise-x86_64-linux.tar.gz or
keywise-aarch64-linux.tar.gz from the releases page:
tar -xzf keywise-x86_64-linux.tar.gz
install -m 755 keywise ~/.local/bin/keywise
The binary is static, so it runs on any distro. A copy on Wayland needs
wl-copy from the wl-clipboard package. A copy on X11 needs xclip from
xclip or xsel from xsel.
sudo apt install wl-clipboard # Wayland
sudo apt install xclip # X11
dnf install and pacman -S take the same package names.
On Windows, download Keywise-<version>-windows-arm64.zip or
-windows-x86_64.zip from the releases page and unzip it. The exe is a
single file and needs no install. It is unsigned, so SmartScreen shows a
warning on first launch. Choose More info, then Run anyway.
Run keywise. It finds the directory holding profiles.ini and opens the
profile your Firefox uses. It prompts for a Primary Password when the
profile has one.
macOS keeps that directory at ~/Library/Application Support/Firefox. On
Linux keywise tries the paths below in order and reads the first one holding
a profiles.ini:
~/.mozilla/firefox # distro package
~/snap/firefox/common/.mozilla/firefox # Ubuntu snap
~/.var/app/org.mozilla.firefox/.mozilla/firefox # Flatpak
One run reads one of them.
| Key | Does |
|---|---|
/ |
Enter the search field. enter or escape leaves it. |
↑ ↓, k j |
Move through the list. |
enter |
Reveal the selected password. Press again to hide it. |
y |
Copy the selected password. The row stays masked. |
q, ctrl-c |
Quit. |
On Linux, y runs wl-copy, xclip or xsel. It reports copied on
every press, including a press on a host with none of them installed. Over
SSH the copy goes through OSC 52. It reaches the clipboard of the terminal
you are sitting at.
y also writes the password to stdout on any run where stdout is a pipe or
a file. On a terminal it writes nothing.
keywise | wl-copy # press y, then q
keywise > /tmp/p # the last y of the run lands in the file
To open another profile:
keywise --list-profiles # one profile per line, name then path
keywise --profile <path> # open that directory
--list-profiles prints the profiles to stdout and the directory it read
to stderr.
On Windows, run Keywise.exe. It reads profiles.ini under
%APPDATA%\Mozilla\Firefox. The Profile menu lists every profile it
found, and --profile <path> opens one directory.
| Key | Does |
|---|---|
Ctrl+F |
Move focus to the search box. |
Enter, double-click |
Reveal the selected password. Press again to hide it. |
Ctrl+C |
Copy the selected password. The row stays masked. |
Esc |
Hide the revealed password. |
| right-click | Reveal and Copy for the row under the cursor. |
Passwords stay masked until you press enter, and only the selected one is
shown. The macOS app shows the same data under the same rules. Each row there
has a copy button, and the row stays masked when you use it.
This reads a profile and writes nothing back. Firefox can stay open while you use it.
The list is a snapshot from the moment you opened the profile. To pick up
a login Firefox saved after that, open the profile again. Windows has the
Profile menu, macOS has the profile picker, and the TUI needs a restart.
It cannot read these profiles:
- One with a Primary Password you do not know. Decryption uses the password you type. There is no recovery path.
- One that Firefox 144 or newer has never opened. Those hold 3DES-encrypted entries only. This tool reports 3DES per entry and stops there.
- One whose
key4.dbuses SQLite's write-ahead log. No Firefox profile measured uses one. A profile that does stops the app withWalJournal.
Copying marks the clipboard entry so a clipboard manager skips it. On
Windows that keeps the password out of Win+V history and out of the
cloud clipboard. Copying never puts the password on screen. There are no
network calls and no telemetry.
The macOS app and the Windows app clear the clipboard 30 seconds after a copy. Both mask a revealed password after the same 30 seconds. Copy something else in the meantime and that copy stays.
The TUI runs no timer. A password it copies stays on the clipboard until
something else replaces it, and a revealed row stays open until you press
enter again. On Linux it writes no marker, so a clipboard manager there
keeps the copy.
Anyone who can read your files or your memory already has this data.
key4.db is readable by your own user, and Firefox exposes the same
logins. Once a password reaches a buffer, copies of it exist. This code
wipes the buffers it owns.
A profile synced to a Mozilla Account holds a chrome://FirefoxAccounts
row. Its password is sync key material, so revealing it hands over the
whole account. All three front ends mark that row and ask a second time
before revealing it.
Needs Zig 0.16.0. On macOS it
also needs Xcode Command Line Tools (xcode-select --install), because the C
ABI library links libc and core/test/oracle.zig links libsqlite3 through
the SDK those tools install.
zig build # core, the TUI, and the C ABI static library
zig build test # the core tests, against the committed fixtures
zig build tui # run the TUI
zig build smoke # the C ABI smoke test
zig build test reads only core/testdata/. It runs on a machine with no
Firefox installed. Pass -Doracle=false to drop the test that diffs the
SQLite reader against the system sqlite3.
The Windows app cross-compiles from macOS or Linux with no Windows SDK:
zig build win -Dtarget=aarch64-windows-gnu -Doptimize=ReleaseSafe
zig build win -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafe
The Linux binaries cross-compile the same way. musl links statically, so one binary per architecture runs on any distro:
zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe
zig build -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe
scripts/linux-launch-check.sh zig-out/bin/keywise drives the paths that run
without a terminal.
The released zips are built at ReleaseSafe. That mode keeps the bounds and
alignment checks over the hand-written key4.db reader.
scripts/release-package.sh <version> <dir> builds and archives every
published artifact. The Linux tarballs, the macOS keywise tarball and the
Windows zips reproduce byte for byte on any macOS host. The macOS app zip
does not. Its hash follows the SDK installed on the build machine.
docs/REPRODUCIBLE.md has the settings and the
measurements.
win/icon.ico is committed, and a build reads that file. python3 scripts/win-make-ico.py rewrites it from macos/Icon.icns.
The macOS app is a separate Swift package. See
macos/README.md for how to build and test it.
scripts/docs-screenshots.sh regenerates the images above. Its win target
needs wine.
core/ key4.db and logins.json decryption, and the C ABI
tui/ the terminal UI, on libvaxis
macos/ the SwiftUI app
win/ the Win32 app
scripts/ the release, CI and fixture tooling
docs/ covers the rest:
docs/FORMAT.md— what Firefox writes into a profile, and the encryption over it.docs/ARCHITECTURE.md— the decision table, the module map, and the C ABI a front end links.docs/PLATFORM.md— what the pinned Zig, Win32, wine and macOS do here.docs/REPRODUCIBLE.md— the settings that make two packagings of one commit write the same bytes.
MIT. See LICENSE.
Firefox is a trademark of the Mozilla Foundation in the US and other countries. This project is not affiliated with Mozilla.


