Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix handling of double-quotes in encrypted file names #173 #174

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ system, you must also run the `--upgrade` command in each repository:
### Fixed

- Prevent `cd` commands printing out excess details when `CDPATH` is set (#156)
- Fix handling of double-quotes in encrypted file names (#173)

### Changed

Expand Down
8 changes: 4 additions & 4 deletions tests/test_contexts.bats
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function teardown {
[[ $(git config --get diff.crypt.binary) = "true" ]]
[[ $(git config --get merge.renormalize) = "true" ]]

[[ "$(git config --get alias.ls-crypt)" = "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; / crypt/{ print \$1 }'" ]]
[[ "$(git config --get alias.ls-crypt)" = '!"$(git config transcrypt.crypt-dir 2>/dev/null || printf %s/crypt ""$(git rev-parse --git-dir)"")"/transcrypt --list' ]]
}

@test "init: show extra context details in --display" {
Expand Down Expand Up @@ -216,7 +216,7 @@ function teardown {
[ "${lines[1]}" = "$SUPER_SECRET_CONTENT_ENC" ]
}

@test "contexts: git ls-crypt lists encrypted file for all contexts" {
@test "contexts: git ls-crypt lists encrypted files for all contexts" {
encrypt_named_file sensitive_file "$SECRET_CONTENT"
encrypt_named_file super_sensitive_file "$SECRET_CONTENT" "super-secret"

Expand All @@ -226,14 +226,14 @@ function teardown {
[ "${lines[1]}" = "super_sensitive_file" ]
}

@test "contexts: git ls-crypt-default lists encrypted file for only 'default' context" {
@test "contexts: git ls-crypt-default lists encrypted files for all contexts" {
encrypt_named_file sensitive_file "$SECRET_CONTENT"
encrypt_named_file super_sensitive_file "$SECRET_CONTENT" "super-secret"

run git ls-crypt-default
[ "$status" -eq 0 ]
[ "${lines[0]}" = "sensitive_file" ]
[ "${lines[1]}" = "" ]
[ "${lines[1]}" = "super_sensitive_file" ]
}

@test "contexts: git ls-crypt-super-secret lists encrypted file for only 'super-secret' context" {
Expand Down
2 changes: 1 addition & 1 deletion tests/test_init.bats
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1
[ "$(git config --get merge.renormalize)" = "true" ]
[ "$(git config --get merge.crypt.name)" = "Merge transcrypt secret files" ]

[ "$(git config --get alias.ls-crypt)" = "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; / crypt/{ print \$1 }'" ]
[ "$(git config --get alias.ls-crypt)" = '!"$(git config transcrypt.crypt-dir 2>/dev/null || printf %s/crypt ""$(git rev-parse --git-dir)"")"/transcrypt --list' ]
}

@test "init: show details for --display" {
Expand Down
65 changes: 46 additions & 19 deletions transcrypt
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,34 @@ derive_context_config_group() {
fi
}

# Internal function that returns a list of filenames for encrypted files in the
# repo, where the filenames are verbatim and not quoted in any way even if they
# contain unusual characters like double-quotes, backslash and control
# characters. We must avoid quoting of filenames to support names containing
# double quotes. #173
_list_encrypted_files() {
local strict_context=${1:-}

IFS=$'\n'
# List files with -z option to disable quoting of filenames, then
# immediately convert NUL-delimited filenames to be newline-delimited to be
# compatibility with bash variables
for file in $(git ls-files -z | tr '\0' '\n'); do
# Check for the suffix ': filter: crypt' that identifies encrypted file
local check
check=$(git check-attr filter "$file" 2>/dev/null)

# Only output names of encrypted files matching the context, either
# strictly (if $1 = "true") or loosely (if $1 is false or unset)
if [[ "$strict_context" == "true" ]] &&
[[ "$check" == *": filter: crypt${CONTEXT_CRYPT_SUFFIX:-}" ]]; then
echo "$file"
elif [[ "$check" == *": filter: crypt${CONTEXT_CRYPT_SUFFIX:-}"* ]]; then
echo "$file"
fi
done
}

# Detect OpenSSL major version 3 or later which requires a compatibility
# work-around to include the prefix 'Salted__' and salt value when encrypting.
#
Expand Down Expand Up @@ -305,7 +333,7 @@ git_pre_commit() {
tmp=$(mktemp)
IFS=$'\n'
slow_mode_if_failed() {
for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do
for secret_file in $(_list_encrypted_files); do
# Skip symlinks, they contain the linked target file path not plaintext
if [[ -L $secret_file ]]; then
continue
Expand Down Expand Up @@ -353,7 +381,7 @@ git_pre_commit() {
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]] && [[ "${BASH_VERSINFO[1]}" -ge 4 ]]; then
num_procs=$(nproc)
num_jobs="\j"
for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do
for secret_file in $(_list_encrypted_files); do
while ((${num_jobs@P} >= num_procs)); do
wait -n
done
Expand Down Expand Up @@ -659,15 +687,15 @@ save_configuration() {
git config merge.crypt.name 'Merge transcrypt secret files'

# add git alias for listing ALL encrypted files regardless of context
git config alias.ls-crypt "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; / crypt/{ print \$1 }'"
git config alias.ls-crypt "!$transcrypt_path --list"

# add a git alias for listing encrypted files in specific context, including 'default'
if [[ "$CONTEXT" = 'default' ]]; then
# List files with gitattribute 'filter=crypt'
git config alias.ls-crypt-default "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; / crypt$/{ print \$1 }'"
git config alias.ls-crypt-default "!$transcrypt_path --list"
else
# List files with gitattribute 'filter=crypt-<CONTEXT>'
git config "alias.ls-crypt-${CONTEXT}" "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; / crypt-${CONTEXT}$/{ print \$1 }'"
git config "alias.ls-crypt-${CONTEXT}" "!$transcrypt_path --context=${CONTEXT} --list"
fi
}

Expand Down Expand Up @@ -810,6 +838,15 @@ uninstall_transcrypt() {
remove_cached_plaintext
fi

# touch all encrypted files to prevent stale stat info
local encrypted_files
encrypted_files=$(git ls-crypt)
if [[ $encrypted_files ]] && [[ $IS_BARE == 'false' ]]; then
cd "$REPO" >/dev/null || die 1 'could not change into the "%s" directory' "$REPO"
# shellcheck disable=SC2086
touch $encrypted_files
fi

# remove helper scripts
# Keep obsolete clean,smudge,textconv,merge refs here to remove them on upgrade
for script in {transcrypt,clean,smudge,textconv,merge}; do
Expand All @@ -831,15 +868,6 @@ uninstall_transcrypt() {
fi
[[ -f "$pre_commit_hook_installed" ]] && rm "$pre_commit_hook_installed"

# touch all encrypted files to prevent stale stat info
local encrypted_files
encrypted_files=$(git ls-crypt)
if [[ $encrypted_files ]] && [[ $IS_BARE == 'false' ]]; then
cd "$REPO" >/dev/null || die 1 'could not change into the "%s" directory' "$REPO"
# shellcheck disable=SC2086
touch $encrypted_files
fi

# remove context settings: cipher & password config, ls-crypt alias variant,
# crypt filter/diff/merge attributes. We do it here instead of `clean_gitconfig`
# to avoid interfering with flushing of credentials
Expand Down Expand Up @@ -964,7 +992,7 @@ upgrade_transcrypt() {
list_files() {
if [[ $IS_BARE == 'false' ]]; then
cd "$REPO" >/dev/null || die 1 'could not change into the "%s" directory' "$REPO"
git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt/{ print $1 }'
_list_encrypted_files true
fi
}

Expand All @@ -973,13 +1001,12 @@ show_raw_file() {
if [[ -f $show_file ]]; then
# ensure the file is currently being tracked
local escaped_file=${show_file//\//\\\/}
if git -c core.quotePath=false ls-files --others -- "$show_file" | awk "/${escaped_file}/{ exit 1 }"; then
file_paths=$(git -c core.quotePath=false ls-tree --name-only --full-name HEAD "$show_file")
else
file_paths=$(_list_encrypted_files | grep "$escaped_file")
if [[ -z "$file_paths" ]]; then
die 1 'the file "%s" is not currently being tracked by git' "$show_file"
fi
elif [[ $show_file == '*' ]]; then
file_paths=$(git ls-crypt)
file_paths=$(_list_encrypted_files)
else
die 1 'the file "%s" does not exist' "$show_file"
fi
Expand Down