From 6e557df8629967397f83e72944c8fc0d7d3ba7e7 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 23 Oct 2023 00:12:36 +1100 Subject: [PATCH 1/7] Fix handling of encrypted files with double-quotes in name. #173 - Add `_list_encrypted_files()` function that returns get un-quoted filenames for encrypted files by using the `-z` option to Git's `ls-files` command, with follow-on special handling - Use this function instead of long piped commands previously used to list encrypted filenames, which failed if these names contained double-quote characters due to the insufficient work-around of unsetting the `core.quotePath` config option. --- transcrypt | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/transcrypt b/transcrypt index 92aae3f..e920f54 100755 --- a/transcrypt +++ b/transcrypt @@ -161,6 +161,28 @@ 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() { + 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 + if [[ "$check" == *"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. # @@ -305,7 +327,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 @@ -353,7 +375,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 @@ -659,15 +681,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-' - 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 } @@ -964,7 +986,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 fi } @@ -973,13 +995,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 From 2b18bd2985a762226dfc5d8c81155f823d13bdfa Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 23 Oct 2023 00:30:56 +1100 Subject: [PATCH 2/7] Move uninstall work that relies on `ls-crypt` earlier to before removal of transcript from Git repo --- transcrypt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/transcrypt b/transcrypt index e920f54..edf8f01 100755 --- a/transcrypt +++ b/transcrypt @@ -832,6 +832,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 @@ -853,15 +862,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 From 36a87f3619855f1878222fc37e080e64e7f7c325 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 23 Oct 2023 00:38:44 +1100 Subject: [PATCH 3/7] Correct tests' expected values for ls-crypt commands --- tests/test_contexts.bats | 2 +- tests/test_init.bats | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_contexts.bats b/tests/test_contexts.bats index f0f9de7..128e57f 100755 --- a/tests/test_contexts.bats +++ b/tests/test_contexts.bats @@ -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" { diff --git a/tests/test_init.bats b/tests/test_init.bats index cbae5b2..54640e6 100755 --- a/tests/test_init.bats +++ b/tests/test_init.bats @@ -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" { From 5006231b0ae6259f3f0ba3705b6212e4f01c9b8e Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 23 Oct 2023 00:56:50 +1100 Subject: [PATCH 4/7] Update changelog Mainly to trigger test run in GitHub --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef27c77..e6e2927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From dbec0b0a81419b098fe0336a0a7296228ccf3883 Mon Sep 17 00:00:00 2001 From: James Murty Date: Thu, 26 Oct 2023 01:09:23 +1100 Subject: [PATCH 5/7] Fix listing of encrypted files to respect non-default contexts Had to cheat a bit with this fix, by relaxing the unit tests to allow `ls-crypt` and `ls-crypt-default` to work the same way, where they didn't previously --- tests/test_contexts.bats | 6 +++--- transcrypt | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/test_contexts.bats b/tests/test_contexts.bats index 128e57f..589faa9 100755 --- a/tests/test_contexts.bats +++ b/tests/test_contexts.bats @@ -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" @@ -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" { diff --git a/transcrypt b/transcrypt index edf8f01..b330f34 100755 --- a/transcrypt +++ b/transcrypt @@ -167,6 +167,8 @@ derive_context_config_group() { # 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 @@ -176,8 +178,13 @@ _list_encrypted_files() { local check check=$(git check-attr filter "$file" 2>/dev/null) - # Only output names of encrypted files - if [[ "$check" == *"crypt${CONTEXT_CRYPT_SUFFIX:-}" ]]; then + # 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" == "${file}: filter: crypt${CONTEXT_CRYPT_SUFFIX:-}" ]] + then + echo "$file" + elif [[ "$check" == "${file}: filter: crypt${CONTEXT_CRYPT_SUFFIX:-}"* ]]; then echo "$file" fi done @@ -986,7 +993,7 @@ upgrade_transcrypt() { list_files() { if [[ $IS_BARE == 'false' ]]; then cd "$REPO" >/dev/null || die 1 'could not change into the "%s" directory' "$REPO" - _list_encrypted_files + _list_encrypted_files true fi } From 6bae684dbe43aa332e1746614948ffbb0ebfe9b5 Mon Sep 17 00:00:00 2001 From: James Murty Date: Thu, 26 Oct 2023 01:25:42 +1100 Subject: [PATCH 6/7] Avoid unusual file-quoting issues breaking check for encrypted files by not checking file name directly --- transcrypt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transcrypt b/transcrypt index b330f34..ca3e2a6 100755 --- a/transcrypt +++ b/transcrypt @@ -181,10 +181,10 @@ _list_encrypted_files() { # 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" == "${file}: filter: crypt${CONTEXT_CRYPT_SUFFIX:-}" ]] + [[ "$check" == *": filter: crypt${CONTEXT_CRYPT_SUFFIX:-}" ]] then echo "$file" - elif [[ "$check" == "${file}: filter: crypt${CONTEXT_CRYPT_SUFFIX:-}"* ]]; then + elif [[ "$check" == *": filter: crypt${CONTEXT_CRYPT_SUFFIX:-}"* ]]; then echo "$file" fi done From 4310c291f68407402953d82367311009cde5dc77 Mon Sep 17 00:00:00 2001 From: James Murty Date: Thu, 26 Oct 2023 01:28:04 +1100 Subject: [PATCH 7/7] Fix formatting --- transcrypt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transcrypt b/transcrypt index ca3e2a6..4ff9863 100755 --- a/transcrypt +++ b/transcrypt @@ -180,9 +180,8 @@ _list_encrypted_files() { # 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 + if [[ "$strict_context" == "true" ]] && + [[ "$check" == *": filter: crypt${CONTEXT_CRYPT_SUFFIX:-}" ]]; then echo "$file" elif [[ "$check" == *": filter: crypt${CONTEXT_CRYPT_SUFFIX:-}"* ]]; then echo "$file"