π enbu
A .env management tool that works entirely within GitHub.
Why
Development requires sensitive information like API keys and database passwords, but existing approaches have problems:
- Slack/Discord/Email lack E2EE
- Confusing characters like
1, I, l and italic rendering cause copy-paste errors
- Every change requires notifying everyone manually
- Even if you encrypt: the delivery channel for the password or decryption key is often insecure
- Dedicated secret managers?
- External services come with cost and operational overhead
- AWS/Google Cloud/1Password require contracts and account management
- Significant organizational burden in both cost and operations
- Just commit it to Git!
- Ciphertext persists permanently in Git history
- Future algorithm weaknesses could allow retroactive decryption
Features
- GitHub-only β No dependency on external platforms
- E2E encrypted β Only each member's local private key can decrypt
- Simple CLI β After setup, just
enbu add and enbu pull
Install
go install github.com/enbu-net/enbu@latest
Or download a binary from Releases.
Quick Start
1. Authenticate
enbu auth login
Log in to GitHub.
For a headless environment, use enbu auth login --device and enter the displayed code on GitHub.
2. Initialize the repository
cd your-repo
enbu init
Run once per user per repository. This automatically:
- Generates an X25519 key pair
- Stores the private key in the OS keychain
- Registers the public key on GHCR
- Creates
enbu.toml
- Updates
.gitignore
3. Add or edit secrets
enbu add DATABASE_URL "postgres://..."
enbu add API_KEY "sk-..."
enbu edit API_KEY "sk-new..."
# Environment-specific secrets
enbu add --env dev DATABASE_URL "postgres://dev/..."
enbu add --env prod DATABASE_URL "postgres://prod/..."
add creates a new secret and fails if the key already exists. Use edit to update an existing secret.
4. Delete secrets
enbu delete API_KEY
5. Pull secrets
enbu pull # Writes to .env file
enbu pull --env dev # Writes to the configured output for dev
6. Add a team member
A new member runs enbu init inside the repository to enter join mode and register their public key.
An existing member then runs enbu sync locally to re-encrypt secrets for the new recipient.
Environments
Manage environments with enbu switch:
enbu switch -c dev # Create and switch to dev
enbu switch -c prod # Create and switch to prod
enbu switch dev # Switch to dev
enbu switch - # Switch back to previous
enbu switch -l # List environments
enbu switch -d staging # Delete an environment
enbu switch -m old new # Rename an environment
Define environments in enbu.toml:
version = "0.1"
default = "dev"
[env.dev]
output = ".env.dev"
[env.prod]
output = ".env.prod"
Use -e/--env with add, edit, delete, pull, and sync to override the current environment. Recipients are shared across all environments β access control is handled by OPA/Rego policy at sync time. Without -e, enbu uses the environment set by switch.
Key Storage
Private keys are stored in the OS secure storage:
| OS |
Backend |
| macOS |
Keychain |
| Linux |
Secret Service (GNOME Keyring / KWallet) |
| Windows |
Credential Manager |
For environments without a keychain (containers, headless servers), specify a fallback via environment variable:
export ENBU_BACKEND=text # Plaintext file (0600 permissions)
JSON output
Pass --json to any command when invoking enbu from a process such as a VS Code extension.
The command writes exactly one JSON value to stdout.
{"ok":true,"data":{"action":"add","environment":"dev","key":"API_KEY"},"warnings":[]}
{"ok":false,"error":{"message":"secret \"API_KEY\" already exists"}}
Successful commands exit with status 0.
Errors are also written to stdout as JSON and exit with status 1.
enbu pull --json does not write an .env file; it returns the decrypted secrets in data.secrets.
Do not log or persist this response.
enbu auth login --device --json is unsupported because Device Flow must display a code before authentication finishes.
Use enbu auth login --json for browser authentication.
How It Works
GHCR (ghcr.io/{owner}/{repo}-enbu)
βββ recipient-{user}-{fingerprint} β Public keys (shared across all environments)
βββ secrets-default β Encrypted secrets for default environment
βββ secrets-dev β Encrypted secrets for dev environment
enbu add β Creates a new secret, encrypts for all recipients' public keys, and pushes as an OCI image artifact
enbu edit β Updates an existing secret in the encrypted bundle and pushes the updated artifact
enbu delete β Removes a secret from the encrypted bundle and pushes the updated artifact
enbu pull β Pulls ciphertext, decrypts with your private key, writes to .env
enbu sync β Re-encrypts with the current recipient list when members are added or removed
Authentication & Initialization Flow
sequenceDiagram
participant User
participant CLI as enbu CLI
participant Auth as auth.enbu.net
participant GitHub as GitHub OAuth
participant GHCR
User->>CLI: enbu auth login
CLI->>CLI: Start 127.0.0.1 callback listener
CLI->>Auth: Create PKCE session
Auth-->>CLI: GitHub authorization URL
CLI-->>User: Open browser
User->>GitHub: Authorize in browser
GitHub-->>CLI: Authorization code via loopback callback
CLI->>Auth: Exchange code with PKCE verifier
Auth-->>CLI: Access token
CLI->>CLI: Store token in OS keychain
CLI-->>User: β Authenticated
User->>CLI: enbu init
CLI->>CLI: Generate age X25519 key pair
CLI->>CLI: Store private key in OS keychain
CLI->>GHCR: Register public key as recipient-{user}-{fingerprint}
Note over GHCR: Recipients are environment-independent
GHCR-->>CLI: Done
CLI-->>User: β Initialized
Secret Addition Flow
sequenceDiagram
participant User
participant CLI as enbu CLI
participant GHCR
User->>CLI: enbu add KEY VALUE
CLI->>GHCR: Fetch all recipient public keys
GHCR-->>CLI: Public key list
CLI->>CLI: Encrypt with age for all public keys
CLI->>GHCR: Push to secrets-default
GHCR-->>CLI: Done
CLI-->>User: β Secret added
Member Addition & Sync Flow
sequenceDiagram
participant New as New Member
participant Member as Existing Member
participant CLI as enbu CLI
participant GHCR
New->>CLI: enbu init (join mode)
CLI->>CLI: Generate age key pair
CLI->>GHCR: Register public key as recipient-{user}-{fingerprint}
CLI-->>New: β Key registered
Member->>CLI: enbu sync
CLI->>GHCR: Fetch all recipient public keys
GHCR-->>CLI: Public key list
CLI->>GHCR: Pull secrets-default
GHCR-->>CLI: Ciphertext
CLI->>CLI: Decrypt with private key β re-encrypt for all public keys
CLI->>GHCR: Update secrets-default
New->>CLI: enbu pull
CLI->>GHCR: Pull secrets-default
GHCR-->>CLI: Ciphertext
CLI->>CLI: Decrypt with private key
CLI-->>New: Write .env