fetch: sanitizePath is purely lexical and does not prevent symlink escape #86
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of the PR #59 review (finding B8), which found the problem but
correctly scoped the fix out of a lint-cleanup PR.
Problem
sanitizePathininternal/cli/fetch.gois the only guard on paths takenfrom a manifest before they are used to create directories and files under
the fetch destination. It performs three purely lexical checks:
filepath.IsAbsfilepath.Clean, then reject a leading../(and re-checkIsAbs)Nothing resolves symlinks. If the destination tree already contains a
symlinked directory - say
./datais a symlink to/etc- then a manifestentry
data/passwdpasses every check and is written through the symlink,outside the destination tree. Both
os.MkdirAlland theos.Createof thetemp file in
downloadFilefollow the symlink.PR #59 narrowed the suppression on the
os.Createline to G304 and replacedits previous, overstated justification with one that states the limitation
explicitly, and moved the
sanitizePathcall insidedownloadFileso thelexical invariant is enforced locally rather than one function away. Neither
change closes this gap; they only stop misdescribing it.
Definition of done
directory, even when the destination tree contains a symlink that a
previous fetch (or anything else) placed there.
fetch: theos.MkdirAllfor parent directories, the temp fileos.Create, and thefinal rename.
subdirectory pointing outside the tree, fetches a manifest with an entry
under that subdirectory, and asserts the write is refused and nothing
outside the destination is touched.
//nolint:gosec // G304: ...comment indownloadFileis updated todescribe whatever guarantee then actually holds, or removed if it is no
longer needed.
Notes
Candidate approaches worth weighing before implementing: resolving the
destination root once with
filepath.EvalSymlinksand verifying everyresolved write path stays under it; refusing to traverse a symlinked
component at all; or
os.OpenRoot/openat2-style rooted file access.Picking among these is part of the work, not a decided outcome.
Related: the mode of directories created by
fetchis0o755on purpose(fetched trees are meant to be readable by other uids); do not "fix" this
by tightening permissions.