Extractor de Fortran de Graphify: endurecimiento incompleto de cpp #include permite la lectura/exfiltración arbitraria de archivos del host al indexar un repositorio no confiable
Resumen
El paso del preprocesador de C del extractor de Fortran (graphify/extractors/fortran.py, _cpp_preprocess) aplica un endurecimiento incompleto (#include). Los flags de -nostdinc -I /dev/null en los que confía (comentario "Security (F-007)") no impiden que un archivo fuente de Fortran con F mayúscula incluya rutas absolutas (#include "/etc/passwd") o realice un salto de directorio fuera del corpus (#include "../../../../home/victim/.ssh/id_rsa"). cpp siempre busca en el directorio del archivo que se está preprocesando en el caso de inclusiones entre comillas y siempre respeta las rutas de inclusión absolutas, independientemente de -nostdinc/-I. Como consecuencia, indexar un repositorio no confiable que contenga un archivo .F/.F90/.F95/.F03/.F08 lee archivos locales arbitrarios del host e inserta su contenido en el grafo de conocimiento, el cual luego se envía al LLM/agente y se escribe en la salida persistente y compartible en disco (graph.json, GRAPH_REPORT.md, la wiki de Obsidian, exportaciones HTML/SVG).
Este es exactamente el "vector de exfiltración de archivos del lado del corpus" contra el cual el comentario F-007 afirma estar defendiéndose; la mitigación es eludible.
Severidad
Alta. Lectura arbitraria de archivos locales en el host, activada por la acción principal de graphify (indexar una carpeta no confiable: "turn any folder of code ... into a queryable knowledge graph"), evadiendo un control de seguridad documentado. Se ejecuta en la ruta de AST predeterminada, sin conexión y sin clave de API (graphify update . / graphify extract), sin necesidad de credenciales, red ni llamadas a LLM. CWE-73 (control externo de nombre/ruta de archivo) / CWE-22 (salto de directorio) / CWE-200 (exposición de información).
Ubicación
graphify/extractors/fortran.py, _cpp_preprocess():
result = subprocess.run(
["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null", str(path.resolve())],
capture_output=True, timeout=30,
)
Se alcanza desde extract_fortran() (mismo archivo) para cualquier sufijo en _FORTRAN_CPP_EXTS = {".F", ".F90", ".F95", ".F03", ".F08"}, los cuales se recolectan por defecto (detect.CODE_EXTENSIONS) y se envían mediante extract._DISPATCH.
Por qué la mitigación no funciona
-nostdinc solo elimina los directorios de inclusión estándar del sistema, y -I /dev/null solo establece la ruta de búsqueda adicional. Ninguno afecta a:
- Inclusiones absolutas —
#include "/etc/passwd"es abierto directamente por cpp. - Inclusiones entre comillas relativas al archivo actual — cpp siempre busca en el directorio que contiene el archivo preprocesado para
#include "...", por lo que#include "../../../../etc/shadow"escapa del corpus.
Prueba de concepto (verificada)
Reproducido contra el código publicado con el comando exacto que construye _cpp_preprocess (cpp -w -P -nostdinc -I /dev/null <file>), y directamente contra extract_fortran().
Secreto en disco (/tmp/secret.txt): TOP_SECRET_...
(a) Inclusión absoluta — evil_abs.F90:
program evil
#include "/absolute/path/to/secret.txt"
end program evil
(b) Salto relativo — evil_rel.F90:
program evil
#include "../secret.txt"
end program evil
Ambos producen una salida preprocesada que contiene el contenido completo del archivo secreto ("SECRET IN OUTPUT: True" para ambos).
Aparición en el grafo — leak.F90 coloca la inclusión en una posición de identificador mediante una continuación libre, de modo que el valor filtrado se convierte en un nodo/arista:
module m
contains
subroutine s
use &
#include "/path/to/token.secret"
end subroutine s
end module m
extract_fortran() sobre este archivo emite un nodo cuya etiqueta es el contenido de una sola línea de token.secret y una arista imports hacia él. Ese nodo se escribe en graph.json / GRAPH_REPORT.md / la wiki y es retornado por las herramientas MCP query_graph / get_node / get_neighbors, es decir, exfiltrado al contexto del agente y a los artefactos compartibles persistidos.
Nota de fidelidad: Fortran no distingue entre mayúsculas y minúsculas, por lo que los identificadores capturados se convierten a minúsculas. Los secretos no sensibles a mayúsculas y minúsculas (rutas de archivo, nombres de host, nombres de usuario, tokens hexadecimales, fragmentos de cadenas de conexión y la mera existencia/contenido parcial de un archivo legible) se filtran de forma íntegra; los bloques de alta entropía sensibles a mayúsculas y minúsculas (base64/JWT) se corrompen parcialmente por la conversión a minúsculas, pero aún confirman la legibilidad y estructura. Los archivos de secretos con un único token por línea (archivos de claves de API, archivos de credenciales/tokens de una sola línea) filtran dicho token completo (en minúsculas). Requiere cpp/gcc en el PATH (prácticamente omnipresente en los entornos dev/CI a los que apunta graphify); retrocede limpiamente a bytes en bruto cuando está ausente.
Corrección sugerida
Reflejar el enfoque de contención ya utilizado en otras partes del código base (_project_xml_is_safe, security.validate_graph_path): antes de invocar cpp, escanear el código fuente en busca de directivas #include y rechazar o neutralizar cualquier inclusión cuyo objetivo sea absoluto o se resuelva fuera de la raíz del corpus/repositorio. Concretamente, en _cpp_preprocess:
- Analizar directivas
#include "..."/#include <...>. - Resolver cada objetivo respecto al directorio del archivo; si es absoluto o su
.resolve()escapa de la raíz de escaneo, eliminar esa línea (o recurrir apath.read_bytes()sin preprocesamiento). - Permitir únicamente inclusiones que se resuelvan en una ruta dentro de la raíz del corpus.
Una alternativa más drástica pero segura es eliminar todas las líneas #include antes del preprocesamiento (las macros se siguen expandiendo; las inclusiones entre archivos pierden fidelidad, pero no se pueden leer archivos del host). Ejecutar cpp dentro de un entorno aislado de sistema de archivos (bwrap/unshare con solo el corpus montado) también resolvería el problema, aunque resulta más pesado.
Detectado por
Escáner de vulnerabilidades Aeon. Semgrep alertó sobre la ruta del subproceso Fortran; la revisión manual de la mitigación F-007 junto con una reproducción empírica con cpp confirmaron la evasión.
Qué hacer
Actualice a la versión 0.9.70 o posterior.
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Python (PyPI) | graphifyy | >= 0.7.2, < 0.9.70 | 0.9.70 |
Referencias
- Vector CVSS
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N- Debilidad (CWE)
CWE-22, CWE-73- Plataforma
- Python (PyPI)
- Paquete
graphifyy- Publicada
- 19.10.2026
- Actualizado
- 20.10.2026 09:53
Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.
OSV / GitHub Advisory Database - GHSA-pcc4-rvhr-2pr8 - CC BY 4.0
Historial de vulnerabilidades: graphifyy
Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
Esta es la única vulnerabilidad conocida de este paquete.
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.