Keymaster is a tiny CLI that lets you store, retrieve, and delete small secrets in your macOS Keychain from scripts β protected by Touch ID or your login password. The first time you access a secret you can Always Allow the binary; every subsequent call prompts for biometrics and automatically falls back to a password sheet when Touch ID is unavailable.
- π Stores secrets in the system Keychain (kSecClassGenericPassword)
- π Biometric protection via Touch ID
- π Automatic fallback to macOS login password
- β‘οΈ Single self-contained binary, no dependencies
- π Friendly CLI (set, get, delete) with built-in help
- π Written in Swift β easy to audit & build
git clone https://github.com/bmansvk/keymaster.git
cd keymasterswiftc keymaster.swift -o keymaster
mv keymaster ~/.local/bin
Tip: Compile with -O for release optimisation.
keymaster set <key> <secret> Store or update <secret> for <key>
keymaster get <key> [options] Print secret to stdout
keymaster delete <key> Remove secret from Keychain
Options:
-h, --help Show detailed help and exit
-d, --description <text> Custom description for biometric prompt (get only)keymaster set github_token "ghp_abc123"
GITHUB_TOKEN=$(keymaster get github_token)
When running get, keymaster will show which key is being read:
Reading key "github_token" from Keychain...
keymaster get vpn_password --description "VPN wants to authenticate"
This will show "VPN wants to authenticate" in the Touch ID/password prompt instead of the default message.
keymaster delete github_token
Inside a Bash script:
#!/usr/bin/env bash
set -euo pipefail
API_KEY=$(keymaster get my_service_api_key)
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/v1/...- Keymaster calls SecItemAdd / SecItemCopyMatching / SecItemDelete from the Security framework.
- Before each operation it triggers LAContext.evaluatePolicy(.deviceOwnerAuthentication).
- macOS shows a single sheet:
- Touch ID or
- Use Passwordβ¦ β falls back to your login password.
- Only on success does the CLI proceed; otherwise it exits with a non-zero status.
Because secrets never leave the Keychain and the binary is code-signed by you, this approach is safer than environment variables or plain files.
Forked from johnthethird/keymaster β thanks for the original idea and groundwork.
Keymasterd is an HTTP server that exposes Keychain secrets over a local HTTP API. Each request triggers biometric/password authentication, making it suitable for automated tools that need secure secret access with user confirmation.
- HTTP API for Keychain access
- Touch ID / password authentication per request
- HTTP Basic Authentication for client verification
- Configurable via command-line arguments and environment variables
- Runs as a macOS launchd service
- Password passed via environment variable (not visible in process list)
swiftc keymasterd.swift -o keymasterdmv keymasterd /usr/local/bin/keymasterd [options]| Option | Description | Default |
|---|---|---|
-p, --port <port> |
Port to listen on | 8787 |
-b, --bind <host> |
Host/IP to bind to | 127.0.0.1 |
-u, --username <user> |
HTTP Basic Auth username | (none) |
-d, --description <text> |
Custom biometric prompt text | "Keymasterd wants to access the Keychain" |
-h, --help |
Display help message |
| Variable | Description |
|---|---|
KEYMASTERD_PASSWORD |
HTTP Basic Auth password (required with -u) |
KEYMASTERD_USERNAME |
HTTP Basic Auth username (alternative to -u) |
KEYMASTERD_PORT |
Port to listen on (alternative to -p) |
KEYMASTERD_BIND |
Host/IP to bind to (alternative to -b) |
Command-line arguments override environment variables.
Retrieve a secret from the Keychain. Triggers biometric/password authentication.
Response:
200 OK- Secret value in plain text401 Unauthorized- Missing or invalid HTTP Basic Auth403 Forbidden- Biometric/password authentication failed404 Not Found- Key not found in Keychain
Health check endpoint. Returns OK if server is running.
KEYMASTERD_PASSWORD=secret123 keymasterd --port 9000 --username admincurl -u admin:secret123 http://localhost:9000/key/github_token#!/usr/bin/env bash
API_KEY=$(curl -s -u admin:secret123 http://localhost:8787/key/my_api_key)
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/v1/...cp com.keymaster.keymasterd.plist ~/Library/LaunchAgents/Update the following in ~/Library/LaunchAgents/com.keymaster.keymasterd.plist:
- Set
KEYMASTERD_PASSWORDto a secure password - Adjust username and other options as needed
launchctl load ~/Library/LaunchAgents/com.keymaster.keymasterd.plistlaunchctl list | grep keymasterd
curl http://localhost:8787/healthtail -f /tmp/keymasterd.loglaunchctl unload ~/Library/LaunchAgents/com.keymaster.keymasterd.plist- Localhost binding: By default, keymasterd binds to
127.0.0.1, restricting access to local processes only - HTTP Basic Auth: Always configure authentication when exposing to any network
- Password via env: The HTTP auth password is passed via
KEYMASTERD_PASSWORDenvironment variable, keeping it hidden frompsoutput - Per-request auth: Each Keychain access triggers Touch ID or password prompt
- HTTPS: For production use over a network, consider placing keymasterd behind an HTTPS reverse proxy
For systems where you don't want keymasterd running continuously, use the inetd-compatible version. Launchd listens on the socket and spawns keymasterd-inetd only when a request arrives.
swiftc keymasterd-inetd.swift -o keymasterd-inetd
mv keymasterd-inetd /usr/local/bin/The inetd version uses environment variables only:
| Variable | Description |
|---|---|
KEYMASTERD_USERNAME |
HTTP Basic Auth username |
KEYMASTERD_PASSWORD |
HTTP Basic Auth password |
KEYMASTERD_DESCRIPTION |
Custom biometric prompt text |
- Copy the plist:
cp com.keymaster.keymasterd-inetd.plist ~/Library/LaunchAgents/-
Edit
~/Library/LaunchAgents/com.keymaster.keymasterd-inetd.plist:- Set
KEYMASTERD_PASSWORDto a secure password - Adjust username as needed
- Set
-
Load the service:
launchctl load ~/Library/LaunchAgents/com.keymaster.keymasterd-inetd.plist- Launchd listens on port 8787
- When a connection arrives, launchd spawns
keymasterd-inetd - The HTTP request is passed via stdin, response via stdout
- Process exits after handling the single request
- No daemon runs between requests
| Feature | keymasterd | keymasterd-inetd |
|---|---|---|
| Running process | Always | On-demand only |
| Memory usage | Constant | Zero when idle |
| First request latency | Instant | ~100ms spawn time |
| Configuration | CLI args + env | Environment only |
| Plist | com.keymaster.keymasterd.plist |
com.keymaster.keymasterd-inetd.plist |
π License
This project is licensed under the MIT License β see LICENSE for details.