I had too many secret files in my .env locally. This became a problem.
We didn't want to use dotenvx as we only need one or two secrets stored. Dotenvx looked like overkill.
We use Rails encrypted credentials, so there's usually only one key to store safely. And only when editing the staging / production credentials.
That's why I made this repo, to store these keys in the Macos keychain. The touchid is required every time I use the key.
So it's also safe from the LLM to read:
This is how it looks:
Disclaimer: this is a quick tool I coded with Claude Code. I'm a CTO with 20 years of coding experience and a CS degree. I'll look into any concerns that get raised. Please file an issue or email me till@tillcarlos.com
Cool?
Then let's enter 🤖 land (everything below and the code)
🤖 Opus, take it away!
What this is about:
Store secrets in macOS Keychain. Unlock with Touch ID. Use them in .env files.
No plaintext keys in your repo. No extra services. Just your fingerprint.
Quick start
# 1. Install git clone https://github.com/tillcarlos/touchenv.git cd touchenv && make install # 2. Store a secret in the Keychain touchenv store MY_API_KEY # 3. Reference it in your .env file # Replace the plaintext value: # MY_API_KEY=sk-abc123 # With a touchenv reference: # MY_API_KEY=touchenv:MY_API_KEY # 4. Run your command with secrets injected touchenv exec .env -- sh -c 'echo $MY_API_KEY'
Important: Always wrap your command in sh -c '...' (single quotes) when you need shell variable expansion. Without it, your current shell expands $MY_API_KEY before touchenv sets it:
# ✗ Won't work — $MY_API_KEY is expanded by your shell (to empty) touchenv exec .env -- echo $MY_API_KEY # ✓ Works — sh expands $MY_API_KEY after touchenv sets it touchenv exec .env -- sh -c 'echo $MY_API_KEY' # ✓ Also works — commands that read their own env don't need sh -c touchenv exec .env -- bin/deploy.sh
Install
git clone https://github.com/tillcarlos/touchenv.git
cd touchenvIt's a single Swift file. Read it first — you should never blindly install something that touches your Keychain:
claude "Is there ANYTHING I should worry about in this repo?"Then install:
The problem
Secrets like API keys and deploy credentials end up as plaintext in .env files, shared over Slack, or committed to repos.
# Don't do this
NODE_STAGING_KEY=SecretKeysShouldNotBeOntheFilesystemThe solution
Replace secret values with a touchenv: reference. The actual secret lives in macOS Keychain, protected by Touch ID.
# Do this
NODE_STAGING_KEY=touchenv:MYPROJECT_NODE_STAGING_KEYUsage
Store a secret
touchenv store MY_SECRET Enter value for 'MY_SECRET': ******** Stored 'MY_SECRET' in Keychain (Touch ID protected)
Or pipe it in:
echo "s3cret" | touchenv store MY_SECRET
Run with secrets
Use touchenv exec to load a .env file, resolve all touchenv: references via a single Touch ID prompt, and run your command:
touchenv exec .env.staging -- bin/deploy.sh stagingOne fingerprint tap unlocks all secrets and runs your command.
npm scripts
{
"scripts": {
"deploy:staging": "touchenv exec .env.staging -- bin/deploy.sh staging",
"credentials:staging": "touchenv exec .env -- tsx src/scripts/credentials.ts staging"
}
}Retrieve a single secret
touchenv get MY_SECRET # Touch ID prompt → prints value to stdoutAll commands
touchenv store <key> Store a secret (interactive prompt or pipe)
touchenv get <key> Retrieve a secret (Touch ID) → stdout
touchenv delete <key> Remove from Keychain
touchenv list List stored keys
touchenv exec <envfile> -- <cmd> Load .env, resolve touchenv: values, run cmd
How it works
Secrets are stored in the macOS Keychain under the touchenv account, with kSecAttrAccessibleWhenUnlockedThisDeviceOnly (no iCloud sync, device-only).
touchenv getandtouchenv execrequire Touch ID (viaLAContext) before reading any secret- Other apps accessing the same Keychain item get a system password prompt
execusesexecvpto replace the touchenv process with your command — stdin, signals, and TTY work correctly for interactive programs- 194KB universal binary (arm64 + x86_64), no dependencies
After rebuilding / updating
When you rebuild and reinstall touchenv, macOS sees a new binary signature and will show a Keychain password prompt:
Click Always Allow once — after that, Touch ID works as before.
Onboarding new devs
- Clone and install touchenv
- Store the required secrets:
touchenv store MY_NODE_KEY touchenv store MY_REGISTRY_PASSWORD
- Add
touchenv exec .env --before your command wherever you need secrets:"deploy:staging": "touchenv exec .env.staging -- bin/deploy.sh staging"
- Run as usual:
pnpm deploy:staging
If a secret is missing, touchenv tells them exactly what to do:
Error: 'MY_NODE_KEY' not found in Keychain
Run: touchenv store MY_NODE_KEY
Requirements
- macOS 12+
- Touch ID (or Apple Watch unlock)
- Swift 5.9+ (for building from source)
License
MIT



