PassStore

command module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: MIT Imports: 4 Imported by: 0

README

pass — Windows Password Store

CI Release

A minimal, secure, Git-backed password manager for Windows. Inspired by passwordstore.org, built natively for PowerShell with no Linux tools required.

    ██████╗  █████╗ ███████╗███████╗
    ██╔══██╗██╔══██╗██╔════╝██╔════╝
    ██████╔╝███████║███████╗███████╗
    ██╔═══╝ ██╔══██║╚════██║╚════██║
    ██║     ██║  ██║███████║███████║
    ╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝

What it does

pass stores each of your passwords as its own small encrypted file inside ~\.password-store\. The whole folder is also a Git repository, so you can push it to a private GitHub repo and clone it on any new PC — your passwords travel with you.

~\.password-store\
├── Email\
│   ├── gmail.age
│   └── work.age
├── Finance\
│   └── bank.age
└── Social\
    └── twitter.age

Every file is encrypted with age using your personal X25519 keypair. Decryption requires your passphrase, which you type once per terminal session. After that, pass remembers it in memory until you close the terminal or run pass lock.


How the encryption works

┌──────────────────────────────────────────────────────────┐
│  SETUP (once)                                            │
│                                                          │
│  pass init  →  generates X25519 keypair                  │
│                │                                         │
│                ├─ private key → encrypted with your      │
│                │                passphrase (scrypt KDF)  │
│                │                saved as ~/.age-identity  │
│                │                                         │
│                └─ public key → saved in                  │
│                               ~/.password-store/         │
│                               .age-recipients            │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  EVERY TIME YOU INSERT / GENERATE                        │
│                                                          │
│  plaintext  →  encrypted with public key  →  .age file   │
│  (no passphrase needed to write)                         │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  EVERY TIME YOU READ                                      │
│                                                          │
│  passphrase  →  unlocks private key  →  decrypts file    │
│  (asked once per session, cached in memory)              │
└──────────────────────────────────────────────────────────┘

Cryptographic primitives used:

  • Key agreement: X25519 (Curve25519)
  • Encryption: ChaCha20-Poly1305 (authenticated)
  • Passphrase KDF: scrypt

If you forget your passphrase, the private key cannot be recovered and all entries become permanently inaccessible. Keep your passphrase somewhere safe.


Entry format

Each decrypted entry is plain text. The first line is always the password. Remaining lines are optional named fields:

MySuperStr0ngPassword
username: john@gmail.com
url: https://gmail.com
notes: personal account, recovery email is work@example.com

You can add any field name you like. pass show --field <name> lets you extract a single field without printing the rest.


Tech stack

Component Library
Language Go 1.21+
CLI framework cobra
Encryption filippo.io/age
Terminal input golang.org/x/term
TUI framework Bubble Tea
TUI components Bubbles
TUI styling Lip Gloss
Git git.exe (external, via exec.Command)

Requirements

Requirement Notes
Windows 10 / 11 Tested on Windows 11
PowerShell 5.1+ or Windows Terminal Any modern terminal works
Git for Windows Only needed for sync features

Go does not need to be installed to run pass.exe — it is a single self-contained binary.


Setup

  1. Go to the Releases page
  2. Download pass_<version>_windows_amd64.zip (or arm64 for ARM devices)
  3. Extract pass.exe to a folder on your PATH (e.g. C:\tools\)
Option B — Install with go install
# Requires Go 1.21+  →  https://go.dev/dl/
go install github.com/ThomasTheCoder198/PassStore/cmd/pass@latest

The binary is placed in $env:GOPATH\bin (usually %USERPROFILE%\go\bin), which is already on your PATH if you installed Go with the official installer.

Option C — Build from source
# 1. Install Go 1.21+  →  https://go.dev/dl/
# 2. Clone this repo
git clone https://github.com/ThomasTheCoder198/PassStore "C:\tools\pass-src"
cd "C:\tools\pass-src"

# 3. Build
go build -o pass.exe .
Add to PATH (if using Option A)
# Add C:\tools to your user PATH permanently
[Environment]::SetEnvironmentVariable(
    "PATH",
    "$env:PATH;C:\tools",
    "User"
)

Restart your terminal. Verify:

pass --help

First-time initialization

# Initialize the store and a git repository inside it
pass init --git

You will be prompted to create a passphrase. This passphrase protects your private key. Choose something strong and memorable — there is no reset.

Enter passphrase for identity file: ████████████
Repeat passphrase:                  ████████████
Store initialized. Identity: ~/.age-identity

Two files are created:

File Purpose
~\.age-identity Your private key, encrypted with your passphrase
~\.password-store\.age-recipients Your public key (used to encrypt entries)

Syncing to another PC

Push from your first PC
# Connect to a private GitHub repo
pass git remote add origin https://github.com/<YOUR_USERNAME>/passwords.git
pass git push -u origin main
Clone on a new PC
# 1. Install pass.exe (see Setup above)
# 2. Clone your store
pass git clone https://github.com/<YOUR_USERNAME>/passwords.git "$env:USERPROFILE\.password-store"

# 3. Copy your identity file from the old PC to this PC
#    Options: USB drive, encrypted cloud storage, SCP, etc.
#    Destination: ~\.age-identity

# 4. Done — type your passphrase on the first command
pass ls

Security note: Keep ~\.age-identity private. Anyone with this file AND your passphrase can decrypt your entire store. The .age-recipients file (public key) inside the store is safe to expose.


Migrating to a new PC (full walkthrough)

This covers every step from old PC to new PC, including what to do if you are replacing the old machine entirely.

Step 1 — On the old PC: make sure everything is pushed
pass git status          # confirm nothing is uncommitted
pass git push            # push latest entries to GitHub
Step 2 — On the old PC: export your identity file

Your private key lives at ~\.age-identity. You need to get this file onto the new PC. Choose one method:

Option A — USB drive (safest)

Copy-Item "$env:USERPROFILE\.age-identity" "E:\age-identity.bak"

Transfer the USB to the new PC, then delete the file from the drive when done.

Option B — Encrypted archive (if USB unavailable)

# Compress and password-protect with 7-Zip (install from 7-zip.org)
& "C:\Program Files\7-Zip\7z.exe" a -p "$env:USERPROFILE\Desktop\identity.7z" "$env:USERPROFILE\.age-identity"
# Upload identity.7z to a private location, download on new PC, then delete it

Do NOT transfer ~\.age-identity over plain email or unencrypted cloud storage. The file is already encrypted with your passphrase, but a copy sitting in email or cloud history is a permanent exposure risk.

Step 3 — On the new PC: install pass

Download or build pass.exe (see Setup) and add it to your PATH.

Step 4 — On the new PC: clone the store
pass git clone https://github.com/<YOUR_USERNAME>/passwords.git "$env:USERPROFILE\.password-store"
Step 5 — On the new PC: place your identity file
# Copy from wherever you transferred it to
Copy-Item "E:\age-identity.bak" "$env:USERPROFILE\.age-identity"

# Restrict permissions to your user account only
icacls "$env:USERPROFILE\.age-identity" /inheritance:r /grant:r "${env:USERNAME}:(R,W)"
Step 6 — Verify
pass ls                          # should show your entry tree
pass show Email/gmail            # enter passphrase → should show password

That is all. Your full store is now available on the new PC.


Changing your passphrase

There is no pass repassphrase command. To change your passphrase, re-encrypt the identity file manually:

# 1. Unlock with the OLD passphrase and export the raw identity
#    (pass show will prompt for the old passphrase and cache it)
pass ls    # just to unlock the session

# 2. Re-encrypt the identity file with the NEW passphrase
#    Run this small PowerShell snippet — it uses the cached identity in the
#    current session, so it does not ask for the old passphrase again.
#    Close and reopen the terminal when done.

Because pass does not expose a repassphrase command yet, the safest manual procedure is:

# 1. Note your current identity path
$idPath = "$env:USERPROFILE\.age-identity"

# 2. Back up the old identity file before touching it
Copy-Item $idPath "$idPath.bak"

# 3. Delete the store and identity
Remove-Item -Recurse -Force "$env:USERPROFILE\.password-store"
Remove-Item -Force $idPath

# 4. Re-initialize with your NEW passphrase
pass init --git

# 5. Re-add your remote and force-push (the new store has a new keypair)
pass git remote add origin https://github.com/<YOUR_USERNAME>/passwords.git
pass git push --force   # ← replaces the old encrypted store on GitHub

# 6. Delete the backup
Remove-Item "$idPath.bak"

Why force-push? The new pass init creates a new keypair. All existing .age files are encrypted to the old public key and unreadable with the new private key. The new store starts empty — you will need to re-insert your passwords. If that is too disruptive, contact support or wait for a future repassphrase command that re-encrypts all entries in one step.


Day-to-day sync workflow

# At the start of the day (pull latest from any other PC)
pass git pull

# After adding or changing passwords (push to GitHub)
pass git push

Auto-commits happen on every mutation, so you only need to push manually. Pull before you push if you use multiple PCs to avoid conflicts.


Usage

Session passphrase

The first command that reads a password will ask for your passphrase. After that, the unlocked key is cached in memory for the life of the process.

pass show Email/gmail
# Enter passphrase: ████  ← asked once
# myGmailPassword!

pass show Finance/bank
# (no prompt — key already cached)
# mySuperBankPass99

Run pass lock or close the terminal to clear the cache.


Commands

pass init

Initialize a new password store.

pass init           # store only
pass init --git     # store + git repository (recommended)

pass ls

List all entries as a tree. Optionally scope to a subfolder.

pass ls
pass ls Email
Password Store
├── Email
│   ├── gmail
│   └── work
├── Finance
│   └── bank
└── Social
    └── twitter

pass show

Decrypt and display an entry. Aliases: pass with just a name also works when the name is unambiguous.

pass show Email/gmail               # print all content
pass show Email/gmail -f username   # print only the username field
pass show Email/gmail -f url        # print only the url field
pass show Email/gmail -c            # copy password to clipboard (clears in 45s)

Flags:

Flag Short Description
--field <name> -f Print only the value of the named field
--clip -c Copy the password (first line) to clipboard; auto-clears after 45 seconds

pass insert

Add a new password entry. You will be prompted to type the password (hidden input, confirmed twice).

pass insert Email/gmail             # single-line password
pass insert Email/gmail --multiline # multi-line (end with empty line)
pass insert Email/gmail --force     # overwrite existing entry

Multiline example — type each field, then press Enter on an empty line:

Enter contents (end with empty line):
myGmailPassword!
username: john@gmail.com
url: https://gmail.com
notes: recovery phone: +1-555-0100
                                    ← empty line ends input

Flags:

Flag Short Description
--multiline -m Read multiple lines (password + fields) until empty line
--force Overwrite if entry already exists

pass generate

Generate a cryptographically random password using crypto/rand.

pass generate Social/twitter            # 20-char password with symbols
pass generate Social/twitter 32         # 32-char password
pass generate Social/twitter --no-symbols   # alphanumeric only
pass generate Social/twitter -c         # generate and copy to clipboard
pass generate Social/twitter --force    # overwrite existing

Default charset: a-zA-Z0-9 + !@#$%^&*()-_=+[]{}|;:,.<>?

Flags:

Flag Short Description
--no-symbols Use only letters and numbers
--clip -c Copy generated password to clipboard (clears in 45s)
--force Overwrite if entry already exists

pass edit

Decrypt an entry, open it in your editor, then re-encrypt and save.

pass edit Email/gmail

Opens $env:EDITOR if set, otherwise falls back to notepad.exe. The plaintext is written to a temp file, opened in the editor, and deleted after you close the editor. Auto-commits the change.

Set your preferred editor:

$env:EDITOR = "code --wait"    # VS Code
$env:EDITOR = "notepad++"      # Notepad++
$env:EDITOR = "vim"            # Vim (if installed)

pass rm

Remove an entry or directory. Aliases: remove, delete.

pass rm Email/gmail             # remove single entry (prompts for confirmation)
pass rm Email/gmail -f          # remove without confirmation
pass rm Email -r                # remove entire folder recursively
pass rm Email -r -f             # remove folder without confirmation

Flags:

Flag Short Description
--recursive -r Remove a directory and everything inside it
--force -f Skip the confirmation prompt

pass mv

Move or rename an entry. Alias: rename.

pass mv Email/gmail Email/gmail-old     # rename
pass mv Email/gmail Personal/gmail      # move to different folder

Creates the destination folder automatically if it does not exist.


pass cp

Copy an entry to a new name.

pass cp Finance/bank Finance/bank-backup

pass find

Find entries by name (case-insensitive substring match).

pass find gmail      # finds Email/gmail
pass find bank       # finds Finance/bank, Finance/bank-backup

pass grep

Search the decrypted content of every entry for a string. Requires your passphrase.

pass grep john@gmail.com    # find entries containing this email
pass grep "Chase Bank"      # find entries with this text in notes

Output format: <entry-name>: <matching line>


pass git

Full proxy to git with the store as the working directory. Any git subcommand works.

pass git status
pass git push
pass git pull
pass git log --oneline
pass git remote add origin https://github.com/<YOUR_USERNAME>/passwords.git
pass git clone https://github.com/<YOUR_USERNAME>/passwords.git

Every mutating command (insert, generate, edit, rm, mv, cp) automatically creates a git commit when the store is a git repository. The commit message describes the operation, e.g.: Add password for Email/gmail.


pass lock

Clear the cached passphrase from the current session. The next command that needs decryption will prompt for it again.

pass lock
# Session locked.

pass ui

Launch the interactive TUI. Also launched by running pass with no arguments.

pass ui
pass          # same thing
┌─────────────────────────────────────────────────────────┐
│    .-""-.    ██████╗  █████╗ ███████╗███████╗           │
│   / o  o \   ██╔══██╗██╔══██╗██╔════╝██╔════╝           │
│  |  (  )  |  ██████╔╝███████║███████╗███████╗           │
│   \_====_/   ██╔═══╝ ██╔══██║╚════██║╚════██║           │
│     |  |     ██║     ██║  ██║███████║███████║           │
│    [____]    ╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝           │
│                                                          │
│  🔐 pass                                    [?] help    │
│                                                          │
│    ▸ Email/                                              │
│        › gmail                                           │
│          work                                            │
│      Finance/                                            │
│          bank                                            │
└─────────────────────────────────────────────────────────┘
  ↑↓ nav · enter open · n new · g gen · c copy · d del · / search · q quit

TUI keyboard shortcuts:

Key Action
/ k Move up
/ j Move down
Enter Open entry (detail view)
n New password (insert form)
g Generate password
c Copy password to clipboard
d Delete entry (confirm prompt)
r Rename entry
/ Search / filter entries
? Toggle keyboard help
Esc Back / cancel
q Quit

Inside the detail view:

Key Action
Space Reveal / hide the password
c Copy password to clipboard
e Edit in $EDITOR
d Delete this entry
Esc Back to list

Error messages

Message Cause
Error: store not found — run 'pass init' Store directory does not exist
Error: identity file not found at ~/.age-identity Private key file is missing
Error: wrong passphrase Incorrect passphrase entered
Error: '<name>' not found Entry does not exist
Error: '<name>' already exists. Use --force to overwrite Entry exists and --force was not passed
Error: '<name>' is a directory; use -r to remove recursively Tried to rm a folder without -r
Error: passphrases do not match The two passphrases entered during init differ
Error: passwords do not match The two passwords entered during insert differ

Directory structure reference

~\
├── .age-identity                  ← your encrypted private key (KEEP THIS SAFE)
└── .password-store\
    ├── .age-recipients            ← your public key (safe to expose)
    ├── .git\                      ← git history (if initialized with --git)
    ├── Email\
    │   ├── gmail.age              ← encrypted entry
    │   └── work.age
    └── Finance\
        └── bank.age

Security considerations

What is protected: The content of every .age file is encrypted and unreadable without your passphrase and private key. An attacker who obtains only the .password-store folder learns nothing about your passwords.

What is NOT protected:

  • Entry namesEmail/gmail.age, Finance/bank.age reveal the existence and names of accounts. Visible in git history and to anyone with filesystem access. Use abstract names if this is a concern.
  • Passphrase in memory — while the terminal session is open and the key is unlocked, the private key exists in process memory. Run pass lock when stepping away.
  • Clipboard — when using --clip, the password sits in the clipboard for up to 45 seconds. Other processes running as the same Windows user can read the clipboard.
  • Temp filespass edit writes a plaintext temp file during editing. It is deleted immediately after the editor closes, but it may persist in filesystem journals or undeleted pages.

Protecting your identity file:

# Restrict ~/.age-identity to your user account only
icacls "$env:USERPROFILE\.age-identity" /inheritance:r /grant:r "${env:USERNAME}:(R,W)"

Tips and tricks

Shell completion — generate tab-completion for PowerShell:

pass completion powershell | Out-File -Append $PROFILE

Quick copy workflow:

pass show Email/gmail -c    # copies password, auto-clears in 45s

Batch search:

pass find ""    # lists every single entry (empty pattern matches all)
pass grep "@"   # find every entry containing an @ (likely all email fields)

Organize with folders — use / to create a hierarchy:

pass insert Work/VPN/company-vpn
pass insert Work/SSH/server-prod
pass insert Personal/Banking/savings

Export a single entry to clipboard for scripting:

$pw = pass show Email/gmail -f username
Write-Host "Logging in as: $pw"

Building from source

# Requirements: Go 1.21+
go build -o pass.exe .

# Cross-compile from Linux/macOS
$env:GOOS = "windows"; $env:GOARCH = "amd64"
go build -o pass.exe .

License

MIT

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
cmd
pass command
internal
tui

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL