Skip to content
Merged
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
7 changes: 4 additions & 3 deletions lib/commands/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ cmd_init() {
case "$cache_dir" in /*) ;; *) cache_dir="$(pwd -P)/$cache_dir" ;; esac
local cache_file="$cache_dir/init-${func_name}.${shell}"
# Bump when generated helpers change, even if GTR_VERSION is unchanged.
# Schema 15 preserves literal backslashes in quoted Bash completion targets.
local cache_schema="${GTR_INIT_CACHE_VERSION:-15}"
# Schema 16 preserves sticky permission bits in BSD trust-storage checks.
local cache_schema="${GTR_INIT_CACHE_VERSION:-16}"
local cache_stamp="# gtr-cache: version=${GTR_VERSION:-unknown} init=${cache_schema} func=$func_name shell=$shell"

# A matching public stamp alone does not make executable cache data trusted.
Expand Down Expand Up @@ -230,8 +230,9 @@ _init_emit_trust_storage_helpers() {
# Read ownership and permission bits on both GNU and BSD systems.
_hooks_storage_stat() {
local metadata
# BSD %Lp drops special bits such as sticky; callers mask the full mode.
metadata=$(stat -c '%u:%a' "$1" 2>/dev/null) ||
metadata=$(stat -f '%u:%Lp' "$1" 2>/dev/null) || return 1
metadata=$(stat -f '%u:%p' "$1" 2>/dev/null) || return 1
case "$metadata" in
*[!0-9:]*|:*|*:) return 1 ;;
esac
Expand Down
3 changes: 2 additions & 1 deletion lib/hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ _GTR_TRUST_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gtr/trusted"
# Read ownership and permission bits on both GNU and BSD systems.
_hooks_storage_stat() {
local metadata
# BSD %Lp drops special bits such as sticky; callers mask the full mode.
metadata=$(stat -c '%u:%a' "$1" 2>/dev/null) ||
metadata=$(stat -f '%u:%Lp' "$1" 2>/dev/null) || return 1
metadata=$(stat -f '%u:%p' "$1" 2>/dev/null) || return 1
case "$metadata" in
*[!0-9:]*|:*|*:) return 1 ;;
esac
Expand Down
10 changes: 5 additions & 5 deletions tests/init.bats
Original file line number Diff line number Diff line change
Expand Up @@ -1167,14 +1167,14 @@ run_generated_v2_trust_upgrade() (
mkdir -p "$XDG_CACHE_HOME/gtr"
for shell_name in bash zsh fish; do
for func_name in gtr gwtr; do
for schema in 14 13 12 11 10 9 8 7 6; do
for schema in 15 14 13 12 11 10 9 8 7 6; do
cache_file="$XDG_CACHE_HOME/gtr/init-$func_name.$shell_name"
printf '# gtr-cache: version=test init=%s func=%s shell=%s\nold-cache\n' \
"$schema" "$func_name" "$shell_name" > "$cache_file"
run cmd_init "$shell_name" --as "$func_name"
[ "$status" -eq 0 ]
[[ "$output" != *old-cache* ]]
[[ "$(head -1 "$cache_file")" == *"init=15"* ]]
[[ "$(head -1 "$cache_file")" == *"init=16"* ]]
done
done
done
Expand Down Expand Up @@ -1203,15 +1203,15 @@ run_documented_init_upgrade() (
if [ "$shell_name" = zsh ]; then
snippet='compdef() { :; }'$'\n'"$snippet"
fi
for schema in 6 7 8 9 10 11 12 13 14 15; do
for schema in 6 7 8 9 10 11 12 13 14 15 16; do
version="$cli_version"
[ "$schema" != 15 ] || version=old-version
[ "$schema" != 16 ] || version=old-version
printf '# gtr-cache: version=%s init=%s func=gtr shell=%s\n' \
"$version" "$schema" "$shell_name" > "$cache_file"
printf '%s\n' 'echo stale > "$STALE_CACHE_MARKER"' >> "$cache_file"
"$shell_name" -c "$snippet"
[ ! -f "$STALE_CACHE_MARKER" ]
[[ "$(head -1 "$cache_file")" == *"version=$cli_version init=15 "* ]]
[[ "$(head -1 "$cache_file")" == *"version=$cli_version init=16 "* ]]
done
done
)
Expand Down
65 changes: 65 additions & 0 deletions tests/trust_storage.bats
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,71 @@ assert_storage_rejected() {
_hooks_are_trusted "$TEST_REPO/.gtrconfig"
}

# Use an owned sticky parent without relying on the host's /tmp layout.
setup_sticky_storage() {
STICKY_PARENT="$TEST_REPO/sticky parent"
mkdir "$STICKY_PARENT"
chmod 1777 "$STICKY_PARENT"
export XDG_CONFIG_HOME="$STICKY_PARENT/private config"
_GTR_TRUST_DIR="$XDG_CONFIG_HOME/gtr/trusted"
TRUST_MARKER=$(_hooks_current_trust_path "$TEST_REPO/.gtrconfig")
}

@test "CLI trust permits private storage beneath sticky ancestors" {
setup_sticky_storage

run bash -c 'printf "y\n" | "$1" trust' -- "$PROJECT_ROOT/bin/gtr"
[ "$status" -eq 0 ]
[ "$(file_mode "$_GTR_TRUST_DIR")" = 700 ]
[ "$(file_mode "$TRUST_MARKER")" = 600 ]
run "$PROJECT_ROOT/bin/gtr" ai 1 --ai true
[ "$status" -eq 0 ]
[ "$(cat "$TEST_REPO/result")" = approved ]

# Removing the sticky protection must invalidate the existing approval.
chmod 777 "$STICKY_PARENT"
assert_storage_rejected
rm "$TEST_REPO/result"
run "$PROJECT_ROOT/bin/gtr" ai 1 --ai true
[ "$status" -eq 0 ]
[ ! -e "$TEST_REPO/result" ]
}

# Approve before making the parent sticky, so this independently tests the
# generated consumer even when CLI approval beneath sticky parents is broken.
exercise_generated_sticky_storage() {
local shell_name="$1"
setup_sticky_storage
chmod 700 "$STICKY_PARENT"
_hooks_mark_trusted "$TEST_REPO/.gtrconfig"
export INIT_FIXTURE="$TEST_REPO/init.$shell_name" REPO_FIXTURE="$TEST_REPO"
cmd_init "$shell_name" --as safe_gtr > "$INIT_FIXTURE"
chmod 1777 "$STICKY_PARENT"

run "$shell_name" -c 'source "$INIT_FIXTURE"; safe_gtr_run_post_cd_hooks "$REPO_FIXTURE"'
[ "$status" -eq 0 ]
[ "$(cat "$TEST_REPO/result")" = approved ]
rm "$TEST_REPO/result"
chmod 777 "$STICKY_PARENT"
run "$shell_name" -c 'source "$INIT_FIXTURE"; safe_gtr_run_post_cd_hooks "$REPO_FIXTURE"'
[ "$status" -eq 0 ]
[ ! -e "$TEST_REPO/result" ]
}

@test "generated Bash helpers permit private storage beneath sticky ancestors" {
exercise_generated_sticky_storage bash
}

@test "generated Zsh helpers permit private storage beneath sticky ancestors" {
command -v zsh >/dev/null || skip 'zsh is not installed'
exercise_generated_sticky_storage zsh
}

@test "generated Fish helpers permit private storage beneath sticky ancestors" {
command -v fish >/dev/null || skip 'fish is not installed'
exercise_generated_sticky_storage fish
}

@test "trust refuses writable existing directories and ancestors without repairing permissions" {
_hooks_mark_trusted "$TEST_REPO/.gtrconfig"
local directory mode
Expand Down
Loading