Git History में लीक हुए Secrets: ढूँढें, Rotate करें, रोकें
कमिट की गई API key हटाने से वह git history से नहीं जाती। लीक secrets कैसे ढूँढें, पहले rotate करें, history सुरक्षित तरीक़े से rewrite करें और अगला लीक रोकें।
· 7 मिनट का लेख · Lina Source LLC
कोई एक .env फ़ाइल कमिट कर देता है, या जल्दी में कुछ टेस्ट करने के लिए एक लाइव API key किसी config module में पेस्ट कर देता है। एक रिव्यूअर ध्यान देता है, अगले कमिट में key हटा दी जाती है, और सब आगे बढ़ जाते हैं। Key अब भी वहीं है। Git हर फ़ाइल का हर वर्ज़न स्टोर करता है, और जो कोई भी रिपॉज़िटरी clone कर सकता है वह उसे जोड़ने वाला कमिट पढ़ सकता है।
हार्डकोडेड credentials CWE-798 के रूप में ट्रैक होते हैं। यह गाइड बताती है कि जब कोई पहले ही history में पहुँच चुका हो तो क्या करें: हर secret ढूँढें, बाक़ी सब से पहले उन्हें rotate करें, तय करें कि history rewrite करना फ़ायदेमंद है या नहीं, और वे guardrails लगाएँ जो अगला लीक रोकें।
लाइन हटाना काफ़ी क्यों नहीं
Secret हटाने वाला कमिट उसके बिना एक नया snapshot जोड़ता है। पिछला snapshot, secret समेत, अब भी branch की history से जुड़ा रहता है। git log -p उसे दिखाता है, पुराने कमिट का git checkout उसे वापस ले आता है, और हर मौजूदा clone और fork के पास पहले से एक कॉपी है। Merge से पहले branch squash करना भी मदद नहीं करता अगर मूल कमिट कभी push हुए थे: remote पर वे अब भी हो सकते हैं, और जिसने उन्हें fetch किया उसके पास वे locally मौजूद हैं।
पब्लिक रिपॉज़िटरी पर मान लें कि secret देखा जा चुका है। ऑटोमेटेड scrapers पब्लिक pushes पर credential पैटर्न्स की ताक में रहते हैं, और push तथा पहले दुरुपयोग के बीच का अंतराल बहुत छोटा हो सकता है। प्राइवेट रिपॉज़िटरी पर एक्सपोज़र कम है पर शून्य नहीं: read एक्सेस वाला हर कर्मचारी, contractor, CI सिस्टम और इंटीग्रेशन इसे रखता है।
क़दम 1: पहले rotate करें
Rotation ही वह इकलौता क़दम है जो जोखिम को सचमुच हटाता है। History rewrite करना secret को भविष्य के clones से छिपाता है; पहले से मौजूद कॉपियों के बारे में वह कुछ नहीं करता। इसलिए रिपॉज़िटरी को छूने से पहले credential रद्द करें और बदलें।
Rotation की योजना ऐसे बनाएँ कि आउटेज न हो। कई प्रोवाइडर दो keys एक साथ वैध रहने देते हैं: नई key बनाएँ, उसे हर उस जगह डिप्लॉय करें जहाँ पुरानी इस्तेमाल हो रही थी, पुष्टि करें कि ट्रैफ़िक शिफ़्ट हो गया, फिर पुरानी रद्द करें। अगर प्रोवाइडर सिर्फ़ एक key देता है, तो छोटी रुकावट स्वीकार करें; कुछ मिनट का डाउनटाइम इससे बेहतर सौदा है कि एक परफ़ेक्ट कटओवर तय करते-करते ज्ञात रूप से लीक हुआ credential चालू छोड़ दिया जाए।
- प्रोवाइडर के कंसोल में नई key बनाएँ और उसे अपने सामान्य कॉन्फ़िगरेशन रास्ते से डिप्लॉय करें।
- पुरानी key रद्द करें। सिर्फ़ इस्तेमाल बंद करना काफ़ी नहीं; बिना इस्तेमाल वाली वैध key भी वैध key ही है।
- कमिट की तारीख़ से लेकर अब तक पुरानी key से हुई गतिविधि के लिए प्रोवाइडर के audit logs जाँचें: API कॉल, नए यूज़र, बदली गई permissions, अप्रत्याशित बिलिंग।
- अगर secret कोई डेटाबेस पासवर्ड या signing key था, तो सोचें कि उससे क्या खुल सकता था। लीक हुआ JWT signing secret मतलब कोई भी टोकन बनाया जा सकता था, इसलिए मौजूदा sessions रद्द करें।
- लिखकर रखें कि क्या एक्सपोज़ हुआ, कितनी देर के लिए और आपने क्या बदला। कोई कस्टमर या ऑडिटर पूछे तो यह काम आएगा।
क़दम 2: जो कुछ भी लीक हुआ, सब ढूँढें
जहाँ एक कमिट किया गया secret है, वहाँ अक्सर और भी होते हैं। सिर्फ़ मौजूदा tree नहीं, पूरी history खोजें, और हर branch तथा tag शामिल करें।
# वे कमिट जिन्होंने history में कहीं भी कोई string जोड़ी या हटाई
git log -p -S "sk_live_" --all
# सभी कमिट के diffs पर regex खोज
git log -p -G "AKIA[0-9A-Z]{16}" --all
# वे फ़ाइलें जो कभी किसी संदिग्ध path पर मौजूद थीं
git log --all --oneline -- .env config/production.json
# समर्पित scanners सैकड़ों ज्ञात credential फ़ॉर्मैट जाँचते हैं
gitleaks git -v .
trufflehog git file://. --only-verifiedgitleaks और trufflehog इसी काम के लिए बने ओपन-सोर्स scanners हैं। वे आम प्रोवाइडर keys के फ़ॉर्मैट जानते हैं, और trufflehog यह भी जाँच सकता है कि मिला हुआ credential अब भी लाइव है या नहीं। पुरानी gitleaks रिलीज़ें git subcommand के बजाय gitleaks detect --source . इस्तेमाल करती हैं। पूरी history पर scanner एक बार चलाएँ, फिर उसे नए कमिट पर चलता रखें। कुछ false positives की उम्मीद रखें, जैसे test fixtures और डॉक्यूमेंटेशन में उदाहरण keys। हर एक की समीक्षा करें, फिर पुष्ट false positives को scanner की allowlist या baseline फ़ाइल में दर्ज करें ताकि अगली बार सिर्फ़ नई फ़ाइंडिंग्स दिखें।
रिपॉज़िटरी के आस-पास की जगहें न भूलें: वे CI logs जिन्होंने environment variables echo किए, issue और pull request टिप्पणियाँ, wiki पेज, रिपॉज़िटरी से बनी Docker image layers, और डीबग करते समय शेयर किए गए gists या pastes।
क़दम 3: तय करें कि history rewrite करनी है या नहीं
एक बार secret rotate हो जाने पर वह बेकार है, इसलिए history rewrite करना containment नहीं बल्कि सफ़ाई है। फिर भी यह तब करने लायक़ है जब रिपॉज़िटरी पब्लिक हो, जब secret अपने अलावा कुछ और भी उजागर करता हो (कोई internal hostname, किसी कस्टमर का नाम), या जब कंप्लायंस इसकी माँग करे। इसकी असली क़ीमत है: हर सहयोगी को फिर से clone करना पड़ता है, खुले pull requests बिगड़ते हैं, और कमिट hashes बदल जाते हैं। जो कुछ भी किसी कमिट को hash से संदर्भित करता है, जैसे रिलीज़ नोट्स, डिप्लॉयमेंट रिकॉर्ड, issue लिंक या दूसरी रिपॉज़िटरीज़ में pinned dependencies, वह ऐसे कमिट की ओर इशारा करेगा जो अब branch पर मौजूद नहीं। Rewrite की घोषणा पहले से करें, कोई शांत समय चुनें, और खुले pull requests पहले merge या बंद कर दें।
इसके लिए Git प्रोजेक्ट पुराने git filter-branch की जगह git filter-repo की सिफ़ारिश करता है। यह एक ताज़ा clone पर काम करता है और एहतियात के तौर पर origin remote हटा देता है, इसलिए push करने से पहले आप उसे वापस जोड़ते हैं।
# एक ताज़ा clone से शुरू करें
git clone [email protected]:acme/api.git api-clean
cd api-clean
# विकल्प A: हर कमिट से एक फ़ाइल हटाएँ
git filter-repo --invert-paths --path config/production.env
# विकल्प B: secret strings जहाँ भी दिखें, वहाँ बदल दें
# replacements.txt में हर लाइन पर एक नियम होता है, उदाहरण के लिए:
# PASTE_THE_OLD_KEY_HERE==>REDACTED
git filter-repo --replace-text ../replacements.txt
# filter-repo origin हटा देता है; उसे वापस जोड़ें और force-push करें
git remote add origin [email protected]:acme/api.git
git push --force --all
git push --force --tagsRewriting क्या नहीं कर सकती
- यह मौजूदा clones, forks, CI caches या backups तक नहीं पहुँच सकती। जिसने rewrite से पहले fetch किया, उसके पास पुराने कमिट बने रहते हैं।
- GitHub पर pull requests के लिए बने references read-only होते हैं और पुराने कमिट को पहुँच में रख सकते हैं। Cached views और वे references हटाने के लिए GitHub Support से संपर्क करना पड़ता है।
- जो सहयोगी पुराने clone से pull या merge करते हैं, वे पुरानी history सीधे वापस push कर सकते हैं। सबसे फिर से clone करने को कहें, और तब तक branch सुरक्षित रखें।
- यह secret को उन बाक़ी जगहों से नहीं हटाती जहाँ उसकी कॉपी गई: logs, टिकट, चैट संदेश या बने हुए artifacts।
इसीलिए क्रम मायने रखता है। पहले rotate, फिर सफ़ाई। लाइव key के साथ rewrite की गई history सुरक्षा का झूठा एहसास भर है। Push के बाद अपना scanner एक ताज़ा clone पर चलाएँ ताकि पुष्टि हो जाए कि secret हर branch और tag से सचमुच चला गया है।
क़दम 4: अगले को रोकें
लक्ष्य यह है कि secret कमिट करना मुश्किल हो और उसे नोटिस करना तेज़। कोई एक नियंत्रण दोनों नहीं करता, इसलिए उन्हें परतों में लगाएँ।
Secrets को कोड के रास्ते से बाहर रखें
Credentials रनटाइम पर environment variables या किसी secret manager से पढ़ें, और स्टार्टअप पर जाँचें कि ज़रूरी वाले मौजूद हैं। Placeholder values के साथ एक .env.example कमिट करें और पहले ही कमिट से .env को .gitignore में डालें। प्रोडक्शन के लिए AWS Secrets Manager, Google Secret Manager, HashiCorp Vault या आपके प्लेटफ़ॉर्म की encrypted environment settings जैसा secret manager आपको access control, audit logs और आसान rotation देता है।
Secrets को कमिट होने से पहले रोकें
एक pre-commit hook ग़लती को डेवलपर की मशीन पर ही पकड़ लेता है, remote तक पहुँचने से पहले। pre-commit फ़्रेमवर्क इसे कॉन्फ़िग की कुछ ही लाइनों में बदल देता है, जिसे हर contributor इंस्टॉल कर सकता है।
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: vX.Y.Z # नवीनतम रिलीज़ tag पर pin करें
hooks:
- id: gitleaks
# हर clone में एक बार इंस्टॉल करें
# pip install pre-commit
# pre-commit installHooks local होते हैं और छोड़े जा सकते हैं, इसलिए backstop के तौर पर वही scanner हर push पर CI में भी चलाएँ। GitHub पर, जहाँ आपका प्लान सपोर्ट करता है, secret scanning और push protection चालू करें; push protection ज्ञात प्रोवाइडर tokens वाले pushes को सर्वर की तरफ़ ही अस्वीकार कर देता है।
जो लीक छूट जाएँ, उनका नुक़सान घटाएँ
- हर key को न्यूनतम permissions तक सीमित करें और, जहाँ प्रोवाइडर अनुमति दे, ख़ास IPs या referrers तक।
- लंबे समय तक चलने वाली static keys के बजाय अल्पकालिक credentials पसंद करें, जैसे CI से आपके क्लाउड प्रोवाइडर तक OIDC federation।
- हर environment के लिए अलग keys इस्तेमाल करें, ताकि लीक हुई test key प्रोडक्शन को न छू सके।
- हर अहम secret के लिए एक rotation runbook रखें, ताकि दबाव में rotate करना सुधारा हुआ काम नहीं, रोज़मर्रा का काम लगे।
कोड रिव्यू कहाँ फ़िट होता है
Scanners ज्ञात फ़ॉर्मैट अच्छी तरह मिलाते हैं; context में वे कमज़ोर हैं, जैसे दो constants से जोड़ा गया पासवर्ड या किसी config fallback में आने वाला डिफ़ॉल्ट credential। कोड पर एक रिव्यू पास इन्हें पकड़ता है। CodeAuditAgent जब किसी पब्लिक रिपॉज़िटरी की डिफ़ॉल्ट branch या पेस्ट किए गए स्निपेट का ऑडिट करता है, तो हार्डकोडेड credentials को कोट किए गए सबूत के साथ CWE-798 के रूप में फ़्लैग करता है। यह मौजूदा कोड पढ़ता है, कमिट history नहीं, इसलिए पिछले कमिट के लिए एक history scanner भी बनाए रखें।
छोटी बात यह: कमिट किया गया secret लीक हुआ secret है। उसे आज ही rotate करें, अगर व्यवधान के लायक़ हो तो history साफ़ करें, और अगला लीक किसी पब्लिक पेज पर नहीं, pre-commit hook पर फ़ेल होने दें।