Dulwich: Symlink directory traversal in filter-branch index_filter via cross-commit state persistence
## Summary Dulwich's `filter_branch.py` `CommitFilter._apply_index_filter()` is vulnerable to symlink directory traversal. When processing commit history, materialized tree entries (including symlinks) persist in the working directory between commits, allowing a symlink from an ancestor commit to redirect file writes from a descendant commit to arbitrary filesystem locations. ## Root Cause `_apply_index_filter()` at `dulwich/filter_branch.py:212` calls `build_index_from_tree(".", tmp_index_path, ...)` which materializes all tree entries to the current working directory. The `finally` block (line 229-230) only cleans up the temporary index file (`os.unlink(tmp_index_path)`) — NOT the filesystem files written to CWD. When `process_commit()` processes parents recursively first (line 260), files materialized from ancestor commits persist and affect processing of descendant commits. On dulwich 1.2.7, `build_file_from_blob()` has no symlink protection, and `validate_path_element` only validates name patterns, not filesystem state. ## Impact An attacker can craft a malicious repository where running `filter_branch` with an index filter writes attacker-controlled content to arbitrary filesystem locations via symlink traversal. This achieves RCE if the write targets `.git/hooks/`. ## Attack Scenario 1. Attacker creates a repository where commit history (linearized) has: - Ancestor commit: tree entry `evil` (mode 120000, symlink → `/target_dir`) - Descendant commit: tree entry `evil/pa
- Products
- dulwich
- Affected versions
- 0.23.1, 0.23.2, 0.24.0, 0.24.1, 0.24.10, 0.24.2, 0.24.3, 0.24.4, 0.24.5, 0.24.6, 0.24.7, 0.24.8, 0.24.9, 0.25.0, 0.25.1, 0.25.2, 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5, 1.2.6, 1.2.7
- Fixed versions
- 1.2.8
- CVEs
- CVE-2026-114097
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 31 Oct 2026, 14:09 UTC · Revision 1
Source evidence
Dulwich: Symlink directory traversal in filter-branch index_filter via cross-commit state persistence
core-osv · Original sourcePublished 2 Oct 2026, 18:53 UTC
## Summary Dulwich's `filter_branch.py` `CommitFilter._apply_index_filter()` is vulnerable to symlink directory traversal. When processing commit history, materialized tree entries (including symlinks) persist in the working directory between commits, allowing a symlink from an ancestor commit to redirect file writes from a descendant commit to arbitrary filesystem locations. ## Root Cause `_apply_index_filter()` at `dulwich/filter_branch.py:212` calls `build_index_from_tree(".", tmp_index_path, ...)` which materializes all tree entries to the current working directory. The `finally` block (line 229-230) only cleans up the temporary index file (`os.unlink(tmp_index_path)`) — NOT the filesystem files written to CWD. When `process_commit()` processes parents recursively first (line 260), files materialized from ancestor commits persist and affect processing of descendant commits. On dulwich 1.2.7, `build_file_from_blob()` has no symlink protection, and `validate_path_element` only validates name patterns, not filesystem state. ## Impact An attacker can craft a malicious repository where running `filter_branch` with an index filter writes attacker-controlled content to arbitrary filesystem locations via symlink traversal. This achieves RCE if the write targets `.git/hooks/`. ## Attack Scenario 1. Attacker creates a repository where commit history (linearized) has: - Ancestor commit: tree entry `evil` (mode 120000, symlink → `/target_dir`) - Descendant commit: tree entry `evil/pa
osv:GHSA-5fqc-mrg8-w798
dulwich
Fixed versions: 1.2.8
Complete source record
{
"id": "740bbc5db4f2939d75b23e276c794af2ba115d5a293d24ea8f7cce954dc99404",
"url": "https://osv.dev/vulnerability/GHSA-5fqc-mrg8-w798",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "Dulwich: Symlink directory traversal in filter-branch index_filter via cross-commit state persistence",
"summary": "## Summary Dulwich's `filter_branch.py` `CommitFilter._apply_index_filter()` is vulnerable to symlink directory traversal. When processing commit history, materialized tree entries (including symlinks) persist in the working directory between commits, allowing a symlink from an ancestor commit to redirect file writes from a descendant commit to arbitrary filesystem locations. ## Root Cause `_apply_index_filter()` at `dulwich/filter_branch.py:212` calls `build_index_from_tree(\".\", tmp_index_path, ...)` which materializes all tree entries to the current working directory. The `finally` block (line 229-230) only cleans up the temporary index file (`os.unlink(tmp_index_path)`) — NOT the filesystem files written to CWD. When `process_commit()` processes parents recursively first (line 260), files materialized from ancestor commits persist and affect processing of descendant commits. On dulwich 1.2.7, `build_file_from_blob()` has no symlink protection, and `validate_path_element` only validates name patterns, not filesystem state. ## Impact An attacker can craft a malicious repository where running `filter_branch` with an index filter writes attacker-controlled content to arbitrary filesystem locations via symlink traversal. This achieves RCE if the write targets `.git/hooks/`. ## Attack Scenario 1. Attacker creates a repository where commit history (linearized) has: - Ancestor commit: tree entry `evil` (mode 120000, symlink → `/target_dir`) - Descendant commit: tree entry `evil/pa",
"evidence": "osv:GHSA-5fqc-mrg8-w798",
"products": [
{
"name": {
"value": "dulwich",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "1.2.8",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": [
{
"value": "0.23.1",
"evidence": "/affected/0/versions"
},
{
"value": "0.23.2",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.0",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.1",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.10",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.2",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.3",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.4",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.5",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.6",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.7",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.8",
"evidence": "/affected/0/versions"
},
{
"value": "0.24.9",
"evidence": "/affected/0/versions"
},
{
"value": "0.25.0",
"evidence": "/affected/0/versions"
},
{
"value": "0.25.1",
"evidence": "/affected/0/versions"
},
{
"value": "0.25.2",
"evidence": "/affected/0/versions"
},
{
"value": "1.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "1.1.0",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.0",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.1",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.2",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.3",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.4",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.5",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.6",
"evidence": "/affected/0/versions"
},
{
"value": "1.2.7",
"evidence": "/affected/0/versions"
}
]
}
],
"severity": "high",
"structured": {
"id": "GHSA-5fqc-mrg8-w798",
"aliases": [
"CVE-2026-114097",
"GHSA-5fqc-mrg8-w798"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H"
}
],
"affected": [
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0.23.1"
},
{
"fixed": "1.2.8"
}
]
}
],
"package": {
"name": "dulwich",
"purl": "pkg:pypi/dulwich",
"ecosystem": "PyPI"
},
"versions": [
"0.23.1",
"0.23.2",
"0.24.0",
"0.24.1",
"0.24.10",
"0.24.2",
"0.24.3",
"0.24.4",
"0.24.5",
"0.24.6",
"0.24.7",
"0.24.8",
"0.24.9",
"0.25.0",
"0.25.1",
"0.25.2",
"1.0.0",
"1.1.0",
"1.2.0",
"1.2.1",
"1.2.2",
"1.2.3",
"1.2.4",
"1.2.5",
"1.2.6",
"1.2.7"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-5fqc-mrg8-w798/GHSA-5fqc-mrg8-w798.json",
"last_known_affected_version_range": "<= 1.2.7"
}
}
],
"modified": "2026-10-21T10:57:27.569314986Z",
"provider": "osv",
"references": [
"https://github.com/jelmer/dulwich/security/advisories/GHSA-5fqc-mrg8-w798",
"https://github.com/jelmer/dulwich/commit/9571ac60b851fce228dae7ededb380c6ce9b3fb8",
"https://github.com/jelmer/dulwich",
"https://github.com/jelmer/dulwich/releases/tag/dulwich-1.2.8"
],
"provider_metadata": {
"cwe_ids": [
"CWE-22",
"CWE-59"
],
"severity": "HIGH",
"github_reviewed": true,
"nvd_published_at": null,
"github_reviewed_at": "2026-10-02T18:53:05Z"
}
},
"related_cves": [
{
"value": "CVE-2026-114097",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/database_specific/severity"
},
"source_id": "core-osv",
"document_id": 392986,
"published_at": "2026-10-02T18:53:05+00:00"
}