flawopen.com/Incidents/rubygems-cdn-cache-api-key-leak-july-2026
RubyGems: API-Key-Leak durch CDN-Cache
Ein gzip/CDN-Fehler legte RubyGems-Schlüssel offen. Hier stehen Fix und Wiederherstellung; KI-Zuordnung der separaten Kampagne ist umstritten.
Stellen Sie sich einen Schalter vor, der für jeden geprüften Kunden einen neuen Generalschlüssel ausgibt und eine Kopie in eine gemeinsame Ablage legt, weil eine Verpackungsmaschine das Etikett des Umschlags verändert hat. Die nächste Person am selben Schalter könnte den Schlüssel des vorherigen Kunden ohne Ausweis erhalten. Der Fix verhindert, dass gemeinsam genutzte Caches private Antworten speichern, und trennt Antworten nach der Autorisierung.
Kernkonzepte & Begriffe
Legacy API key- Ein langlebiges RubyGems-Zugangstoken mit weitreichenden Verwaltungsrechten. Laut Advisory konnte es neue Gem-Versionen veröffentlichen, Versionen zurückziehen, Eigentümer hinzufügen sowie Webhooks oder Trusted-Publishing-Einstellungen ändern; eine Kontoanmeldung oder das Umschreiben bereits veröffentlichter Releases war nicht möglich.
Shared cache / POP- Ein CDN-PoP speichert eine Antwort zur Wiederverwendung für spätere Anfragen. Wird eine authentifizierte Antwort mit einem Geheimnis ohne private Cache-Regel und ohne autorisierungsabhängigen Cache-Schlüssel geteilt, kann ein anderer Aufrufer sie erhalten.
Compression middleware- RubyGems zufolge löste Accept-Encoding: gzip eine Rack-Middleware-Interaktion aus, durch die der privaten Antwort die Direktive private und Vary: Authorization fehlten. Normale Anfragen ohne gzip zeigten dieses Header-Verhalten nicht.
CWE-200 and CWE-524- Das GitHub Security Advisory nennt
CWE-200für die Offenlegung sensibler Informationen undCWE-524für die Verwendung eines Caches mit sensiblen Informationen. Die Einträge beschreiben Offenlegung und Cache-Grenze; eine CVE wurde nicht vergeben. RubyGems package-flood attribution- RubyGems bestätigte missbräuchliche Paketveröffentlichungen im Mai 2026, konnte aber nicht feststellen, ob KI-Agenten die Pakete erstellt oder veröffentlicht hatten. Diese Kampagne ist vom Cachefehler zu trennen: Die Quellen belegen nicht, dass sie den Schlüsselabfluss verursacht hat.
Framework-Zuordnungen
Vorfall-Zeitstrahl
RubyGems 3.2.0 stellte den üblichen gem-signin-Ablauf auf Schlüssel mit eingeschränktem Geltungsbereich um; der Server ließ den alten GET-Endpunkt weiterhin zu.
Truffle Security meldete den Cachefehler an RubyGems.
RubyGems spielte den Header-Fix ein und entfernte betroffene Fastly-Cacheobjekte.
RubyGems veröffentlichte GHSA-9j48-x3c3-mrp2 und widerrief nach eigenen Angaben alle älteren Schlüssel. GHSA bewertet den Fehler mit 7,3 High; RubyGems nennt einen gesamten Umgebungswert von 7,2 High.
RubyGems beschrieb separat die Paketveröffentlichungskampagne vom Mai und erklärte, sie nicht KI-Agenten zuordnen zu können.
Schritt-für-Schritt Angriffsablauf
Ein alter Endpunkt stellt ein Token aus
Ein authentifizierter GET-Aufruf an /api/v1/api_key erstellt einen älteren API-Schlüssel mit weitreichenden Rechten und gibt ihn zurück. RubyGems zufolge nutzten gem-Clients vor Version 3.2.0 diesen Pfad; der Server behielt ihn, obwohl neuere Clients auf Schlüssel mit eingeschränktem Geltungsbereich wechselten.
Gzip verändert den Header-Pfad
Der Client fordert gzip an. RubyGems führt das Cacheverhalten auf das Zusammenspiel von Rack::Deflater und Rack::ETag zurück: Die komprimierte Antwort erhielt nur ein einfaches Cache-Control: no-cache, aber weder private noch Vary: Authorization.
Das CDN verwendet eine authentifizierte Antwort wieder
In der betroffenen Fastly-Konfiguration konnte die Antwort bis zu einer Stunde an einem PoP liegen. Der Cache-Schlüssel trennte Aufrufer nicht nach Authorization; dadurch konnte der Edge-Knoten die vorherige Antwort zurückgeben, ohne RubyGems zur Authentifizierung des nächsten Clients aufzurufen.
Ein anderer Aufrufer erhält den Schlüssel
Eine Anfrage mit derselben Cache-Variante über denselben PoP konnte den Schlüssel des vorherigen Nutzers erhalten. RubyGems zufolge konnte dies versehentlich zwischen Anmeldungen passieren; außerdem beschreibt der Anbieter das nicht authentifizierte Abrufen einer gespeicherten Antwort. Die verfügbaren Protokolle zeigen keinen missbräuchlichen Einsatz, decken aber nur einen begrenzten Zeitraum ab.
Der Schlüsselumfang begrenzt die Folgen
Ein abgeflossener älterer Schlüssel konnte neue Gem-Versionen, Rücknahmen, Eigentümerwechsel sowie Änderungen an Webhooks oder Trusted Publishers erlauben. Veröffentlichte Releases ließen sich nicht umschreiben. RubyGems sagt, dass MFA für UI und API allein mit dem Schlüssel ausgeführte Push-, Yank- und Eigentümeraktionen blockiert.
Der Fix verhindert gemeinsame Nutzung und ersetzt exponierte Schlüssel
Der Patch ergänzt Cache-Control: private, no-store, Surrogate-Control: max-age=0 und Vary: Authorization. RubyGems leerte den Fastly-Cache, widerrief ältere Schlüssel und stellte den GET-Anmeldeendpunkt ein. Empfohlen werden eingeschränkte Schlüssel, API-MFA und Trusted Publishing für CI.
Quellcode: Verwundbar vs. Sicher
Der ungepatchte Controller-Auszug wurde aus den im öffentlichen RubyGems-Fix entfernten Zeilen rekonstruiert; die unsicheren Cache-Header entstanden durch das Zusammenspiel der Rack-Middleware und nicht durch eine öffentliche Cache-Regel in dieser Aktion. Der gehärtete Callback und die Headerwerte entsprechen Commit d3d11c0. private, no-store verhindert gemeinsame Speicherung, Surrogate-Control deaktiviert Proxy-Caching und Vary: Authorization trennt Antworten in Caches, die dennoch speichern. Dies ist Quellcodeanalyse, keine Anfrageanleitung und kein eigenständiger Patch; wenden Sie den vollständigen Herstellerfix an und löschen Sie alte Cacheeinträge.
class Api::V1::ApiKeysController < Api::BaseController
include ApiKeyable
# Im Controller vor der Korrektur gab es keinen deny_shared_cache-Callback.
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
# Durch die Korrektur zu Api::BaseController hinzugefügt.
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
Checkliste für Engineering & Systemsicherheit
- ✓Wenn Sie ältere RubyGems.org-Zugangsdaten genutzt haben, ersetzen Sie sie: RubyGems hat alle alten Schlüssel widerrufen. Prüfen Sie Versionen, unerwartete Rücknahmen, Eigentümer, Trusted Publishers und Webhooks; bereits veröffentlichte Releases wurden dadurch nicht umgeschrieben.
- ✓Verwenden Sie API-Schlüssel mit minimalem Umfang für die benötigten Gems und Aktionen, aktivieren Sie MFA für UI und API und bevorzugen Sie Trusted Publishing/OIDC gegenüber langlebigen CI-Geheimnissen. Prüfen Sie, ob jeder Workflow nur das vorgesehene Gem veröffentlichen kann.
- ✓Für authentifizierte Antworten oder Geheimnisse hinter einer CDN setzen Sie am Ursprung Cache-Control: private, no-store, deaktivieren Sie Proxy-Caching und berücksichtigen Sie jede Autorisierungsdimension in Vary/Cache-Schlüssel. Testen Sie den echten CDN-Pfad mit und ohne Komprimierung.
- ✓Ergänzen Sie einen Integrationstest, der den Credential-Endpunkt mit Accept-Encoding: gzip aufruft und private, no-store, Surrogate-Control: max-
age=0sowie Vary: Authorization verlangt. Prüfen Sie außerdem, dass eine zweite Identität nicht die erste Antwort aus einem gemeinsamen Edge-Cache erhält. - ✓Bei einem Cache-Leck löschen Sie gespeicherte Objekte, bevor Sie Ersatzgeheimnisse ausgeben. Behandeln Sie historische Protokolle als begrenzte Belege, nicht als Beweis für eine fehlende Offenlegung, und dokumentieren Sie den tatsächlich prüfbaren Zeitraum.
- ✓Bleiben Sie bei der Attribution präzise: RubyGems bestätigte keine KI-Autorschaft der Paketkampagne vom Mai 2026 und fand keinen Beleg für erfolgreiche API-Schlüsseldiebstähle.
Quellen
- 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