Skip to content

fix: canonicalize root in safeJoin to handle symlinked storage roots#64

Open
kylezengo wants to merge 1 commit into
profullstack:mainfrom
kylezengo:fix/safejoin-symlinked-root
Open

fix: canonicalize root in safeJoin to handle symlinked storage roots#64
kylezengo wants to merge 1 commit into
profullstack:mainfrom
kylezengo:fix/safejoin-symlinked-root

Conversation

@kylezengo

Copy link
Copy Markdown

Fixes #62

Problem

When the configured storage root is reached through a symlink — including the macOS system case where /var/private/varsafeJoin incorrectly rejects valid child paths.

The root cause: filepath.EvalSymlinks on a child path resolves to the canonical filesystem path, but within() compares against the original lexical root. When those two forms differ (symlinked root), the check produces a false-positive escape error.

Fix

Canonicalize the root once with filepath.EvalSymlinks before the symlink guard loop, then compare resolved paths against the canonical root. The initial lexical check (which guards against path traversal in rel before any I/O) still uses the original root so the returned path keeps the caller's expected prefix.

Tests

Two regression tests added to internal/files/files_test.go:

  • TestSafeJoinSymlinkedRoot — creates a real directory, symlinks a second name at it, and verifies that a file under the symlinked root is accepted rather than rejected.
  • TestSafeJoinChildSymlinkEscapeStillBlocked — same symlinked root setup, but plants an escaping child symlink (escape → /etc) and verifies it is still rejected.

When the configured storage root (or a system temp dir on macOS where
/var → /private/var) is reached through a symlink, filepath.EvalSymlinks
on a child path resolves to the canonical form, but within() was comparing
against the lexical root — causing valid paths to be rejected with
"files: path escapes its area".

Fix: resolve the root once with EvalSymlinks before the symlink guard
loop, and compare resolved paths against the canonical root. The initial
lexical containment check (line 108) still uses the original root so
that the returned path keeps the caller's expected prefix.

Adds two regression tests:
  - TestSafeJoinSymlinkedRoot: valid file under a symlinked root is accepted
  - TestSafeJoinChildSymlinkEscapeStillBlocked: escaping child symlink is still rejected

Fixes profullstack#62
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.

Files safeJoin rejects valid paths under symlinked storage roots

1 participant