https://github.com/dotsecenv/dotsecenv
safe environment secrets
https://github.com/dotsecenv/dotsecenv
bash environment-variables fish secure-coding zsh
Last synced: 11 days ago
JSON representation
safe environment secrets
- Host: GitHub
- URL: https://github.com/dotsecenv/dotsecenv
- Owner: dotsecenv
- License: apache-2.0
- Created: 2025-12-14T22:11:06.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-01-11T00:57:19.000Z (16 days ago)
- Last Synced: 2026-01-11T05:53:34.923Z (16 days ago)
- Topics: bash, environment-variables, fish, secure-coding, zsh
- Language: Go
- Homepage: https://dotsecenv.com
- Size: 248 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# dotsecenv: safe environment secrets
[](https://github.com/dotsecenv/dotsecenv/actions/workflows/ci.yml)
[](https://github.com/dotsecenv/dotsecenv/actions/workflows/release.yml)
[](https://github.com/dotsecenv/dotsecenv/actions/workflows/action-e2e.yml)
[](https://github.com/dotsecenv/packages/actions/workflows/publish.yml)
[](https://github.com/dotsecenv/homebrew-tap/actions/workflows/post-release.yml)
[](https://github.com/dotsecenv/plugin/actions/workflows/ci.yml)
[](https://github.com/dotsecenv/website/actions/workflows/deploy-website.yml)
A complete Go CLI application for securely managing environment secrets with GPG-based encryption, multi-user support, and FIPS 186-5 compliant algorithm defaults.
## Quick Start
### Store your first secret
```bash
# After installation (see below)
dotsecenv init config
dotsecenv init vault -v ~/.local/share/dotsecenv/vault
dotsecenv login # 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)
```bash
mise use github:dotsecenv/dotsecenv
```
### MacOS/Homebrew
```bash
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](https://get.dotsecenv.com).
#### Binary Download
Download the latest release for your platform from the [Releases page](https://github.com/dotsecenv/dotsecenv/releases).
#### Linux Packages (.deb / .rpm / .archlinux)
Download the appropriate package from the [Releases page](https://github.com/dotsecenv/dotsecenv/releases) and install it:
**Debian/Ubuntu:**
```bash
sudo dpkg -i dotsecenv_amd64.deb
```
**RHEL/CentOS/Fedora:**
```bash
sudo rpm -i dotsecenv_amd64.rpm
```
**Arch Linux:**
```bash
sudo pacman -U dotsecenv_amd64.pkg.tar.zst
```
#### Windows
> **NOTICE:** dotsecenv on Windows is currently WIP. You can follow [this issue](https://github.com/dotsecenv/dotsecenv/issues/8) for updates.
Download the `.zip` file for your architecture from the [Releases page](https://github.com/dotsecenv/dotsecenv/releases):
- `dotsecenv_vX.X.X_Windows_x86_64.zip` for 64-bit Intel/AMD
- `dotsecenv_vX.X.X_Windows_arm64.zip` for ARM64
Extract and add the binary location to your PATH.
**GPG Requirement**: Install [Gpg4win](https://www.gpg4win.org/) for GPG support. If GPG is not in your PATH, `dotsecenv init config` will attempt to detect it automatically, or you can set `gpg_program` in your config file.
#### Build from Source
```bash
# 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:
```yaml
- 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](#security-features) 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):**
```yaml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: dotsecenv/dotsecenv@v0
- run: dotsecenv secret get DATABASE_URL
```
**Pin to a specific version:**
```yaml
- uses: dotsecenv/dotsecenv@v0
with:
version: v0.0.1
```
**Build from source:**
```yaml
- uses: dotsecenv/dotsecenv@v0
with:
build-from-source: true
```
### Shell Completions
dotsecenv supports shell completions for Bash, Zsh, Fish, and PowerShell.
#### Bash
Requires the `bash-completion` package:
```bash
# 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`:
```bash
# 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`:
```zsh
# dotsecenv completions
if command -v dotsecenv &> /dev/null; then
eval "$(dotsecenv completion zsh)"
fi
```
#### Fish
Add to `~/.config/fish/config.fish`:
```fish
# dotsecenv completions
if command -v dotsecenv &> /dev/null
dotsecenv completion fish | source
end
```
#### PowerShell
Add to your PowerShell profile (`$PROFILE`):
```powershell
# dotsecenv completions
if (Get-Command dotsecenv -ErrorAction SilentlyContinue) {
dotsecenv completion powershell | Out-String | Invoke-Expression
}
```
#### 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.:
```env
A_SECRET={dotsecenv}
ANOTHER_SECRET={dotsecenv/SOME_OTHER_KEY}
MY_NAMESPACED_SECRET={dotsecenv/my::SECRET}
```
The three keys will be available as environment variables, when cd-ing into `/path/to/project/`.
#### Install shell plugins
You can install zsh/bash/fish plugins with:
```bash
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](https://github.com/dotsecenv/plugin#installation).
### Basic Usage
```bash
# 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 ...
## Skip GPG detection (for systems without GPG installed)
dotsecenv init config --no-gpg-program
## Set GPG program path explicitly (without validation)
dotsecenv init config --gpg-program /usr/local/bin/gpg
# 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
# 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 (auto-adds identity if needed)
dotsecenv secret share MY_SECRET
# Revoke access to a secret
dotsecenv secret revoke MY_SECRET
# Describe vaults with identities and secrets
dotsecenv vault describe
dotsecenv vault describe --json
# Run health checks on vaults and environment
dotsecenv vault doctor
dotsecenv vault doctor --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 [--gpg-program\|--no-gpg-program]` | 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 describe [--json]` | Describe vaults with identities and secrets |
| `vault doctor [--json]` | Run health checks and fix issues |
| `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 AES-256-GCM (RFC 9580)
- **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:
1. **`-c` flag** (highest priority): Explicitly specify a config file path
2. **`DOTSECENV_CONFIG` environment variable**: Override the default location
3. **XDG default**: `$XDG_CONFIG_HOME/dotsecenv/config` (typically `~/.config/dotsecenv/config`)
4. **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:
```yaml
# Default configuration uses FIPS 186-5 compliant algorithm minimums
approved_algorithms:
- algo: ECC
curves:
- P-384
- P-521
min_bits: 384
- algo: EdDSA
curves:
- Ed25519
- Ed448
min_bits: 255
- algo: RSA
min_bits: 2048
vault:
- /path/to/vault1
strict: false
gpg:
program: gpg # Path to GPG executable
```
### GPG Configuration
The `gpg.program` option specifies the path to the GPG executable. The behavior depends on whether the value is specified and whether strict mode is enabled:
**Resolution order:**
1. **Explicit configuration**: If `gpg.program` is set, it must be an absolute path to an existing, executable program
2. **PATH inference**: If `gpg.program` is not set (or empty), dotsecenv will look up `gpg` from your system PATH and print a warning to stderr
3. **Strict mode**: In strict mode (`strict: true`), `gpg.program` must be explicitly configured - PATH inference is not allowed
**Examples:**
```yaml
# Explicit path (recommended for production/strict mode)
gpg:
program: /usr/bin/gpg
# Not specified - will infer from PATH with a warning
gpg:
program: ""
# Windows with Gpg4win
gpg:
program: "C:\\Program Files (x86)\\GnuPG\\bin\\gpg.exe"
```
**Automatic detection**: When running `dotsecenv init config`, dotsecenv will detect available GPG installations and set `gpg.program` to the detected absolute path. If multiple GPG installations are found, you'll be prompted to choose one.
**When to use explicit paths:**
- In strict mode (required)
- When you have multiple GPG versions installed
- When GPG is installed in a non-standard location
- In CI/CD environments where PATH may vary
## 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):**
```json
{
"version": 1,
"identities": [
["FINGERPRINT1", 2],
["FINGERPRINT2", 3]
],
"secrets": { "SECRET_KEY": { "secret": 4, "values": [5, 6] } }
}
```
**Identity Entry:**
```json
{
"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:**
```json
{
"type": "secret",
"data": {
"added_at": "2024-11-09T12:05:00Z",
"hash": "sha256:...",
"key": "DATABASE_URL",
"signature": "base64...",
"signed_by": "1E378219F90018AB2102B2131C238966B12A6F21"
}
}
```
**Secret Value Entry:**
```json
{
"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
- **[RFC 9580](https://www.rfc-editor.org/rfc/rfc9580.html) OpenPGP compliance**: Modern OpenPGP standard with mandatory AEAD encryption
- **AES-256-GCM symmetric encryption**: NIST-approved authenticated encryption ([SP 800-38D](https://csrc.nist.gov/pubs/sp/800/38/d/final))
- **[FIPS 186-5](https://csrc.nist.gov/pubs/fips/186-5/final) digital signatures**: RSA, ECDSA, and EdDSA signature schemes for vault entry authenticity and non-repudiation
- **FIPS 186-5 compliant defaults**: Algorithm minimums meet the Digital Signature Standard requirements
- **FIPS 140-3 Crypto**: Release binaries are built with Go's native FIPS 140-3 cryptographic module (`GOFIPS140=v1.0.0`) for NIST-validated cryptographic primitives on all platforms
- Multi-recipient PGP encryption with hybrid cryptography
- Hash-based integrity checking (SHA-256/SHA-512)
- GPG agent integration for secure key management
- Full secret encryption/decryption lifecycle
- Validation logic with optional auto-fix
- SUID mode restrictions for elevated privilege protection
- [SLSA Build Level 3](https://slsa.dev/spec/v1.2/build-requirements): Release binaries include verifiable provenance attestations generated via GitHub's [attest-build-provenance](https://github.com/actions/attest-build-provenance) action on hardened GitHub-hosted runners
### SUID Mode Restrictions
When running with SUID privileges, the following restrictions apply:
- `-c` and `-v` flags are blocked
- `DOTSECENV_CONFIG` and `DOTSECENV_FINGERPRINT` environment variables are ignored
- Config defaults to `/etc/dotsecenv/config`
- Write operations are blocked: `login`, `init config`, `init vault`, `secret put`, `secret share`, `secret revoke`
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`) |
## Known Limitations
### Ed448 Keys (GnuPG v5 Format)
Ed448 keys generated by GnuPG 2.4+ use the OpenPGP v5 key format, which is not yet fully supported by the underlying cryptographic library (gopenpgp/go-crypto). This results in a parsing error when trying to add Ed448 identities:
```shell
failed to get public key: failed to parse public key: gopenpgp: error in reading key ring: openpgp: invalid data: first packet was not a public/private key
```
**Workaround**: Use Ed25519 keys instead, which are fully supported and provide equivalent security for most use cases. Ed25519 keys use the OpenPGP v4 format which has full library support.
**Status**: This limitation will be resolved when:
- GnuPG adopts RFC 9580 v6 format for Ed448 keys, OR
- go-crypto adds compatibility for GnuPG's v5 Ed448 format
Ed448 is included in the approved algorithms configuration to ensure readiness when support becomes available.
## FAQ
### How do I generate a GPG key?
```bash
# 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`:
```bash
# 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`):
```bash
export GPG_TTY=$(tty)
```
Or for fish shell, add the following to `~/.config/fish/config.fish`:
```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.
```shell
$ 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
### Setup
```bash
# Install all development tools (lefthook, golangci-lint, syft, goreleaser)
make install-tools
```
### Building
```bash
# Build with FIPS 140-3 crypto (no CGO required)
make build
```
The default `make build` uses Go's native FIPS 140-3 cryptographic module (`GOFIPS140=v1.0.0`), which provides NIST-validated cryptographic primitives on all platforms without requiring CGO. See [go.dev/blog/fips140](https://go.dev/blog/fips140) for details.
### Testing
```bash
# Run all tests
make test
# Run tests with race condition detection
make test-race
# Run end-to-end tests
make e2e
```
### Linting
```bash
# 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](https://github.com/releasetools/cli) simplifies this process:
```bash
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
1. **Private Keys**: Never commit GPG private keys to repositories
2. **Key Management**: Use gpg-agent with passphrase protection
3. **Vault Files**: Can be committed to git, technically safe in public repositories, but not recommended
4. **Multi-User Systems**: Use strong user isolation and file permissions
5. **Monitoring**: Audit all secret access in production environments
6. **Rotation**: Periodically rotate encryption keys by updating and then re-encrypting secrets
## License
Apache 2.0 License. See LICENSE file for details.
## Acknowledgments
- [SOPS](https://github.com/getsops/sops) for the idea of storing encrypted secrets alongside source code
- [ProtonMail gopenpgp](https://github.com/protonmail/gopenpgp) for PGP cryptography
- [Cobra](https://github.com/spf13/cobra) for CLI framework
- [Go standard library](https://golang.org/) for easy multi-platform functionality