GHSA-8W8G-WQ8H-FQ33

Vulnerability from github – Published: 2026-10-02 18:53 – Updated: 2026-10-02 18:53
VLAI
Summary
Dulwich: Symlink write-through in checkout(paths=[]) via raw os.open bypasses all symlink protections
Details

Summary

Dulwich's porcelain.checkout(paths=[...]) code path writes files using raw os.open(file_path, O_WRONLY|O_CREAT|O_TRUNC, mode) followed by f.write(obj.data). This code path does NOT call build_file_from_blob() at all, completely bypassing any symlink protections (including the unreleased d09f8af fix). os.open without O_NOFOLLOW follows symlinks at both the target file and intermediate directories, allowing arbitrary file writes.

Root Cause

At dulwich/porcelain/__init__.py:5661-5675, the checkout(paths=[...]) implementation:

file_path = _checked_worktree_path(r, path)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
with os.fdopen(os.open(file_path, flags, mode), "wb") as f:
    f.write(obj.data)

_checked_worktree_path() (line 601-631) only performs name validation — checking that the path doesn't start with / or \\ and that components pass INVALID_DOTNAMES checks. It performs zero filesystem symlink detection.

Impact

An attacker can craft a malicious repository that, when a victim clones it and runs checkout(paths=[...]), writes attacker-controlled content (with attacker-controlled permissions) to any filesystem location accessible to the user. Writing to .git/hooks/post-checkout achieves RCE on the next git checkout.

Attack Scenario

  1. Attacker creates a repository where HEAD has trigger as a symlink (mode 120000, content ../../.git/hooks/post-checkout), and tag v1.0 has trigger as an executable file (mode 100755, content #!/bin/sh\nmalicious_payload)
  2. Victim clones the repository — worktree has trigger → ../../.git/hooks/post-checkout (a symlink)
  3. Victim runs porcelain.checkout(repo, target="v1.0", paths=["trigger"]) to restore a specific file from a tag
  4. _checked_worktree_path(r, "trigger") passes — name validation only, no symlink check
  5. os.open("trigger", O_WRONLY|O_CREAT|O_TRUNC, 0o755) follows the symlink → opens .git/hooks/post-checkout for writing
  6. f.write(obj.data) writes the malicious payload to the hook
  7. Next checkout operation triggers the hook → RCE

Suggested Fix

Replace the raw os.open path with a call to build_file_from_blob (once that function is hardened against intermediate symlinks), or add explicit symlink detection: resolve the path with os.path.realpath() and verify it stays within the worktree root before opening.

Reported by zx (Jace)

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.2.7"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "dulwich"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.24.0"
            },
            {
              "fixed": "1.2.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-59",
      "CWE-61"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-02T18:53:35Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nDulwich\u0027s `porcelain.checkout(paths=[...])` code path writes files using raw `os.open(file_path, O_WRONLY|O_CREAT|O_TRUNC, mode)` followed by `f.write(obj.data)`. This code path does NOT call `build_file_from_blob()` at all, completely bypassing any symlink protections (including the unreleased d09f8af fix). `os.open` without `O_NOFOLLOW` follows symlinks at both the target file and intermediate directories, allowing arbitrary file writes.\n\n## Root Cause\n\nAt `dulwich/porcelain/__init__.py:5661-5675`, the `checkout(paths=[...])` implementation:\n\n```python\nfile_path = _checked_worktree_path(r, path)\nos.makedirs(os.path.dirname(file_path), exist_ok=True)\nflags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC\nwith os.fdopen(os.open(file_path, flags, mode), \"wb\") as f:\n    f.write(obj.data)\n```\n\n`_checked_worktree_path()` (line 601-631) only performs name validation \u2014 checking that the path doesn\u0027t start with `/` or `\\\\` and that components pass `INVALID_DOTNAMES` checks. It performs zero filesystem symlink detection.\n\n## Impact\n\nAn attacker can craft a malicious repository that, when a victim clones it and runs `checkout(paths=[...])`, writes attacker-controlled content (with attacker-controlled permissions) to any filesystem location accessible to the user. Writing to `.git/hooks/post-checkout` achieves RCE on the next git checkout.\n\n## Attack Scenario\n\n1. Attacker creates a repository where HEAD has `trigger` as a symlink (mode 120000, content `../../.git/hooks/post-checkout`), and tag `v1.0` has `trigger` as an executable file (mode 100755, content `#!/bin/sh\\nmalicious_payload`)\n2. Victim clones the repository \u2014 worktree has `trigger` \u2192 `../../.git/hooks/post-checkout` (a symlink)\n3. Victim runs `porcelain.checkout(repo, target=\"v1.0\", paths=[\"trigger\"])` to restore a specific file from a tag\n4. `_checked_worktree_path(r, \"trigger\")` passes \u2014 name validation only, no symlink check\n5. `os.open(\"trigger\", O_WRONLY|O_CREAT|O_TRUNC, 0o755)` follows the symlink \u2192 opens `.git/hooks/post-checkout` for writing\n6. `f.write(obj.data)` writes the malicious payload to the hook\n7. Next checkout operation triggers the hook \u2192 RCE\n\n## Suggested Fix\n\nReplace the raw `os.open` path with a call to `build_file_from_blob` (once that function is hardened against intermediate symlinks), or add explicit symlink detection: resolve the path with `os.path.realpath()` and verify it stays within the worktree root before opening.\n\nReported by **zx (Jace)**",
  "id": "GHSA-8w8g-wq8h-fq33",
  "modified": "2026-10-02T18:53:35Z",
  "published": "2026-10-02T18:53:35Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/jelmer/dulwich/security/advisories/GHSA-8w8g-wq8h-fq33"
    },
    {
      "type": "WEB",
      "url": "https://github.com/jelmer/dulwich/commit/9389fcb5cb9113adfc7f207d8be86a56904db3e8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/jelmer/dulwich"
    },
    {
      "type": "WEB",
      "url": "https://github.com/jelmer/dulwich/releases/tag/dulwich-1.2.8"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Dulwich: Symlink write-through in checkout(paths=[]) via raw os.open bypasses all symlink protections"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.