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
35 changes: 16 additions & 19 deletions hooks/1password-validate-mounted-env-files/hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,17 @@ has_toml_mount_paths_field() {
return 1
}

# Parse TOML file and extract mount paths from top-level mount_paths field
# Extract quoted strings (double or single) from TOML array content.
# Outputs each extracted value on its own line.
# Usage: extract_toml_array_items "array_content_string"
extract_toml_array_items() {
local content="$1"
# Match both double-quoted and single-quoted strings using grep + sed
# This handles: "value", 'value', and mixed arrays
echo "$content" | grep -oE '"[^"]+"|'"'"'[^'"'"']+'"'" | sed 's/^["'"'"']//;s/["'"'"']$//' || true
}

# Parse TOML file and extract mount paths from top-level mount_paths field
# Returns newline-separated list of mount paths
# Returns empty string (but exit code 0) if mount_paths = []
# Returns exit code 1 if mount_paths field doesn't exist
Expand All @@ -274,9 +284,10 @@ parse_toml_mount_paths() {
# Pure bash TOML parsing for environments entries
# Handles formats like:
# mount_paths = [".env", "billing.env"]
# mount_paths = ['.env', 'billing.env']
# mount_paths = [
# ".env",
# "billing.env"
# 'billing.env'
# ]
# mount_paths = []
local in_mount_paths_array=false
Expand Down Expand Up @@ -305,13 +316,7 @@ parse_toml_mount_paths() {
array_content="$array_part"
in_mount_paths_array=false # Array is complete on one line

# Extract quoted strings from the array content
while [[ "$array_content" =~ \"([^\"]+)\" ]]; do
mount_paths="${mount_paths}${BASH_REMATCH[1]}"$'\n'
# Remove the matched string and any following comma/whitespace
array_content="${array_content#*\"${BASH_REMATCH[1]}\"}"
array_content=$(echo "$array_content" | sed 's/^[[:space:]]*,[[:space:]]*//;s/^[[:space:]]*//')
done
mount_paths="$(extract_toml_array_items "$array_content")"
# Check for mount_paths = [ (multi-line array start)
elif [[ "$line" =~ ^mount_paths[[:space:]]*=[[:space:]]*\[ ]]; then
found_mount_paths_field=true
Expand All @@ -322,11 +327,7 @@ parse_toml_mount_paths() {
# If array closes on same line, process it
if [[ "$array_content" =~ \] ]]; then
array_content="${array_content%\]*}"
while [[ "$array_content" =~ \"([^\"]+)\" ]]; do
mount_paths="${mount_paths}${BASH_REMATCH[1]}"$'\n'
array_content="${array_content#*\"${BASH_REMATCH[1]}\"}"
array_content=$(echo "$array_content" | sed 's/^[[:space:]]*,[[:space:]]*//;s/^[[:space:]]*//')
done
mount_paths="$(extract_toml_array_items "$array_content")"
in_mount_paths_array=false
array_content=""
fi
Expand All @@ -338,11 +339,7 @@ parse_toml_mount_paths() {
local line_content="${line%\]*}"
array_content="${array_content} ${line_content}"
# Process the complete array content
while [[ "$array_content" =~ \"([^\"]+)\" ]]; do
mount_paths="${mount_paths}${BASH_REMATCH[1]}"$'\n'
array_content="${array_content#*\"${BASH_REMATCH[1]}\"}"
array_content=$(echo "$array_content" | sed 's/^[[:space:]]*,[[:space:]]*//;s/^[[:space:]]*//')
done
mount_paths="$(extract_toml_array_items "$array_content")"
in_mount_paths_array=false
array_content=""
else
Expand Down
133 changes: 133 additions & 0 deletions tests/hooks/1password-validate-mounted-env-files.bats
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,136 @@ canonical_one_root='{"client":"cursor","event":"before_shell_execution","type":"
[[ "$output" == '{"decision":"allow","message":""}' ]]
}

# ============================================================================
# TOML mount_paths parsing tests (extract_toml_array_items / parse_toml_mount_paths)
# ============================================================================

# Source the hook functions for unit testing.
# Uses awk to extract top-level function definitions (handles nested braces).
_extract_func() {
awk "/^$1\(\)/,/^}/" "$2"
}

TOML_TMPFILE=""

setup_toml_tests() {
source "${PROJECT_ROOT}/lib/json.sh"
source "${PROJECT_ROOT}/lib/os.sh"
source "${PROJECT_ROOT}/lib/paths.sh"
source "${PROJECT_ROOT}/lib/logging.sh"

eval "$(_extract_func normalize_toml_line "${HOOK_SCRIPT}")"
eval "$(_extract_func extract_toml_array_items "${HOOK_SCRIPT}")"
eval "$(_extract_func has_toml_mount_paths_field "${HOOK_SCRIPT}")"
eval "$(_extract_func parse_toml_mount_paths "${HOOK_SCRIPT}")"

TOML_TMPFILE=$(mktemp)
}

teardown() {
if [[ -n "$TOML_TMPFILE" ]]; then
rm -f "$TOML_TMPFILE"
fi
}

# Helper: assert output contains exactly the expected lines (order-independent).
assert_lines() {
local expected=("$@")
local actual_count
actual_count=$(echo "$output" | wc -l | tr -d ' ')
[[ "$actual_count" -eq ${#expected[@]} ]]
for expected_line in "${expected[@]}"; do
echo "$output" | grep -qxF "$expected_line"
done
}

@test "parse_toml_mount_paths handles double-quoted items" {
setup_toml_tests
echo 'mount_paths = [".env", ".env.test"]' > "$TOML_TMPFILE"

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env" ".env.test"
}

@test "parse_toml_mount_paths handles single-quoted items" {
setup_toml_tests
printf "mount_paths = ['.env', '.env.test']\n" > "$TOML_TMPFILE"

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env" ".env.test"
}

@test "parse_toml_mount_paths handles mixed single and double quotes" {
setup_toml_tests
printf "mount_paths = ['.env', \".env.test\"]\n" > "$TOML_TMPFILE"

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env" ".env.test"
}

@test "parse_toml_mount_paths handles multi-line single-quoted items" {
setup_toml_tests
cat > "$TOML_TMPFILE" <<'TOML'
mount_paths = [
'.env',
'.env.test'
]
TOML

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env" ".env.test"
}

@test "parse_toml_mount_paths handles multi-line mixed quotes" {
setup_toml_tests
cat > "$TOML_TMPFILE" <<'TOML'
mount_paths = [
'.env',
".env.test"
]
TOML

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env" ".env.test"
}

@test "parse_toml_mount_paths still handles empty array" {
setup_toml_tests
echo 'mount_paths = []' > "$TOML_TMPFILE"

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
[[ -z "$output" ]]
}

@test "parse_toml_mount_paths handles paths with spaces" {
setup_toml_tests
printf "mount_paths = ['.env file', \"other env\"]\n" > "$TOML_TMPFILE"

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env file" "other env"
}

@test "parse_toml_mount_paths handles single item array" {
setup_toml_tests
printf "mount_paths = ['.env']\n" > "$TOML_TMPFILE"

run parse_toml_mount_paths "$TOML_TMPFILE"

[[ $status -eq 0 ]]
assert_lines ".env"
}