Skip to content

Conversation

@davidpoblador
Copy link

Description

Fixes #2333 - Resolves ForbiddenPathError being incorrectly raised for legitimate external symlinks during copier update.

Problem

The current security check resolves symlinks before validating whether paths are within the destination directory:

if not cwd.joinpath(dst_relpath).resolve().is_relative_to(cwd):
    raise ForbiddenPathError(path=dst_relpath)

This causes false positives when destination directories contain symlinks pointing outside the project (e.g., .env.local → external secrets directory), as .resolve() returns the external target path rather than the symlink path itself.

Solution

This PR modifies the security check to detect existing symlinks before resolution:

  • For existing symlinks: Validate the symlink path itself (which is within the project)
  • For regular files/directories: Resolve as before to catch path traversal attacks
dst_path = cwd.joinpath(dst_relpath)
if dst_path.is_symlink():
    # For existing symlinks, check the symlink itself, not its target
    check_path = dst_path
else:
    # For regular files, resolve as normal to catch path traversal
    check_path = dst_path.resolve()

if not check_path.is_relative_to(cwd):
    raise ForbiddenPathError(path=dst_relpath)

Testing

  • Added comprehensive test case: test_symlink_to_external_path_does_not_raise_forbidden_error
  • All existing security tests continue to pass
  • Path traversal protection remains intact for non-symlink cases

Use Cases Enabled

This fix enables common legitimate patterns:

  • Environment files: .env.local → external secrets directory
  • Shared configurations between projects
  • Security-sensitive files stored outside version control

Backward Compatibility

  • No breaking changes
  • Existing security behavior preserved
  • Only affects the specific edge case of external symlinks

Related

See issue #2333 for detailed problem analysis and reproduction steps.


Thank you for reviewing this fix! I tried to be conservative in the approach to maintain the security intent while resolving the symlink issue. Please let me know if you'd like any adjustments.

When checking if rendered paths are outside the destination directory,
the current logic resolves symlinks before checking. This causes
ForbiddenPathError for legitimate external symlinks (e.g., environment
files stored outside the project).

This fix checks if the destination path is already a symlink before
resolving. For existing symlinks, it validates the symlink path itself
rather than the resolved target, preventing false positives while
maintaining security against path traversal attacks.

Fixes issue where 'copier update' fails with ForbiddenPathError when
destination contains symlinks pointing outside the project directory.

- Add symlink detection before path resolution
- Add comprehensive test case for external symlinks
- Preserve existing security behavior for non-symlink paths
@davidpoblador davidpoblador marked this pull request as draft September 25, 2025 18:18
@davidpoblador
Copy link
Author

Converting to draft because I want to ensure this is working as intended.

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.

ForbiddenPathError raised for legitimate external symlinks during update

1 participant