Shopping cart

Subtotal $0.00

View cartCheckout

Building better devs

TnewsTnews
  • Home
  • Notícias
  • Um Token no Dockerfile Entregou o GitHub Inteiro de Uma Startup de IA
Notícias

Um Token no Dockerfile Entregou o GitHub Inteiro de Uma Startup de IA

Email : 9

O pesadelo começou com um docker pull

Imagina a cena: você é um pesquisador de segurança, está enumerando subdomínios de uma empresa de IA que processa milhões de inferências por dia, e encontra um registry Docker público. Sem senha. Sem autenticação. Aberto pro mundo.

Você puxa a imagem, roda um docker history, e lá está: um token do GitHub com acesso admin a todos os repositórios da empresa. Código-fonte do produto principal, repositório de GitOps com deploy automático, canal de distribuição via Homebrew, e até repos privados de clientes.

Isso aconteceu com a Baseten, uma startup de infraestrutura de IA com mais de 60 milhões de dólares em funding. E o pesquisador da Strix.ai levou exatos 25 minutos do primeiro curl até ter acesso admin ao GitHub da empresa.

Eu sei que você está pensando “comigo isso não acontece”. Fica comigo até o final e depois me conta.

Quem é a Baseten (e por que você deveria se preocupar)

A Baseten é uma plataforma de MLOps sediada em San Francisco. Se você já deployou um modelo de machine learning em produção, provavelmente conhece o problema que eles resolvem: servir modelos treinados com baixa latência, autoscaling, e sem precisar virar engenheiro de infraestrutura.

Empresas usam a Baseten para rodar LLMs, modelos de visão computacional e pipelines de inferência em GPUs como NVIDIA B200 e A100. A plataforma é construída sobre o Truss, um framework open source deles para empacotar modelos. O serviço integra diretamente com Kubernetes, suporta auto-scaling baseado em fila de requests, e lida com o lifecycle inteiro do modelo: do upload até o endpoint em produção.

Por que isso importa pra você? Porque a Baseten não é uma startup obscura de garagem. É o tipo de empresa que processa dados sensíveis de clientes (modelos proprietários, datasets de treinamento, configurações de deploy) e que integra diretamente no pipeline de produção de outras empresas. Se alguém compromete a Baseten, compromete também quem roda modelos na infraestrutura deles. Efeito cascata clássico de supply chain.

O registry aberto: Harbor sem cadeado

Tudo começou quando a equipe da Strix enumerou certificados TLS e subdomínios da Baseten. É um processo padrão em qualquer pentest: você busca em Certificate Transparency logs, enumera DNS records, e mapeia a superfície de ataque exposta. Entre os resultados, um endereço chamou atenção:


gcp-us-east4-zlw.registry.baseten.co

Era uma instância do Harbor, um registry de containers open source muito popular em ambientes Kubernetes. Empresas usam o Harbor pra armazenar suas imagens Docker internas, controlar acesso, e rodar scans de vulnerabilidade antes de deployar.

O problema? Estava configurado para acesso público. Qualquer pessoa podia listar projetos, ver tags e puxar imagens sem nenhuma credencial.


# Listar repositórios - sem autenticação
curl -s https://gcp-us-east4-zlw.registry.baseten.co/v2/_catalog

# Puxar uma imagem específica
docker pull gcp-us-east4-zlw.registry.baseten.co/baseten/baseten-app:latest

Isso por si só já seria grave. Imagens Docker de produção contêm código compilado, configurações, dependências, e às vezes, credenciais embutidas nas camadas. Qualquer um poderia baixar o binário da aplicação inteira, fazer engenharia reversa, encontrar endpoints internos, e mapear a arquitetura do sistema. Mas o que veio a seguir transformou um problema de exposição em um comprometimento total.

O token escondido no docker history

Depois de puxar a imagem baseten/baseten-app, o pesquisador rodou o comando que todo auditor de segurança conhece (e que a maioria dos devs ignora):


docker history --no-trunc baseten/baseten-app:latest

Esse comando mostra todas as instruções que foram usadas pra construir a imagem, incluindo os valores de build arguments expandidos. É como ler o Dockerfile original, mas pior: mostra os valores reais que foram passados em tempo de build.

E lá, numa instrução RUN, estava um GITHUB_TOKEN da conta basetenbot:


RUN git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"

O git config --global com a URL autenticada cria um segundo vetor de exposição: além de aparecer no histórico, a URL com o token fica gravada no arquivo .gitconfig dentro da imagem. Dois lugares diferentes vazando a mesma credencial.

O problema é que ${GITHUB_TOKEN} foi passado como build arg. E build args no Docker são expandidos e gravados no histórico da imagem. Não importa se o Dockerfile parece limpo. Não importa se o token foi usado só numa camada intermediária. O docker history guarda tudo.

A equipe da Strix confirmou usando o TruffleHog, uma ferramenta de scan de segredos:


trufflehog docker --image baseten/baseten-app:latest

O token apareceu nos campos history[].created_by dos blobs da imagem. Validado, ativo, e com permissões que ninguém esperava.

O que 25 minutos de acesso significam

Com o token em mãos, o pesquisador verificou as permissões via API do GitHub:


curl -s -H "Authorization: token ghp_XXXXX..." \
  https://api.github.com/user

# Listar repos acessíveis
curl -s -H "Authorization: token ghp_XXXXX..." \
  "https://api.github.com/user/repos?per_page=100&type=all" | \
  jq '.[] | {name, permissions, private}'

A conta basetenbot tinha:

Repositório Permissão Risco
Repo principal do produto Admin + push Injeção de código no produto
Repo de GitOps (deploys) Admin + push Deploy de código malicioso em produção
Canal Homebrew Admin + push Distribuição de binários comprometidos
Repos privados de clientes Read/write Acesso a dados de terceiros

Vamos ser claros sobre o que isso significa na prática. Com acesso admin ao repo de GitOps, um atacante poderia:

  1. Modificar manifests do Kubernetes para injetar containers maliciosos
  2. Alterar pipelines de CI/CD para incluir steps que exfiltram segredos
  3. Fazer push de código diretamente no branch principal sem review
  4. Acessar variáveis de ambiente e secrets configurados no repositório
  5. Comprometer o canal Homebrew para distribuir versões trojanizadas do CLI

E o detalhe que dói: o token estava ativo desde março de 2023. Mais de três anos exposto. Três anos em que qualquer pessoa com acesso ao registry público poderia ter encontrado a mesma coisa.

A timeline completa do incidente

Data Evento
Março 2023 Token embutido durante build da imagem
13 de julho 2026 Strix descobre e reporta a vulnerabilidade
14 de julho (manhã) Baseten torna o Harbor privado
14 de julho (16:34) Token rotacionado, issue classificada como crítica
17 de julho Remediação completa de todos os findings

Crédito pra Baseten: a resposta foi rápida e profissional. Em menos de 24 horas, o registry foi fechado e o token revogado. A equipe de segurança engajou imediatamente, classificou como crítico, e completou a remediação em quatro dias.

Mas o ponto que não dá pra ignorar: a exposição durou mais de três anos. E ninguém percebeu até um pesquisador externo decidir olhar. Quantas pessoas viram aquele registry nesse período? Impossível saber.

Por que build args vazam (e você provavelmente está fazendo isso errado)

Esse é o erro mais comum e mais perigoso na construção de imagens Docker. A própria documentação do Docker avisa, em letras que ninguém lê:

“Build arguments can be recorded in the image’s metadata.”

Mas muita gente ainda faz isso:


# ERRADO - o token fica gravado no histórico
ARG GITHUB_TOKEN
RUN git clone https://${GITHUB_TOKEN}@github.com/empresa/repo.git

“Ah, mas eu deleto depois”:


ARG GITHUB_TOKEN
RUN git clone https://${GITHUB_TOKEN}@github.com/empresa/repo.git && \
    rm -rf .git

O token ainda está no docker history. A camada intermediária guarda a instrução completa com o valor expandido. Deletar o arquivo clonado não apaga o histórico de build. São coisas completamente separadas.

E tem um detalhe que pega muita gente: mesmo com multi-stage builds, se o build arg foi usado no stage 1, ele aparece no histórico do stage 1, que ainda faz parte da imagem final como metadado.

A forma correta: BuildKit secrets

O Docker BuildKit resolve isso com secret mounts. O segredo fica disponível durante o build mas não é gravado em nenhuma camada:


# syntax=docker/dockerfile:1
FROM golang:1.22 AS builder

# O segredo é montado como arquivo temporário
RUN --mount=type=secret,id=github_token \
    GITHUB_TOKEN=$(cat /run/secrets/github_token) && \
    git clone https://${GITHUB_TOKEN}@github.com/empresa/repo.git

FROM alpine:3.19
COPY --from=builder /app/bin /usr/local/bin/

E no build:


DOCKER_BUILDKIT=1 docker build \
  --secret id=github_token,src=./token.txt \
  -t minha-app .

A diferença fundamental: com secret mounts, o conteúdo do segredo existe apenas na memória durante aquela instrução RUN. Ele não é gravado no filesystem da camada, no histórico, nem nos metadados da imagem. Pode rodar docker history --no-trunc o dia inteiro que o token não vai aparecer.

Multi-stage builds como segunda camada de defesa

Mesmo usando secret mounts, multi-stage builds adicionam proteção extra. A ideia é separar o ambiente de build (onde você precisa de credenciais) do ambiente de runtime (que é distribuído):


# Stage 1: build com acesso a segredos
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) && \
    echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && \
    npm ci && \
    rm .npmrc
COPY . .
RUN npm run build

# Stage 2: imagem final sem nenhum vestígio
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/node_modules /app/node_modules
CMD ["node", "/app/dist/index.js"]

A imagem final só contém o que foi copiado explicitamente. Nenhuma camada do stage de build, nenhum histórico de comandos que usaram segredos, nenhum .npmrc esquecido.

Harbor: o registry que todo mundo esquece de configurar

O Harbor é um excelente registry de containers. Suporte a replicação, scan de vulnerabilidades integrado com Trivy, controle granular de acesso, assinatura de imagens via Notary. O CNCF graduou o projeto, o que significa maturidade e governance sólida.

Mas a configuração padrão dele é um desastre de segurança:

  • Usuário admin padrão: admin
  • Senha padrão: Harbor12345
  • Sem obrigatoriedade de trocar a senha no primeiro login
  • Projetos podem ser públicos por padrão

Em 2026, o CVE-2026-4404 (CVSS 9.4) formalizou esse problema. Com credenciais padrão, um atacante pode sobrescrever imagens existentes, injetar containers maliciosos, e comprometer toda a cadeia de supply chain de qualquer empresa que faz pull daquele registry.

Se você usa Harbor, faça isso agora:


# Verifique se o Harbor está exposto publicamente
curl -s https://seu-harbor.exemplo.com/api/v2.0/projects \
  -H "Authorization: Basic $(echo -n 'admin:Harbor12345' | base64)"

# Se retornar 200, você tem um problema sério

Checklist de segurança para Harbor

Item Prioridade Descrição
Trocar senha admin Crítica Primeira coisa após instalar
Autenticação LDAP/OIDC Alta Nunca depender de contas locais
VPN ou IP allowlist Alta Nunca expor na internet pública
Scan automático (Trivy) Média Escanear imagens no push
Desabilitar projetos públicos Média Default deve ser privado
Atualizar para 2.15.0+ Alta Patches de segurança críticos

Como auditar suas próprias imagens agora

Você provavelmente está lendo isso e pensando “eu nunca faria isso”. Eu já ouvi essa frase de três CTOs diferentes antes de encontrar tokens nos Dockerfiles deles. Bora verificar?

Passo 1: Inspecione o histórico


# Para cada imagem no seu registry
docker history --no-trunc sua-imagem:latest 2>/dev/null | \
  grep -iE "(token|password|secret|key|auth|apikey|api_key)"

Se aparecer qualquer coisa que pareça uma credencial, pare tudo e rotacione imediatamente.

Passo 2: Rode o TruffleHog


# Instalar
brew install trufflehog
# ou
pip install trufflehog

# Escanear imagem Docker
trufflehog docker --image sua-imagem:latest

# Escanear registry inteiro
trufflehog docker --image registry.exemplo.com/projeto/app:latest

O TruffleHog verifica não só o histórico, mas também os conteúdos de cada layer, arquivos de configuração, e variáveis de ambiente.

Passo 3: Verifique os blobs diretamente


# Salvar a imagem como tar
docker save sua-imagem:latest -o imagem.tar

# Extrair e inspecionar
mkdir imagem-extraida && tar -xf imagem.tar -C imagem-extraida
cat imagem-extraida/manifest.json | jq .

# Inspecionar cada layer
for layer in imagem-extraida/*/layer.tar; do
  echo "=== $layer ==="
  tar -tf "$layer" | grep -iE "(\.env|\.npmrc|\.gitconfig|credentials|\.aws)"
done

Procure especificamente por arquivos de configuração que normalmente contêm credenciais: .env, .npmrc, .gitconfig, .aws/credentials, application.properties.

Passo 4: Automatize no CI/CD

Adicione scan de segredos como step obrigatório no pipeline. Se o scan encontrar algo, o build falha:


# GitHub Actions
- name: Scan Docker image for secrets
  uses: trufflesecurity/trufflehog@main
  with:
    extra_args: docker --image ${{ env.IMAGE_TAG }}

# GitLab CI
scan-secrets:
  stage: security
  script:
    - trufflehog docker --image ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}
  allow_failure: false

Sem exceções. Sem “ah mas é só o ambiente de staging”. Se o token é válido em staging, provavelmente é válido em produção também.

O padrão que se repete

O caso da Baseten não é isolado. Um estudo da Truffle Security em 2025 encontrou que 8% das imagens públicas no Docker Hub contêm pelo menos um segredo válido. São milhões de imagens. Milhões de tokens, senhas e chaves de API dormindo em camadas Docker que qualquer um pode inspecionar.

O padrão é sempre o mesmo: dev cria o Dockerfile com pressa, passa o token como build arg porque é “mais fácil”, o CI funciona, o deploy sobe, e ninguém mais olha pra aquela imagem. Até que alguém olha.

E a parte mais perturbadora: a maioria desses segredos ainda estava ativa quando foi descoberta. Porque ninguém audita imagens Docker depois que elas são construídas. O CI roda, o deploy acontece, e aquele ARG GITHUB_TOKEN do Dockerfile de 2023 fica lá, silencioso, esperando alguém curioso o suficiente pra rodar um docker history.

A Baseten respondeu rápido e profissionalmente. Mas e se quem encontrasse esse token não fosse um pesquisador de segurança? E se fosse alguém com intenções diferentes? Admin access ao GitHub de uma empresa de IA que processa inferências pra centenas de outras empresas… o estrago potencial é difícil até de calcular.

Então antes de fechar essa aba, faça uma coisa: rode docker history --no-trunc na última imagem que você buildou. Se aparecer alguma coisa que parece um segredo, você já sabe o que fazer.


Fonte de inspiração: We got admin access to Baseten’s production GitHub in 25 minutes (Strix.ai)

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts