From dcd93e699ad68b12da23925d49cec9c7449fe782 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:57:47 -0500 Subject: [PATCH] fix: safe symlink handling in uninstall - Broken symlinks now removable (lexists check via is_symlink fallback) - Symlinks never hashed (avoids following to external targets) - Symlinks only removed with force=True, otherwise skipped --- src/specify_cli/integrations/manifest.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/specify_cli/integrations/manifest.py b/src/specify_cli/integrations/manifest.py index da71a2bd4..f461a729a 100644 --- a/src/specify_cli/integrations/manifest.py +++ b/src/specify_cli/integrations/manifest.py @@ -159,15 +159,22 @@ class IntegrationManifest: normed.relative_to(root) except (ValueError, OSError): continue - if not path.exists(): + if not path.exists() and not path.is_symlink(): continue # Skip directories — manifest only tracks files if not path.is_file() and not path.is_symlink(): skipped.append(path) continue - if not force and _sha256(path) != expected_hash: - skipped.append(path) - continue + # Never follow symlinks when comparing hashes. Only remove + # symlinks when forced, to avoid acting on tampered entries. + if path.is_symlink(): + if not force: + skipped.append(path) + continue + else: + if not force and _sha256(path) != expected_hash: + skipped.append(path) + continue path.unlink() removed.append(path) # Clean up empty parent directories up to project root