{"id":34743217,"url":"https://github.com/dotsecenv/dotsecenv","last_synced_at":"2026-05-15T01:15:33.490Z","repository":{"id":329820812,"uuid":"1116454799","full_name":"dotsecenv/dotsecenv","owner":"dotsecenv","description":"safe environment secrets","archived":false,"fork":false,"pushed_at":"2026-03-28T14:52:33.000Z","size":2806,"stargazers_count":3,"open_issues_count":3,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-28T15:25:07.295Z","etag":null,"topics":["bash","environment-variables","fish","secure-coding","zsh"],"latest_commit_sha":null,"homepage":"https://dotsecenv.com","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dotsecenv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-14T22:11:06.000Z","updated_at":"2026-03-28T11:57:54.000Z","dependencies_parsed_at":"2026-01-30T03:00:30.007Z","dependency_job_id":null,"html_url":"https://github.com/dotsecenv/dotsecenv","commit_stats":null,"previous_names":["dotsecenv/dotsecenv"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/dotsecenv/dotsecenv","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotsecenv%2Fdotsecenv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotsecenv%2Fdotsecenv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotsecenv%2Fdotsecenv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotsecenv%2Fdotsecenv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dotsecenv","download_url":"https://codeload.github.com/dotsecenv/dotsecenv/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotsecenv%2Fdotsecenv/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31232811,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-31T09:14:28.471Z","status":"ssl_error","status_checked_at":"2026-03-31T09:14:19.506Z","response_time":111,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","environment-variables","fish","secure-coding","zsh"],"created_at":"2025-12-25T04:27:14.021Z","updated_at":"2026-05-15T01:15:33.459Z","avatar_url":"https://github.com/dotsecenv.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dotsecenv: safe environment secrets\n\n[![CI](https://github.com/dotsecenv/dotsecenv/actions/workflows/ci.yml/badge.svg)](https://github.com/dotsecenv/dotsecenv/actions/workflows/ci.yml)\n[![Release](https://github.com/dotsecenv/dotsecenv/actions/workflows/release.yml/badge.svg)](https://github.com/dotsecenv/dotsecenv/actions/workflows/release.yml)\n[![GitHub Action E2E](https://github.com/dotsecenv/dotsecenv/actions/workflows/action-e2e.yml/badge.svg)](https://github.com/dotsecenv/dotsecenv/actions/workflows/action-e2e.yml)\n[![Contrib E2E](https://github.com/dotsecenv/dotsecenv/actions/workflows/contrib-e2e.yml/badge.svg)](https://github.com/dotsecenv/dotsecenv/actions/workflows/contrib-e2e.yml)\n[![Hermetic E2E](https://github.com/dotsecenv/dotsecenv/actions/workflows/hermetic-e2e.yml/badge.svg)](https://github.com/dotsecenv/dotsecenv/actions/workflows/hermetic-e2e.yml)\n[![Publish Packages](https://github.com/dotsecenv/packages/actions/workflows/publish.yml/badge.svg)](https://github.com/dotsecenv/packages/actions/workflows/publish.yml)\n[![Homebrew install](https://github.com/dotsecenv/homebrew-tap/actions/workflows/post-release.yml/badge.svg)](https://github.com/dotsecenv/homebrew-tap/actions/workflows/post-release.yml)\n[![Shell plugins CI](https://github.com/dotsecenv/plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/dotsecenv/plugin/actions/workflows/ci.yml)\n[![Publish Website](https://github.com/dotsecenv/website/actions/workflows/deploy-website.yml/badge.svg)](https://github.com/dotsecenv/website/actions/workflows/deploy-website.yml)\n\nA complete Go CLI application for securely managing environment secrets with GPG-based encryption, multi-user support, and FIPS 186-5 compliant algorithm defaults.\n\n## Quick Start\n\n### Store your first secret\n\n```bash\n# After installation (see below)\ndotsecenv init config\ndotsecenv init vault -v ~/.local/share/dotsecenv/vault\ndotsecenv login \u003cYOUR GPG FINGERPRINT\u003e # gpg --list-public-keys\necho \"xyz\" | dotsecenv secret store TEST_SECRET\n# Subsequently, you can decrypt secrets\n# as long as you hold the corresponding secret key in GPG agent\ndotsecenv secret get TEST_SECRET # should output \"xyz\"\n```\n\n\u003e **Encryption defaults.** Vaults use **AES-256-GCM** symmetric\n\u003e encryption (RFC 9580 / NIST SP 800-38D) wrapped in GPG\n\u003e multi-recipient asymmetric encryption. FIPS-compliant defaults are\n\u003e applied out of the box, with no extra flags at `init` time.\n\u003e To enforce specific algorithms across a team (or narrow them\n\u003e further), drop a `policy.d/*.yaml` fragment with\n\u003e `approved_algorithms`; see [example 04](./examples/04-policy-directory/).\n\n### Common recipes\n\nShort, self-contained how-tos for the workflows beyond the quickstart:\n\n- [Add a secret (and the append-only audit trail)](./recipes/add-secret.md)\n- [Migrate from a `.env` file](./recipes/migrate-from-dotenv.md)\n- [Rotate a compromised GPG key](./recipes/rotate-compromised-key.md)\n\n### Installation\n\n#### Install Script (recommended)\n\nThe universal install script (`install.sh`) is the fastest way to install dotsecenv on macOS and Linux. It auto-detects your platform, downloads the correct binary, verifies checksums and GPG signatures, and installs shell completions, man pages, the shell plugin, and the Terraform credentials helper automatically.\n\n##### Usage\n\n```bash\n# Piped (simplest)\ncurl -fsSL https://get.dotsecenv.com/install.sh | bash\n\n# Piped with CLI flags\ncurl -fsSL https://get.dotsecenv.com/install.sh | bash -s -- [OPTIONS]\n\n# Piped with environment variables\nVERSION=v1.2.3 curl -fsSL https://get.dotsecenv.com/install.sh | bash\n\n# Downloaded and run locally\ncurl -fsSL https://get.dotsecenv.com/install.sh -o install.sh\nchmod +x install.sh\n./install.sh --version v1.2.3\n```\n\nThe script works with either `curl` or `wget`.\n\n##### Options\n\nEvery setting can be configured via a CLI flag or an environment variable. **Precedence order:** CLI flags \u003e environment variables \u003e defaults.\n\n| Setting | CLI Flag | Env Var | Default | Description |\n|---------|----------|---------|---------|-------------|\n| Version | `--version VERSION` | `VERSION` | `latest` | Version to install (e.g., `v1.2.3`) |\n| Install directory | `--install-dir DIR` | `INSTALL_DIR` | `~/.local/bin` | Binary install path |\n| System-wide install | `--system` | `SYSTEM_INSTALL` | `0` (no) | Install to system paths (`/usr/local/bin` on macOS, `/usr/local/bin` on Linux) instead of user-level paths |\n| Shell plugin | `--[no-]install-shell-plugin` | `INSTALL_SHELL_PLUGIN` | `1` (yes) | Install the shell plugin for zsh/bash/fish |\n| Completions | `--[no-]install-completions` | `INSTALL_COMPLETIONS` | `1` (yes) | Install shell completions for bash, zsh, and fish |\n| Man pages | `--[no-]install-man-pages` | `INSTALL_MAN_PAGES` | `1` (yes) | Install man pages |\n| TF credentials helper | `--[no-]install-tf-credentials-helper` | `INSTALL_TF_CREDENTIALS_HELPER` | `1` (yes) | Install the Terraform credentials helper |\n| Verification | `--[no-]verify` | `VERIFY` | `1` (yes) | Verify SHA-256 checksums and GPG signatures |\n| Help | `-h`, `--help` | — | — | Show help and exit |\n\n##### What the Installer Does\n\n1. **Detects your platform** — OS (Linux or macOS) and architecture (x86_64 or arm64)\n2. **Resolves the version** — Queries the GitHub API for the latest release, or uses the version you specified\n3. **Downloads the release archive** — From GitHub Releases\n4. **Verifies integrity** — SHA-256 checksum verification and GPG signature verification (when `gpg` is available)\n5. **Installs the binary** — To `~/.local/bin` by default; pass `--system` to install to `/usr/local/bin` instead (uses `sudo` when needed)\n6. **Installs shell completions** — For bash, zsh, and fish, placed under `~/.local/share/` by default (or system directories with `--system`)\n7. **Installs man pages** — To `~/.local/share/man/man1/` by default (or `/usr/local/share/man/man1/` / `/usr/share/man/man1/` with `--system`)\n8. **Installs the shell plugin** — Auto-detects your plugin manager (Oh My Zsh, Zinit, Antidote, Oh My Bash, Fisher, Oh My Fish) and installs accordingly. Falls back to cloning to `~/.local/share/dotsecenv/plugin` with manual sourcing instructions.\n9. **Installs the Terraform credentials helper** — To `~/.terraform.d/plugins/` (skip with `--no-install-tf-credentials-helper`)\n\n##### Examples\n\n**CI/CD: Install specific version, skip interactive components:**\n\n```bash\nVERSION=v1.2.3 INSTALL_SHELL_PLUGIN=0 INSTALL_MAN_PAGES=0 \\\n  curl -fsSL https://get.dotsecenv.com/install.sh | bash\n```\n\n**Custom install directory:**\n\n```bash\ncurl -fsSL https://get.dotsecenv.com/install.sh | bash -s -- \\\n  --install-dir /opt/dotsecenv/bin\n```\n\n**System-wide install:**\n\n```bash\ncurl -fsSL https://get.dotsecenv.com/install.sh | bash -s -- --system\n```\n\n**Minimal install (binary only):**\n\n```bash\ncurl -fsSL https://get.dotsecenv.com/install.sh | bash -s -- \\\n  --no-install-shell-plugin \\\n  --no-install-completions \\\n  --no-install-man-pages \\\n  --no-install-tf-credentials-helper\n```\n\n**Download and inspect before running:**\n\n```bash\ncurl -fsSL https://get.dotsecenv.com/install.sh -o install.sh\nless install.sh  # review the script\nbash install.sh --version v1.2.3\n```\n\n#### Mise\n\n```bash\nmise use github:dotsecenv/dotsecenv\n```\n\n#### macOS (Homebrew)\n\n```bash\nbrew tap dotsecenv/tap\nbrew install dotsecenv\n```\n\n#### Linux Package Managers\n\nPackage repositories for Debian/Ubuntu, RHEL/CentOS/Fedora, and Arch Linux are available at [get.dotsecenv.com](https://get.dotsecenv.com).\n\n#### Binary Download\n\nDownload the latest release for your platform from the [Releases page](https://github.com/dotsecenv/dotsecenv/releases).\n\n#### Linux Packages (.deb / .rpm / .archlinux)\n\nDownload the appropriate package from the [Releases page](https://github.com/dotsecenv/dotsecenv/releases) and install it:\n\n**Debian/Ubuntu:**\n\n```bash\nsudo dpkg -i dotsecenv_amd64.deb\n```\n\n**RHEL/CentOS/Fedora:**\n\n```bash\nsudo rpm -i dotsecenv_amd64.rpm\n```\n\n**Arch Linux:**\n\n```bash\nsudo pacman -U dotsecenv_amd64.pkg.tar.zst\n```\n\n#### Windows\n\n\u003e **NOTICE:** dotsecenv on Windows is currently WIP. You can follow [this issue](https://github.com/dotsecenv/dotsecenv/issues/8) for updates.\n\nDownload the `.zip` file for your architecture from the [Releases page](https://github.com/dotsecenv/dotsecenv/releases):\n\n- `dotsecenv_vX.X.X_Windows_x86_64.zip` for 64-bit Intel/AMD\n- `dotsecenv_vX.X.X_Windows_arm64.zip` for ARM64\n\nExtract and add the binary location to your PATH.\n\n**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.\n\n#### Build from Source\n\n```bash\n# Clone the repository\ngit clone https://github.com/dotsecenv/dotsecenv.git\ncd dotsecenv\n\n# Build using make\nmake build\n\n# Binary will be at bin/dotsecenv\n```\n\n### GitHub Action\n\nUse the official GitHub Action to install `dotsecenv` in your CI/CD workflows:\n\n```yaml\n- uses: dotsecenv/dotsecenv@v0\n```\n\nThe action downloads the appropriate binary for your runner's architecture and verifies its integrity.\n\n#### Inputs\n\nRelease 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.\n\n| Input               | Default  | Description                                        |\n| ------------------- | -------- | -------------------------------------------------- |\n| `version`           | `latest` | Version to install (e.g., `v1.2.3` or `latest`)    |\n| `build-from-source` | `false`  | Build from source instead of downloading a release |\n| `verify-provenance` | `true`   | Verify GPG signatures, checksums, and attestations |\n\n#### Outputs\n\n| Output        | Description                                 |\n| ------------- | ------------------------------------------- |\n| `version`     | The version of dotsecenv that was installed |\n| `binary-path` | Full path to the installed binary           |\n\n#### Examples\n\n**Basic usage (latest release):**\n\n```yaml\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: dotsecenv/dotsecenv@v0\n      - run: dotsecenv secret get DATABASE_URL\n```\n\n**Pin to a specific version:**\n\n```yaml\n- uses: dotsecenv/dotsecenv@v0\n  with:\n    version: v0.0.1\n```\n\n**Build from source:**\n\n```yaml\n- uses: dotsecenv/dotsecenv@v0\n  with:\n    build-from-source: true\n```\n\n### Shell Completions\n\ndotsecenv supports shell completions for Bash, Zsh, Fish, and PowerShell.\n\n#### Bash\n\nRequires the `bash-completion` package:\n\n```bash\n# macOS\nbrew install bash-completion@2\n\n# Debian/Ubuntu\nsudo apt install bash-completion\n\n# RHEL/CentOS/Fedora\nsudo dnf install bash-completion\n\n# Arch\nsudo pacman -S bash-completion\n```\n\nAdd to `~/.bashrc` or `~/.bash_profile`:\n\n```bash\n# Load bash-completion (macOS with Homebrew)\n[[ -r \"$(brew --prefix)/etc/profile.d/bash_completion.sh\" ]] \u0026\u0026 . \"$(brew --prefix)/etc/profile.d/bash_completion.sh\"\n\n# dotsecenv completions\nif command -v dotsecenv \u0026\u003e /dev/null; then\n  eval \"$(dotsecenv completion bash)\"\nfi\n```\n\n#### Zsh\n\nAdd to `~/.zshrc`:\n\n```zsh\n# dotsecenv completions\nif command -v dotsecenv \u0026\u003e /dev/null; then\n  eval \"$(dotsecenv completion zsh)\"\nfi\n```\n\n#### Fish\n\nAdd to `~/.config/fish/config.fish`:\n\n```fish\n# dotsecenv completions\nif command -v dotsecenv \u0026\u003e /dev/null\n  dotsecenv completion fish | source\nend\n```\n\n#### PowerShell\n\nAdd to your PowerShell profile (`$PROFILE`):\n\n```powershell\n# dotsecenv completions\nif (Get-Command dotsecenv -ErrorAction SilentlyContinue) {\n  dotsecenv completion powershell | Out-String | Invoke-Expression\n}\n```\n\n#### Pre-installed Paths\n\nIf you installed via a package manager (Homebrew, deb, rpm, Arch), completions are pre-installed at these paths:\n\n| Shell | Homebrew (macOS/Linux)                                            | Linux Packages (deb/rpm/Arch)                         |\n| ----- | ----------------------------------------------------------------- | ----------------------------------------------------- |\n| Bash  | `$(brew --prefix)/etc/bash_completion.d/dotsecenv`                | `/usr/share/bash-completion/completions/dotsecenv`    |\n| Zsh   | `$(brew --prefix)/share/zsh/site-functions/_dotsecenv`            | `/usr/share/zsh/site-functions/_dotsecenv`            |\n| Fish  | `$(brew --prefix)/share/fish/vendor_completions.d/dotsecenv.fish` | `/usr/share/fish/vendor_completions.d/dotsecenv.fish` |\n\n### Shell Plugins\n\nShell plugins that automatically load `.env` and `.secenv` files when entering directories\nare available for `zsh`, `bash`, and `fish`.\n\nFor example, given a `/path/to/project/.secenv` file, e.g.:\n\n```env\nA_SECRET={dotsecenv}\nANOTHER_SECRET={dotsecenv/SOME_OTHER_KEY}\nMY_NAMESPACED_SECRET={dotsecenv/my::SECRET}\n```\n\nThe three keys will be available as environment variables, when cd-ing into `/path/to/project/`.\n\n#### Install shell plugins\n\nYou can install zsh/bash/fish plugins with:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/dotsecenv/plugin/main/install.sh | bash\n```\n\nFor plugin manager installation and additional details, see [github.com/dotsecenv/plugin#installation](https://github.com/dotsecenv/plugin#installation).\n\n### Basic Usage\n\n```bash\n# Initialize configuration\n## Create default config\ndotsecenv init config\n## Specify where to store the config\ndotsecenv init config -c /path/to/config\n## Initialize the config with a single vault\ndotsecenv init config -v /path/to/vault\n## Customize both the config and the vault location\ndotsecenv init config -c ... -v ...\n## Pin GPG to an absolute path (default: gpg.program=PATH, resolved at runtime)\ndotsecenv init config --gpg-program /usr/local/bin/gpg\n\n# Initialize a vault\n## Interactive prompt, asking which vault to initialize\ndotsecenv init vault\n## Specify which vault to initialize\ndotsecenv init vault -v /path/to/vault\n\n# Login with your GPG fingerprint\ndotsecenv login \u003cFINGERPRINT\u003e\n\n# Store a secret (reads value from stdin)\necho \"secret-value\" | dotsecenv secret store MY_SECRET\n\n# Retrieve a secret\ndotsecenv secret get MY_SECRET\ndotsecenv secret get MY_SECRET --all   # All values across all vaults\ndotsecenv secret get MY_SECRET --last  # Most recent value across all vaults\ndotsecenv secret get MY_SECRET --json  # Output as JSON\n\n# Share a secret with another identity (auto-adds identity if needed)\ndotsecenv secret share MY_SECRET \u003cTARGET_FINGERPRINT\u003e\n\n# Revoke access to a secret\ndotsecenv secret revoke MY_SECRET \u003cTARGET_FINGERPRINT\u003e\n\n# Describe vaults with identities and secrets\ndotsecenv vault describe\ndotsecenv vault describe --json\n\n# Run health checks on vaults and environment\ndotsecenv vault doctor\ndotsecenv vault doctor --json\n\n# Validate vault and config\ndotsecenv validate\ndotsecenv validate --fix  # Attempt to fix issues\n```\n\n## Command Reference\n\n### Global Flags\n\n| Flag       | Short | Description                                 |\n| ---------- | ----- | ------------------------------------------- |\n| `--config` | `-c`  | Path to config file                         |\n| `--vault`  | `-v`  | Path to vault file or vault index (1-based) |\n| `--silent` | `-s`  | Silent mode (suppress warnings)             |\n\n### Commands\n\n| Command                                         | Description                                  |\n| ----------------------------------------------- | -------------------------------------------- |\n| `init config [--gpg-program PATH]`              | Initialize configuration file                |\n| `init vault`                                    | Initialize vault file(s)                     |\n| `login FINGERPRINT`                             | Initialize user identity                     |\n| `secret store SECRET`                           | Store an encrypted secret (reads from stdin) |\n| `secret get SECRET [--all\\|--last\\|--json]`     | Retrieve a secret value                      |\n| `secret share SECRET FINGERPRINT [--all]`       | Share a secret with another identity         |\n| `secret revoke SECRET FINGERPRINT [--all]`      | Revoke access to a secret                    |\n| `vault describe [--json]`                       | Describe vaults with identities and secrets  |\n| `vault doctor [--json]`                         | Run health checks and fix issues             |\n| `validate [--fix]`                              | Validate vault and config integrity          |\n| `version`                                       | Show version information                     |\n| `completion`                                    | Generate shell completion scripts            |\n\n## Features\n\n- **Explicit Initialization**: Safe bootstrapping of configuration and vaults\n- **Encrypted at Rest**: All secrets are encrypted using AES-256-GCM (RFC 9580)\n- **Multi-User Support**: Secrets can be encrypted for multiple identities using GPG multi-recipient encryption\n- **Portable Vault**: The vault file can be safely committed to git and shared between machines\n- **Secret Sharing**: Share and revoke access to secrets with other team members\n- **Append-Only Design**: Cryptographic history is preserved for audit trails\n- **GPG Agent Integration**: Leverages gpg-agent for secure key management\n- **XDG Compliance**: Respects XDG Base Directory Specification for configuration files\n- **JSON Output**: Machine-readable output format for scripting\n\n## Claude Code\n\nInstall the dotsecenv plugin for [Claude Code](https://claude.ai/claude-code):\n\n```\n/plugin marketplace add dotsecenv/dotsecenv\n/plugin install dotsecenv@dotsecenv\n```\n\nSee the [Claude Code guide](https://dotsecenv.com/guides/claude-code/) for details.\n\n## Configuration\n\n### Config File Resolution\n\nThe configuration file location is determined in the following order of precedence:\n\n1. **`-c` flag** (highest priority): Explicitly specify a config file path\n2. **`DOTSECENV_CONFIG` environment variable**: Override the default location\n3. **XDG default**: `$XDG_CONFIG_HOME/dotsecenv/config` (typically `~/.config/dotsecenv/config`)\n\nWhen 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):\n\n```\nwarning: DOTSECENV_CONFIG environment variable ignored because -c flag was specified\n```\n\n### Config File Format\n\nExample config:\n\n```yaml\n# Default configuration uses FIPS 186-5 compliant algorithm minimums\napproved_algorithms:\n  - algo: ECC\n    curves:\n      - P-384\n      - P-521\n    min_bits: 384\n  - algo: EdDSA\n    curves:\n      - Ed25519\n      - Ed448\n    min_bits: 255\n  - algo: RSA\n    min_bits: 2048\nvault:\n  - /path/to/vault1\ngpg:\n  program: gpg # Path to GPG executable\n```\n\n### GPG Configuration\n\nThe `gpg.program` option specifies the path to the GPG executable.\n\n**Resolution order:**\n\n1. **Explicit configuration**: If `gpg.program` is set, it must be an absolute path to an existing, executable program\n2. **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\n\n**Examples:**\n\n```yaml\n# Explicit path (recommended for production)\ngpg:\n  program: /usr/bin/gpg\n\n# Not specified - will infer from PATH with a warning\ngpg:\n  program: \"\"\n\n# Windows with Gpg4win\ngpg:\n  program: \"C:\\\\Program Files (x86)\\\\GnuPG\\\\bin\\\\gpg.exe\"\n```\n\n**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.\n\n**When to use explicit paths:**\n\n- When you have multiple GPG versions installed\n- When GPG is installed in a non-standard location\n- In CI/CD environments where PATH may vary\n\n## Policy Directory\n\nSystem administrators can drop YAML policy fragments into `/etc/dotsecenv/policy.d/`\nto constrain every user of the binary. User configs keep full local autonomy\nfor fields that policy doesn't touch.\n\nWhen the policy directory does not exist, no policy is enforced and the binary\nbehaves exactly as before (today's behavior is preserved). When the directory\nexists, fragments are loaded in lexical filename order and merged into an\neffective policy that constrains every user.\n\n### Supported policy fields\n\n```yaml\n# /etc/dotsecenv/policy.d/00-corp-baseline.yaml\n\n# Allow-list fields: cross-fragment union; user vs policy is intersection.\napproved_algorithms:\n  - algo: ECC\n    curves: [P-384, P-521]\n    min_bits: 384\n  - algo: EdDSA\n    curves: [Ed25519, Ed448]\n    min_bits: 255\n\napproved_vault_paths:\n  - /var/lib/dotsecenv/vault\n  - ~/.local/share/dotsecenv/vault\n  - ~/work/*/.dotsecenv/vault\n\n# Scalar fields: cross-fragment last-wins; policy overrides user.\nbehavior:\n  restrict_to_configured_vaults: true\n  require_explicit_vault_upgrade: true\n\ngpg:\n  program: /usr/bin/gpg\n```\n\n**Allow-list cross-fragment merge** (`approved_algorithms`,\n`approved_vault_paths`): union of all fragments' entries. For\n`approved_algorithms`, same-`algo` entries collapse — curves union and\n`min_bits` takes the minimum (most-permissive reconciliation). For\n`approved_vault_paths`, deduped union of patterns.\n\n**Allow-list user vs policy** (intersection): the user's `approved_algorithms`\nis intersected with the policy's; the user's `cfg.Vault` is filtered to\nentries matching at least one `approved_vault_paths` pattern. Policy is a\nceiling; users can be stricter locally; users cannot exceed policy. Dropped\nentries emit stderr warnings explaining what changed (suppressed by `-s`).\n\n**Vault path matching** uses `path/filepath.Match` (single-segment globs:\n`*`, `?`, `[abc]`); `~` expands to the user's home. There is no `**`\nrecursive globbing — admins enumerate explicitly or use single-segment\npatterns. Explicit `-v` flags are subject to the same filter and are\n*rejected* (not silently dropped) when not allowed by policy.\n\n**Scalar cross-fragment merge** (`behavior.*` per sub-field, `gpg.program`):\n**last-fragment-to-set wins** in lexical filename order, matching the Unix\n`*.d` drop-in convention used by `sudoers.d`, `systemd unit drop-ins`,\n`nginx conf.d`, etc. Naming: `00-base, 50-team, 99-overrides` — `99-`\noutranks `00-` for scalars. When a later fragment overrides an earlier\n*different* value, dotsecenv emits a stderr warning citing both:\n\n```\nwarning: policy conflict on gpg.program: overridden to \"/usr/bin/gpg\" by /etc/dotsecenv/policy.d/99-team.yaml; previous value \"/opt/homebrew/bin/gpg\" from /etc/dotsecenv/policy.d/00-corp-baseline.yaml\n```\n\nSame value across fragments emits no warning (it's redundant, not a\nconflict). For `behavior.*`, sub-fields are independent — fragment A\nsetting `behavior.X` and fragment B setting `behavior.Y` do not conflict.\n\n**Scalar user vs policy** (policy overrides user): if policy sets a scalar,\nthat value replaces the user's. When the user had a *different* value, a\nstderr warning surfaces the override:\n\n```\nwarning: policy overrides user gpg.program: user value \"/opt/homebrew/bin/gpg\" replaced by \"/usr/bin/gpg\" from /etc/dotsecenv/policy.d/00-corp-baseline.yaml\n```\n\nFor `behavior.*`, \"set\" detection uses pointer nil-ness (`*bool`): an\nomitted field is \"no opinion\"; `false` is a real value distinct from\nomitted. For `gpg.program`, empty string is \"no opinion\"; the `\"PATH\"`\nsentinel is a real value distinct from empty.\n\nConflict warnings and user-override warnings both respect the `-s` silent\nflag.\n\n### Fail-closed on broken policy\n\nAny error loading policy aborts startup. A partially-readable or malformed\npolicy directory is indistinguishable from tampering, so the only safe\nbehavior is to refuse to run. The error is classified by category and\nreturned with the matching exit code (see `policy validate` table below);\n`dotsecenv policy validate` and `dotsecenv policy list` continue to work\neven when policy is broken (so admins can diagnose).\n\n### Forbidden keys in policy fragments\n\nHard-rejected at load with an explicit error citing the offending fragment:\n\n- `login` — identity is per-user (cryptographically bound to a private key)\n- `vault` — would erase user vaults; use `approved_vault_paths` instead\n\n### Permissions\n\nThe policy directory and each fragment must be:\n\n- Owned by `root` (uid 0)\n- Mode `0644` or stricter (no group/other write bits)\n\ndotsecenv refuses to load any fragment that fails this check. Linux/macOS\nonly — Windows uses ACLs and policy is not yet supported there.\n\n### Inspecting policy\n\n```bash\n# Print the effective policy with per-field origin attribution\ndotsecenv policy list\ndotsecenv policy list --json\n\n# Validate fragment structure (useful in CI for ops repos shipping policy)\ndotsecenv policy validate\ndotsecenv policy validate --json   # error embedded in JSON object\n```\n\n`policy validate` exits with distinct codes per error category:\n\n| Code | Meaning |\n|------|---------|\n| 0    | No policy enforced or all fragments structurally valid |\n| 2    | Malformed YAML or forbidden key |\n| 8    | Insecure permissions or unreadable fragment |\n| 1    | Empty allow-list field (omit the field instead) |\n\n### Out of scope\n\n- Project-level `.dotsecenv/policy.yaml`\n- Remote/centralized policy distribution\n- Encrypted/signed policy fragments\n- Windows policy support (Linux/macOS only)\n- `**` recursive glob support in `approved_vault_paths`\n- Per-command policy re-evaluation for stale logins\n\n## Vault File Format\n\nDefault vault location: `$XDG_DATA_HOME/dotsecenv/vault`\n\nThe vault uses a JSONL (JSON Lines) format for efficient append operations and indexed lookups.\nEach entry includes a hash and cryptographic signature to prevent against tampering.\n\n**Header (Line 1):**\n\n```json\n{\n  \"version\": 1,\n  \"identities\": [\n    [\"FINGERPRINT1\", 2],\n    [\"FINGERPRINT2\", 3]\n  ],\n  \"secrets\": { \"SECRET_KEY\": { \"secret\": 4, \"values\": [5, 6] } }\n}\n```\n\n**Identity Entry:**\n\n```json\n{\n  \"type\": \"identity\",\n  \"data\": {\n    \"added_at\": \"2024-11-09T12:00:00Z\",\n    \"algorithm\": \"ECC\",\n    \"algorithm_bits\": 521,\n    \"curve\": \"P-521\",\n    \"created_at\": \"2024-10-01T10:00:00Z\",\n    \"fingerprint\": \"1E378219F90018AB2102B2131C238966B12A6F21\",\n    \"hash\": \"sha256:...\",\n    \"public_key\": \"base64...\",\n    \"signed_by\": \"1E378219F90018AB2102B2131C238966B12A6F21\",\n    \"signature\": \"base64...\",\n    \"uid\": \"user@example.com\"\n  }\n}\n```\n\n**Secret Definition Entry:**\n\n```json\n{\n  \"type\": \"secret\",\n  \"data\": {\n    \"added_at\": \"2024-11-09T12:05:00Z\",\n    \"hash\": \"sha256:...\",\n    \"key\": \"DATABASE_URL\",\n    \"signature\": \"base64...\",\n    \"signed_by\": \"1E378219F90018AB2102B2131C238966B12A6F21\"\n  }\n}\n```\n\n**Secret Value Entry:**\n\n```json\n{\n  \"type\": \"value\",\n  \"secret\": \"DATABASE_URL\",\n  \"data\": {\n    \"added_at\": \"2024-11-09T12:05:00Z\",\n    \"available_to\": [\"1E378219F90018AB2102B2131C238966B12A6F21\"],\n    \"hash\": \"sha256:...\",\n    \"signature\": \"base64...\",\n    \"signed_by\": \"1E378219F90018AB2102B2131C238966B12A6F21\",\n    \"value\": \"base64-encrypted-value\"\n  }\n}\n```\n\n## Security Features\n\n- **[RFC 9580](https://www.rfc-editor.org/rfc/rfc9580.html) OpenPGP compliance**: Modern OpenPGP standard with mandatory AEAD encryption\n- **AES-256-GCM symmetric encryption**: NIST-approved authenticated encryption ([SP 800-38D](https://csrc.nist.gov/pubs/sp/800/38/d/final))\n- **[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\n- **FIPS 186-5 compliant defaults**: Algorithm minimums meet the Digital Signature Standard requirements\n- **FIPS 140-3 Crypto**: Release binaries are built with Go's native FIPS 140-3 cryptographic module (`GOFIPS140=v1.26.0`) for NIST-validated cryptographic primitives on all platforms\n- Multi-recipient PGP encryption with hybrid cryptography\n- Hash-based integrity checking (SHA-256/SHA-512)\n- GPG agent integration for secure key management\n- Full secret encryption/decryption lifecycle\n- Validation logic with optional auto-fix\n- [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\n- **Hermetic E2E Testing**: Every pull request runs e2e tests in a network-isolated Linux namespace with eBPF verification via [harden-runner](https://github.com/step-security/harden-runner), proving zero external network connections. See [Security Model](https://dotsecenv.com/concepts/security-model/#hermetic-testing).\n\n## Exit Codes\n\n| Code | Name                  | Description                      |\n| ---- | --------------------- | -------------------------------- |\n| `0`  | Success               | Operation completed successfully |\n| `1`  | General Error         | Unspecified error                |\n| `2`  | Config Error          | Configuration file issue         |\n| `3`  | Vault Error           | Vault file issue                 |\n| `4`  | GPG Error             | GPG operation failed             |\n| `5`  | Auth Error            | Authentication failed            |\n| `6`  | Validation Error      | Validation failed                |\n| `7`  | Fingerprint Required  | No fingerprint configured        |\n| `8`  | Access Denied         | Permission denied                |\n| `9`  | Algorithm Not Allowed | Algorithm not in allow-list      |\n\n## Environment Variables\n\n| Variable           | Description                                             |\n| ------------------ | ------------------------------------------------------- |\n| `DOTSECENV_CONFIG` | Override config file path                               |\n| `XDG_CONFIG_HOME`  | Override config directory (defaults to: `~/.config`)    |\n| `XDG_DATA_HOME`    | Override data directory (defaults to: `~/.local/share`) |\n\n## Known Limitations\n\n### Ed448 Keys (GnuPG v5 Format)\n\nEd448 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:\n\n```shell\nfailed 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\n```\n\n**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.\n\n**Status**: This limitation will be resolved when:\n\n- GnuPG adopts RFC 9580 v6 format for Ed448 keys, OR\n- go-crypto adds compatibility for GnuPG's v5 Ed448 format\n\nEd448 is included in the approved algorithms configuration to ensure readiness when support becomes available.\n\n## FAQ\n\n### How do I generate a GPG key?\n\n```bash\n# Generate a new GPG key and choose sensible defaults, i.e.:\n# - (9) ECC (sign and encrypt)\n# - (1) Curve 25519\n# - Key expiration: 1y\ngpg --full-generate-key\n```\n\n### agent_genkey failed: No pinentry\n\nIf you are unable to generate a key due to this error, install `pinentry`:\n\n```bash\n# macOS\nbrew install pinentry-mac\n\n# Linux\nsudo apt-get install pinentry\nsudo dnf install pinentry-tty\nsudo yum install pinentry-tty\nsudo pacman -S pinentry-tty\n# etc.\n```\n\nIn 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`).\n\n### gpg: signing failed: Inappropriate ioctl for device\n\nThis error occurs when GPG cannot find the terminal for pinentry input. Add the following to your shell profile (`~/.bashrc`, `~/.zshrc`):\n\n```bash\nexport GPG_TTY=$(tty)\n```\n\nOr for fish shell, add the following to `~/.config/fish/config.fish`:\n\n```fish\nset -gx GPG_TTY (tty)\n```\n\nThen restart your shell or run the command directly.\n\n### gpg: decryption failed\n\nIf you encounter this error, first try to define GPG_TTY (see above) before searching\nfor other possible solutions.\n\n```shell\n$ dotsecenv secret get bla\nfailed to decrypt secret: failed to decrypt with gpg-agent: exit status 2\nGPG error: gpg: decryption failed: No secret key\n```\n\n## Development\n\n### Setup\n\n```bash\n# Install all development tools (lefthook, golangci-lint, syft, goreleaser)\nmake install-tools\n```\n\n### Building\n\n```bash\n# Build with FIPS 140-3 crypto (no CGO required)\nmake build\n```\n\nThe default `make build` uses Go's native FIPS 140-3 cryptographic module (`GOFIPS140=v1.26.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.\n\n### Testing\n\n```bash\n# Run all tests\nmake test\n\n# Run tests with race condition detection\nmake test-race\n\n# Run end-to-end tests\nmake build e2e\n```\n\n### Linting\n\n```bash\n# Run linting (installs golangci-lint if needed)\nmake lint\n```\n\n### Releasing\n\nReleases 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.\n\nThe [releasetools-cli](https://github.com/releasetools/cli) simplifies this process:\n\n```bash\nrt git::release --major --sign --force --push v0.1.2\n```\n\nThis creates both `v0.1.2` and `v0` tags pointing to the same commit, signs them, and pushes to the remote.\n\n## Security Considerations\n\n### What dotsecenv Protects Against\n\n- Accidental exposure of unencrypted secrets in version control\n- Secrets stored in plaintext on disk\n- Access to secrets by unauthorized users without GPG keys\n- Tampering with vault entries (signature verification)\n- Stealth network exfiltration of secrets during CI/CD (hermetic testing)\n\n### What dotsecenv DOES NOT Protect Against\n\n- Operating system-level access (root/admin can always read memory)\n- Compromised GPG private keys\n- Quantum computing attacks (future consideration)\n- Side-channel attacks\n- Physical memory dumps\n- Environment snooping\n\n### Recommendations\n\n1. **Private Keys**: Never commit GPG private keys to repositories\n2. **Key Management**: Use gpg-agent with passphrase protection\n3. **Vault Files**: Can be committed to git, technically safe in public repositories, but not recommended\n4. **Multi-User Systems**: Use strong user isolation and file permissions\n5. **Monitoring**: Audit all secret access in production environments\n6. **Rotation**: Periodically rotate encryption keys by updating and then re-encrypting secrets\n\n## License\n\nApache 2.0 License. See LICENSE file for details.\n\n## Acknowledgments\n\n- [SOPS](https://github.com/getsops/sops) for the idea of storing encrypted secrets alongside source code\n- [ProtonMail gopenpgp](https://github.com/protonmail/gopenpgp) for PGP cryptography\n- [Cobra](https://github.com/spf13/cobra) for CLI framework\n- [Go standard library](https://golang.org/) for easy multi-platform functionality\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdotsecenv%2Fdotsecenv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdotsecenv%2Fdotsecenv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdotsecenv%2Fdotsecenv/lists"}