FlawOpen

flawopen.com/Incidents/rubygems-cdn-cache-api-key-leak-july-2026

● RubyGems.org / Ruby Central · CWE-200 / CWE-524 · July 2026 · CVSS 7.3 · उच्च
सुरक्षा अनुसंधान · FlawOpen

RubyGems: CDN कैश से API कुंजी का खुलासा

gzip/CDN कैश दोष से RubyGems की पुरानी कुंजियाँ उजागर हुईं। यहाँ सुधार और रिकवरी के कदम हैं; अलग पैकेज अभियान में AI की भूमिका विवादित है।

💡 आसान भाषा में (ELI5)

एक ऐसे काउंटर की कल्पना करें जो हर सत्यापित ग्राहक के लिए नई मास्टर चाबी बनाता है, लेकिन पैकिंग मशीन द्वारा लिफाफे का लेबल बदलने के कारण उसकी प्रति साझा ट्रे में रख देता है। उसी काउंटर पर अगला व्यक्ति बिना पहचान दिखाए पिछले ग्राहक की चाबी उठा सकता है। सुधार साझा कैश को निजी उत्तर रखने से रोकता है और प्राधिकरण के आधार पर उत्तर अलग करता है।

इस पेज के मुख्य शब्द

Legacy API key
RubyGems की लंबे समय तक मान्य रहने वाली व्यापक gem-प्रबंधन अनुमति वाली कुंजी। सलाह के अनुसार इससे नया gem संस्करण प्रकाशित करना, संस्करण हटाना, मालिक जोड़ना और webhook या trusted-publisher सेटिंग बदलना संभव था; इससे खाते में लॉगिन या प्रकाशित रिलीज़ बदलना संभव नहीं था।
Shared cache / POP
CDN का Point of Presence (POP) उत्तर को बाद के अनुरोधों के लिए सहेजता है। यदि संवेदनशील जानकारी वाला प्रमाणित उत्तर निजी कैश नीति और प्राधिकरण-आधारित कैश कुंजी के बिना साझा हो, तो दूसरा कॉलर वही उत्तर पा सकता है।
Compression middleware
RubyGems के अनुसार Accept-Encoding: gzip ने Rack middleware के बीच ऐसी प्रक्रिया शुरू की जिससे उत्तर में private निर्देश और Vary: Authorization नहीं रहे। gzip के बिना साधारण अनुरोधों में वही हेडर व्यवहार नहीं था।
CWE-200 and CWE-524
GitHub Security Advisory CWE-200 को संवेदनशील जानकारी के खुलासे और CWE-524 को संवेदनशील जानकारी रखने वाले कैश के उपयोग से जोड़ता है। ये खुलासे और कैश सीमा को दर्शाते हैं; कोई CVE जारी नहीं हुआ।
RubyGems package-flood attribution
RubyGems ने मई 2026 में पैकेज प्रकाशित करने के दुरुपयोग की पुष्टि की, पर यह निर्धारित नहीं कर सकी कि AI एजेंटों ने उन्हें बनाया या प्रकाशित किया था। इस अभियान को कैश दोष से अलग रखें; स्रोत यह नहीं दिखाते कि अभियान ने कुंजियाँ उजागर कीं।

फ्रेमवर्क मैपिंग

  • इस गाइड में बताई गई कमजोरी के लिए CWE संदर्भ: CWE-200 CWE-524

घटना का समयक्रम (Timeline)

2020-12-10

RubyGems 3.2.0 ने सामान्य gem signin प्रक्रिया को सीमित दायरे वाली कुंजी बनाने में बदला, लेकिन सर्वर ने पुराना GET मार्ग चालू रखा।

2026-07-06

Truffle Security ने कैश समस्या RubyGems को बताई।

2026-07-09

RubyGems ने उत्तर-हेडर सुधार लागू किया और Fastly से प्रभावित कैश ऑब्जेक्ट हटाए।

2026-07-22

RubyGems ने GHSA-9j48-x3c3-mrp2 प्रकाशित किया और कहा कि सभी पुरानी कुंजियाँ रद्द कर दी गईं। GHSA का स्कोर 7.3 High है; RubyGems ने पर्यावरण सहित कुल स्कोर 7.2 High बताया।

2026-09-11

RubyGems ने मई के पैकेज-प्रकाशन अभियान का अलग विवरण दिया और कहा कि इसे AI एजेंटों से जोड़ना संभव नहीं है।

हमले का चरण-दर-चरण प्रवाह

चरण 1

पुराना मार्ग क्रेडेंशियल बनाता है

/api/v1/api_key पर प्रमाणित GET अनुरोध व्यापक अनुमति वाली पुरानी API कुंजी बनाकर लौटाता है। RubyGems कहता है कि संस्करण 3.2.0 से पुराने gem क्लाइंट इस मार्ग का उपयोग करते थे; नए क्लाइंट सीमित कुंजियों पर चले गए, फिर भी सर्वर ने यह मार्ग बनाए रखा।

चरण 2

gzip हेडर-प्रक्रिया बदलता है

क्लाइंट gzip मांगता है। RubyGems इस कैश व्यवहार को Rack::Deflater और Rack::ETag की परस्पर प्रक्रिया से जोड़ता है: संपीड़ित उत्तर में केवल साधारण Cache-Control: no-cache रह गया, private और Vary: Authorization नहीं थे।

चरण 3

CDN प्रमाणित उत्तर दोबारा उपयोग करता है

प्रभावित Fastly विन्यास में उत्तर किसी POP पर एक घंटे तक रखा जा सकता था। कैश कुंजी Authorization के अनुसार कॉलरों को अलग नहीं करती थी, इसलिए edge अगली पहचान की पुष्टि के लिए RubyGems से संपर्क किए बिना पिछला उत्तर लौटा सकता था।

चरण 4

दूसरे कॉलर को कुंजी मिलती है

उसी POP से होकर उसी कैश वैरिएंट वाला अनुरोध पिछले उपयोगकर्ता की कुंजी पा सकता था। RubyGems कहता है कि यह लॉगिन के दौरान अनजाने में हो सकता था; उसने कैश उत्तर को बिना प्रमाणीकरण पढ़े जाने की संभावना भी बताई। उपलब्ध लॉग में दुर्भावनापूर्ण उपयोग नहीं मिला, लेकिन वे सीमित समय को कवर करते थे।

चरण 5

कुंजी की अनुमति प्रभाव सीमित करती है

लीक हुई पुरानी कुंजी से नए gem संस्करण प्रकाशित करना, संस्करण हटाना, मालिक बदलना और webhook या trusted-publisher सेटिंग बदलना संभव था। प्रकाशित रिलीज़ बदली नहीं जा सकती थीं। RubyGems के अनुसार UI और API दोनों पर MFA मांगने से केवल कुंजी के आधार पर publish, yank और मालिक बदलने की कार्रवाई रुकती है।

चरण 6

सुधार साझा उपयोग रोकता और कुंजियाँ रद्द करता है

पैच Cache-Control: private, no-store, Surrogate-Control: max-age=0 और Vary: Authorization जोड़ता है। RubyGems ने Fastly कैश साफ किया, पुरानी कुंजियाँ रद्द कीं और GET साइन-इन मार्ग बंद किया। वह सीमित कुंजियों, API MFA और CI में trusted publishing की सलाह देता है।

सोर्स कोड: कमज़ोर बनाम सुरक्षित कार्यान्वयन

असुधारे गए कंट्रोलर अंश को RubyGems के सार्वजनिक सुधार में हटाई गई पंक्तियों से पुनर्निर्मित किया गया है; असुरक्षित कैश हेडर Rack middleware के मेल से बने थे, इस action द्वारा सार्वजनिक कैश नीति तय करने से नहीं। सुरक्षित callback और हेडर commit d3d11c0 से मेल खाते हैं। private, no-store साझा भंडारण रोकता है, Surrogate-Control proxy caching रोकता है और Vary: Authorization उन कैश में भी उत्तर अलग करता है जो फिर भी उन्हें रखें। यह स्रोत-कोड विश्लेषण है, अनुरोध चलाने की विधि या स्वतंत्र पैच नहीं; विक्रेता का पूरा सुधार लागू करें और पुराने कैश हटाएँ।

कमज़ोर कार्यान्वयन
class Api::V1::ApiKeysController < Api::BaseController
  include ApiKeyable

  # सुधार से पहले के कंट्रोलर में 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

# सुधार में Api::BaseController में जोड़ा गया।
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

इंजीनियरिंग और सिस्टम सुरक्षा चेकलिस्ट

स्रोत