Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-plan-file-validator.sh"
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-plan-file-validator.sh\""
}
]
}
Expand All @@ -17,7 +17,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-write-validator.sh"
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-write-validator.sh\""
}
]
},
Expand All @@ -26,7 +26,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-edit-validator.sh"
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-edit-validator.sh\""
}
]
},
Expand All @@ -35,7 +35,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-read-validator.sh"
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-read-validator.sh\""
}
]
},
Expand All @@ -44,7 +44,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-bash-validator.sh"
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-bash-validator.sh\""
}
]
}
Expand All @@ -55,7 +55,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-post-bash-hook.sh"
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-post-bash-hook.sh\""
}
]
}
Expand All @@ -65,7 +65,7 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/loop-codex-stop-hook.sh",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/loop-codex-stop-hook.sh\"",
"timeout": 7200
}
]
Expand Down
8 changes: 7 additions & 1 deletion hooks/lib/loop-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,12 @@ to_lower() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}

# Normalize Windows path separators to the POSIX-style separators used by the
# hook validators' string patterns.
normalize_path_separators() {
echo "${1//\\//}"
}

# Check if a path (lowercase) matches a round file pattern
# Usage: is_round_file "$lowercase_path" "summary|prompt|todos|contract"
is_round_file_type() {
Expand Down Expand Up @@ -1252,7 +1258,7 @@ is_cancel_authorized() {
# Check if a path is inside .humanize/rlcr directory
is_in_humanize_loop_dir() {
local path="$1"
echo "$path" | grep -q '\.humanize/rlcr/'
normalize_path_separators "$path" | grep -q '\.humanize/rlcr/'
}

# Check if a git add command would add .humanize files to version control
Expand Down
1 change: 1 addition & 0 deletions hooks/loop-edit-validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ if [[ "$TOOL_NAME" != "Edit" ]]; then
fi

FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // ""')
FILE_PATH=$(normalize_path_separators "$FILE_PATH")
FILE_PATH_LOWER=$(to_lower "$FILE_PATH")

# Extract session_id from hook input for session-aware loop filtering
Expand Down
1 change: 1 addition & 0 deletions hooks/loop-read-validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ if ! require_tool_input_field "$HOOK_INPUT" "file_path"; then
fi

FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // ""')
FILE_PATH=$(normalize_path_separators "$FILE_PATH")
FILE_PATH_LOWER=$(to_lower "$FILE_PATH")

# Extract session_id from hook input for session-aware loop filtering
Expand Down
1 change: 1 addition & 0 deletions hooks/loop-write-validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ if ! require_tool_input_field "$HOOK_INPUT" "file_path"; then
fi

FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // ""')
FILE_PATH=$(normalize_path_separators "$FILE_PATH")
FILE_PATH_LOWER=$(to_lower "$FILE_PATH")

# Extract session_id from hook input for session-aware loop filtering
Expand Down
114 changes: 114 additions & 0 deletions tests/test-allowlist-validators.sh
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,50 @@ else
fail "Write validator round-3-contract.md" "exit 2 with round error" "exit $EXIT_CODE, output: $RESULT"
fi

WINDOWS_LOOP_DIR="${LOOP_DIR//\//\\}"

# Test 11c: Write validator blocks stale summary with Windows separators
echo "Test 11c: Write validator blocks Windows-path round-3-summary.md"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\round-3-summary.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Write", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-write-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "round"; then
pass "Write validator blocks Windows-path round-3-summary.md"
else
fail "Write validator Windows-path round-3-summary.md" "exit 2 with round error" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 11d: Write validator allows current summary with Windows separators
echo "Test 11d: Write validator allows Windows-path round-5-summary.md"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\round-5-summary.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Write", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-write-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 0 ]]; then
pass "Write validator allows Windows-path round-5-summary.md"
else
fail "Write validator Windows-path round-5-summary.md" "exit 0" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 11e: Write validator blocks plan.md backup with Windows separators
echo "Test 11e: Write validator blocks Windows-path plan.md backup"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\plan.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Write", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-write-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "plan"; then
pass "Write validator blocks Windows-path plan.md backup"
else
fail "Write validator Windows-path plan.md backup" "exit 2 with plan error" "exit $EXIT_CODE, output: $RESULT"
fi

echo ""
echo "=== Test: Edit Validator Allowlist ==="
echo ""
Expand Down Expand Up @@ -274,6 +318,48 @@ else
fail "Edit validator round-0-contract.md" "exit 2 with round error" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 13d: Edit validator blocks stale summary with Windows separators
echo "Test 13d: Edit validator blocks Windows-path round-3-summary.md"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\round-3-summary.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Edit", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-edit-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "round"; then
pass "Edit validator blocks Windows-path round-3-summary.md"
else
fail "Edit validator Windows-path round-3-summary.md" "exit 2 with round error" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 13e: Edit validator allows current summary with Windows separators
echo "Test 13e: Edit validator allows Windows-path round-5-summary.md"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\round-5-summary.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Edit", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-edit-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 0 ]]; then
pass "Edit validator allows Windows-path round-5-summary.md"
else
fail "Edit validator Windows-path round-5-summary.md" "exit 0" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 13f: Edit validator blocks plan.md backup with Windows separators
echo "Test 13f: Edit validator blocks Windows-path plan.md backup"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\plan.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Edit", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-edit-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "plan"; then
pass "Edit validator blocks Windows-path plan.md backup"
else
fail "Edit validator Windows-path plan.md backup" "exit 2 with plan error" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 14: Edit validator blocks round-4-todos.md
echo "Test 14: Edit validator blocks round-4-todos.md"
HOOK_INPUT='{"tool_name": "Edit", "tool_input": {"file_path": "'$LOOP_DIR'/round-4-todos.md"}}'
Expand Down Expand Up @@ -369,6 +455,34 @@ else
fail "Read validator round-3-contract.md" "exit 2 with round error" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 18c: Read validator blocks stale summary with Windows separators
echo "Test 18c: Read validator blocks Windows-path round-3-summary.md"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\round-3-summary.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Read", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-read-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "round"; then
pass "Read validator blocks Windows-path round-3-summary.md"
else
fail "Read validator Windows-path round-3-summary.md" "exit 2 with round error" "exit $EXIT_CODE, output: $RESULT"
fi

# Test 18d: Read validator allows current contract with Windows separators
echo "Test 18d: Read validator allows Windows-path round-5-contract.md"
WINDOWS_FILE_PATH="${WINDOWS_LOOP_DIR}\\round-5-contract.md"
HOOK_INPUT=$(jq -nc --arg file_path "$WINDOWS_FILE_PATH" '{tool_name: "Read", tool_input: {file_path: $file_path}}')
set +e
RESULT=$(echo "$HOOK_INPUT" | "$PROJECT_ROOT/hooks/loop-read-validator.sh" 2>&1)
EXIT_CODE=$?
set -e
if [[ $EXIT_CODE -eq 0 ]]; then
pass "Read validator allows Windows-path round-5-contract.md"
else
fail "Read validator Windows-path round-5-contract.md" "exit 0" "exit $EXIT_CODE, output: $RESULT"
fi

echo ""
echo "=== Test: Bash Validator Allowlist (Path-Restricted) ==="
echo ""
Expand Down