Stop Using .env Files: Secure Your API Keys with pass
The problem: You have an OpenClaw agent that needs API keys—Todoist, Slack, Gemini, GitHub. Where do you put them?
Most people create a .env file:
TODOIST_API_KEY=abc123xyz
SLACK_TOKEN=xoxb-123456...
GEMINI_API_KEY=blahblahblahThis file lives on your disk. Unencrypted. If someone gets access to your machine (or your backups), they have all your keys. If you accidentally commit it to Git, it’s public forever. Your CI/CD logs might print environment variables by mistake.
There’s a better way: pass. It’s a dead-simple password manager that encrypts secrets on disk and only decrypts them when you actually need them.
This guide is for technical people who’ve never heard of pass. No jargon. Just practical examples.
What is pass?
Think of pass as a folder on your computer (~/.password-store/) where secrets are stored encrypted. When you need a secret, you ask pass for it, enter your password once, and the secret appears in memory—decrypted and ready to use.
No files sitting around with plaintext keys. No accidental commits. No logging secrets by mistake.
That’s it. That’s the entire concept.
Install It
macOS:
brew install passLinux (Debian/Ubuntu):
sudo apt-get install passVerify:
pass --versionSet Up a GPG Key
pass uses GPG (a Unix encryption standard) to encrypt your secrets. If you don’t have a GPG key, create one:
gpg --gen-keyAnswer the prompts:
- Real name: Your name
- Email: Any email (doesn’t have to be real)
- Passphrase: Create a strong password (you’ll type this when accessing secrets)
Get your key ID:
gpg --list-secret-keys --keyid-format SHORTLook for something like: sec rsa4096/ABC12345
Your key ID is ABC12345 (the part after the slash).
Initialize pass
pass init ABC12345(Replace ABC12345 with your actual key ID)
That’s it. pass has created an encrypted folder at ~/.password-store/ where all your secrets will live.
Add Your First Secrets
Let’s add some API keys:
pass insert todoist/api-keypass will prompt you to enter the secret. Paste your Todoist API key and press Enter twice.
Repeat for other keys:
pass insert slack/bot-token
pass insert gemini/api-key
pass insert github/personal-tokenView what you’ve stored:
passOutput:
Password Store
├── todoist
│ └── api-key
├── slack
│ └── bot-token
├── gemini
│ └── api-key
└── github
└── personal-tokenEverything is encrypted on disk. You can’t read the actual values by opening the files—you have to ask pass for them.
Retrieve a Secret (How It Actually Works)
pass todoist/api-keypass will:
- Ask you for your GPG passphrase (your password)
- Decrypt the secret
- Print it to the terminal
You can copy/paste it from there. The secret is now in your clipboard or terminal, ready to use.
In a script:
MY_API_KEY=$(pass todoist/api-key)
echo $MY_API_KEYReal-World Use Case: Run OpenClaw With Secrets
Let’s say you have a startup script for OpenClaw that needs API keys. Instead of a .env file:
Create scripts/run-openclaw.sh:
#!/bin/bash
# Fetch secrets from pass at runtime
export TODOIST_API_KEY=$(pass todoist/api-key)
export SLACK_TOKEN=$(pass slack/bot-token)
export GEMINI_API_KEY=$(pass gemini/api-key)
# Run OpenClaw with secrets in environment
openclaw status
# Done! When the script exits, secrets are cleared from memory.Make it executable and run:
chmod +x scripts/run-openclaw.sh
./scripts/run-openclaw.shThe first time, you’ll see:
gpg: Enter passphrase for your key:Type your GPG passphrase. The secrets are decrypted, loaded into environment variables, and openclaw runs. When the script finishes, the secrets are gone.
For Python Users: A Simple Helper
If you’re building a Python agent, here’s a helper function to fetch secrets:
#!/usr/bin/env python3
import subprocess
import os
def get_secret(path):
"""Fetch a secret from pass."""
result = subprocess.run(
['pass', path],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
# Load secrets
TODOIST_API_KEY = get_secret('todoist/api-key')
SLACK_TOKEN = get_secret('slack/bot-token')
GEMINI_API_KEY = get_secret('gemini/api-key')
print(f"Loaded {len(TODOIST_API_KEY)} chars from Todoist")Or, as environment variables:
import os
# Fetch all at once
os.environ['TODOIST_API_KEY'] = get_secret('todoist/api-key')
os.environ['SLACK_TOKEN'] = get_secret('slack/bot-token')
os.environ['GEMINI_API_KEY'] = get_secret('gemini/api-key')
# Now use them
print(os.environ['TODOIST_API_KEY'])Why This Is Better Than .env Files
.env Files | pass |
|---|---|
| Plaintext on disk ❌ | Encrypted on disk ✅ |
| Easy to accidentally commit ❌ | Safe to back up ✅ |
| All secrets always in memory | Secrets only when needed |
| One place leaks everything | Rotate keys easily |
| No backup protection | Encrypted backups safe |
The trade-off: You need to type your GPG passphrase once per session. It’s worth it.
Update a Secret
If your Todoist API key expires or you need to rotate it:
pass edit todoist/api-keyYour editor opens. Replace the old key with the new one. Save and close.
Next time your script runs, it fetches the new key automatically. No code changes needed.
Security Reality Check
What pass Protects
✅ Secrets at rest (on disk): Encrypted with your GPG key. Unreadable without your passphrase.
✅ Accidental commits: Encrypted files are safe to back up. No plaintext .env in Git history.
✅ Casual leaks: Not scattered across your machine as plaintext files.
What pass Does NOT Protect
❌ If your machine is compromised: An attacker with root access can still access your secrets. ❌ Typed passphrases: If you use a weak GPG passphrase, an attacker can brute-force it. ❌ Memory while running: Once a secret is in an environment variable, it’s readable by that process.
The honest truth: pass isn’t Fort Knox. It’s good enough for real life. You’re not protecting against nation-state attackers; you’re protecting against:
- Accidentally committing
.envto public repos - Your laptop being stolen with unencrypted backups
- Someone briefly accessing your machine and stealing keys
- Logs accidentally printing secrets
For production infrastructure with truly sensitive data, you’d use something like HashiCorp Vault or AWS Secrets Manager. But for local development and personal OpenClaw agents? pass is perfect.
Backup Your GPG Key
The one thing you can’t lose: your GPG key. If your hard drive dies and you don’t have a backup of your key, you lose access to all your secrets forever.
Backup your key:
gpg --export-secret-keys ABC12345 > ~/gpg-backup.ascStore this file somewhere safe (encrypted backup, password manager, etc.). If your machine dies, you can restore it:
gpg --import ~/gpg-backup.ascCommon Questions
Q: Is pass production-ready?
A: Yes. It’s been around for 10+ years and is used by serious security-minded people. But it’s minimal—it doesn’t do fancy stuff like key rotation or permission management. For production secrets, consider Vault or managed secret services.
Q: What if I forget my GPG passphrase?
A: You’re locked out forever. There’s no recovery. Choose a passphrase you’ll remember, or use a password manager to store it (yes, recursive, but now your passphrases are encrypted with your OS password).
Q: Can multiple people share a pass store?
A: Technically yes, but it’s complicated (requires multiple GPG keys + sharing). For teams, Vault or AWS Secrets Manager is better.
Q: Will typing my passphrase every time get annoying?
A: You only type it once per session. If you open a new terminal, you might need to type it again—but usually your SSH agent or GPG agent caches it. It’s not painful.
Q: Can I use this with CI/CD?
A: Not directly—CI systems can’t prompt for your passphrase. You’d need to export the secret to the CI environment (defeats the purpose). For CI, use a dedicated secrets management system (GitHub Secrets, GitLab CI Variables, etc.).
Q: Is this compatible with OpenClaw?
A: Completely. OpenClaw runs scripts with environment variables, and pass feeds secrets into environment variables. No special integration needed.
Next Steps
- Install
passand set up a GPG key - Store your first API key:
pass insert todoist/api-key - Create a startup script that fetches secrets at runtime
- Test it works: Run your script and verify secrets are loaded
- Delete your
.envfile (back it up first, just in case)
That’s it. You’re now storing secrets the right way.
TL;DR
pass= encrypted password manager, built on Unix standards- Install:
brew install pass(macOS) orapt-get install pass(Linux) - Setup: Create a GPG key, initialize pass, add secrets
- Use:
MY_KEY=$(pass path/to/secret)in scripts - Benefit: No plaintext keys on disk. Secrets only loaded at runtime.
- Trade-off: Type your passphrase once per session.
Done. You’re secure.