From b2d320145c70b319d547ae15ebe3ec83bc34fade Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka <35694946+techiro@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:00:22 +0900 Subject: [PATCH 1/2] fix(hooks): add command-boundary anchors to prevent false positives (closes #60) --- hooks/scripts/block-cli-workarounds.sh | 43 +++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/hooks/scripts/block-cli-workarounds.sh b/hooks/scripts/block-cli-workarounds.sh index 624388c..b8b14fe 100644 --- a/hooks/scripts/block-cli-workarounds.sh +++ b/hooks/scripts/block-cli-workarounds.sh @@ -38,28 +38,29 @@ deny_with_cli_check() { esac } -# Block project creation via CLI -if echo "$COMMAND" | grep -qE '(flutter|dart)\s+create'; then - deny_with_cli_check "Do not use 'flutter create' or 'dart create'. Use the very_good_cli MCP 'create' tool instead." -fi - -if echo "$COMMAND" | grep -qE 'very_good\s+create'; then - deny_with_cli_check "Do not use 'very_good create' via shell. Use the very_good_cli MCP 'create' tool instead." -fi - -# Block test runs via CLI -if echo "$COMMAND" | grep -qE '(flutter|dart)\s+test'; then - deny_with_cli_check "Do not use 'flutter test' or 'dart test'. Use the very_good_cli MCP 'test' tool instead." -fi - -if echo "$COMMAND" | grep -qE 'very_good\s+test'; then - deny_with_cli_check "Do not use 'very_good test' via shell. Use the very_good_cli MCP 'test' tool instead." -fi +# Split on shell operators and check the first two tokens of each subcommand. +# This avoids false positives from file paths (.dart) or quoted strings. +BLOCKED=$(echo "$COMMAND" | awk '{ + n = split($0, parts, /[;&|]+/) + for (i = 1; i <= n; i++) { + gsub(/^[[:space:]]+/, "", parts[i]) + split(parts[i], w, /[[:space:]]+/) + b = w[1]; s = w[2] + if ((b == "flutter" || b == "dart") && s == "create") { print "create"; exit } + if ((b == "flutter" || b == "dart") && s == "test") { print "test"; exit } + if (b == "very_good" && s == "create") { print "vg_create"; exit } + if (b == "very_good" && s == "test") { print "vg_test"; exit } + if (b == "very_good" && s == "packages") { print "vg_packages"; exit } + } +}') -# Block license check via CLI -if echo "$COMMAND" | grep -qE 'very_good\s+packages'; then - deny_with_cli_check "Do not use 'very_good packages' via shell. Use the very_good_cli MCP 'packages_get' or 'packages_check_licenses' tool instead." -fi +case "$BLOCKED" in + create) deny_with_cli_check "Do not use 'flutter create' or 'dart create'. Use the very_good_cli MCP 'create' tool instead." ;; + test) deny_with_cli_check "Do not use 'flutter test' or 'dart test'. Use the very_good_cli MCP 'test' tool instead." ;; + vg_create) deny_with_cli_check "Do not use 'very_good create' via shell. Use the very_good_cli MCP 'create' tool instead." ;; + vg_test) deny_with_cli_check "Do not use 'very_good test' via shell. Use the very_good_cli MCP 'test' tool instead." ;; + vg_packages) deny_with_cli_check "Do not use 'very_good packages' via shell. Use the very_good_cli MCP 'packages_get' or 'packages_check_licenses' tool instead." ;; +esac # Not a blocked command — allow exit 0 From 9a62e4551a70294053ccbb5813bbf5bb514061a8 Mon Sep 17 00:00:00 2001 From: Hirokazu Tanaka <35694946+techiro@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:38:22 +0900 Subject: [PATCH 2/2] test(hooks): add tests for block-cli-workarounds --- hooks/scripts/block-cli-workarounds_test.sh | 97 +++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100755 hooks/scripts/block-cli-workarounds_test.sh diff --git a/hooks/scripts/block-cli-workarounds_test.sh b/hooks/scripts/block-cli-workarounds_test.sh new file mode 100755 index 0000000..5bb03f0 --- /dev/null +++ b/hooks/scripts/block-cli-workarounds_test.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# Tests for block-cli-workarounds.sh +# +# Usage: bash hooks/scripts/block-cli-workarounds_test.sh +# +# The hook reads a JSON payload from stdin containing tool_input.command, +# then exits 0 with a deny JSON on stdout if blocked, or exits 0 silently +# if allowed. We check stdout for the deny marker to determine the result. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HOOK="$SCRIPT_DIR/block-cli-workarounds.sh" + +PASSED=0 +FAILED=0 + +# Run hook with a command and check if it was blocked or allowed. +# Usage: run_hook "command string" +# Returns: "blocked" or "allowed" +run_hook() { + local cmd="$1" + local payload + payload=$(jq -n --arg c "$cmd" '{"tool_input":{"command":$c}}') + local output + output=$(echo "$payload" | bash "$HOOK" 2>/dev/null) || true + if echo "$output" | grep -q '"permissionDecision"'; then + echo "blocked" + else + echo "allowed" + fi +} + +assert_blocked() { + local cmd="$1" + local result + result=$(run_hook "$cmd") + if [ "$result" = "blocked" ]; then + printf " \033[32mPASS\033[0m blocked: %s\n" "$cmd" + PASSED=$((PASSED + 1)) + else + printf " \033[31mFAIL\033[0m expected blocked but allowed: %s\n" "$cmd" + FAILED=$((FAILED + 1)) + fi +} + +assert_allowed() { + local cmd="$1" + local result + result=$(run_hook "$cmd") + if [ "$result" = "allowed" ]; then + printf " \033[32mPASS\033[0m allowed: %s\n" "$cmd" + PASSED=$((PASSED + 1)) + else + printf " \033[31mFAIL\033[0m expected allowed but blocked: %s\n" "$cmd" + FAILED=$((FAILED + 1)) + fi +} + +echo "=== block-cli-workarounds tests ===" +echo "" +echo "--- Should be BLOCKED ---" +assert_blocked "dart test" +assert_blocked "flutter test" +assert_blocked "dart test test/routing/foo_test.dart" +assert_blocked "flutter test --coverage" +assert_blocked "dart create my_app" +assert_blocked "flutter create my_app" +assert_blocked "very_good create flutter_app --project-name my_app" +assert_blocked "very_good test --coverage --min-coverage 100" +assert_blocked "very_good packages check licenses" +assert_blocked "cd /path && dart test" +assert_blocked "ENV=1 && flutter test --coverage" + +echo "" +echo "--- Should be ALLOWED ---" +assert_allowed "dart analyze lib/foo.dart" +assert_allowed "dart format lib/foo.dart" +assert_allowed "dart pub get" +assert_allowed "dart fix --apply" +assert_allowed "flutter pub get" +assert_allowed "flutter analyze" +assert_allowed "git add lib/router.dart test/router_test.dart" +assert_allowed "dart analyze lib/foo.dart test/bar_test.dart" +assert_allowed "git commit -m 'fix dart test hook'" +assert_allowed "echo 'flutter create is blocked'" +assert_allowed "gh pr create --body 'use dart test instead'" +assert_allowed "git log --grep='dart test'" +assert_allowed "ls" +assert_allowed "pwd" + +echo "" +echo "=== Results: $PASSED passed, $FAILED failed ===" + +if [ "$FAILED" -gt 0 ]; then + exit 1 +fi