{"id":50699626,"url":"https://github.com/ralscha/env-vault","last_synced_at":"2026-06-09T08:32:49.536Z","repository":{"id":349959343,"uuid":"1202954256","full_name":"ralscha/env-vault","owner":"ralscha","description":"Store environment variables secretly","archived":false,"fork":false,"pushed_at":"2026-05-31T13:50:55.000Z","size":67,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T15:22:20.196Z","etag":null,"topics":["environment-variables","go","secrets"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ralscha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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":"2026-04-06T15:16:21.000Z","updated_at":"2026-05-31T13:50:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ralscha/env-vault","commit_stats":null,"previous_names":["ralscha/env-vault"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ralscha/env-vault","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralscha%2Fenv-vault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralscha%2Fenv-vault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralscha%2Fenv-vault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralscha%2Fenv-vault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ralscha","download_url":"https://codeload.github.com/ralscha/env-vault/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ralscha%2Fenv-vault/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34098932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["environment-variables","go","secrets"],"created_at":"2026-06-09T08:32:48.955Z","updated_at":"2026-06-09T08:32:49.528Z","avatar_url":"https://github.com/ralscha.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# env-vault\n\nStore named sets of environment variables in a local encrypted vault and inject them directly into processes — no plaintext files, no shell history exposure.\n\n## Install\n\nBuild from source:\n\n```sh\ngit clone https://github.com/ralscha/env-vault\ncd env-vault\ngo build -o env-vault ./cmd/cli\ngo build -o env-vault-tui ./cmd/tui\n```\n\nThe CLI and TUI ship as separate binaries. Use `env-vault` for scripting and `env-vault-tui` for interactive management.\n\n## TUI\n\nThe TUI is optimized for day-to-day vault management with an explorer-like layout:\n\n- Searchable entity list on the left for apps and groups.\n- Detail pane on the right with direct keys, linked groups, and a resolved preview.\n- Modal forms for creating entities, editing keys, linking groups, renaming, copying, and removing entries.\n\nRun it with the same vault-location and password flags as the CLI:\n\n```sh\nenv-vault-tui\nenv-vault-tui --dir ~/.config/my-vault\nprintf '%s' \"$ENV_VAULT_PASSWORD\" | env-vault-tui --password-stdin\n```\n\nInside the TUI:\n\n- `/` filters by entity name, kind, key name, or linked group.\n- `n` creates an app or group.\n- `a` adds or updates a direct key.\n- `d` removes a direct key.\n- `l` and `u` link or unlink groups on the selected app.\n- `r`, `y`, and `x` rename, copy, or remove the selected entity.\n- `v` toggles secret visibility in the detail pane.\n\n## Concepts\n\n**Groups** hold shared environment variables. **Apps** link one or more groups and can define their own variables that override linked values.\n\nBoth apps and groups live in one flat namespace — names must be unique across both kinds.\n\n**Selections** are comma-separated lists of app and/or group names. They are merged left to right: later entries override earlier ones. Within an app, group values are applied in link order, then the app's own variables are applied last.\n\nThe vault is encrypted on disk using [age](https://age-encryption.org) with a post-quantum hybrid recipient. The master password protects a separate encrypted age identity file using age scrypt. Neither file is readable without the password.\n\n## Quick Example\n\nThe complete flow: create a vault, store secrets, run a command with them injected.\n\n```sh\n# 1. Create the vault (prompts for a master password)\nenv-vault init\n\n# 2. Create a shared group with an API key (prompts for the value)\nenv-vault set --group llm OPENAI_API_KEY\n\n# 3. Create an app that links the group\nenv-vault set --app chat MODEL gpt-4o\nenv-vault link chat llm\n\n# 4. Run a command with the chat app's secrets injected\nenv-vault exec chat -- printenv OPENAI_API_KEY\nenv-vault exec chat -- printenv MODEL\n\n# 5. Or drop into a subshell with secrets in the environment\nenv-vault shell chat\n```\n\n## Commands\n\nShared flags apply to every command that reads or writes the vault:\n\n| Flag | Description |\n|---|---|\n| `--dir PATH` | Vault directory (default: `~/.env-vault`) |\n| `--password-stdin` | Read the master password from stdin |\n| `--password-file PATH` | Read the master password from a file |\n| `--password-fd N` | Read the master password from an open file descriptor |\n| `--unlock-window DURATION` | Start or extend a short-lived helper process so later commands can reuse the decrypted identity automatically |\n\n---\n\n### `init`\n\nCreate a new vault. Prompts for a master password interactively unless a non-interactive source is supplied.\n\n```\nenv-vault init [--dir PATH] [--work-factor N] [--password-stdin|--password-file PATH|--password-fd N]\n```\n\n`--work-factor N` controls the scrypt difficulty (default: 18). Higher values are slower but more resistant to brute force.\n\n```sh\nenv-vault init\nenv-vault init --dir ~/.config/my-vault\necho \"my-password\" | env-vault init --password-stdin\n```\n\n---\n\n### `info`\n\nShow the paths to the vault data file and the encrypted identity file.\n\n```\nenv-vault info [--dir PATH]\n```\n\n```sh\nenv-vault info\n```\n\n---\n\n### `set`\n\nAdd or update a variable in an app or group. Without a value argument, prompts for the value with hidden input.\n\n```\nenv-vault set [shared flags] [--stdin|--interactive] [--app|--group] NAME [KEY [VALUE]]\n```\n\n- `--app` or `--group` explicitly creates that kind of entity if it does not exist yet. Omit the flag to update an existing entity of either kind.\n- `--stdin` reads the value from stdin (avoids shell history; requires a different unlock source than `--password-stdin`).\n- `--interactive` starts a multi-key edit session in a prompt loop.\n- Passing `VALUE` as a literal argument works but may leak through shell history and process inspection.\n\n```sh\n# Prompt for the value (safest)\nenv-vault set --group llm OPENAI_API_KEY\n\n# Multiple keys in one session\nenv-vault set --group --interactive llm\n\n# Read value from stdin\necho \"sk-abc123\" | env-vault set --stdin --group llm OPENAI_API_KEY\n\n# Inline value (shell history risk)\nenv-vault set --group remotedb DB_HOST db.example.com\nenv-vault set --app chat MODEL gpt-4o\n```\n\n---\n\n### `exec`\n\nRun a command with secrets injected as environment variables. Use `--` to separate vault arguments from the command.\n\n```\nenv-vault exec [shared flags] NAME[,NAME...] -- COMMAND [ARGS...]\n```\n\nThe selection is merged before injection. The child process inherits the current environment plus all resolved secrets (secrets override any existing env vars with the same name).\n\n```sh\nenv-vault exec chat -- printenv OPENAI_API_KEY\nenv-vault exec chat,remotedb -- ./start-server\nenv-vault exec --unlock-window 5m chat -- make test\n```\n\n`exec` is the preferred way to consume secrets. Unlike `export`, it never writes plaintext to disk or a terminal.\n\n---\n\n### `shell`\n\nStart a subshell with secrets injected into its environment.\n\n```\nenv-vault shell [shared flags] [--shell PATH] [--allow-nested] NAME[,NAME...] [-- SHELL_ARGS...]\n```\n\n- `--shell PATH` overrides the shell binary (default: `$SHELL`).\n- `--allow-nested` removes the guard that prevents starting an `env-vault shell` inside another one.\n- The `ENV_VAULT` and `ENV_VAULT_PROFILE` environment variables are set inside the subshell to indicate the active vault and selection.\n\n```sh\nenv-vault shell chat\nenv-vault shell chat,remotedb\nenv-vault shell --shell /bin/zsh chat\n```\n\n---\n\n### `link`\n\nAdd a group to an app so the group's variables are included when the app is resolved.\n\n```\nenv-vault link [shared flags] APP GROUP\n```\n\nGroups are merged in the order they are linked. Variables defined directly on the app override all group values.\n\n```sh\nenv-vault link chat llm\nenv-vault link chat remotedb\n```\n\n---\n\n### `unlink`\n\nRemove a group from an app.\n\n```\nenv-vault unlink [shared flags] APP GROUP\n```\n\n```sh\nenv-vault unlink chat remotedb\n```\n\n---\n\n### `list`\n\nList all apps and groups, or list the resolved keys for a specific selection.\n\n```\nenv-vault list [shared flags] [--json] [NAME[,NAME...]]\n```\n\n- Without a name: shows all apps and groups in the vault.\n- With a name or selection: shows the resolved environment variable keys.\n- `--json` emits machine-readable output.\n\n```sh\nenv-vault list\nenv-vault list chat\nenv-vault list chat,llm --json\nenv-vault ls\n```\n\n`ls` is an alias for `list`.\n\n---\n\n### `show`\n\nDisplay detailed information about one entity or a resolved selection.\n\n```\nenv-vault show [shared flags] [--resolved] [--json] [app|group] NAME[,NAME...]\n```\n\n- `app` or `group` narrows the lookup when both exist (they cannot, but the qualifier is accepted).\n- `--resolved` also prints the merged variable values with per-key provenance (which entity each key came from).\n- `--json` emits machine-readable metadata.\n\n```sh\nenv-vault show group llm\nenv-vault show app chat\nenv-vault show --resolved chat\nenv-vault show --resolved --json chat,llm\nenv-vault inspect group llm\n```\n\n`inspect` is an alias for `show`.\n\n---\n\n### `edit`\n\nOpen an app or group's direct key/value pairs in your `$EDITOR`.\n\n```\nenv-vault edit [shared flags] [--editor PATH] NAME\n```\n\nThe editor receives a temporary plaintext file. The file is removed after the editor exits. Treat `edit` like `export` from a local-exposure standpoint — plaintext values are on disk while the editor is open.\n\n```sh\nenv-vault edit chat\nenv-vault edit --editor vim llm\n```\n\n---\n\n### `unset`\n\nRemove a single key from an app or group.\n\n```\nenv-vault unset [shared flags] [--force] NAME KEY\n```\n\nPrompts for confirmation unless `--force` is given.\n\n```sh\nenv-vault unset chat OPENAI_API_KEY\nenv-vault unset --force remotedb DB_PASSWORD\n```\n\n---\n\n### `remove`\n\nDelete an app or group entirely.\n\n```\nenv-vault remove [shared flags] [--force] NAME\n```\n\nPrompts for confirmation unless `--force` is given. Removing a group automatically unlinks it from apps that reference it.\n\n```sh\nenv-vault remove old-app\nenv-vault remove --force temp-group\nenv-vault rm old-app\n```\n\n`rm` is an alias for `remove`.\n\n---\n\n### `rename`\n\nRename an app or group. When a group is renamed, all apps that link it are updated automatically.\n\n```\nenv-vault rename [shared flags] OLD_NAME NEW_NAME\n```\n\n```sh\nenv-vault rename remotedb remote-db\n```\n\n---\n\n### `copy`\n\nDuplicate an app or group under a new name with fresh timestamps.\n\n```\nenv-vault copy [shared flags] SOURCE_NAME DEST_NAME\n```\n\n```sh\nenv-vault copy chat chat-staging\n```\n\n---\n\n### `export`\n\nWrite plaintext secrets to stdout or a file.\n\n```\nenv-vault export [shared flags] [--format env|export-env|json|dotenv] [--metadata] [--output FILE] [--force-stdout] NAME[,NAME...]\n```\n\n| Format | Output |\n|---|---|\n| `env` | POSIX-safe `KEY='value'` lines |\n| `export-env` | POSIX-safe `export KEY='value'` lines for shell sourcing |\n| `json` | flat JSON object |\n| `dotenv` | `.env`-style `KEY=\"value\"` lines |\n\n- `--metadata` wraps the output with entity metadata plus both the direct and resolved env maps (JSON only).\n- `--output FILE` writes to a file with mode 0600 and prints a warning.\n- `--force-stdout` is required to print plaintext secrets to an interactive terminal.\n\n`exec` and `shell` are safer for most use cases. Use `export` when you need the data in a file or a format another tool consumes.\n\n```sh\nenv-vault export --format export-env --force-stdout chat\nenv-vault export --format json --output secrets.json chat\nenv-vault export --format json --metadata chat,llm\nsource \u003c(env-vault export --format export-env --force-stdout llm)\n```\n\n---\n\n### `unlock`\n\nInspect or stop the short-lived unlock helper started by `--unlock-window`.\n\n```\nenv-vault unlock [--dir PATH] status|clear\n```\n\n- `status` shows whether the helper is running and prints its recent activity log.\n- `clear` stops the helper and removes its socket.\n\nThe helper keeps the decrypted age identity in memory only. It communicates over a Unix domain socket (macOS/Linux) or a named pipe (Windows) accessible only to the same user.\n\n```sh\nenv-vault unlock status\nenv-vault unlock clear\n```\n\n---\n\n### `completion`\n\nPrint a shell completion script.\n\n```\nenv-vault completion bash|zsh|fish|powershell\n```\n\n```sh\n# Bash\neval \"$(env-vault completion bash)\"\n\n# Zsh\nenv-vault completion zsh \u003e ~/.zsh/completions/_env-vault\n\n# Fish\nenv-vault completion fish \u003e ~/.config/fish/completions/env-vault.fish\n```\n\nThe generated scripts cover commands and flags. They do not query vault contents for dynamic app and group names.\n\n---\n\n## Non-interactive Unlock\n\nFor scripts and CI, supply the password without a prompt:\n\n```sh\n# From a file\nenv-vault exec --password-file ~/.vault-pass chat -- ./deploy.sh\n\n# From stdin\necho \"my-password\" | env-vault list --password-stdin\n\n# Start one unlock window, then reuse it across several commands\nenv-vault exec --password-file ~/.vault-pass --unlock-window 2m chat -- make build\nenv-vault exec chat -- make test\nenv-vault list chat\n```\n\n`--password-stdin` reads from stdin before the command runs. If you also need `set --stdin`, use `--password-file` or `--password-fd` instead.\n\n---\n\n## Security Notes\n\n- Secrets prompted interactively use hidden terminal input and are held in locked memory via [memguard](https://github.com/awnumar/memguard).\n- `set --stdin` and `set --interactive` avoid placing secret values in shell history.\n- Passing `VALUE` inline on the command line can leak through shell history and `ps` output.\n- `exec` and `shell` are the safe default workflows. `export` writes plaintext and should be treated as an escape hatch.\n- `export` refuses to print to an interactive terminal unless `--force-stdout` is given.\n- `export --output FILE` creates the file with mode 0600 and prints a warning.\n- `edit` writes a temporary plaintext file to disk while your editor is open.\n- The `--unlock-window` helper uses same-user IPC only (Unix socket or Windows named pipe).\n- Atomic writes protect both encrypted files against corruption.\n- Once secrets reach a child process, that process and the OS control further exposure.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fralscha%2Fenv-vault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fralscha%2Fenv-vault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fralscha%2Fenv-vault/lists"}