Skip to content

Prevent silent nested-name collisions during macOS staging #11

Description

@tuki0918

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

  1. On a case-sensitive source volume, create one directory containing a.txt and A.txt with different contents.
  2. Add that directory to a package on a normal case-insensitive macOS installation.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions