{"id":47222985,"url":"https://github.com/tvanreenen/key","last_synced_at":"2026-04-02T18:34:02.151Z","repository":{"id":344213000,"uuid":"1179228618","full_name":"tvanreenen/key","owner":"tvanreenen","description":"A file-based CLI secret manager with native macOS auth","archived":false,"fork":false,"pushed_at":"2026-03-26T15:04:19.000Z","size":4225,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-27T00:26:49.207Z","etag":null,"topics":["cli","macos","password-manager","touch-id","user-presence"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tvanreenen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"buy_me_a_coffee":"tim.vanreenen"}},"created_at":"2026-03-11T20:24:32.000Z","updated_at":"2026-03-26T15:07:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tvanreenen/key","commit_stats":null,"previous_names":["tvanreenen/key"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/tvanreenen/key","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvanreenen%2Fkey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvanreenen%2Fkey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvanreenen%2Fkey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvanreenen%2Fkey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tvanreenen","download_url":"https://codeload.github.com/tvanreenen/key/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tvanreenen%2Fkey/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","macos","password-manager","touch-id","user-presence"],"created_at":"2026-03-13T19:09:59.481Z","updated_at":"2026-04-02T18:34:02.144Z","avatar_url":"https://github.com/tvanreenen.png","language":"Swift","funding_links":["https://buymeacoffee.com/tim.vanreenen"],"categories":[],"sub_categories":[],"readme":"# Key\n\n![Key](.github/assets/hero.png)\n\n`key` is a macOS secret manager for people who like what the venerable [`pass`](https://www.passwordstore.org/) gets right:\n\n- Secrets are stored as encrypted files, not in an opaque, app-specific database\n- Flexible directory structure lets you organize and reason about secrets hierarchically\n- Small, CLI-first command set with full flexibility from the shell\n\n`key` uses the same model, but instead of pass’s GPG agent workflow, it relies on native macOS encryption and authentication. Under the hood it works a bit like `ssh-agent` or `gpg-agent`: a short-lived helper caches unlocked key material in memory. The difference is that `key` does it the native macOS way, using `launchd`, XPC, Mach services, and Keychain [`userPresence`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/userpresence).\n\n## How it works\n\n- Each secret is stored as an individually encrypted file on disk, under `~/.key` by default.\n- All secret files are encrypted and decrypted using a single, randomly generated 256-bit symmetric vault key.\n- That vault key is stored securely in your macOS Keychain (not the secrets themselves!).\n- Access to the vault key in Keychain is protected by macOS local authentication—Touch ID, Apple Watch, or your system password—using [`userPresence`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/userpresence).\n- The CLI talks to an on-demand LaunchAgent helper over XPC using a Mach service.\n- After a successful unlock, the helper keeps the vault key in memory for a short idle window and reuses it across separate CLI invocations without prompting again.\n- When the helper has been idle long enough, it clears the in-memory key and exits.\n\nThe result: your secrets stay encrypted on the filesystem, protected by a single key, and only you can access that key thanks to native macOS authentication.\n\n## Install\n\nInstall via Homebrew from the [tvanreenen/tap](https://github.com/tvanreenen/homebrew-tap) tap:\n\n```bash\nbrew tap tvanreenen/tap\nbrew install --cask key\n```\n\nOpen `Key.app` once after install so it can register Key Agent with macOS before you use the `key` CLI.\n\n## CLI\n\nThe CLI is intentionally small:\n\n```bash\nkey unlock                              # authenticate and warm the helper session\nkey lock                                # clear the helper session and stop the helper\nkey get \u003cname\u003e                          # print a secret or current TOTP code\nkey copy \u003cname\u003e                         # copy a secret or current TOTP code\nkey add \u003cname\u003e [--totp]                 # add a new secret or TOTP seed from stdin or prompt\nkey edit \u003cname\u003e [--totp]                # update a secret or TOTP seed from stdin or prompt\nkey list                                # list stored secrets\nkey duplicate \u003csrc\u003e \u003cdst\u003e [--force]     # duplicate an entry\nkey rename \u003csrc\u003e \u003cdst\u003e [--force]        # rename an entry\nkey remove \u003cname\u003e [--force]             # remove a secret\nkey config get \u003cconfig-name\u003e            # print a config value\nkey config set \u003cconfig-name\u003e \u003cvalue\u003e    # update a config value\nkey config list                         # list known config values\nkey version [--json]                    # print the CLI version\n```\n\n## Configuration\n\nCurrently supported config names:\n\n- `vault-dir`\n\nBy default, `key` stores its config in `~/Library/Application Support/Key/config.toml` and its encrypted secret files in `~/.key`.\n\nIf you want to move the vault, move the files yourself and then update the configured path:\n\n```bash\nmv ~/.key ~/Secrets/key-vault\nkey config set vault-dir ~/Secrets/key-vault\n```\n\nIf `~/.key` already exists and contains unrelated files, Key will refuse to adopt it as the default vault root. In that case, choose another vault directory with `key config set vault-dir \u003cpath\u003e`.\n\n## TOTP\n\n`key` supports a narrow first pass of TOTP:\n\n```bash\nkey add github/mfa --totp\nkey edit github/mfa --totp\nkey get github/mfa\nkey copy github/mfa\n```\n\nFor now, `--totp` accepts only bare Base32 seeds. `get` and `copy` return the current code for TOTP entries.\n\n## Generating passwords\n\nUnlike most password managers, `key` does not include a built-in password generator. Instead, it is designed to accept input via stdin, so you can add or edit secrets either by securely typing them in (using your terminal's secure input), or by piping in passwords generated by any tool or method you prefer:\n\n```bash\nopenssl rand -base64 32 | key add aws/prod/token\nopenssl rand -hex 32 | key add api/key\npwgen -sy 24 1 | key edit github/personal\ndiceware -n 6 | key add personal/passphrase\nxkcdpass -n 4 | key add outlook/work\nuuidgen | key add app/token\nhead -c 32 /dev/urandom | base64 | key add backup/recovery\n```\n\n## Fuzzy picking with fzf\n\nIf you use [`fzf`](https://github.com/junegunn/fzf), `key list` composes cleanly with it:\n\n```bash\nkey get \"$(key list | fzf)\"\nkey copy \"$(key list | fzf)\"\nkey edit \"$(key list | fzf)\"\nkey remove \"$(key list | fzf)\"\n```\n\nThis stays fully optional. `key` does not depend on `fzf`, but the combination works well when you want fuzzy selection across a larger store.\n\n## Security without the lock-in\n\n`key` uses standard AES-256-GCM encryption with zero custom cryptography. If you have both the vault key and your `.secret` files, you're not locked in: you can decrypt your secrets using any tool that supports AES-GCM, letting you move your data or audit it without relying on the app.\n\n**Where the files live:** Secrets are under `~/.key` by default. An entry like `github/personal` is stored as `~/.key/github/personal.secret`. The active vault path is configured in `~/Library/Application Support/Key/config.toml` and can be inspected with `key config get vault-dir`.\n\n**Payload format:** Each `.secret` file contains a JSON object:\n\n```json\n{\n  \"version\": 2,\n  \"type\": \"secret\",\n  \"alg\": \"AES.GCM\",\n  \"nonce\": \"\u003cbase64-encoded 96-bit nonce\u003e\",\n  \"ciphertext\": \"\u003cbase64-encoded AES-GCM ciphertext + 16-byte auth tag\u003e\"\n}\n```\n\nFor TOTP entries, the envelope is the same except `type` is `totp`; the decrypted plaintext is the normalized Base32 seed rather than a password.\n\nWithout the vault key (the 256-bit secret kept in your Keychain), the file contents are completely opaque.\n\n**How to decrypt:** To unlock a secret yourself, parse the JSON and base64-decode both `nonce` and `ciphertext`. Split the decoded ciphertext into the payload (everything except the final 16 bytes) and the authentication tag (the last 16 bytes). Decrypt the payload using the vault key and nonce with AES-256-GCM—the result will be your UTF-8 plaintext.\n\n## Nerdy details about the macOS integration\n\n`key` is not just a standalone CLI binary. To use the stronger macOS Keychain and user-presence path correctly, it is structured as three pieces:\n\n1. `Key.app`\n2. `key` CLI client\n3. LaunchAgent helper\n\n### `Key.app`\n\nThe host app exists to give the project a proper macOS app identity, signing context, entitlements, and release shape, and to register the bundled LaunchAgent helper on first launch. It is not intended to be a full GUI password manager.\n\n### `key` CLI client\n\nThe CLI is the user-facing interface. It handles:\n\n- command parsing\n- stdin and secure prompt input\n- stdout and stderr output\n- clipboard writes for `key copy`\n\nThe CLI does **not** directly access the protected vault key.\n\n### LaunchAgent helper\n\nThe helper is the privileged side of the system. It is managed by `launchd`, reachable through a Mach service, and owns:\n\n- Keychain access\n- [`userPresence`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/userpresence)-gated vault key retrieval\n- encryption and decryption\n- on-disk secret file access\n- the short-lived in-memory unlock session\n\nThis split gives `key` a shape that is similar in spirit to `ssh-agent` or `gpg-agent`: a user-session helper keeps unlocked key material in memory so repeated CLI commands can reuse it. The difference is that `key` uses the native macOS service model instead of a Unix socket convention:\n\n- `launchd` starts the helper on demand\n- the CLI talks to it over XPC using a Mach service\n- the helper exits when it has been idle, so nothing is permanently running\n\nThat gives `key` a few nice properties:\n\n- reliable unlock reuse across separate CLI invocations\n- no long-lived decrypted secrets on disk\n- no permanently running background process when idle\n- native macOS process management, signing, and IPC\n\nConceptually, a `get` looks like this:\n\n1. `key get github/personal`\n2. if needed, `launchd` starts the helper when the CLI connects to its Mach service\n3. the CLI sends a request to the helper over XPC\n4. if the helper is locked, it asks macOS for access to the vault key\n5. macOS enforces the Keychain item's [`userPresence`](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags/userpresence) requirement through its normal local-authentication path\n6. the helper decrypts the secret file\n7. the CLI prints the result to stdout\n\nConceptually, an explicit unlock looks like this:\n\n1. `key unlock`\n2. the CLI connects to the helper's Mach service\n3. if needed, `launchd` starts the helper\n4. the helper asks macOS for access to the vault key\n5. on success, the helper keeps the vault key in memory for a short idle window\n6. later `get`, `copy`, `add`, or `edit` requests can reuse that in-memory authorization without prompting again\n7. after the helper has been idle long enough, it drops the key and exits\n\nThat is the tradeoff that makes the native macOS auth path possible while keeping the day-to-day interface CLI-first. This is intentionally macOS-specific and optimizes for native platform integration over cross-platform portability.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftvanreenen%2Fkey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftvanreenen%2Fkey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftvanreenen%2Fkey/lists"}