Shell Plugins
The dotsecenv shell plugins automatically load secrets from .secenv files when you cd into a directory. When you leave, they are automatically unloaded—keeping your environment clean and secure.
How It Works
Section titled “How It Works”- On
cdinto a directory: The plugin checks for a.secenvfile - Security check: The file must be owned by you (or root) and not world-writable
- Trust prompt: You’re prompted to allow loading (can be remembered)
- Auto-load: Plain variables are loaded first, then secrets are fetched from your vault
- Tree-scoped persistence: Secrets remain loaded in subdirectories, only unloading when you leave the tree entirely
~/project $ cd myapp/dotsecenv: found .secenv in /home/user/myappLoad secrets? [y]es / [n]o / [a]lways: ydotsecenv: loaded 2 secret(s) from .secenv: DATABASE_URL, API_KEY
~/project/myapp $ cd src/ # Secrets persist in subdirectories~/project/myapp/src $ cd ../.. # Leave the treedotsecenv: unloaded 2 secret(s): DATABASE_URL, API_KEYTree-Scoped Loading
Section titled “Tree-Scoped Loading”Secrets are tree-scoped, meaning they persist while you navigate within subdirectories:
~/project $ cd myapp/dotsecenv: loaded 2 secret(s) from .secenv: DATABASE_URL, API_KEY
~/project/myapp $ cd src/components/# Secrets still available - we're in the myapp tree
~/project/myapp/src/components $ echo $DATABASE_URLpostgres://...
~/project/myapp/src/components $ cd ~/other-project/dotsecenv: unloaded 2 secret(s): DATABASE_URL, API_KEY# Secrets unloaded - we left the myapp treeThis behavior is ideal for common workflows where you navigate between source files while keeping your secrets loaded.
Nested .secenv Files
Section titled “Nested .secenv Files”If a subdirectory has its own .secenv, secrets layer on top of the parent:
~/project $ cd myapp/dotsecenv: loaded 1 secret(s) from .secenv: DATABASE_URL
~/project/myapp $ cd prod/dotsecenv: loaded 1 secret(s) from .secenv: API_KEY# Both DATABASE_URL (from parent) and API_KEY (from prod) are available
~/project/myapp/prod $ cd ..dotsecenv: unloaded 1 secret(s): API_KEY# prod's API_KEY unloaded, parent's DATABASE_URL remainsIf a child directory defines the same variable as the parent, the child’s value takes precedence. When you leave the child, the parent’s value is restored (re-fetched from the vault for security).
Re-entry Reloads Fresh
Section titled “Re-entry Reloads Fresh”When you return to a directory with .secenv, secrets are reloaded fresh from your vault:
~/project/myapp $ cd ~/other-project/dotsecenv: unloaded 2 secret(s): DATABASE_URL, API_KEY
~/other-project $ cd ~/project/myapp/dotsecenv: loaded 2 secret(s) from .secenv: DATABASE_URL, API_KEY# Fresh reload ensures you get the latest valuesInstallation
Section titled “Installation”Oh-My-Zsh:
git clone https://github.com/dotsecenv/plugin.git \ ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/dotsecenvThen add dotsecenv to your plugins in ~/.zshrc:
plugins=(... dotsecenv)Antigen:
antigen bundle dotsecenv/pluginManual:
source /path/to/dotsecenv.plugin.zshAdd to your ~/.bashrc:
source /path/to/dotsecenv.plugin.bashOr use the installer:
curl -fsSL https://raw.githubusercontent.com/dotsecenv/plugin/main/install.sh | bashFisher:
fisher install dotsecenv/pluginManual:
source /path/to/conf.d/dotsecenv.fishThe dse Command
Section titled “The dse Command”The plugins provide the dse command with subcommands for common operations:
| Command | Description |
|---|---|
dse | Shorthand for dotsecenv (pass-through) |
dse get NAME | Shorthand for dotsecenv secret get NAME |
dse cp NAME | Copy a secret directly to clipboard |
dse reload | Reload secrets in the current directory |
Pass-through to dotsecenv
Section titled “Pass-through to dotsecenv”Use dse as a shorthand for the dotsecenv command:
# Instead of:dotsecenv vault describe
# Use:dse vault describedse get — Quick Secret Retrieval
Section titled “dse get — Quick Secret Retrieval”Retrieve a secret with minimal typing:
# Instead of:dotsecenv secret get DATABASE_PASSWORD
# Use:dse get DATABASE_PASSWORDdse cp — Copy to Clipboard
Section titled “dse cp — Copy to Clipboard”Copy a secret directly to your clipboard without displaying it:
dse cp API_KEY# Output: dotsecenv: secret copied to clipboardClipboard support:
- macOS: Uses
pbcopy(built-in) - Wayland: Uses
wl-copy - X11: Uses
xcliporxsel
dse reload — Reload Current Directory
Section titled “dse reload — Reload Current Directory”Re-trigger secret loading for the current directory without leaving and re-entering:
dse reloadThis is useful when:
- You’ve added new secrets to your vault
- You’ve modified the
.secenvfile - A secret fetch failed and you want to retry
Trust System
Section titled “Trust System”The plugin uses a trust system to prevent malicious .secenv files from auto-loading secrets:
Trust Prompt Options
Section titled “Trust Prompt Options”When you cd into a directory with an untrusted .secenv:
dotsecenv: found .secenv in /path/to/projectLoad secrets? [y]es / [n]o / [a]lways:| Option | Behavior |
|---|---|
| y / yes | Load secrets for this session only |
| n / no | Skip loading for this session |
| a / always | Permanently trust this directory |
Persistent Trust
Section titled “Persistent Trust”Directories trusted with “always” are saved to:
~/.config/dotsecenv/trusted_dirsTo remove trust:
# Edit the file and remove the directory pathvim ~/.config/dotsecenv/trusted_dirsSecurity Checks
Section titled “Security Checks”Before loading any file, the plugin performs security checks:
- Ownership: File must be owned by you or root
- Permissions: File must not be world-writable
If a file fails these checks:
dotsecenv: refusing to load /path/.secenv - not owned by current user or rootdotsecenv: refusing to load /path/.secenv - world-writableFile Format
Section titled “File Format”The .secenv file uses a simple KEY=value format with the {dotsecenv} syntax for loading secrets:
# Load secret with matching nameDATABASE_PASSWORD={dotsecenv}
# Load secret with different nameDB_PASS={dotsecenv/DATABASE_PASSWORD}
# Plain values work tooNODE_ENV=production
# Load from a namespaceAPI_KEY={dotsecenv/production::API_KEY}See Your First Secret for more details on file syntax.
.env vs .secenv
Section titled “.env vs .secenv”The dotsecenv plugin only handles .secenv files. This is intentional—we don’t reinvent the wheel.
Tools like direnv, autoenv, and zsh-autoenv have been managing .env files for years. They’re mature, battle-tested, and do their job well. There’s no reason for dotsecenv to duplicate that functionality or force you to learn subtle differences in behavior.
Use them together:
| File | Tool | Purpose |
|---|---|---|
.env | direnv, autoenv, etc. | Non-sensitive configuration |
.secenv | dotsecenv | Secrets from your vault |
Loading Order
Section titled “Loading Order”When loading a .secenv file:
- Phase 1: Load plain variables (any
KEY=valuewithout{dotsecenv}) - Phase 2: Fetch and load secrets (any
KEY={dotsecenv}orKEY= {dotsecenv / name})
This ensures plain variables are available immediately, while secrets are fetched from your vault.
Troubleshooting
Section titled “Troubleshooting”Secrets not loading on cd
Section titled “Secrets not loading on cd”-
Check the plugin is loaded:
Terminal window type dse # Should show it's a function -
Verify
.secenvexists and has correct permissions:Terminal window ls -la .secenv -
Try manual reload:
Terminal window dse reload
“No TTY for trust prompt”
Section titled ““No TTY for trust prompt””The trust prompt requires an interactive terminal. This happens when:
- Running in a script
- Running in a non-interactive shell
Solution: Pre-trust the directory:
echo "/path/to/project" >> ~/.config/dotsecenv/trusted_dirsSecret fetch errors
Section titled “Secret fetch errors”If a secret fails to fetch, you’ll see:
dotsecenv: error fetching secret 'SECRET_NAME' for VAR:<error details>Common causes:
- Secret doesn’t exist in vault
- GPG agent not running
- Vault locked
Next Steps
Section titled “Next Steps”- Your First Secret — Learn
.secenvfile syntax - CLI Reference — Full command documentation
- Threat Model — Understand the trust model