flawopen.com/Incidents/rubygems-cdn-cache-api-key-leak-july-2026
RubyGems: vazamento de chave API por cache CDN
Falha gzip/CDN expôs chaves RubyGems. Veja correção e recuperação; a autoria da campanha de pacotes por IA não foi confirmada.
Imagine um balcão que cria uma chave mestra nova para cada cliente verificado e deixa uma cópia numa bandeja compartilhada porque uma máquina de embalagem alterou o rótulo do envelope. A próxima pessoa atendida naquele mesmo balcão poderia receber a chave do cliente anterior sem mostrar documento. A correção impede caches compartilhados de guardar respostas privadas e separa as respostas conforme a autorização.
Conceitos Centrais e Termos
Legacy API key- Uma credencial RubyGems duradoura, com amplas permissões de gerenciamento de gems. O aviso diz que ela podia publicar uma nova versão, remover versões, adicionar responsáveis e alterar webhooks ou configurações de trusted publishing; não permitia entrar na conta nem reescrever uma versão já publicada.
Shared cache / POP- Um ponto de presença da CDN guarda uma resposta para reutilizá-la em outras requisições. Se uma resposta autenticada com credencial for compartilhada sem política privada e sem uma chave de cache que considere autorização, outra pessoa pode receber a resposta.
Compression middleware- A RubyGems informa que Accept-Encoding: gzip acionava uma interação do Rack que deixava a resposta sem a diretiva private e sem Vary: Authorization. Requisições comuns sem gzip não mostravam o mesmo comportamento de cabeçalho.
CWE-200 and CWE-524- O GitHub Security Advisory associa
CWE-200à exposição de informação sensível eCWE-524ao uso de cache com informação sensível. Eles representam a divulgação e o limite do cache; não foi atribuído CVE. RubyGems package-flood attribution- A RubyGems confirmou publicação abusiva de pacotes em maio de 2026, mas disse que não conseguiu determinar se agentes de IA os criaram ou publicaram. Separe essa campanha da falha de cache: as fontes não demonstram que ela causou a exposição de chaves.
Mapeamentos de frameworks
Linha do Tempo do Incidente
O RubyGems 3.2.0 mudou o fluxo padrão de gem signin para criar chaves com escopo, mas o servidor manteve disponível a rota GET legada.
A Truffle Security comunicou a falha de cache à RubyGems.
A RubyGems implantou a correção dos cabeçalhos e limpou objetos afetados do cache Fastly.
A RubyGems publicou GHSA-9j48-x3c3-mrp2 e informou que revogou todas as chaves legadas. O GHSA atribui 7,3 (High); a RubyGems informa pontuação ambiental geral 7,2 (High).
A RubyGems descreveu separadamente a campanha de publicação de pacotes de maio e disse não poder atribuí-la a agentes de IA.
Fluxo de Ataque Passo a Paso
Uma rota legada emite uma credencial
Uma requisição autenticada GET para /api/v1/api_key cria e devolve uma chave legada com amplas permissões. A RubyGems diz que clientes gem anteriores à versão 3.2.0 usavam essa rota; o servidor a manteve mesmo depois que clientes novos migraram para chaves com escopo.
Gzip muda o caminho dos cabeçalhos
O cliente solicita gzip. A RubyGems atribui o comportamento de cache à interação entre Rack::Deflater e Rack::ETag: a resposta comprimida ficava apenas com Cache-Control: no-cache, sem diretiva private e sem Vary: Authorization.
A CDN reutiliza uma resposta autenticada
Na configuração afetada da Fastly, a resposta podia ficar armazenada num POP por até uma hora. A chave de cache não separava clientes pelo cabeçalho Authorization; assim, a borda podia devolver a resposta anterior sem consultar a RubyGems para autenticar o próximo cliente.
Outra pessoa recebe a chave
Uma requisição com a mesma variante de cache, encaminhada pelo mesmo POP, podia receber a chave do usuário anterior. A RubyGems diz que isso podia ocorrer acidentalmente entre logins e também descreve consulta não autenticada de uma resposta em cache. Os registros disponíveis não mostraram uso malicioso, mas cobriam apenas um período limitado.
O escopo da chave limita as consequências
Uma chave legada vazada podia autorizar novas versões de gems, remoções, mudanças de responsáveis e alterações em webhooks ou trusted publishers. Versões já publicadas não podiam ser reescritas. Segundo a RubyGems, MFA exigida tanto na interface quanto na API bloqueia ações de publicação, remoção e alteração de responsáveis feitas só com a chave.
A correção impede o compartilhamento e aposenta as chaves expostas
O patch adiciona Cache-Control: private, no-store, Surrogate-Control: max-age=0 e Vary: Authorization. A RubyGems limpou o cache da Fastly, revogou chaves legadas e aposentou a rota GET de login. Recomenda chaves com escopo, MFA na API e trusted publishing para CI.
Código-Fonte: Vulnerável vs. Seguro
O trecho vulnerável do controller foi reconstruído a partir das linhas removidas no patch público da RubyGems; os cabeçalhos de cache inseguros vinham da interação do middleware Rack, não de uma política pública configurada nessa ação. O callback endurecido e os cabeçalhos correspondem ao commit d3d11c0. private, no-store impede armazenamento compartilhado, Surrogate-Control desativa cache de proxy e Vary: Authorization separa qualquer cache que ainda armazene uma resposta. É análise de código-fonte, não um roteiro de requisição nem um patch isolado; aplique a correção completa do fornecedor e limpe entradas antigas.
class Api::V1::ApiKeysController < Api::BaseController
include ApiKeyable
# O controlador antes da correção não tinha o callback deny_shared_cache.
def show
authenticate_or_request_with_http_basic do |username, password|
user = User.authenticate(username.strip, password)
check_mfa(user) do
key = generate_unique_rubygems_key
api_key = user.api_keys.build(
legacy_key_defaults.merge(hashed_key: hashed_key(key))
)
save_and_respond(api_key, key)
end
end
end
end
class Api::V1::ApiKeysController < Api::BaseController
include ApiKeyable
before_action :deny_shared_cache
def show
authenticate_or_request_with_http_basic do |username, password|
user = User.authenticate(username.strip, password)
check_mfa(user) do
key = generate_unique_rubygems_key
api_key = user.api_keys.build(
legacy_key_defaults.merge(hashed_key: hashed_key(key))
)
save_and_respond(api_key, key)
end
end
end
end
# Adicionado a Api::BaseController pela correção.
def deny_shared_cache
response.headers["Cache-Control"] = "private, no-store"
response.headers["Surrogate-Control"] = "max-age=0"
response.headers["Vary"] = [response.headers["Vary"], "Authorization"].compact_blank.join(", ")
end
Lista de Verificação de Segurança para Engenharia
- ✓Se você usava credenciais legadas da RubyGems.org, substitua-as: a RubyGems revogou todas. Audite versões, remoções inesperadas, responsáveis, trusted publishers e webhooks; não presuma que versões existentes foram reescritas.
- ✓Use chaves de API com escopo limitado à gem e às ações necessárias, habilite MFA para ações na interface e na API e prefira trusted publishing/OIDC a segredos duradouros na CI. Confirme que cada fluxo só publica a gem pretendida.
- ✓Para respostas autenticadas ou com segredos atrás de uma CDN, defina Cache-Control: private, no-store na origem, desative cache de proxy e inclua cada dimensão de autorização na política Vary/chave de cache. Teste com e sem compressão pelo caminho real da CDN.
- ✓Adicione teste de integração que consulte a rota de credenciais com Accept-Encoding: gzip e exija private, no-store, Surrogate-Control: max-
age=0e Vary: Authorization. Verifique também que uma segunda identidade não recebe a primeira resposta do cache compartilhado. - ✓Ao corrigir vazamento por cache, limpe objetos armazenados antes de emitir novos segredos; trate registros históricos como evidência limitada, não como prova de que não houve exposição. Registre qual período pode ser investigado.
- ✓Mantenha a atribuição precisa: a RubyGems não confirmou autoria por IA na campanha de maio de 2026 e não encontrou evidências de sucesso nas tentativas de roubo de chaves API.
Fontes
- RubyGems — Security advisory: Possible leak of legacy API keys via improper cache configuration
- RubyGems.org — GHSA-9j48-x3c3-mrp2
- RubyGems.org — remediation commit d3d11c0
- Truffle Security — Cache vulnerability in RubyGems
- RubyGems — API key scopes
- RubyGems — May spam-publishing campaign update
- CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor
- CWE-524 — Use of Cache Containing Sensitive Information