From 43dceb705eee8e17ca4a47038b0903a94b799b77 Mon Sep 17 00:00:00 2001 From: johnson Date: Mon, 27 Jul 2026 14:59:36 +0000 Subject: [PATCH 1/3] fix: reject obfuscation path collisions --- lib/obfuscate.sh | 18 +++++++++++++++++- test/obfuscate.bats | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/obfuscate.sh b/lib/obfuscate.sh index 391abfe..631499b 100644 --- a/lib/obfuscate.sh +++ b/lib/obfuscate.sh @@ -83,6 +83,8 @@ build_obfuscation_plan() { if ! awk -F '\t' -v OFS='\t' -v candidates="$candidates" ' FILENAME != candidates { if ($1 != "") { + if (($1 in id_names) && id_names[$1] != $2) duplicate_ids[$1] = 1 + id_names[$1] = $2 ids[$1] = 1 if (!($2 in names)) names[$2] = $1 } @@ -97,7 +99,11 @@ build_obfuscation_plan() { if (base ~ /^[a-f0-9]{8}$/) { print "invalid", "-", relpath } else if (relpath in names) { - print "known", names[relpath], relpath + if (names[relpath] in duplicate_ids) { + print "collision", names[relpath], relpath + } else { + print "known", names[relpath], relpath + } } else { print "new", "-", relpath } @@ -114,6 +120,16 @@ build_obfuscation_plan() { refuse_if_hex_basename "$relpath" return 1 fi + if [ "$kind" = "collision" ]; then + rm -rf "$workspace" + echo "Error: manifest ID '$id' maps to multiple readable paths" >&2 + return 1 + fi + if [ "$kind" = "known" ] && [ -e "$notes_dir/$id" ]; then + rm -rf "$workspace" + echo "Error: refusing to overwrite existing obfuscated path: $id" >&2 + return 1 + fi done < "$plan_file" rm -rf "$workspace" diff --git a/test/obfuscate.bats b/test/obfuscate.bats index fabdeb4..ac9f9d5 100644 --- a/test/obfuscate.bats +++ b/test/obfuscate.bats @@ -174,6 +174,40 @@ setup() { [ -f "$NOTES_CALLER_PWD/notes/$alpha_id" ] } +@test "obfuscate refuses to overwrite an existing known-ID destination" { + notes obfuscate + local alpha_id + alpha_id=$(grep $'\talpha\.md$' "$NOTES_CALLER_PWD/notes/.manifest" | cut -f1) + git -C "$NOTES_CALLER_PWD" commit -q --no-verify -m "obfuscated" + + # Simulate an interrupted or stale state containing both representations. + printf 'dirty readable content\n' > "$NOTES_CALLER_PWD/notes/alpha.md" + local obfuscated_before + obfuscated_before=$(cat "$NOTES_CALLER_PWD/notes/$alpha_id") + + run notes obfuscate alpha.md + + [ "$status" -ne 0 ] + [[ "$output" == *"refusing to overwrite existing obfuscated path: $alpha_id"* ]] + [ "$(cat "$NOTES_CALLER_PWD/notes/$alpha_id")" = "$obfuscated_before" ] + [ "$(cat "$NOTES_CALLER_PWD/notes/alpha.md")" = "dirty readable content" ] +} + +@test "obfuscate refuses a manifest ID shared by planned readable paths" { + notes obfuscate + local alpha_id + alpha_id=$(grep $'\talpha\.md$' "$NOTES_CALLER_PWD/notes/.manifest" | cut -f1) + notes deobfuscate + printf '%s\tbeta.md\n' "$alpha_id" >> "$NOTES_CALLER_PWD/notes/.manifest" + + run notes obfuscate alpha.md beta.md + + [ "$status" -ne 0 ] + [[ "$output" == *"manifest ID '$alpha_id' maps to multiple readable paths"* ]] + [ -f "$NOTES_CALLER_PWD/notes/alpha.md" ] + [ -f "$NOTES_CALLER_PWD/notes/beta.md" ] +} + @test "full obfuscate batches Fold-scale known-entry classification and staging" { local count=535 i=1 id name mock_bin command real_command call_log rm -rf "$NOTES_CALLER_PWD/notes" From 559be4323493e81550a7a12b840a2fa73f355e27 Mon Sep 17 00:00:00 2001 From: johnson Date: Mon, 27 Jul 2026 15:05:43 +0000 Subject: [PATCH 2/3] docs: update test count --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59f8186..ae7a974 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **Collective memory, encrypted.** -[![tests: 416](https://img.shields.io/badge/tests-416-brightgreen?style=flat)](test/) +[![tests: 418](https://img.shields.io/badge/tests-418-brightgreen?style=flat)](test/) ![lints: 8](https://img.shields.io/badge/lints-8-blue?style=flat) [![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE) From 95c61d687ba3f5904123b5213d7d6d5de4e110ad Mon Sep 17 00:00:00 2001 From: junior Date: Mon, 27 Jul 2026 12:23:18 -0400 Subject: [PATCH 3/3] fix: preserve dangling obfuscated destinations --- README.md | 2 +- lib/obfuscate.sh | 2 +- test/obfuscate.bats | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae7a974..2bf7ce3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ **Collective memory, encrypted.** -[![tests: 418](https://img.shields.io/badge/tests-418-brightgreen?style=flat)](test/) +[![tests: 419](https://img.shields.io/badge/tests-419-brightgreen?style=flat)](test/) ![lints: 8](https://img.shields.io/badge/lints-8-blue?style=flat) [![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat)](LICENSE) diff --git a/lib/obfuscate.sh b/lib/obfuscate.sh index 631499b..1bc3eb5 100644 --- a/lib/obfuscate.sh +++ b/lib/obfuscate.sh @@ -125,7 +125,7 @@ build_obfuscation_plan() { echo "Error: manifest ID '$id' maps to multiple readable paths" >&2 return 1 fi - if [ "$kind" = "known" ] && [ -e "$notes_dir/$id" ]; then + if [ "$kind" = "known" ] && { [ -e "$notes_dir/$id" ] || [ -L "$notes_dir/$id" ]; }; then rm -rf "$workspace" echo "Error: refusing to overwrite existing obfuscated path: $id" >&2 return 1 diff --git a/test/obfuscate.bats b/test/obfuscate.bats index ac9f9d5..786ebec 100644 --- a/test/obfuscate.bats +++ b/test/obfuscate.bats @@ -193,6 +193,23 @@ setup() { [ "$(cat "$NOTES_CALLER_PWD/notes/alpha.md")" = "dirty readable content" ] } +@test "obfuscate refuses to replace a dangling known-ID symlink" { + notes obfuscate + local alpha_id + alpha_id=$(grep $'\talpha\.md$' "$NOTES_CALLER_PWD/notes/.manifest" | cut -f1) + rm "$NOTES_CALLER_PWD/notes/$alpha_id" + printf 'readable content\n' > "$NOTES_CALLER_PWD/notes/alpha.md" + ln -s missing-target "$NOTES_CALLER_PWD/notes/$alpha_id" + + run notes obfuscate alpha.md + + [ "$status" -ne 0 ] + [[ "$output" == *"refusing to overwrite existing obfuscated path: $alpha_id"* ]] + [ -L "$NOTES_CALLER_PWD/notes/$alpha_id" ] + [ "$(readlink "$NOTES_CALLER_PWD/notes/$alpha_id")" = "missing-target" ] + [ "$(cat "$NOTES_CALLER_PWD/notes/alpha.md")" = "readable content" ] +} + @test "obfuscate refuses a manifest ID shared by planned readable paths" { notes obfuscate local alpha_id