🤫

Keep secrets out of AI reach

When AI coding assistants can read your .env files, that's a problem. Store secrets in macOS Keychain with Touch ID protection. AI sees what you need, not the values.

GitHub stars
Touch ID protected
100% local
macOS Keychain

The problem with .env files

.env files

# Your .env file - AI can read this
STRIPE_SECRET_KEY=sk_live_abc123...
DATABASE_URL=postgres://user:pass@host
OPENAI_API_KEY=sk-proj-xyz789...

# AI assistant asks:
"I see your STRIPE_SECRET_KEY, want me to
 set up the payment webhook?"

# Your key is now in the conversation,
# potentially in logs, training data...

.secrets.json manifest

# Your .secrets.json - AI sees this
{
  "project": "my-app",
  "secrets": [
    "STRIPE_SECRET_KEY",
    "DATABASE_URL",
    "OPENAI_API_KEY"
  ]
}

# AI assistant suggests:
"Run: secret-lover run -- npm run dev"

# Values stay in Keychain, injected only
# at runtime with your Touch ID approval

Why you can trust it

No server. No account. No magic. Just a shell script wrapping macOS security tools you already have.

100% local

Nothing leaves your machine. No servers, no accounts, no telemetry. Works offline.

Apple's security

Secrets stored in macOS Keychain - the same place Safari stores your passwords. Encrypted by Apple.

Fully auditable

~200 lines of bash. No dependencies. Read the whole thing in 5 minutes. Uses standard `security` CLI.

Touch ID approval

Every secret access requires your fingerprint. See exactly what's being accessed. Deny anytime.

Works with Claude Code, Cursor, etc.

AI reads your manifest, suggests the right command. You approve with Touch ID.

# In your project, create .secrets.json:
~/myproject/.secrets.json
{
  "project": "myproject",
  "secrets": ["DATABASE_URL", "API_KEY"]
}

# Claude Code sees the manifest, suggests:
"I see you need DATABASE_URL and API_KEY.
 Run: secret-lover run -- npm run dev"

# You run it, Touch ID pops up:
$ secret-lover run -- npm run dev
[Touch ID prompt: "secret-lover" wants to access "DATABASE_URL"]

# You approve, secrets injected for that command only:
Ready on http://localhost:3000

Per-project secrets

Different projects, different credentials. Same workflow.

# Project A - Production Supabase
~/project-a/.secrets.json
{
  "project": "project-a",
  "secrets": ["SUPABASE_URL", "SUPABASE_KEY"]
}

$ cd ~/project-a
$ secret-lover add SUPABASE_URL "https://abc.supabase.co"
Stored: SUPABASE_URL (project: project-a)

# Project B - Staging Supabase (completely separate)
~/project-b/.secrets.json
{
  "project": "project-b",
  "secrets": ["SUPABASE_URL", "SUPABASE_KEY"]
}

$ cd ~/project-b
$ secret-lover add SUPABASE_URL "https://xyz.supabase.co"
Stored: SUPABASE_URL (project: project-b)

# Shared secrets work everywhere
$ secret-lover add OPENAI_KEY "sk-..." --global
Stored: OPENAI_KEY (global)

# Each project gets its own SUPABASE_URL, shares OPENAI_KEY

Auto-detected

Project name comes from .secrets.json or folder name. No config needed.

Fallback to global

If a secret isn't project-specific, it checks global. Shared keys just work.

See everything

secret-lover list --all shows secrets organized by project.

Don't want to install anything?

Paste this into your CLAUDE.md. Claude Code will manage secrets via macOS Keychain directly — no tool needed.

Copy to ~/.claude/CLAUDE.md
## Secrets Management

Never store secrets in .env files. Use macOS Keychain instead.

Projects declare what secrets they need in `.secrets.json`:
```json
{
  "secrets": ["API_KEY", "DATABASE_URL"],
  "env": { "NODE_ENV": "production" }
}
```

**Reading secrets from Keychain:**
```bash
security find-generic-password -a "SECRET_NAME" -s "my-project" -w
```

**Storing a new secret:**
```bash
security add-generic-password -a "SECRET_NAME" -s "my-project" -w "value"
```

**Running a command with secrets injected:**
Read each name from the `secrets` array in `.secrets.json`, fetch from
Keychain using the command above, and export as environment variables
before running the command. Use the project directory name as the
service name (-s flag).

For static values in the `env` object, export them directly.

**Rules:**
- Never create .env files or suggest `export VAR=value` in shell
- Never hardcode secret values in code
- If `.secrets.json` exists, check it to know what secrets are needed
- If a secret is missing from Keychain, tell me to add it

That's it. Claude Code will use Keychain commands instead of .env files — same idea as secret-lover, zero install.

Quick reference

Common commands at a glance:

# Store a secret (Touch ID protected):
secret-lover add MY_API_KEY "your-secret-value"

# Retrieve a secret (prompts Touch ID):
secret-lover get MY_API_KEY

# Run a command with secrets injected:
secret-lover run -- npm run dev

# List all your secrets:
secret-lover list --all

# Migrate existing secrets to Touch ID:
secret-lover migrate

Uses a Swift helper for Touch ID integration. Falls back to password if Touch ID unavailable.

vs cross-keychain

Different tools for different problems.

secret-lover cross-keychain
Platform macOS only Windows, macOS, Linux
Touch ID Yes (native Swift) No
Dependencies None (bash + Swift) Node.js + native modules
Use case CLI for dev workflows Programmatic API for apps
Project namespacing Built-in No
AI-focused Yes (.secrets.json manifest) No

Use cross-keychain when:

Building a Node.js app that needs to store user credentials programmatically across platforms.

Use secret-lover when:

You're a developer using AI assistants and want Touch ID-protected secrets that stay out of AI context.

Install

If you want the nicer workflow with .secrets.json manifests:

curl -sL https://secret-lover.dev/install.sh | bash