|
178 | 178 | [ "$cont" = "true" ] |
179 | 179 | } |
180 | 180 |
|
| 181 | +@test "handler: plugin self-allow via \$CLAUDE_PLUGIN_ROOT (fake path)" { |
| 182 | + # Claude Code sets CLAUDE_PLUGIN_ROOT for every hook invocation. Using this |
| 183 | + # env var is the authoritative way to locate the plugin install, so the |
| 184 | + # self-allow must work even for totally synthetic paths. |
| 185 | + fake_root="$TMP/fakeplugin" |
| 186 | + mkdir -p "$fake_root/scripts" |
| 187 | + cmd="bash $fake_root/scripts/verify.sh" |
| 188 | + payload="$(jq -cn --arg c "$cmd" '{tool_name:"Bash",tool_input:{command:$c}}')" |
| 189 | + run bash -c "CLAUDE_PLUGIN_ROOT='$fake_root' printf '%s' \"\$1\" | CLAUDE_PLUGIN_ROOT='$fake_root' bash '$HANDLER'" _ "$payload" |
| 190 | + [ "$status" -eq 0 ] |
| 191 | + decision="$(jq -r '.hookSpecificOutput.permissionDecision' <<<"$output")" |
| 192 | + reason="$(jq -r '.hookSpecificOutput.permissionDecisionReason' <<<"$output")" |
| 193 | + [ "$decision" = "allow" ] |
| 194 | + [[ "$reason" == *"self-allow"* ]] |
| 195 | +} |
| 196 | + |
| 197 | +@test "handler: plugin self-allow via \$CLAUDE_PLUGIN_ROOT for realistic cache install path" { |
| 198 | + # Real-world install path shape: |
| 199 | + # ~/.claude/plugins/cache/<marketplace>/<plugin-name>/<version>/ |
| 200 | + # The old regex required literal `claude-passthru` in the path, so this |
| 201 | + # shape (which is what users actually get) never matched. |
| 202 | + real_root="$USER_ROOT/.claude/plugins/cache/passthru/passthru/0.1.0" |
| 203 | + mkdir -p "$real_root/scripts" |
| 204 | + cmd="bash $real_root/scripts/verify.sh" |
| 205 | + payload="$(jq -cn --arg c "$cmd" '{tool_name:"Bash",tool_input:{command:$c}}')" |
| 206 | + run bash -c "CLAUDE_PLUGIN_ROOT='$real_root' printf '%s' \"\$1\" | CLAUDE_PLUGIN_ROOT='$real_root' bash '$HANDLER'" _ "$payload" |
| 207 | + [ "$status" -eq 0 ] |
| 208 | + decision="$(jq -r '.hookSpecificOutput.permissionDecision' <<<"$output")" |
| 209 | + [ "$decision" = "allow" ] |
| 210 | +} |
| 211 | + |
| 212 | +@test "handler: plugin self-allow via fallback regex for cache/passthru/passthru/<ver>/ path (no env)" { |
| 213 | + # Same realistic install path but without CLAUDE_PLUGIN_ROOT set. The |
| 214 | + # fallback regex must still recognise `passthru` as a path segment so |
| 215 | + # manual pipe-testing and legacy harnesses continue to work. |
| 216 | + cmd='bash /Users/alice/.claude/plugins/cache/passthru/passthru/0.1.0/scripts/verify.sh' |
| 217 | + payload="$(jq -cn --arg c "$cmd" '{tool_name:"Bash",tool_input:{command:$c}}')" |
| 218 | + run_handler "$payload" |
| 219 | + [ "$status" -eq 0 ] |
| 220 | + decision="$(jq -r '.hookSpecificOutput.permissionDecision' <<<"$output")" |
| 221 | + [ "$decision" = "allow" ] |
| 222 | +} |
| 223 | + |
| 224 | +@test "handler: self-allow via \$CLAUDE_PLUGIN_ROOT rejects unknown script names" { |
| 225 | + # Defense in depth: even if the prefix matches CLAUDE_PLUGIN_ROOT, only |
| 226 | + # the plugin's known scripts are self-allowed. An arbitrary foo.sh living |
| 227 | + # under the plugin root should not get a free pass. |
| 228 | + fake_root="$TMP/fakeplugin" |
| 229 | + mkdir -p "$fake_root/scripts" |
| 230 | + cmd="bash $fake_root/scripts/evil.sh" |
| 231 | + payload="$(jq -cn --arg c "$cmd" '{tool_name:"Bash",tool_input:{command:$c}}')" |
| 232 | + run bash -c "CLAUDE_PLUGIN_ROOT='$fake_root' printf '%s' \"\$1\" | CLAUDE_PLUGIN_ROOT='$fake_root' bash '$HANDLER'" _ "$payload" |
| 233 | + [ "$status" -eq 0 ] |
| 234 | + # Should fall through to passthrough (no rules -> continue:true). |
| 235 | + cont="$(jq -r '.continue' <<<"$output")" |
| 236 | + [ "$cont" = "true" ] |
| 237 | +} |
| 238 | + |
181 | 239 | # --------------------------------------------------------------------------- |
182 | 240 | # Real-world round-trip |
183 | 241 | # --------------------------------------------------------------------------- |
|
0 commit comments