Summary
The macOS writer resolves duplicate top-level input names, but recursively copies nested directory contents without first validating the complete set of destination-relative paths. The staging writer uses FileManager.createFile, which is not an exclusive-create primitive and can truncate an existing case-insensitive alias.
Windows uses FileMode.CreateNew and fails instead of overwriting.
Impact
When input comes from a case-sensitive APFS, SMB, or other volume and staging is on the usual case-insensitive macOS temporary volume, nested files such as a.txt and A.txt can collapse to one staging path. One file can be overwritten or truncated without the package creation operation reporting data loss.
Evidence
macOS/BundlePack/App/PackageBuilder.swift:83-112
macOS/BundlePack/App/PackageBuilder.IO.swift:117-188,386-410
Windows/BundlePack.Core/BundlePackService.IO.cs:216-227,256-262
Reproduction outline
- On a case-sensitive source volume, create one directory containing
a.txt and A.txt with different contents.
- Add that directory to a package on a normal case-insensitive macOS installation.
- Inspect the staged or completed payload and compare the expected file count and hashes.
The code path is confirmed. The audit did not run this destructive data-loss fixture against user data.
Proposed change
- Enumerate the complete input tree before staging.
- Compute the same canonical output-path key used by readers for every relative path.
- Reject all collisions before copying the first byte.
- Create destination files with exclusive, no-follow semantics.
- Keep the post-copy private staging validation as defense in depth.
Acceptance criteria
- Case-only, NFC, full-case-fold, file-versus-directory-prefix, and repeated-top-level collisions are detected before copying.
- Package creation fails with an actionable error that names both conflicting source paths.
- No existing staged file is truncated or replaced.
- Tests cover a case-sensitive source and a case-insensitive staging destination.
- Zero-byte files and empty directories continue to work.
Compatibility constraints
Do not silently drop, rename, merge, or overwrite nested input. Preserve the current top-level numeric suffix behavior unless a separate product decision changes it.
Summary
The macOS writer resolves duplicate top-level input names, but recursively copies nested directory contents without first validating the complete set of destination-relative paths. The staging writer uses
FileManager.createFile, which is not an exclusive-create primitive and can truncate an existing case-insensitive alias.Windows uses
FileMode.CreateNewand fails instead of overwriting.Impact
When input comes from a case-sensitive APFS, SMB, or other volume and staging is on the usual case-insensitive macOS temporary volume, nested files such as
a.txtandA.txtcan collapse to one staging path. One file can be overwritten or truncated without the package creation operation reporting data loss.Evidence
macOS/BundlePack/App/PackageBuilder.swift:83-112macOS/BundlePack/App/PackageBuilder.IO.swift:117-188,386-410Windows/BundlePack.Core/BundlePackService.IO.cs:216-227,256-262Reproduction outline
a.txtandA.txtwith different contents.The code path is confirmed. The audit did not run this destructive data-loss fixture against user data.
Proposed change
Acceptance criteria
Compatibility constraints
Do not silently drop, rename, merge, or overwrite nested input. Preserve the current top-level numeric suffix behavior unless a separate product decision changes it.