# ksm: shell secrets in the macOS Keychain, masked on screen
GreeNx · · 3 min read
macOS ships security, a command that stores secrets in the Keychain instead of plain-text files. The usual way to use it from zsh is an export line in ~/.zshrc:
export GITHUB_TOKEN=$(security find-generic-password -a $(whoami) -s GITHUB_TOKEN -w)
The value lives in the Keychain rather than in a dotfile. Two chores remain:
- You write and maintain one export line per key.
envandprintenvstill print raw values. A screen share, a recording, or an AI assistant reading your shell exposes the real tokens.
ksm automates the workflow: one command stores a key, every new shell exports all stored keys, and echo, env, and printenv print **** in place of each managed value.
Install ksm
ksm needs macOS and zsh.
curl -fsSL https://raw.githubusercontent.com/GreeNxGit/ksm/main/install | bash
The installer copies ksm.zsh to ~/.ksm/, creates a dedicated keychain at ~/Library/Keychains/ksm.keychain-db (separate from your login keychain), and adds source ~/.ksm/ksm.zsh to your ~/.zshrc. It ends with a ksm doctor report:
== ksm doctor ==
backend: OK (security)
keychain: OK (/Users/you/Library/Keychains/ksm.keychain-db)
zshrc: OK (ksm.zsh sourced)
account: you
keys: 0 managed in this shell
masking: on (KSM_MASK=1)
Start a new shell, or run exec zsh to reload the current one.
Save your first key
ksm set OPENAI_API_KEY
# Enter value for OPENAI_API_KEY:
# ✓ OPENAI_API_KEY stored
The prompt hides what you type. ksm set stores the value in the keychain and exports it to the current shell. On screen, echo $OPENAI_API_KEY answers ****.
You can also pipe a value in. This line generates a random 32-character hex secret and stores it as API_SECRET:
ksm rand | ksm set API_SECRET
Every new shell starts loaded
There is no ksm load step. When a shell starts, the sourced ksm.zsh reads the key names from the keychain and exports every stored value. gh, git, and your editor read $GITHUB_TOKEN with no extra setup.
If you prefer to load keys by hand, set KSM_AUTO_CACHE=0 before the source line in ~/.zshrc. Then ksm cache NAME loads a stored key into the current shell, with no output.
Masked on screen
ksm.zsh wraps echo, env, and printenv, so managed values print as ****:
echo "token: $GITHUB_TOKEN" # token: ****
env | grep GITHUB_TOKEN # GITHUB_TOKEN=****
printenv GITHUB_TOKEN # ****
ksm ls lists the keys with a masked preview: the first two characters of each value, then ****.
ksm ls
# GITHUB_TOKEN gh****
# OPENAI_API_KEY sk****
ksm has no get command and never prints a raw secret to stdout. To use a secret outside the shell, copy it to the clipboard with ksm cp GITHUB_TOKEN. When you need the raw value in the shell, command echo $GITHUB_TOKEN bypasses the wrapper.
What masking does not do
The wrappers guard against shoulder-surfing and scrollback. They are not a security boundary: printf, command echo, and any program that reads the environment still get the real value.
The keychain has an empty password and a 24-hour lock timeout. Those settings buy the silent, prompt-free experience, and they also mean that any process running as your user can read every secret. ksm stops screen leaks and keeps secrets out of dotfiles. It does not defend against untrusted code on your machine.
Manage keys day to day
ksm rm NAMEdeletes the key from the keychain and unsets it in the current shell.ksm cache NAMEre-fetches and exports a stored key, with no output.ksm rand [size]prints a random hex secret, 32 characters by default.ksm doctorchecks the install: thesecuritybackend, the keychain file, the~/.zshrcsource line, and the key count.
Set these before the source line in ~/.zshrc: KSM_MASK=0 turns masking off, and KSM_KEYCHAIN=work keeps secrets in a keychain named work. The README covers every setting, including KSM_ACCOUNT, and the uninstall steps.
ksm runs no background service and makes no network calls at runtime. The tool is one zsh file with no dependencies beyond what macOS already ships. The source is on GitHub: GreeNxGit/ksm.