lsm — Local Secrets Manager
A lightweight CLI for managing per-app, per-environment secrets encrypted with age. No remote services, no billing, no accounts.
Install
Homebrew (macOS / Linux)
brew install llbbl/tap/lsm
Recent Homebrew requires trusting a third-party tap once; if the install is
refused, run brew trust llbbl/tap and retry (or brew tap llbbl/tap first,
then brew install lsm).
Go
go install github.com/llbbl/lsm/cmd/lsm@latest
Curl (Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/llbbl/lsm/main/scripts/install.sh | sh
It downloads the matching archive, verifies its checksum, and installs lsm
to ~/.local/bin (override with LSM_BIN; pin a version with LSM_VERSION).
Windows
Download the .zip from the releases page and follow the bundled INSTALL.md.
Quick Start
# 1. Generate encryption key
lsm init
# 2. Register your project
cd ~/Web/myapp
lsm link myapp
# 3. Import existing .env file
lsm import .env.local
# 4. Run with secrets injected
lsm exec -- pnpm dev
# 5. Export for deployment (writes to myapp.production.env)
lsm dump --env production
How It Works
Secrets are stored as age-encrypted .env files in ~/.lsm/:
~/.lsm/
key.txt # age private key (chmod 600)
config.yaml # default env + app registry
myapp.dev.age # encrypted secrets
myapp.production.age
backend.dev.age
The central config.yaml maps app names to project directories:
env: dev
apps:
myapp: /Users/you/Web/myapp
backend: /Users/you/Web/backend
When you run any lsm command, it resolves which app you mean by checking your current directory against this registry.
Commands
lsm init # Generate age key pair
lsm link <app> # Register current directory as an app
lsm set <KEY> <VALUE> # Set a secret (use '-' to read from stdin)
lsm get <KEY> # Get a secret value
lsm delete <KEY> # Remove a secret
lsm list # List secret keys (no values)
lsm dump # Export to .env file (masked terminal output)
lsm exec -- <command> # Inject secrets and run command
lsm edit # Edit secrets in $EDITOR
lsm import <file> # Import from .env file (or '-' for stdin)
lsm apps # List all registered apps
lsm envs <app> # List environments for an app
lsm gh push # Push local secrets to GitHub Actions
lsm gh status # Compare local secrets with GitHub Actions
GitHub Actions secrets (lsm gh)
lsm gh pushes your locally-encrypted secrets to GitHub Actions and reports
drift between the two.
cd ~/Web/myapp # must be the directory you ran `lsm link` in
lsm gh push # set repo Actions secrets from local store
lsm gh push --gh-env production # target a GitHub environment instead
lsm gh push --prune # also delete remote secrets no longer local
lsm gh status # show in-sync / local-only / remote-only
Requirements and behavior:
- Directory-bound.
lsm gh operates on the app registered for the current
directory via lsm link. It does not accept --app; run it from the
project root. The repo defaults to the origin remote (override with
--repo OWNER/REPO), and the env resolves from --env, .lsm.yaml, or the
global default.
- Requires the GitHub CLI.
gh must be installed and authenticated
(gh auth login).
- Values never touch argv or disk. Each value is streamed to
gh secret set on stdin; no plaintext temp files, no backup file.
- Write-only. GitHub's secrets API cannot return secret values.
lsm gh status shows secret names and GitHub's update timestamps only — values
can never be pulled back from GitHub.
Non-gh commands accept --app, --env, and --dir flags to override auto-detection.
See docs/commands.md for detailed usage and examples.
App & Env Resolution
lsm resolves the app name and environment in this order:
- CLI flags (
--app, --env)
.lsm.yaml in current directory (for backward compatibility)
- Central registry lookup by current directory path
If no app is found, lsm stops and asks you to run lsm link <app>, pass --app, or create .lsm.yaml; it does not guess from the directory name. The environment resolves from --env, .lsm.yaml, then the default env in ~/.lsm/config.yaml.
The recommended approach is to use lsm link to register your projects. After linking, lsm automatically knows which app you're working on based on your current directory:
cd ~/Web/myapp
lsm link myapp # one-time setup
# Now all commands auto-resolve to app=myapp
lsm set DB_URL postgres://localhost
lsm exec -- pnpm dev
lsm dump --env production
Security
- Age encryption (X25519 + ChaCha20-Poly1305)
- Private key stays in
~/.lsm/key.txt (chmod 600)
exec injects secrets into the subprocess only — not your shell
dump masks values in terminal output, writes real values to file only
- Encrypted at rest — safe for screensharing
Docs
Observability
lsm can optionally emit an audit log of every access and ship it via OTLP to your observability stack (Loki/Grafana, Honeycomb, Datadog, etc.). Off by default — enable via the otlp: block in ~/.lsm/config.yaml.
For users running the LGTM stack, drop-in Grafana dashboard + Loki alert rules live in observability/. lsm itself has no Grafana/Loki dependency; these are pure config artifacts.
Changelog
See CHANGELOG.md for release history. This project uses Conventional Commits and git-cliff for automated changelog generation.
License
BSD 3-Clause. See LICENSE.