Pass Plugin
Our pass plugin enables loading secrets from pass (the standard unix password manager) using declarative instructions within your .env files.
Pass stores each secret as a GPG-encrypted file in ~/.password-store, organized in a simple directory hierarchy. This plugin shells out to the pass CLI, so it works with your existing GPG agent, git-backed stores, and all standard pass configuration.
Features
Section titled “Features”- Zero-config - Works with your existing pass store out of the box
- GPG-backed encryption - Leverages pass’s native GPG security model
- Auto-infer entry paths from variable names for convenience
- Bulk-load secrets with
passBulk()via@setValuesBulk - Multiple store instances for accessing different pass stores
- Name prefixing for scoped entry access
allowMissingoption for graceful handling of optional secrets- In-session caching - Each entry is decrypted only once per resolution
- Helpful error messages with resolution tips
Installation and setup
Section titled “Installation and setup”In a JS/TS project, you may install the @varlock/pass-plugin package as a normal dependency.
Otherwise you can just load it directly from your .env.schema file, as long as you add a version specifier.
See the plugins guide for more instructions on installing plugins.
# 1. Load the plugin# @plugin(@varlock/pass-plugin)## 2. Initialize the plugin - no arguments needed for default setup# @initPass()Prerequisites
Section titled “Prerequisites”You must have pass installed on your system:
# macOSbrew install pass
# Ubuntu/Debiansudo apt-get install pass
# Archpacman -S passYour password store must already be initialized (pass init "Your GPG Key ID"). See the pass documentation for setup details.
Custom store path
Section titled “Custom store path”If your password store is in a non-standard location, use storePath:
# @plugin(@varlock/pass-plugin)# @initPass(storePath=/path/to/custom/store)This sets PASSWORD_STORE_DIR for all pass commands issued by this plugin instance.
Name prefixing
Section titled “Name prefixing”Use namePrefix to scope all entry lookups under a common prefix:
# @plugin(@varlock/pass-plugin)# @initPass(namePrefix=production/app/)# ---
# Fetches "production/app/DATABASE_PASSWORD" from the storeDATABASE_PASSWORD=pass()
# Fetches "production/app/stripe-key"STRIPE_KEY=pass("stripe-key")Multiple instances
Section titled “Multiple instances”Access multiple different password stores (e.g., personal and team):
# @plugin(@varlock/pass-plugin)# @initPass(id=personal)# @initPass(id=team, storePath=/shared/team-store)# ---
MY_TOKEN=pass(personal, "tokens/github")SHARED_KEY=pass(team, "api-keys/stripe")Loading secrets
Section titled “Loading secrets”Once the plugin is installed and initialized, you can start adding config items that load values using the pass() resolver function.
Basic usage
Section titled “Basic usage”Fetch secrets from your pass store:
# Entry path defaults to the variable nameDATABASE_PASSWORD=pass()API_KEY=pass()
# Or explicitly specify the entry pathSTRIPE_KEY=pass("services/stripe/live-key")
# Nested entriesDB_URL=pass("production/database/url")When called without arguments, pass() automatically uses the config item key as the entry path. This provides a convenient convention-over-configuration approach.
Handling optional secrets
Section titled “Handling optional secrets”Use allowMissing when a secret may not exist in the store:
# Returns empty string instead of erroring if entry doesn't existOPTIONAL_KEY=pass("monitoring/datadog-key", allowMissing=true)Multiline entries
Section titled “Multiline entries”By default, pass() returns only the first line of the entry (the password), matching pass’s own convention where the password lives on line 1 and metadata follows. This is the same behavior as pass -c (copy to clipboard).
To retrieve the full multiline content, use multiline=true:
# Only returns the first line (the password)DB_PASSWORD=pass("production/database")
# Returns all lines (password + metadata)DB_FULL_ENTRY=pass("production/database", multiline=true)Bulk loading secrets
Section titled “Bulk loading secrets”Use passBulk() with @setValuesBulk to fetch all entries under a directory in your pass store in one go, instead of wiring up each secret individually:
# @plugin(@varlock/pass-plugin)# @initPass()# @setValuesBulk(passBulk("services"))# ---
# These will be populated from entries under services/ in the pass storeSTRIPE_KEY=DATABASE_URL=passBulk() lists entries via pass ls, then fetches each one in parallel. Each entry returns the first line only (matching the pass() default).
You can customize the scope:
# Load everything from the store root# @setValuesBulk(passBulk())
# Load from a specific subdirectory# @setValuesBulk(passBulk("production/api"))
# With a named instance# @setValuesBulk(passBulk(team, "shared"))Reference
Section titled “Reference”Root decorators
Section titled “Root decorators”@initPass()
Section titled “@initPass()”Initialize a pass plugin instance for accessing secrets from a password store.
Key/value args:
storePath(optional): Custom password store path (overridesPASSWORD_STORE_DIR, defaults to~/.password-store)namePrefix(optional): Prefix automatically prepended to all entry pathsid(optional): Instance identifier for multiple instances
# Default setup# @initPass()
# Custom store location# @initPass(storePath=/path/to/store)
# With prefix and ID# @initPass(id=prod, namePrefix=production/)Resolver functions
Section titled “Resolver functions”pass()
Section titled “pass()”Fetch a secret from the pass store. Returns the first line of the entry (the password) by default, matching pass’s convention.
Array args:
instanceId(optional): instance identifier to use when multiple plugin instances are initializedentryPath(optional): path to the entry in the pass store. If omitted, uses the variable name.
Key/value args:
allowMissing(optional): iftrue, returns empty string instead of erroring when the entry doesn’t existmultiline(optional): iftrue, returns the full entry content instead of just the first line
# Auto-infer entry path from variable nameDATABASE_PASSWORD=pass()
# Explicit entry pathSTRIPE_KEY=pass("services/stripe/live-key")
# With instance IDTEAM_SECRET=pass(team, "shared/api-key")
# Allow missing entriesOPTIONAL=pass("maybe/exists", allowMissing=true)
# Get full multiline contentFULL_ENTRY=pass("services/config", multiline=true)passBulk()
Section titled “passBulk()”Fetch all entries under a directory in the pass store at once. Intended for use with @setValuesBulk.
Lists entries via pass ls, then fetches each one in parallel. Each entry returns the first line only (matching the pass() default).
Array args:
instanceId(optional): instance identifier to use when multiple plugin instances are initializedpathPrefix(optional): directory prefix to load entries from
# Load all entries from the store root# @setValuesBulk(passBulk())
# Load entries under a specific path# @setValuesBulk(passBulk("services"))
# With instance ID# @setValuesBulk(passBulk(team, "shared"))Example configurations
Section titled “Example configurations”Simple development setup
Section titled “Simple development setup”# @plugin(@varlock/pass-plugin)# @initPass()# ---
# Entry paths match variable namesDATABASE_URL=pass()REDIS_URL=pass()STRIPE_KEY=pass()Production with path organization
Section titled “Production with path organization”# @plugin(@varlock/pass-plugin)# @initPass(namePrefix=production/)# ---
# Fetches production/database/url, production/database/password, etc.DB_URL=pass("database/url")DB_PASSWORD=pass("database/password")STRIPE_KEY=pass("api/stripe-key")SENDGRID_KEY=pass("api/sendgrid-key")Team and personal stores
Section titled “Team and personal stores”# @plugin(@varlock/pass-plugin)# @initPass(id=personal)# @initPass(id=team, storePath=/shared/team-pass-store)# ---
# Personal dev tokensGH_TOKEN=pass(personal, "tokens/github")
# Shared team secretsSHARED_DB=pass(team, "databases/staging")SHARED_API_KEY=pass(team, "api-keys/internal")Troubleshooting
Section titled “Troubleshooting”pass command not found
Section titled “pass command not found”- Install pass using your system package manager (see Prerequisites)
- Ensure
passis in yourPATH
Entry not found
Section titled “Entry not found”- Verify the entry exists:
pass show <path> - List available entries:
pass ls - Check for typos in the entry path
- If using
namePrefix, remember it’s prepended automatically
GPG decryption failed
Section titled “GPG decryption failed”- Ensure your GPG key is available:
gpg --list-keys - Start the GPG agent:
gpgconf --launch gpg-agent - You may need to enter your GPG passphrase
Password store not initialized
Section titled “Password store not initialized”- Run
pass init "Your GPG Key ID"to initialize the store - See
pass init --helpfor details