feat(dotenv): support glob patterns in allowed and disallowed lists

The allowed and disallowed list files now support zsh glob patterns
(e.g., /path/to/projects/*) in addition to exact paths. This is useful
for git worktree setups and other workflows where multiple directories
share a common prefix. Comment lines and blank lines are now also
supported in list files.
This commit is contained in:
Thomas Witt 2026-02-12 16:59:11 +02:00
parent c954bbb168
commit 8d5b8c2157
No known key found for this signature in database
GPG Key ID: 857B3DC11C10C2F2
2 changed files with 61 additions and 4 deletions

View File

@ -97,6 +97,35 @@ change.
NOTE: if a directory is found in both the allowed and disallowed lists, the disallowed list
takes preference, _i.e._ the .env file will never be sourced.
### Glob/Wildcard Patterns
Entries in the allowed and disallowed list files are matched as zsh patterns against the
directory path, so wildcards work in addition to exact paths. This is useful when you want
to allow or disallow entire directory trees at once.
For example, if you use [git worktrees](https://git-scm.com/docs/git-worktree) and all your
worktrees live under a common prefix, you can add a single pattern instead of allowing each
one individually:
```sh
# In your dotenv-allowed.list file:
/Users/me/Dev/my-project-wt-*
```
Note that entries are matched against the whole path as a string (as in
`[[ $dir == pattern ]]`), not with filename globbing: `*` and `?` match any characters
**including `/`**, so `/Users/me/*` also matches nested directories like `/Users/me/a/b`.
The basic zsh pattern operators are supported: `*`, `?`, character classes like `[abc]`,
and alternation like `(foo|bar)`. Operators that require `EXTENDED_GLOB` (such as `#`,
`^` and `~`) are **not** enabled by the plugin.
If a literal path contains pattern metacharacters (`*`, `?`, `[`, `(`, etc.), escape them
with a backslash to match the path exactly. Paths added by answering [a]lways or n[e]ver
at the prompt are escaped automatically. Malformed patterns are treated as non-matching.
Lines starting with `#` are treated as comments. Blank lines are ignored, and leading and
trailing whitespace around an entry is stripped.
## Named Pipe (FIFO) Support
The plugin supports `.env` files provided as UNIX named pipes (FIFOs) in addition to regular files.

View File

@ -272,6 +272,34 @@ _dotenv_check_syntax() {
}
}
_dotenv_list_match() {
emulate -L zsh
local dirpath=$1 list_file=$2 line
local -i matched
[[ -r $list_file ]] || return 1
while IFS= read -r line || [[ -n $line ]]; do
# tolerate CRLF line endings and surrounding whitespace
line="${line%$'\r'}"
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" || "$line" == \#* ]] && continue
# a malformed pattern raises a fatal zsh error; contain it and
# treat the entry as non-matching
matched=1
{
[[ $dirpath == ${~line} ]] && matched=0
} always {
(( TRY_BLOCK_ERROR )) && TRY_BLOCK_ERROR=0
}
(( matched )) || return 0
done < "$list_file" 2>/dev/null
return 1
}
source_env() {
if [[ ! -f "$ZSH_DOTENV_FILE" ]] && [[ ! -p "$ZSH_DOTENV_FILE" ]]; then
return
@ -285,12 +313,12 @@ source_env() {
touch "$ZSH_DOTENV_DISALLOWED_LIST"
# early return if disallowed
if command grep -Fx -q "$dirpath" "$ZSH_DOTENV_DISALLOWED_LIST" &>/dev/null; then
if _dotenv_list_match "$dirpath" "$ZSH_DOTENV_DISALLOWED_LIST"; then
return
fi
# check if current directory's .env file is allowed or ask for confirmation
if ! command grep -Fx -q "$dirpath" "$ZSH_DOTENV_ALLOWED_LIST" &>/dev/null; then
if ! _dotenv_list_match "$dirpath" "$ZSH_DOTENV_ALLOWED_LIST"; then
# get cursor column and print new line before prompt if not at line beginning
local column
echo -ne "\e[6n" > /dev/tty
@ -306,8 +334,8 @@ source_env() {
# check input
case "$confirmation" in
[yY]) ;;
[aA]) echo "$dirpath" >> "$ZSH_DOTENV_ALLOWED_LIST" ;;
[eE]) echo "$dirpath" >> "$ZSH_DOTENV_DISALLOWED_LIST"; return ;;
[aA]) print -r -- "${(b)dirpath}" >> "$ZSH_DOTENV_ALLOWED_LIST" ;;
[eE]) print -r -- "${(b)dirpath}" >> "$ZSH_DOTENV_DISALLOWED_LIST"; return ;;
*) return ;; # interpret anything else as a no
esac
fi