🔐 Keep env secrets in Keychain. Inject them on demand. 🔐
keyenv is a macOS command-line tool for developers who want safer local
credential storage. It keeps values in the login Keychain and injects them only
into commands launched explicitly through keyenv run, while applications keep
using their normal environment APIs.
Credential values are never printed or placed in command arguments. Launched applications and their child processes inherit the resolved environment.
Install
keyenv requires macOS and Python 3.11 or newer.
uv tool install keyenv-macos
keyenv --help
The distribution is named keyenv-macos; the installed command is keyenv.
Configure
Add a value-free .keyenv.toml to the root of each project:
[keyenv]
version = 1
# Optional additions to the built-in browser/mobile public-prefix denylist:
public_prefixes = ["MY_CLIENT_PUBLIC_"]
[secrets.OPENROUTER_API_KEY]
account = "my-project/OPENROUTER_API_KEY"
required = true
Authorize the account for this canonical project root, store the credential through the hidden interactive prompt, then check its source:
keyenv authorize OPENROUTER_API_KEY
keyenv set OPENROUTER_API_KEY
keyenv doctor
Commit .keyenv.toml, but keep credential values out of it.
Authorization stores only a path digest in Keychain; it never reads the
credential. If the project moves, transfer each account explicitly with
keyenv authorize --rebind NAME.
Commands
Run these from a configured project directory:
keyenv authorize NAME # bind one account to this project root
keyenv authorize --rebind NAME # transfer an existing binding to this root
keyenv set NAME # store and verify one declared credential
keyenv doctor # report credential names and sources only
keyenv run -- COMMAND [ARGS...] # launch a command with resolved credentials
keyenv migrate # copy legacy entries and retain the originals
keyenv migrate --delete-legacy # delete legacy entries after full verification
keyenv --version # print the installed version
For example:
keyenv run -- uv run python app.py
keyenv run -- uv run jupyter lab
keyenv run -- pnpm dev
keyenv run replaces itself with the requested command, so the launched process
owns its signals and exit status. Security or operational failures exit with
status 1; invalid command-line usage exits with status 2.
Notes
- Credentials resolve from a non-empty process environment value, the current Keychain service, the legacy Keychain service, and finally missing state, in that order. Existing environment values therefore keep CI and provider-native injection working.
- Keychain accounts have one authorized project-root owner. Projects that need
the same underlying value should use distinct account names. A
runcommand whose declared values all come from the process environment performs no Keychain authorization or credential reads for those values. keyenv runmust start inside the manifest project root, and manifest files may not be symbolic links.- The native macOS Keychain backend is required. Configuring another
keyringbackend causes operational commands to fail safely. keyenv runrefuses to launch while a declared credential orVERCEL_OIDC_TOKENhas a populated assignment in a project dotenv file.- Dotenv filenames are matched case-insensitively. Scanning covers project output
trees such as
.next,build, anddist, while excluding only.git, Python virtual environments,node_modules, and__pycache__. Directory symlinks or broken links in the scanned tree cause a safe refusal. Dotenv candidates must resolve to regular files and may not exceed 1 MiB. - Secret names must be uppercase shell identifiers. Built-in browser and mobile
public prefixes include
NEXT_PUBLIC_,NUXT_PUBLIC_,VITE_,VUE_APP_,REACT_APP_,GATSBY_,EXPO_PUBLIC_, andPUBLIC_. Manifest additions are additive and cannot remove these defaults. - Migration copies and verifies legacy entries under
io.github.tsilva.keyenv.v1. It retains the originals unless--delete-legacyis supplied and every required entry is safe. - For linked Vercel projects, use
vercel env run -e development -- keyenv run -- COMMANDinstead ofvercel env pull, which writes plaintext files. - A launched application and its descendants can read injected values. Code already running as the same macOS user is outside this protection boundary. Report suspected vulnerabilities through SECURITY.md without including credential values.
Development
uv sync --locked --all-groups --no-config --exclude-newer "7 days"
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked mypy
uv run --locked python -m unittest discover -s tests -v
KEYENV_INTEGRATION=1 uv run --locked python -m unittest discover -s tests -p 'test_integration_keychain.py' -v
KEYENV_DIST_DIR="$(mktemp -d)"
UV_OFFLINE=1 uv build --no-build-isolation --no-sources --out-dir "$KEYENV_DIST_DIR"
rm -- "$KEYENV_DIST_DIR/.gitignore"
uv run --locked python scripts/check_artifacts.py "$KEYENV_DIST_DIR"
The integration test uses disposable synthetic entries in the login Keychain and removes them afterward.
Architecture
License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file keyenv_macos-0.1.1.tar.gz.
File metadata
- Download URL: keyenv_macos-0.1.1.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
594fe0aa1788a20236b3fda3317cd11f4a51b95f6efeab69ec83246267ea0f73
|
|
| MD5 |
c51856157b1db05eb8d7ee9e5ce28532
|
|
| BLAKE2b-256 |
dd5573ace195a07d200de6d58a50aa894bc4a43430f8f8e613e94776b9cd9a93
|
Provenance
The following attestation bundles were made for keyenv_macos-0.1.1.tar.gz:
Publisher:
release.yml on tsilva/keyenv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
keyenv_macos-0.1.1.tar.gz -
Subject digest:
594fe0aa1788a20236b3fda3317cd11f4a51b95f6efeab69ec83246267ea0f73 - Sigstore transparency entry: 2370826816
- Sigstore integration time:
-
Permalink:
tsilva/keyenv@308806212792b86cc2b1493fda6dac0154a63b76 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tsilva
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@308806212792b86cc2b1493fda6dac0154a63b76 -
Trigger Event:
push
-
Statement type:
File details
Details for the file keyenv_macos-0.1.1-py3-none-any.whl.
File metadata
- Download URL: keyenv_macos-0.1.1-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ded5b1f42ebc12d3a0bb9256c1ad5a0a84694a8908c9005ff14d6d87d72b87f
|
|
| MD5 |
f84ca0e3a1144ab6567d188d86be8fd1
|
|
| BLAKE2b-256 |
cadccc30533cb85f6698062df9ef35fdd3dc9eb471f58206b245eb4be02360cd
|
Provenance
The following attestation bundles were made for keyenv_macos-0.1.1-py3-none-any.whl:
Publisher:
release.yml on tsilva/keyenv
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
keyenv_macos-0.1.1-py3-none-any.whl -
Subject digest:
5ded5b1f42ebc12d3a0bb9256c1ad5a0a84694a8908c9005ff14d6d87d72b87f - Sigstore transparency entry: 2370826843
- Sigstore integration time:
-
Permalink:
tsilva/keyenv@308806212792b86cc2b1493fda6dac0154a63b76 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/tsilva
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@308806212792b86cc2b1493fda6dac0154a63b76 -
Trigger Event:
push
-
Statement type: