Skip to content

fix: create resolvable symlinks on Windows#5

Merged
MarshallOfSound merged 1 commit into
mainfrom
sam/fix-windows-symlinks
Jun 5, 2026
Merged

fix: create resolvable symlinks on Windows#5
MarshallOfSound merged 1 commit into
mainfrom
sam/fix-windows-symlinks

Conversation

@MarshallOfSound

Copy link
Copy Markdown
Member

What

Symlinks created during extraction on Windows were never resolvable. Any consumer that opens a file through a link chain — like electron/fuses reading Electron.app/Contents/Frameworks/Electron Framework.framework/Electron Framework out of the darwin Electron zip — got ENOENT (see the Windows CI failure on electron/fuses#120).

Why

Two bugs in make_symlink on Windows:

  1. Forward slashes in targets. Zip archives store POSIX-style targets (Versions/Current/Electron Framework), and Rust's std passes them verbatim to CreateSymbolicLink. NTFS reparse-point resolution does not treat / as a separator, so the link never resolves. Node's fs.symlink rewrites / to \ for exactly this reason, which is why the original JS extract-zip worked.
  2. Dead directory-link fallback. symlink_file(...).or_else(symlink_dir(...)) never falls back, because symlink_file succeeds even when the target is a directory — it just creates a file-type link that Windows refuses to traverse as a path component. So Versions/Current -> A came out untraversable.

How

  • Rewrite / to \ in the target before creating the link.
  • Pick the link type up front: verify_symlink_target now returns the resolved target path, and a Windows-only helper follows any remaining hops (pending links from this archive first, then the on-disk tree) to determine whether the target is a directory. Unresolvable targets (dangling, loops) default to file-type — no worse than a dangling link, and never an escape since the path was already verified to stay inside dir.

No change to the validation logic itself; the security boundary check is untouched.

Testing

  • New runtime-generated framework fixture mirroring the macOS bundle shape: a file link that crosses a directory link, with the links ordered before the file to exercise the pending-link map.
  • New test asserting the chain is traversable and the file is readable through both links. It runs on Windows whenever the symlink privilege is held (probed at runtime), and on all other platforms unconditionally.
  • Windows-only code type-checked against x86_64-pc-windows-msvc; full suite (52 tests) green on macOS.

Symlinks extracted on Windows were never resolvable, which broke
consumers that open files through the link chain inside the darwin
Electron zip (e.g. Electron Framework.framework/Electron Framework).

Two bugs in make_symlink:

- The archive's POSIX-style target was passed verbatim to
  CreateSymbolicLink, but NTFS reparse points don't treat '/' as a
  separator. Rewrite '/' to '\' before creating the link, matching
  what Node's fs.symlink does.
- symlink_file succeeds even when the target is a directory (it just
  creates an untraversable file-type link), so the symlink_dir
  fallback was dead code. Decide the link type up front instead:
  verify_symlink_target now returns the resolved target path, and a
  windows-only helper follows any remaining hops (pending links from
  the archive, then the on-disk tree) to check whether it is a
  directory. Unresolvable targets default to file-type, which is no
  worse than a dangling link.

The new framework fixture mirrors the macOS bundle shape (file link
crossing a directory link) and the test runs on Windows whenever the
symlink privilege is available.
@MarshallOfSound MarshallOfSound requested a review from a team as a code owner June 5, 2026 00:34
@MarshallOfSound MarshallOfSound merged commit d3f1eaf into main Jun 5, 2026
16 checks passed
@MarshallOfSound MarshallOfSound deleted the sam/fix-windows-symlinks branch June 5, 2026 00:43
@electron-npm-package-publisher

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants