README
¶
dotsecenv: Safe Environment Secrets
A complete Go CLI application for securely managing environment secrets with GPG-based encryption, multi-user support, and FIPS 140-3 compliance.
Quick Start
Store your first secret
# After installation (see below)
dotsecenv init config
dotsecenv init vault -v ~/.local/share/dotsecenv/vault
dotsecenv login <YOUR GPG FINGERPRINT> # gpg --list-public-keys
echo "xyz" | dotsecenv secret put TEST_SECRET
# Subsequently, you can decrypt secrets
# as long as you hold the corresponding secret key in GPG agent
dotsecenv secret get TEST_SECRET # should output "xyz"
Installation
Mise (universal)
mise use ubi:dotsecenv/dotsecenv
MacOS/Homebrew
brew tap dotsecenv/tap
brew install dotsecenv
Linux package managers
Package repositories for Debian/Ubuntu, RHEL/CentOS/Fedora, and Arch Linux are available at get.dotsecenv.com.
Binary Download
Download the latest release for your platform from the Releases page.
Linux Packages (.deb / .rpm / .archlinux)
Download the appropriate package from the Releases page and install it:
Debian/Ubuntu:
sudo dpkg -i dotsecenv_amd64.deb
RHEL/CentOS/Fedora:
sudo rpm -i dotsecenv_amd64.rpm
Arch Linux:
sudo pacman -U dotsecenv_amd64.pkg.tar.zst
Windows
Please open a GitHub issue if you need a Windows variant!
Build from Source
# Clone the repository
git clone https://github.com/dotsecenv/dotsecenv.git
cd dotsecenv
# Build using make
make build
# Binary will be at bin/dotsecenv
GitHub Action
Use the official GitHub Action to install dotsecenv in your CI/CD workflows:
- uses: dotsecenv/dotsecenv@v0
The action downloads the appropriate binary for your runner's architecture and verifies its integrity.
Inputs
Release binaries achieve SLSA Build Level 3 compliance with verified provenance attestations. Using build-from-source: true or verify-provenance: false bypasses these security guarantees and is generally NOT recommended.
| Input | Default | Description |
|---|---|---|
version |
latest |
Version to install (e.g., v1.2.3 or latest) |
build-from-source |
false |
Build from source instead of downloading a release |
verify-provenance |
true |
Verify GPG signatures, checksums, and attestations |
Outputs
| Output | Description |
|---|---|
version |
The version of dotsecenv that was installed |
binary-path |
Full path to the installed binary |
Examples
Basic usage (latest release):
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: dotsecenv/dotsecenv@v0
- run: dotsecenv secret get DATABASE_URL
Pin to a specific version:
- uses: dotsecenv/dotsecenv@v0
with:
version: v0.0.1
Build from source:
- uses: dotsecenv/dotsecenv@v0
with:
build-from-source: true
Shell Completions
dotsecenv supports shell completions for Bash, Zsh, and Fish.
Bash
Requires the bash-completion package:
# macOS
brew install bash-completion@2
# Debian/Ubuntu
sudo apt install bash-completion
# RHEL/CentOS/Fedora
sudo dnf install bash-completion
# Arch
sudo pacman -S bash-completion
Add to ~/.bashrc or ~/.bash_profile:
# Load bash-completion (macOS with Homebrew)
[[ -r "$(brew --prefix)/etc/profile.d/bash_completion.sh" ]] && . "$(brew --prefix)/etc/profile.d/bash_completion.sh"
# dotsecenv completions
if command -v dotsecenv &> /dev/null; then
eval "$(dotsecenv completion bash)"
fi
Zsh
Add to ~/.zshrc:
# dotsecenv completions
if command -v dotsecenv &> /dev/null; then
eval "$(dotsecenv completion zsh)"
fi
Fish
Add to ~/.config/fish/config.fish:
# dotsecenv completions
if command -v dotsecenv &> /dev/null
dotsecenv completion fish | source
end
Pre-installed Paths
If you installed via a package manager (Homebrew, deb, rpm, Arch), completions are pre-installed at these paths:
| Shell | Homebrew (macOS/Linux) | Linux Packages (deb/rpm/Arch) |
|---|---|---|
| Bash | $(brew --prefix)/etc/bash_completion.d/dotsecenv |
/usr/share/bash-completion/completions/dotsecenv |
| Zsh | $(brew --prefix)/share/zsh/site-functions/_dotsecenv |
/usr/share/zsh/site-functions/_dotsecenv |
| Fish | $(brew --prefix)/share/fish/vendor_completions.d/dotsecenv.fish |
/usr/share/fish/vendor_completions.d/dotsecenv.fish |
Shell Plugins
Shell plugins that automatically load .env and .secenv files when entering directories
are available for zsh, bash, and fish.
For example, given a /path/to/project/.secenv file, e.g.:
MY_SECRET={dotsecenv/my:secret}
MY_SECRET will be available as an environment variable, when cd-ing into /path/to/project/.
Install shell plugins
You can install zsh/bash/fish plugins with:
curl -fsSL https://raw.githubusercontent.com/dotsecenv/plugin/main/install.sh | bash
For plugin manager installation and additional details, see github.com/dotsecenv/plugin#installation.
Basic Usage
# Initialize configuration
## Create default config
dotsecenv init config
## Specify where to store the config
dotsecenv init config -c /path/to/config
## Initialize the config with a single vault
dotsecenv init config -v /path/to/vault
## Customize both the config and the vault location
dotsecenv init config -c ... -v ...
# Initialize a vault
## Interactive prompt, asking which vault to initialize
dotsecenv init vault
## Specify which vault to initialize
dotsecenv init vault -v /path/to/vault
# Login with your GPG fingerprint
dotsecenv login <FINGERPRINT>
# Add an identity to the vault
dotsecenv vault identity add <FINGERPRINT>
dotsecenv vault identity add <FINGERPRINT> --all # Add to all vaults
# Store a secret (reads value from stdin)
echo "secret-value" | dotsecenv secret put MY_SECRET
# Retrieve a secret
dotsecenv secret get MY_SECRET
dotsecenv secret get MY_SECRET --all # All values across all vaults
dotsecenv secret get MY_SECRET --last # Most recent value across all vaults
dotsecenv secret get MY_SECRET --json # Output as JSON
# Share a secret with another identity
dotsecenv secret share MY_SECRET <TARGET_FINGERPRINT>
# Revoke access to a secret
dotsecenv secret revoke MY_SECRET <TARGET_FINGERPRINT>
# List vaults and secrets
dotsecenv vault list
dotsecenv vault list --json
# List identities in vaults
dotsecenv vault identity list
dotsecenv vault identity list --json
# Validate vault and config
dotsecenv validate
dotsecenv validate --fix # Attempt to fix issues
Command Reference
Global Flags
| Flag | Short | Description |
|---|---|---|
--config |
-c |
Path to config file |
--vault |
-v |
Path to vault file or vault index (1-based) |
--silent |
-s |
Silent mode (suppress warnings) |
Commands
| Command | Description |
|---|---|
init config [--fips] |
Initialize configuration file |
init vault |
Initialize vault file(s) |
login FINGERPRINT |
Initialize user identity |
secret put SECRET |
Store an encrypted secret (reads from stdin) |
secret get SECRET [--all|--last|--json] |
Retrieve a secret value |
secret share SECRET FINGERPRINT [--all] |
Share a secret with another identity |
secret revoke SECRET FINGERPRINT [--all] |
Revoke access to a secret |
vault list [--json] |
List configured vaults and their secrets |
vault identity add FINGERPRINT [--all] |
Add an identity to vault(s) |
vault identity list [--json] |
List identities in configured vaults |
validate [--fix] |
Validate vault and config integrity |
version |
Show version information |
completion |
Generate shell completion scripts |
Features
- Explicit Initialization: Safe bootstrapping of configuration and vaults
- Encrypted at Rest: All secrets are encrypted using FIPS 140-3 compliant algorithms (AES-256-GCM)
- Multi-User Support: Secrets can be encrypted for multiple identities using GPG multi-recipient encryption
- Portable Vault: The vault file can be safely committed to git and shared between machines
- Secret Sharing: Share and revoke access to secrets with other team members
- Append-Only Design: Cryptographic history is preserved for audit trails
- GPG Agent Integration: Leverages gpg-agent for secure key management
- XDG Compliance: Respects XDG Base Directory Specification for configuration files
- SUID Mode Support: Restricted operations when running with elevated privileges
- JSON Output: Machine-readable output format for scripting
Configuration
Config File Resolution
The configuration file location is determined in the following order of precedence:
-cflag (highest priority): Explicitly specify a config file pathDOTSECENV_CONFIGenvironment variable: Override the default location- XDG default:
$XDG_CONFIG_HOME/dotsecenv/config(typically~/.config/dotsecenv/config) - SUID mode:
/etc/dotsecenv/config(when running with elevated privileges)
When both -c and DOTSECENV_CONFIG are specified, the -c flag takes precedence and a warning is printed to stderr (unless -s silent mode is enabled):
warning: DOTSECENV_CONFIG environment variable ignored because -c flag was specified
In SUID mode, the DOTSECENV_CONFIG environment variable is ignored for security reasons.
Config File Format
Example config:
approved_algorithms:
- algo: ECC
curves:
- P-256
- P-384
- P-521
min_bits: 256
- algo: EdDSA
curves:
- Ed25519
min_bits: 255
- algo: RSA
min_bits: 1024
vault:
- /path/to/vault1
strict: false
FIPS Mode
Initialize with FIPS 140-3 compliant algorithms only:
dotsecenv init config --fips
This enforces stricter algorithm requirements:
- RSA: minimum 3072 bits
- ECC: minimum P-384 curve (384 bits)
Vault File Format
Default vault location: $XDG_DATA_HOME/dotsecenv/vault
The vault uses a JSONL (JSON Lines) format for efficient append operations and indexed lookups. Each entry includes a hash and cryptographic signature to prevent against tampering.
Header (Line 1):
{
"version": 1,
"identities": [
["FINGERPRINT1", 2],
["FINGERPRINT2", 3]
],
"secrets": { "SECRET_KEY": { "secret": 4, "values": [5, 6] } }
}
Identity Entry:
{
"type": "identity",
"data": {
"added_at": "2024-11-09T12:00:00Z",
"algorithm": "ECC",
"algorithm_bits": 521,
"curve": "P-521",
"created_at": "2024-10-01T10:00:00Z",
"fingerprint": "1E378219F90018AB2102B2131C238966B12A6F21",
"hash": "sha256:...",
"public_key": "base64...",
"signed_by": "1E378219F90018AB2102B2131C238966B12A6F21",
"signature": "base64...",
"uid": "user@example.com"
}
}
Secret Definition Entry:
{
"type": "secret",
"data": {
"added_at": "2024-11-09T12:05:00Z",
"hash": "sha256:...",
"key": "DATABASE_URL",
"signature": "base64...",
"signed_by": "1E378219F90018AB2102B2131C238966B12A6F21"
}
}
Secret Value Entry:
{
"type": "value",
"secret": "DATABASE_URL",
"data": {
"added_at": "2024-11-09T12:05:00Z",
"available_to": ["1E378219F90018AB2102B2131C238966B12A6F21"],
"hash": "sha256:...",
"signature": "base64...",
"signed_by": "1E378219F90018AB2102B2131C238966B12A6F21",
"value": "base64-encrypted-value"
}
}
Security Features
- FIPS 140-3 algorithm enforcement (if desired)
- Multi-recipient PGP encryption
- Detached signatures for identity and secret verification
- Hash-based integrity checking
- GPG agent integration for key management
- Full secret encryption/decryption lifecycle
- Validation logic with optional auto-fix
- SUID mode restrictions for elevated privilege protection
- SLSA Build Level 3: Release binaries include verifiable provenance attestations generated via GitHub's attest-build-provenance action on hardened GitHub-hosted runners
SUID Mode Restrictions
When running with SUID privileges, the following restrictions apply:
-cand-vflags are blockedDOTSECENV_CONFIGandDOTSECENV_FINGERPRINTenvironment variables are ignored- Config defaults to
/etc/dotsecenv/config - Write operations are blocked:
login,init config,init vault,secret put,secret share,secret revoke,vault identity add
This prevents privilege escalation attacks when the binary is installed with elevated permissions.
Exit Codes
| Code | Name | Description |
|---|---|---|
0 |
Success | Operation completed successfully |
1 |
General Error | Unspecified error |
2 |
Config Error | Configuration file issue |
3 |
Vault Error | Vault file issue |
4 |
GPG Error | GPG operation failed |
5 |
Auth Error | Authentication failed |
6 |
Validation Error | Validation failed |
7 |
Fingerprint Required | No fingerprint configured |
8 |
Access Denied | Permission denied |
9 |
Algorithm Not Allowed | Algorithm not in allow-list |
Environment Variables
| Variable | Description |
|---|---|
DOTSECENV_CONFIG |
Override config file path (ignored in SUID mode) |
DOTSECENV_FINGERPRINT |
Override fingerprint from config (ignored in SUID mode) |
XDG_CONFIG_HOME |
Override config directory (defaults to: ~/.config) |
XDG_DATA_HOME |
Override data directory (defaults to: ~/.local/share) |
FAQ
How do I generate a GPG key?
# Generate a new GPG key and choose sensible defaults, i.e.:
# - (9) ECC (sign and encrypt)
# - (1) Curve 25519
# - Key expiration: 1y
gpg --full-generate-key
agent_genkey failed: No pinentry
If you are unable to generate a key due to this error, install pinentry:
# macOS
brew install pinentry-mac
# Linux
sudo apt-get install pinentry
sudo dnf install pinentry-tty
sudo yum install pinentry-tty
sudo pacman -S pinentry-tty
# etc.
In rare cases you may need to add a pinentry-program line to your ~/.gnupg/gpg-agent.conf and restart the gpg-agent (killall gpg-agent).
gpg: signing failed: Inappropriate ioctl for device
This error occurs when GPG cannot find the terminal for pinentry input. Add the following to your shell profile (~/.bashrc, ~/.zshrc):
export GPG_TTY=$(tty)
Or for fish shell, add the following to ~/.config/fish/config.fish:
set -gx GPG_TTY (tty)
Then restart your shell or run the command directly.
gpg: decryption failed
If you encounter this error, first try to define GPG_TTY (see above) before searching for other possible solutions.
$ dotsecenv secret get bla
failed to decrypt secret: failed to decrypt with gpg-agent: exit status 2
GPG error: gpg: decryption failed: No secret key
Development
Building
# Build the binary
make build
Testing
# Run all tests
make test
# Run tests with race condition detection
make test-race
# Run end-to-end tests
make e2e
Linting
# Run linting (installs golangci-lint if needed)
make lint
Releasing
Releases are triggered by pushing a signed semver tag. Following GitHub Actions conventions, a major version tag (e.g., v0) should also be maintained to allow users to pin to a major version.
The releasetools-cli simplifies this process:
rt git::release --major --sign --force --push v0.1.2
This creates both v0.1.2 and v0 tags pointing to the same commit, signs them, and pushes to the remote.
Security Considerations
What dotsecenv Protects Against
- Accidental exposure of unencrypted secrets in version control
- Secrets stored in plaintext on disk
- Access to secrets by unauthorized users without GPG keys
- Tampering with vault entries (signature verification)
- Privilege escalation via SUID binaries
What dotsecenv DOES NOT Protect Against
- Operating system-level access (root/admin can always read memory)
- Compromised GPG private keys
- Quantum computing attacks (future consideration)
- Side-channel attacks
- Physical memory dumps
- Environment snooping
Recommendations
- Private Keys: Never commit GPG private keys to repositories
- Key Management: Use gpg-agent with passphrase protection
- Vault Files: Can be committed to git, technically safe in public repositories, but not recommended
- Multi-User Systems: Use strong user isolation and file permissions
- Monitoring: Audit all secret access in production environments
- Rotation: Periodically rotate encryption keys by updating and then re-encrypting secrets
- FIPS Mode: Use
--fipsflag for environments requiring FIPS 140-3 compliance
License
Apache 2.0 License. See LICENSE file for details.
Acknowledgments
- SOPS for the idea of storing encrypted secrets alongside source code
- ProtonMail gopenpgp for PGP cryptography
- Cobra for CLI framework
- Go standard library for easy multi-platform functionality