Skip to content

Password-store

Integrate with the standard Unix password manager (pass) to store and retrieve secrets from GPG-encrypted files.

Quick start

sh
# Install pass (password-store)
brew install pass  # macOS
# OR: sudo apt install pass  # Linux

# Initialize password-store (one-time setup)
pass init <your-gpg-key-id>

Add these definitions to fnox.toml. Merge them into any existing tables with the same names:

toml
[providers]
pass = { type = "password-store", prefix = "fnox/" }
sh
# Store a secret in password-store
fnox set DATABASE_URL "postgresql://localhost/mydb" --provider pass

# Retrieve from password-store
fnox get DATABASE_URL

# Use in shell commands
fnox exec -- npm start

Prerequisites

  • GPG (GNU Privacy Guard) installed and configured
  • GPG key pair generated
  • pass (password-store) installed

Installation

Install GPG

bash
brew install gnupg
bash
sudo apt install gnupg
bash
sudo dnf install gnupg2
bash
sudo pacman -S gnupg

Install password-store

bash
brew install pass
bash
sudo apt install pass
bash
sudo dnf install pass
bash
sudo pacman -S pass

Setup

1. Generate GPG key (if needed)

If you don't have a GPG key:

bash
# Generate a new GPG key
gpg --full-generate-key

# List your GPG keys
gpg --list-secret-keys --keyid-format LONG

Note your key ID from the output (the long hex string after sec).

2. Initialize password-store

bash
# Initialize with your GPG key ID
pass init <your-gpg-key-id>

# Example:
pass init 3AA5C34371567BD2

# Or with email:
pass init user@example.com

This creates ~/.password-store/ directory.

3. (Optional) configure custom store directory

sh
# Set custom store location
export PASSWORD_STORE_DIR=/path/to/custom/store

# Or configure in fnox

Add these definitions to fnox.toml. Merge them into any existing tables with the same names:

toml
[providers]
pass = { type = "password-store", store_dir = "/path/to/custom/store" }

Configuration

Add password-store provider to fnox.toml:

toml
[providers]
pass = { type = "password-store", prefix = "fnox/" }

Configuration options

toml
[providers.pass]
type = "password-store"
prefix = "fnox/"  # Optional: prepend to all secret paths (default: none)
store_dir = "./password-store"  # Optional: custom store location (default: ~/.password-store)
gpg_opts = "--no-throw-keyids"  # Optional: extra GPG options, passed as PASSWORD_STORE_GPG_OPTS

Relative store_dir paths are resolved from the config file that declares the provider. Paths beginning with ~ expand to your home directory, and absolute paths are used unchanged.

How it works

  1. Storage: Secrets are stored as GPG-encrypted files in ~/.password-store/ (or custom location)
  2. Config: fnox.toml contains only the secret path/reference (not the actual value)
  3. Encryption: When you run fnox set, it calls pass insert to GPG-encrypt and store the secret
  4. Retrieval: When you run fnox get, it calls pass show to decrypt and retrieve the secret
  5. Prefix: If configured, the prefix is prepended to the secret path (e.g., value = "api-key" becomes fnox/api-key)
  6. Hierarchy: Supports nested paths for organizing secrets (e.g., work/github/token)

Usage

Store a secret

bash
# Simple secret
fnox set DATABASE_URL "postgresql://localhost/mydb" --provider pass

# With nested path (using --key-name)
fnox set DB_PASSWORD "secret123" --provider pass --key-name "database/production"

# With prefix configured, "database/production" becomes "fnox/database/production"

Your fnox.toml:

toml
[secrets]
DATABASE_URL = { provider = "pass", value = "DATABASE_URL" }  # Stored at fnox/DATABASE_URL
DB_PASSWORD = { provider = "pass", value = "database/production" }  # Stored at fnox/database/production

Retrieve a secret

bash
fnox get DATABASE_URL

Run commands with secrets

bash
fnox exec -- npm run dev

List secrets in password-store

bash
# View password-store structure
pass

# Or with specific prefix
pass ls fnox/

Reference formats

toml
[secrets]
# Simple name (with prefix)
MY_SECRET = { provider = "pass", value = "api-key" }
# → Stored at: fnox/api-key.gpg

# Nested path
DB_PASSWORD = { provider = "pass", value = "database/production" }
# → Stored at: fnox/database/production.gpg

# Without prefix in config
API_TOKEN = { provider = "pass", value = "tokens/github" }
# → Stored at: tokens/github.gpg (no prefix)

Selecting a line

A common pass convention is to pack related values into a single entry: the password on line 1 and other fields (username, URL, etc.) on the lines below. The pass CLI exposes this with pass show <entry> --clip=N to copy the Nth line.

fnox supports the same with the line field on a secret. It is 1-indexed and selects a single line from whatever the provider returned:

toml
[providers.pass]
type = "password-store"
prefix = "fnox/"

[secrets]
# `pass show fnox/database` returns:
#   <password>
#   <username>
DB_PASSWORD = { provider = "pass", value = "database", line = 1 }
DB_USERNAME = { provider = "pass", value = "database", line = 2 }

Create a multi-line entry with pass insert -m (see Multiline Secrets below). Without line, fnox returns the entire entry unchanged.

line is mutually exclusive with json_path. It is also a read-only selector — see the warning under Multiline Secrets for how to edit one line of an existing entry.

Git integration

password-store has built-in git support:

bash
# Initialize git repo in password-store
pass git init

# Add remote
pass git remote add origin https://github.com/username/password-store.git

# Configure git
pass git config user.name "Your Name"
pass git config user.email "you@example.com"

# Changes are automatically committed
fnox set API_KEY "new-key" --provider pass  # Auto-commits!

# Push changes
pass git push

Team workflow

Encrypt for each teammate

Encrypt for multiple team members (more secure):

bash
# Re-init with multiple GPG keys
pass init <key-id-1> <key-id-2> <key-id-3>

# Or add recipients later
cd ~/.password-store
echo "<key-id-1> <key-id-2> <key-id-3>" > .gpg-id
pass init -p / $(cat .gpg-id)

Then push to shared git repository:

bash
pass git push

Team members pull:

bash
git clone https://github.com/team/password-store.git ~/.password-store
pass  # Verify they can decrypt

Multi-environment example

toml
# Development (password-store)
[providers]
pass = { type = "password-store", prefix = "fnox/dev/" }

[secrets]
DATABASE_URL = { provider = "pass", value = "database-url" }  # fnox/dev/database-url

# Production (AWS Secrets Manager)
[profiles.production.providers]
aws = { type = "aws-sm", region = "us-east-1" }

[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "database-url" }

Bootstrap pattern

Store provider tokens in password-store:

toml
[providers]
pass = { type = "password-store", prefix = "tokens/" }
aws = { type = "aws-sm", region = "us-east-1" }

[secrets]
AWS_ACCESS_KEY_ID = { provider = "pass", value = "aws-access-key" }
AWS_SECRET_ACCESS_KEY = { provider = "pass", value = "aws-secret-key" }
DATABASE_URL = { provider = "aws", value = "db-url" }  # Retrieved from AWS

Bootstrap:

bash
export AWS_ACCESS_KEY_ID=$(fnox get AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(fnox get AWS_SECRET_ACCESS_KEY)
fnox exec -- ./deploy.sh  # Now can access AWS secrets

Multiline secrets

password-store fully supports multiline secrets:

bash
# Store multiline secret
fnox set SSH_PRIVATE_KEY --from-file ~/.ssh/id_rsa --provider pass

# Or using heredoc with pass directly
pass insert -m work/ssh-key <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
EOF

Editing existing multiline entries

fnox set always replaces the entire pass entry — it cannot edit a single line in place. To change one field of a multi-line entry without losing the others, use pass edit <entry> (or pass insert -m -f to re-write all lines) directly. This applies whether or not the secret uses the line selector.

Environment variables

password-store respects standard environment variables:

bash
# Custom store location
export PASSWORD_STORE_DIR=/path/to/store
export FNOX_PASSWORD_STORE_DIR=/path/to/store  # fnox-specific

# GPG options
export PASSWORD_STORE_GPG_OPTS="--no-throw-keyids"
export FNOX_PASSWORD_STORE_GPG_OPTS="--armor"  # fnox-specific

The FNOX_* variants take priority over the standard ones, and store_dir/gpg_opts in the provider config take priority over both.

Sync across machines

Using git

bash
# Machine 1: Push
pass git push

# Machine 2: Pull
cd ~/.password-store
git pull

Using sync service

password-store is just a directory of GPG files. Sync with:

  • Dropbox: Symlink ~/.password-store to Dropbox
  • Syncthing: Sync the directory
  • rsync: Manual sync between machines

Usage notes

The provider reads and writes entries through pass. GPG recipients control decryption; git can synchronize the encrypted files but is not a secret-access audit log. Keep private keys backed up separately.

Troubleshooting

"password store is empty"

Initialize password-store:

bash
pass init <your-gpg-key-id>

"GPG: decryption failed: No secret key"

Your GPG private key is not available:

bash
# Check available keys
gpg --list-secret-keys

# Import key if needed
gpg --import private-key.gpg

"GPG: public key decryption failed: Inappropriate ioctl for device"

Set GPG TTY:

bash
export GPG_TTY=$(tty)

# Add to shell profile
echo 'export GPG_TTY=$(tty)' >> ~/.bashrc

"pass: passphrase entry cancelled"

GPG agent needs unlocking. Enter your GPG key passphrase when prompted.

Custom store directory not working

Ensure PASSWORD_STORE_DIR or store_dir in config is set:

bash
export PASSWORD_STORE_DIR=/path/to/store
# OR in fnox.toml:
# pass = { type = "password-store", store_dir = "/path/to/store" }

Changes not being committed to git

Ensure git is initialized:

bash
cd ~/.password-store
git status  # Should show a git repo
# If not:
pass git init

Security considerations

  • Encryption: GPG encrypts files with your public key
  • Access control: Filesystem permissions + GPG key passphrase
  • Git history: Old secrets remain in git history (use pass git carefully)
  • Key security: Protect your GPG private key
  • Passphrase: Use a strong GPG key passphrase

Third-party tools

password-store has a rich ecosystem:

Next steps

MIT LicenseCopyright © 2026jdx.dev