Skip to main content

Manage .envrc with managed blocks and OS-backed secrets.

Project description

envrcctl

envrcctl is a CLI tool that manages .envrc files safely through a managed block, with secrets stored in your OS key store instead of the file.

It is designed for macOS first, with Linux support via SecretService.

Features

  • Safe, structured edits to .envrc (managed block only)
  • Non-secret environment variables (CRUD)
  • Secrets stored in Keychain (macOS) or SecretService (Linux)
  • Inheritance control (source_up on/off)
  • Exec-based secret injection (envrcctl exec -- ..., TTY-guarded on Linux, TTY + macOS auth on macOS)
  • Secret injection for direnv (eval "$(envrcctl inject)", TTY-guarded on Linux, TTY + macOS auth on macOS)
  • Secret kinds (runtime/admin), with exec injecting runtime only
  • Secret get with clipboard default and TTY guard on Linux, plus macOS auth on macOS
  • On macOS, inject and exec retrieve multiple runtime secrets with a single device owner authentication prompt
  • Tamper-evident local audit log for secret access events
  • Diagnostics and migration helpers
  • Shell completion scripts

Requirements

  • Python 3.14+
  • direnv
  • macOS Keychain (built-in) or Linux SecretService (secret-tool)
  • device owner authentication (TouchID or Apple Watch)

Installation

macOS (Homebrew)

Tap and install:

brew tap rioriost/envrcctl
brew install envrcctl

After release, Homebrew will download the release from GitHub.

Install direnv with Homebrew:

brew install direnv

Linux (pipx, recommended)

pipx install envrcctl

Linux (uv)

uv tool install envrcctl

From source (macOS/Linux)

git clone <REPO_URL>
cd envrcctl
uv sync
uv run python -m envrcctl.main --help

Build the macOS auth helper (macOS only)

The macOS device owner authentication flow requires a native helper named envrcctl-macos-auth.

Build it and place the binary at either:

  • src/envrcctl/envrcctl-macos-auth
  • or a custom path set via ENVRCCTL_MACOS_AUTH_HELPER

Example build flow:

swiftc -O -framework LocalAuthentication -framework Security \
  scripts/macos/envrcctl-macos-auth.swift \
  -o src/envrcctl/envrcctl-macos-auth
chmod +x src/envrcctl/envrcctl-macos-auth

You can also use the repository build script:

sh scripts/build_macos_auth_helper.sh

If you want to write the helper to a custom location, pass the source and output paths explicitly:

sh scripts/build_macos_auth_helper.sh \
  scripts/macos/envrcctl-macos-auth.swift \
  /usr/local/bin/envrcctl-macos-auth

If you install the helper elsewhere, set:

export ENVRCCTL_MACOS_AUTH_HELPER=/path/to/envrcctl-macos-auth

Quick Start

  1. Initialize a managed block in .envrc:
envrcctl init

If .envrc already exists, you'll be prompted to confirm. Use --yes to skip the prompt in non-interactive runs. Add --inject to explicitly insert the inject line.

  1. Add non-secret variables:
envrcctl set FOO bar
envrcctl get FOO
envrcctl list
  1. Enable inheritance:
envrcctl inherit on
  1. Store a secret:
envrcctl secret set OPENAI_API_KEY --account openai:prod
  1. Add the inject line explicitly:
envrcctl init --inject

This inserts eval "$(envrcctl inject)" into the managed block.

  1. Allow direnv:
direnv allow

Commands

Non-secret variables

envrcctl set VAR value
envrcctl unset VAR
envrcctl get VAR
envrcctl list

Secrets

envrcctl secret set OPENAI_API_KEY --account openai:prod --kind runtime
envrcctl secret set OPENAI_API_KEY --account openai:admin --kind admin
envrcctl secret unset OPENAI_API_KEY
envrcctl secret list
envrcctl secret get OPENAI_API_KEY
envrcctl secret get OPENAI_API_KEY --plain

envrcctl secret get behavior is platform-specific:

  • On Linux, the current TTY-based behavior remains in place.
  • On macOS, secret get requires the existing interactive-shell check and successful macOS device owner authentication before revealing or copying the secret.

In practice, macOS authentication may use Touch ID and, when supported by your system configuration, Apple Watch approval or password fallback.

For CI-safe input:

echo -n "$OPENAI_API_KEY" | envrcctl secret set OPENAI_API_KEY --account openai:prod --stdin

Exec secrets without stdout

envrcctl exec -- python script.py
envrcctl exec -k OPENAI_API_KEY -- python script.py

Exec injects runtime secrets only.

envrcctl exec behavior is platform-specific:

  • On Linux, the current behavior remains in place.
  • On macOS, exec requires the existing interactive-shell check and successful macOS device owner authentication before runtime secrets are injected into the child process.
  • When multiple runtime secrets are selected, macOS performs a single authentication step and then retrieves all requested secrets in one helper call.

In practice, macOS authentication may use Touch ID and, when supported by your system configuration, Apple Watch approval or password fallback.

Inject secrets for direnv

envrcctl inject

Linux keeps the current behavior: non-interactive runs are blocked unless --force is provided.

On macOS, envrcctl inject requires both:

  • the existing interactive-shell check
  • successful macOS device owner authentication

When multiple runtime secrets are present, inject performs one authentication step and retrieves all eligible secrets in a single bulk helper request.

That authentication is expected to be satisfied through macOS mechanisms such as Touch ID and, when your system offers it, Apple Watch approval or password fallback.

If the native helper is missing or not executable, inject fails closed with an authentication-helper error. Build or install envrcctl-macos-auth before using macOS-authenticated secret commands.

Effective view (masked)

envrcctl eval

Audit log

envrcctl audit list
envrcctl audit show --index 0
envrcctl audit verify

envrcctl records tamper-evident local audit events for:

  • secret get
  • inject
  • exec

The audit log:

  • never stores plaintext secret values
  • stores variable names, secret ref metadata, working directory, and exec command metadata
  • chains events with prev_hash and hash so silent modification or deletion is detectable

Default audit log storage locations:

  • macOS: ~/Library/Application Support/envrcctl/audit/
  • Linux: $XDG_STATE_HOME/envrcctl/audit/ when XDG_STATE_HOME is set
  • Linux fallback: ~/.local/state/envrcctl/audit/

The audit store currently uses:

  • audit.jsonl for append-only event records
  • latest_hash for the latest chain hash
  • meta.json for metadata

envrcctl audit verify checks the hash chain and reports failures if audit records appear to have been modified.

Diagnostics

envrcctl doctor

doctor also checks audit health and warns when:

  • the audit chain does not verify
  • the audit store permissions are insecure

Migration

envrcctl migrate

You'll be prompted when unmanaged exports or secret refs are detected. Use --yes to confirm in non-interactive runs.

Backend Selection (macOS/Linux)

envrcctl selects a backend automatically by platform, or via ENVRCCTL_BACKEND.

Supported schemes:

  • kc — macOS Keychain
  • ss — SecretService via secret-tool

Example:

ENVRCCTL_BACKEND=ss envrcctl secret set OPENAI_API_KEY --account openai:prod

Secret references are stored as:

<scheme>:<service>:<account>:<kind>

kind is runtime or admin (default: runtime).

Example:

kc:st.rio.envrcctl:openai:prod:runtime
kc:st.rio.envrcctl:openai:admin:admin

Shell Completion

envrcctl --install-completion
envrcctl --show-completion bash
envrcctl --show-completion zsh
envrcctl --show-completion fish

Generated scripts are stored under completions/. To refresh:

uv run python scripts/generate_completions.py

Security Notes

  • Secrets are never written to .envrc
  • Secrets are never passed in CLI arguments
  • .envrc updates are atomic
  • On Linux, inject is blocked in non-interactive environments unless --force is provided
  • On macOS, inject requires both the interactive-shell check and successful device owner authentication
  • On Linux, secret get is clipboard-only by default and plaintext output is TTY-guarded
  • On macOS, secret get requires both the interactive-shell check and successful device owner authentication
  • On Linux, exec keeps the current behavior
  • On macOS, exec requires both the interactive-shell check and successful device owner authentication before runtime secrets are injected into the child process
  • On macOS, authentication is mediated by the OS and may use Touch ID, Apple Watch approval, or password fallback depending on system support and configuration
  • On macOS, authenticated commands require the native helper envrcctl-macos-auth
  • The helper is discovered from ENVRCCTL_MACOS_AUTH_HELPER or src/envrcctl/envrcctl-macos-auth
  • If the helper is missing, invalid, or not executable, macOS secret-accessing commands fail closed
  • Secret-access actions are recorded in a local tamper-evident audit log
  • Audit records never include plaintext secret values
  • Audit integrity is based on a hash chain and can be checked with envrcctl audit verify
  • The tool refuses to write to world-writable .envrc

Development

uv sync
.venv/bin/envrcctl --help

Acknowledgements

Based on the article below, I added commands such as exec. Thank you for the helpful hints.
もう.envにAPIキーを平文で置くのはやめた — macOS Keychain管理CLI「LLM Key Ring」

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

envrcctl-0.2.0.tar.gz (56.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

envrcctl-0.2.0-py3-none-any.whl (56.5 kB view details)

Uploaded Python 3

File details

Details for the file envrcctl-0.2.0.tar.gz.

File metadata

  • Download URL: envrcctl-0.2.0.tar.gz
  • Upload date:
  • Size: 56.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for envrcctl-0.2.0.tar.gz
Algorithm Hash digest
SHA256 3560d97038001d7daacd82f5817fafa3fbe05c933f51df612248b830db75bab3
MD5 5f13cd0ba2d5fba150f5f84deb02829e
BLAKE2b-256 976acd022d2924056817bf6d04518a6ceb4a688bf91fce4be71e1b7f9aaac05a

See more details on using hashes here.

File details

Details for the file envrcctl-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: envrcctl-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 56.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for envrcctl-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 51d55bc14109e1ccd72432039d493eda676d9eb9bc81bba3dd3ba86b32d77c23
MD5 3e31e67a5ff060959f2d0ca16dc8bcd9
BLAKE2b-256 863243066133047934bc6021c9ed09519758e942f74e496b6fbcd7c560849580

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page