Repository navigation
Prevent path redirection in writeToFile via symlinked path components - #2156
Merged
Merged
Conversation
writeToFile opened the final user-supplied path directly with os.Create/os.OpenFile, following symlinks in both the parent directory and the final filename. This allowed template output to be redirected outside the intended directory or to clobber an existing file. Reject symlinked parent and final path components using os.Lstat before opening, and route all write, chown, and chmod operations through the validated path.
Open the destination with O_NOFOLLOW so the open fails if the final path component is a symlink, closing the TOCTOU window between the pre-check and the open. Apply ownership and permissions via the open file descriptor (f.Chown/f.Chmod) instead of the path so metadata always targets the file we opened. O_NOFOLLOW is provided per-platform (no-op on Windows).
Gate MkdirAll on os.IsNotExist so writeToFile only creates the parent directory when it is genuinely missing, and surfaces other stat errors (e.g. permission denied) directly instead of masking them with a MkdirAll failure.
Return early if os.Lstat on the destination fails for any reason other than the file not existing (e.g. permission denied), rather than proceeding to open. This keeps the final-component check consistent with the parent directory check above and avoids continuing when we cannot verify the target is safe.
Surabhi-1605
force-pushed
the
fix-writetofile-symlink
branch
from
July 8, 2026 10:21
acce9d9 to
bea967e
Compare
social4hyq
pushed a commit
to social4hyq/homebrew-core
that referenced
this pull request
Sep 20, 2026
consul-template 0.42.1 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>0.42.1 (July 08, 2026) IMPROVEMENTS: cve: fix path redirection and unintended file overwrite in writeToFile helper through symlink attack hashicorp/consul-template#2156 update: go version to 1.26.4 hashicorp/consul-template#2155 update: Bump github.com/hashicorp/consul/api v1.34.0 to 1.34.4 https://github.com/hashicorp/consul-template/pull/2155</pre> <p>View the full release notes at <a href="https://github.com/hashicorp/consul-template/releases/tag/v0.42.1">https://github.com/hashicorp/consul-template/releases/tag/v0.42.1</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!13782
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
writeToFiletemplate helper opened the final user-supplied path directly withos.Create/os.OpenFile, which follow symlinks in both intermediate directory components and the final filename. This allowed template output to be redirected outside the intended directory, or to overwrite an existing file via a pre-positioned symlink — a particular concern givenwriteToFileis documented for writing certificate and private-key material.Unlike the main renderer's
AtomicWrite(temp-file + rename, which replaces the directory entry rather than following it),writeToFilewrote directly to the named path and lost that protection.Changes
os.Lstat), which would redirect the whole write into an attacker-controlled directory.os.Lstat), which would clobber an unintended target.O_NOFOLLOW(via a per-platform constant; no-op on Windows) so the open itself fails if the final component is a symlink, closing the TOCTOU window between the pre-check and the open.f.Chown/f.Chmod) instead of the path, so metadata always targets the file that was opened rather than a path that could be swapped afterward.os.IsNotExist), and return immediately if the destination cannot be stat'd for any reason other than non-existence (e.g. permission denied), instead of proceeding to open.os.Lstatis used rather thanfilepath.EvalSymlinkscontainment (as used by the sandboxedfilehelper) becausewriteToFilehas no configured sandbox boundary, and anEvalSymlinks-based ancestor check produces false positives on OS-managed symlinks (e.g. macOS/var -> /private/var).Known limitation
Only the immediate parent directory is checked, not grandparent/ancestor components. This is deliberate:
writeToFilehas no sandbox to anchor a full ancestor walk, and walking every ancestor breaks compatibility on systems with OS-managed symlinks in common path prefixes.O_NOFOLLOWonly guards the final path component; a residual TOCTOU on ancestor directories remains, consistent with this limitation.Tests
Adds
Test_writeToFile_symlink_rejection:rejects_final_component_symlink— errors, sensitive file byte-verified unchangedrejects_directory_component_symlink— errors, redirect target byte-verified unchangedallows_normal_path— succeedsappend_rejects_final_component_symlink— errors in append mode, sensitive file unchangedSub-tests skip gracefully where
os.Symlinkis unsupported and skip entirely as root. All pre-existingTest_writeToFilecases continue to pass.