[v5] git: worktree, make the filesystem wrapper a symlink-safe boundary by pjbgf · Pull Request #2277 · go-git/go-git · GitHub
Skip to content

[v5] git: worktree, make the filesystem wrapper a symlink-safe boundary - #2277

Merged
pjbgf merged 1 commit into
releases/v5.xfrom
checkout-v5
Jul 29, 2026
Merged

[v5] git: worktree, make the filesystem wrapper a symlink-safe boundary#2277
pjbgf merged 1 commit into
releases/v5.xfrom
checkout-v5

Conversation

@pjbgf

@pjbgf pjbgf commented Jul 24, 2026

Copy link
Copy Markdown
Member

worktreeFilesystem validated path strings but did not account for symlinks already present on disk. As a result a worktree operation on an otherwise valid path could resolve through a symlinked component and act outside the worktree.

Enforce the invariant at the boundary. validNoLeadingSymlink rejects paths whose leading directories resolve through an existing symlink, and it runs on every operation. Mutating operations go through validWritePath (Create, OpenFile, MkdirAll, Symlink, Rename, Remove) and reads through validReadPath (Open, Stat, ReadDir, Lstat, Readlink, Chroot). This also covers Worktree.Remove, RemoveGlob and rmFileAndDirsIfEmpty, which delete through the wrapper. Chroot additionally refuses a symlink as the final component so a sub-filesystem such as a submodule worktree cannot be scoped to a redirected target.

The wrapper stops at leading-component traversal. Final-component no-follow during materialisation is enforced at the call site. checkoutFile removes a blocking symlink, whether in a leading component or at the final path, before writing. A forced checkout therefore replaces it with a real entry and succeeds, matching upstream create_directories and its unlink-before-write behaviour. Callers that do not remove it first get a safe error at the boundary.

Mirrors upstream Git's has_symlink_leading_path in symlinks.c and the check_leading_path and unlink-before-write guards in entry.c.

Back-ports #2276.

Copilot AI review requested due to automatic review settings July 24, 2026 22:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens worktree filesystem operations against symlink-based path escapes by enforcing a “no leading symlink traversal” invariant at the worktreeFilesystem boundary, and by explicitly removing blocking symlinks at checkout materialisation time.

Changes:

  • Add leading-component symlink checks for both read and write operations in worktreeFilesystem (plus an extra final-component check for Chroot).
  • Ensure checkout materialisation removes any blocking symlinks before writing tracked entries.
  • Add regression tests covering leading-symlink traversal rejection and forced-checkout symlink replacement behavior.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.

File Description
worktree.go Adds clearBlockingSymlinks and uses it to ensure checkout writes don’t follow/overwrite through planted symlinks.
worktree_fs.go Introduces validWritePath / validNoLeadingSymlink and applies read/write boundary validation; tightens Chroot semantics.
worktree_fs_test.go Adds tests to pin both boundary behavior (reject traversal) and checkout behavior (replace blocking symlinks).

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

Comment thread worktree_fs.go
Comment thread worktree_fs.go Outdated
Comment thread worktree.go
Comment thread worktree_fs_test.go Outdated
worktreeFilesystem validated path strings but did not account for
symlinks already present on disk. As a result a worktree operation on an
otherwise valid path could resolve through a symlinked component and act
outside the worktree.

Enforce the invariant at the boundary. validNoLeadingSymlink rejects
paths whose leading directories resolve through an existing symlink, and
it runs on every operation. Mutating operations go through validWritePath
(Create, OpenFile, MkdirAll, Symlink, Rename, Remove) and reads through
validReadPath (Open, Stat, ReadDir, Lstat, Readlink, Chroot). This also
covers Worktree.Remove, RemoveGlob and rmFileAndDirsIfEmpty, which delete
through the wrapper. Chroot additionally refuses a symlink as the final
component so a sub-filesystem such as a submodule worktree cannot be
scoped to a redirected target.

The wrapper stops at leading-component traversal. Final-component
no-follow during materialisation is enforced at the call site.
checkoutFile removes a blocking symlink, whether in a leading component
or at the final path, before writing. A forced checkout therefore
replaces it with a real entry and succeeds, matching upstream
create_directories and its unlink-before-write behaviour. Callers that do
not remove it first get a safe error at the boundary.

Mirrors upstream Git's has_symlink_leading_path in symlinks.c and the
check_leading_path and unlink-before-write guards in entry.c.

Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
@pjbgf
pjbgf merged commit 3eeb238 into releases/v5.x Jul 29, 2026
11 checks passed
@pjbgf
pjbgf deleted the checkout-v5 branch July 29, 2026 09:01
Sirherobrine23 pushed a commit to Sirherobrine23/gitea-runner that referenced this pull request Aug 8, 2026
…rity] (#1156)

This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) | `v5.19.1` → `v5.19.2` | ![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgo-git%2fgo-git%2fv5/v5.19.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgo-git%2fgo-git%2fv5/v5.19.1/v5.19.2?slim=true) |

---

### go-git: Worktree operations may follow symlinks
[CVE-2026-71556](https://nvd.nist.gov/vuln/detail/CVE-2026-71556) / [GHSA-hc8v-wwc9-vgxm](GHSA-hc8v-wwc9-vgxm)

<details>
<summary>More information</summary>

#### Details
##### Impact

A symlink traversal issue in `go-git` could allow worktree operations to modify files outside the intended worktree path.

The `worktreeFilesystem` wrapper rejected dangerous path strings, including paths containing `.git`, parent-directory components, or control characters. However, it did not prevent filesystem operations from following symbolic links that were already present in the worktree.

As a result, a path that is safe when evaluated as a string could still resolve into the repository's Git metadata directory. For example, if `s` is a symbolic link to `.git`, writing to `s/config` would modify `.git/config`.

A symbolic link at the final path component could also be followed. For example, if `s` points directly to `.git/config`, opening `s` for writing with truncation could overwrite the repository configuration.

Exploitation requires an attacker to be able to introduce or control a symbolic link in the worktree and cause the application to perform a write through that path.

Applications using `storage/memory` for their Storer, or `go-billy/memfs` for their `Worktree`, are not affected by this vulnerability.

##### Patches

The issue has been addressed by making the worktree filesystem wrapper a symlink-safe boundary.

Worktree operations now reject paths where an existing symbolic link in any path component could cause the operation to escape the intended worktree location, including symbolic links at the final component.

Users of filesystem-backed worktrees should upgrade to a patched version.

##### Credits

Thanks to @&#8203;kodareef5 for reporting this issue and working with the go-git security team toward its resolution. 🥇
We would also like to thank @&#8203;HughLewis20, who independently reported the same issue while a fix was already in progress.

#### Severity
- CVSS Score: 7.1 / 10 (High)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L`

#### References
- [https://github.com/go-git/go-git/security/advisories/GHSA-hc8v-wwc9-vgxm](https://github.com/go-git/go-git/security/advisories/GHSA-hc8v-wwc9-vgxm)
- [https://github.com/go-git/go-git/commit/008a78f2dd86f52544ddff8b8e8ddeecdf3f7aab](https://github.com/go-git/go-git/commit/008a78f2dd86f52544ddff8b8e8ddeecdf3f7aab)
- [https://github.com/go-git/go-git/commit/661d1c7f101d34e002a3cfcf8dbea5b7421d07ac](https://github.com/go-git/go-git/commit/661d1c7f101d34e002a3cfcf8dbea5b7421d07ac)
- [https://github.com/go-git/go-git](https://github.com/go-git/go-git)
- [https://github.com/go-git/go-git/releases/tag/v5.19.2](https://github.com/go-git/go-git/releases/tag/v5.19.2)
- [https://github.com/go-git/go-git/releases/tag/v6.0.0-alpha.5](https://github.com/go-git/go-git/releases/tag/v6.0.0-alpha.5)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-hc8v-wwc9-vgxm) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### go-git: Malicious reference names may modify files outside the reference storage
[CVE-2026-71557](https://nvd.nist.gov/vuln/detail/CVE-2026-71557) / [GHSA-qgq7-7hm3-q39j](GHSA-qgq7-7hm3-q39j)

<details>
<summary>More information</summary>

#### Details
##### Impact
A path traversal issue in `go-git` could allow malicious reference names to access files outside the repository's intended reference storage.

Loose references are stored under `.git/<reference-name>`. The reference name was previously used as a path without verifying that the resolved path remained within the reference storage. A name such as `refs/heads/../../config` could therefore resolve to unrelated repository metadata such as `.git/config` or `.git/HEAD`.

A malicious Git server could advertise such a reference name. The name may also survive refspec mapping; for example, it could be mapped to `refs/remotes/origin/../../config` during a clone or fetch operation.

This vulnerability affects filesystem-backed repositories using the `storage/filesystem` package and its `dotgit` reference storage. Users relying exclusively on the in-memory storage implementation, `storage/memory`, are not affected, because reference names are not resolved as filesystem paths.

Exploitation requires an application using `go-git` with filesystem-backed storage to interact with a malicious Git server or otherwise process attacker-controlled reference names.

##### Patches
The issue has been addressed by validating reference names at the `dotgit` storage entry points and rejecting names whose resolved paths could escape the reference storage.

Users of filesystem-backed storage should upgrade to a patched version.

##### Workarounds
Applications that exclusively use `storage/memory` are not affected and do not require a workaround for this vulnerability.

For applications using filesystem-backed storage, avoid cloning from or fetching from untrusted Git servers until an upgrade is possible.

Applications that directly construct or process reference names may also validate them before passing them to filesystem-backed `go-git` storage. Application-level validation should only be considered a temporary mitigation and does not replace upgrading to a patched version.

##### References
- Fixes:
  - go-git/go-git#2247
  - go-git/go-git#2254

##### Credits

Thanks to @&#8203;Saku0512 for reporting this issue and @&#8203;Sahana2524 for proposing the initial fix. 🙇

#### Severity
- CVSS Score: 6.3 / 10 (Medium)
- Vector String: `CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:L`

#### References
- [https://github.com/go-git/go-git/security/advisories/GHSA-qgq7-7hm3-q39j](https://github.com/go-git/go-git/security/advisories/GHSA-qgq7-7hm3-q39j)
- [https://github.com/go-git/go-git/pull/2247](https://github.com/go-git/go-git/pull/2247)
- [https://github.com/go-git/go-git/pull/2254](https://github.com/go-git/go-git/pull/2254)
- [https://github.com/go-git/go-git/commit/4a0e66d555de5f9a30c31e2df64f445f42bd01e7](https://github.com/go-git/go-git/commit/4a0e66d555de5f9a30c31e2df64f445f42bd01e7)
- [https://github.com/go-git/go-git/commit/da9f7d8a0e98b475600177348d6ece384a370f36](https://github.com/go-git/go-git/commit/da9f7d8a0e98b475600177348d6ece384a370f36)
- [https://github.com/go-git/go-git](https://github.com/go-git/go-git)
- [https://github.com/go-git/go-git/releases/tag/v5.19.2](https://github.com/go-git/go-git/releases/tag/v5.19.2)
- [https://github.com/go-git/go-git/releases/tag/v6.0.0-alpha.5](https://github.com/go-git/go-git/releases/tag/v6.0.0-alpha.5)

This data is provided by [OSV](https://osv.dev/vulnerability/GHSA-qgq7-7hm3-q39j) and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>go-git/go-git (github.com/go-git/go-git/v5)</summary>

### [`v5.19.2`](https://github.com/go-git/go-git/releases/tag/v5.19.2)

[Compare Source](go-git/go-git@v5.19.1...v5.19.2)

#### What's Changed

- build: Update module golang.org/x/crypto to v0.52.0 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;2150](go-git/go-git#2150)
- build: Update module github.com/go-git/go-git/v5 to v5.19.1 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;2141](go-git/go-git#2141)
- build: Update module golang.org/x/net to v0.55.0 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;2152](go-git/go-git#2152)
- git: Worktree: Add stores index entires with backslashes on Windows by [@&#8203;joshblum](https://github.com/joshblum) in [#&#8203;2262](go-git/go-git#2262)
- storage: dotgit, reject path traversal in reference names by [@&#8203;pjbgf](https://github.com/pjbgf) in [#&#8203;2254](go-git/go-git#2254)
- build: Update module golang.org/x/net to v0.56.0 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;2267](go-git/go-git#2267)
- build: Update module golang.org/x/text to v0.39.0 \[SECURITY] (releases/v5.x) by [@&#8203;go-git-renovate](https://github.com/go-git-renovate)\[bot] in [#&#8203;2268](go-git/go-git#2268)
- \[v5] git: worktree, make the filesystem wrapper a symlink-safe boundary by [@&#8203;pjbgf](https://github.com/pjbgf) in [#&#8203;2277](go-git/go-git#2277)

**Full Changelog**: <go-git/go-git@v5.19.1...v5.19.2>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - ""
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1156
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
rjosephwright added a commit to cloudboss/unobin that referenced this pull request Aug 10, 2026
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