close
Skip to content

[PHP] Mount parent directory for file symlinks so __DIR__ works#3377

Merged
adamziel merged 3 commits intotrunkfrom
fix/file-symlink-mount-parent-directory
Mar 12, 2026
Merged

[PHP] Mount parent directory for file symlinks so __DIR__ works#3377
adamziel merged 3 commits intotrunkfrom
fix/file-symlink-mount-parent-directory

Conversation

@adamziel
Copy link
Collaborator

Summary

When a symlink points to a file, the readlink interceptor previously mounted only that single file via NODEFS. PHP's __DIR__ inside the mounted file would resolve to its parent path — which was just empty MEMFS scaffolding created by mkdirTree. Any require() call to a sibling file would fail.

This is exactly what happens when WordPress boots through a symlinked wp-load.php: the file itself loads fine, but require_once(__DIR__ . '/wp-includes/version.php') fails because the parent directory is empty.

The fix mounts the parent directory instead of just the file. The readlink return path doesn't change — it still points to the specific file under /internal/symlinks/. The only difference is that the NODEFS mount now covers the whole directory, so sibling files are accessible through __DIR__.

Test plan

  • All 20 existing symlink tests pass (both absolute and relative symlink suites)
  • New test: verifies that scandir() on the parent directory of a resolved file symlink returns sibling files, confirming __DIR__ would work correctly inside a symlinked PHP file

When a symlink points to a file, NODEFS previously mounted only that
single file. PHP's __DIR__ would then resolve to the parent path,
which was just empty MEMFS scaffolding – so require() calls to
sibling files (e.g. wp-includes/version.php from wp-load.php) failed.

Now, file symlinks mount the parent directory instead. The readlink
return path is unchanged, but the NODEFS mount covers the whole
directory, making siblings accessible through __DIR__.
@adamziel adamziel requested review from a team, Copilot and mho22 March 12, 2026 12:33
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Collaborator

@mho22 mho22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

When the readlink interceptor resolves a symlink, it mounts the target
into the VFS under /internal/symlinks/. Previously, file symlinks
mounted only the individual file, and directory symlinks mounted only
the target directory. This meant that upward traversal from __DIR__
(e.g. __DIR__ . '/../../') would land in empty MEMFS scaffolding.

Now, the first symlink resolution mounts the host filesystem root at
/internal/symlinks via NODEFS. This makes the entire host tree
accessible under that prefix, so any depth of ../  traversal works.
Subsequent symlink resolutions reuse the existing mount.

The scaffolding logic (mkdirTree + writeFile) still runs on the first
call to create /internal/symlinks as a mount point. The NODEFS mount
then shadows it. On later calls the target file is found through
NODEFS and the scaffolding is skipped.
Mounting the host filesystem root is too risky. Revert to mounting
just the symlink target's parent directory, which is enough for
__DIR__ sibling access. Add a TODO for the ../../ traversal case.
@adamziel adamziel merged commit 93c68a1 into trunk Mar 12, 2026
43 checks passed
@adamziel adamziel deleted the fix/file-symlink-mount-parent-directory branch March 12, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants