اگر امروز کد خود را در یک مخزن عمومی منتشر میکنید، احتمالاً کلیدهای API شما همین حالا هم برای همیشه در تاریخچه گیت ثبت شدهاند — حتی اگر دیروز فایل .gitignore را اضافه کرده باشید. این واقعیت تکاندهنده، نتیجهی بررسی جامع ۱۰۰۰ مخزن گیتهاب توسط شرکت Exact Solution است.
این فرآیند با یک بازرسی داخلی ساده از وابستگیهای متنباز شروع شد. تیم بررسی در یک «سربالیدگی» فنی افتاد؛ جایی که هر مخزن آنها را به مخزن دیگری هدایت کرد و الگوهای تکراری عجیبی را آشکار ساخت. طی یک سال، آنها ترکیبی متنوع از پروژههای تکنفره، کدهای استارتاپی، کتابخانههای متنباز و ابزارهای داخلی را که بهطور تصادفی عمومی شده بودند، بررسی کردند.
بسیاری از برنامهنویسان به گیتهاب صرفاً به چشم یک ابزار ذخیرهسازی نگاه میکنند، اما تنظیمات پیشفرض پنهان این پلتفرم اغلب شکافهای امنیتی بحرانی ایجاد میکنند. این آسیبپذیریها محدود به تازهکارها نیست؛ این بازرسی اشتباهات مشابهی را در مخازنی با ۵ ستاره و مخازنی با ۵۰۰۰ ستاره پیدا کرده است. در پروژههایی که توسط یک نفر مدیریت میشدند یا آنهایی که ۵۰ مشارکتکننده داشتند، خطاهای یکسانی دیده شد. مشکل، نبود مهارت نیست، بلکه نبود آگاهی درباره نحوه مدیریت دادهها در گیت و گیتهاب است. این مشکلات بر اساس سطح تجربه تفکیک نمیشوند، بلکه بر این اساس هستند که آیا کسی پیش از وقوع اشتباه، شما را آگاه کرده است یا خیر.
خطر اسرار ماندگار
بحرانیترین شکست، ثبت اسراری مثل فایلهای .env، اعتبارنامههای دیتابیس یا کلیدهای API است. طبق گزارش Exact Solution، این خطرناکترین اشتباه لیست است و راهکار آن هم بیشترین سوءتفاهم را دارد. بسیاری تصور میکنند افزودن فایلی به .gitignore بعد از یک کامیت، آن را حذف میکند؛ اما اینطور نیست. فایل بهطور دائمی در تاریخچه گیت باقی میماند. هر کسی که مخزن را کلون کند و دستور git log را اجرا کند، میتواند آن را بیابد و هر کسی که پروژه را فورک (Fork) کند، این اسرار را با خود میبرد.
در بسیاری از مخازن، جستجو در تاریخچه با دستور git log --all --full-history -- .env نشان میدهد که برنامهنویسان مدتها بعد از نشت رمز، سعی کردهاند با پیام «حذف فایل .env» آن را پاک کنند، در حالی که رمز قبلاً ثبت شده بود. حتی اگر اعتبارنامهها در یک مقطع زمانی حذف شوند، ترکیب گسترش کد و ماندگاری تاریخچه به یک مهاجم مصمم اجازه میدهد تا مدتها بعد از اینکه برنامهنویسان پروژه را رها کردهاند، به این داراییها دسترسی پیدا کند.
برای رفع واقعی این مشکل، برنامهنویس باید تاریخچه را بازنویسی کند. دو نکته کلیدی در اینجا وجود دارد:
- ابزارها: از
git filter-repoاستفاده کنید (نصب باpip install git-filter-repo). ازgit filter-branchاستفاده نکنید چون قدیمی، منسوخ و کند است. دستورgit filter-repo --path .env --invert-pathsاستاندارد حذف است. - چرخش کلیدها: حذف فایل کافی نیست. کلید در لحظه لمس اولین کامیت، لو رفته است. هر توکن، کلید و اعتباری که در آن فایل بود باید فوراً تغییر کند (Rotate شود).
برای پیشگیری، ابزارهایی مثل trufflehog یا git-secrets باید به عنوان pre-commit hook — یعنی ابزارهایی که مثل یک نگهبان قبل از ثبت نهایی کد، آن را بازرسی میکنند — تعریف شوند. برای مثال، با نصب pre-commit از طریق pip و افزودن مخزن trufflehog به فایل .pre-commit-config.yaml میتوانید کامیت را قبل از ورود به تاریخچه مسدود کنید؛ به این معنی که فقط یکبار روی هر دستگاه نیاز به تنظیم دارد.
شکست در حاکمیت و بازبینی
نبود حفاظت از شاخه (Branch Protection) یک مشکل سیستماتیک است که در اکثریت مخازنی با بیش از یک مشارکتکننده دیده میشود. وقتی شاخه main محافظت نشده باشد، هر کسی با دسترسی write میتواند مستقیماً کد بفرستد، روی کار همکارش force push کند یا کل شاخه را حذف کند. بدون قوانین حفاظتی، برنامهنویسان ممکن است ناخواسته ریسکهای امنیتی ایجاد کنند یا با یک پوش (Push) مستقیم، محیط تولید (Production) را مختل کنند.
Exact Solution پیکربندی سختگیرانهای را در بخش Settings $
ightarrow$ Branches $
ightarrow$ Branch protection rules توصیه میکند. چکلیست ضروری شامل موارد زیر است:
- ✅ الزام به ارسال Pull Request قبل از ادغام
- ✅ الزام به تأیید (Approval): حداقل ۱ نفر
- ✅ لغو تأییدهای قدیمی (Stale) هنگام ارسال کامیتهای جدید
- ✅ الزام به پاس شدن تستهای وضعیت (Status Checks) قبل از ادغام
- ✅ الزام به بهروز بودن شاخهها قبل از ادغام
- ✅ عدم اجازه دور زدن (Bypass) تنظیمات فوق
- ❌ غیرفعال کردن Allow force pushes
- ❌ غیرفعال کردن Allow deletions
تنظیم «عدم اجازه دور زدن» (Do not allow bypassing) متداولترین موردی است که فراموش میشود. بدون این گزینه، ادمینها و مالکان مخزن میتوانند هر قانونی را دور بزنند؛ این موضوع در مخازنی که توسعهدهنده ارشد احتمالاً کسی است که عصر جمعه کدی خراب را پوش میکند، حفاظت را بیمعنی میکند.
شکست دوم، حجم بالای پولریکوئستها (PR) است. این مورد در اسکنهای امنیتی ظاهر نمیشود، اما در تاریخچه کامیتها به شکل PRهایی با ۴۰۰۰ خط کد دیده میشود که تنها در ۱۱ دقیقه تأیید شدهاند. برنامهنویسان اغلب دو هفته روی یک قابلیت کار میکنند، تغییرات را در ۱۵ فایل جمع میکنند و یک PR عظیم باز میکنند. بازبین (Reviewer) که با تغییری مواجه است که بررسی دقیق آن در کمتر از یک ساعت عملاً غیرممکن است، معمولاً بخشهای بدیهی را سریع نگاه میکند، کامنتی درباره نام یک متغیر میگذارد و آن را تأیید میکند. در نتیجه، منطق اصلی کد بازبینی نمیشود.
تحقیقات درباره اثربخشی بازبینی کد صریح است: با افزایش اندازه PR، بازبینها در هر خط کد، عیوب کمتری پیدا میکنند. بعد از حدود ۴۰۰ خط کد تغییر یافته، کیفیت بهشدت افت میکند و پس از ۱۰۰۰ خط، این فرآیند عملاً تشریفاتی میشود. راهکار ساختاری این است: قانونی وضع شود که هیچ PRی با بیش از ۴۰۰ خط تغییرات ادغام نشود. این مورد را میتوان با یک GitHub Action (مثلاً در مسیر .github/workflows/pr-size-check.yml) اجرا کرد که با استفاده از GitHub API و ابزار jq مقدار .additions + .deletions را محاسبه کرده و در صورت بالا بودن عدد، خطا دهد.

شکافهای مستندسازی و نگهداری
فایلهای README اغلب میگویند پروژه «چیست»، اما نمیگویند «چطور» باید از آن استفاده کرد. این شکست فنی بهطور مداوم به پذیرش پروژه و قابلیت نگهداری آن ضربه میزند. اکثر READMEها یک توضیح یکپاراگرافی و لیستی از ویژگیها دارند، اما یک دستور نصب را بدون توضیح پیشنیازها یا تنظیمات محیطی قرار میدهند.
یک README کاربردی باید شامل بخشهای دقیق و تفصیلی باشد:
- پیشنیازها: نسخههای دقیق، مانند Node.js 18+ (نه ۱۶ یا ۲۰، چون پروژه از fetch داخلی نسخه ۱۸ استفاده میکند) و PostgreSQL 14+.
- نصب: توالی شفاف دستورات، مانند
cp .env.example .envو سپسnpm installوnpm run db:migrateو در نهایتnpm run dev. - خروجی مورد انتظار: نمونههایی مانند "Server running at http://localhost:3000" و "Database connected: true".
- خطاهای رایج: این غافلشدهترین بخش است. باید خطاهای خاصی مثل
ECONNREFUSED 5432(عدم اجرای PostgreSQL) یاMODULE_NOT_FOUND(نیاز به اجرای npm install) را لیست کند.
همچنین سیستم ردیابی مشکلات (Issue Tracking) اغلب نادیده گرفته میشود. مخازن معمولاً فاقد قالب، برچسب یا سیستم تریاژ هستند و Issue Tracker به یک لیست کارهای بدون صاحب تبدیل میشود. یک Issue ضعیف شبیه «کار نمیکند» است، در حالی که یک مورد قابل پیگیری عنوانی دارد مانند: [BUG] Login fails with OAuth providers when redirect URI contains query params همراه با برچسبهای bug و authentication و priority:high و یک توسعهدهنده مسئول و یک Milestone (مثلاً v2.1.0).
پیادهسازی قالبهای Issue گیتهاب (مانند .github/ISSUE_TEMPLATE/bug_report.yml) تنها ۲۰ دقیقه زمان میبرد اما گزارشهای مبهم را حذف میکند، زیرا کاربر را مجبور میکند مراحل بازتولید خطا، اطلاعات محیطی و رفتار مورد انتظار در مقابل رفتار واقعی را ارائه دهد.
امنیت خودکار و دسترسیها
«پوسیدگی وابستگیها» قاتلی خاموش است که با گذشت زمان تشدید میشود. پروژهای شروع میشود، وابستگیها نصب میشوند و محصول عرضه میشود — اما سپس ۱۸ ماه هیچکس npm audit یا pip check نمیگیرد. درخت وابستگیها آسیبپذیریهای افشاششدهای را جمع میکند که نادیده میمانند چون هیچ هشدار خودکاری تنظیم نشده است.
گیتهاب در زمینه امنیت پیشرو است، اما اکثریت حوادث ناشی از اشتباهات کاربر است، نه نقص پلتفرم. فعال کردن Dependabot تنها ۴ دقیقه زمان میبرد. یک فایل .github/dependabot.yml میتواند برای بهروزرسانیهای هفتگی با محدودیت ۱۰ عدد PR باز پیکربندی شود. با استفاده از برچسب automerge-patch در ترکیب با یک GitHub Action (.github/workflows/dependabot-automerge.yml)، برنامهنویسان میتوانند ادغام بهروزرسانیهای سطح Patch را خودکار کنند. این کار بار نگهداری برای اصلاحات جزئی را حذف کرده و فقط بهروزرسانیهای Minor و Major را برای بازبینی دستی باقی میگذارد.
در نهایت، گردشکارهای GitHub Actions اغلب از دسترسیهای بیش از حد رنج میبرند. این سریعترین دسته در حال رشد از اشتباهات است. بهطور پیشفرض، GITHUB_TOKEN در بسیاری از ورکفلوها دسترسی write به کل مخزن دارد. ورکفلویی که فقط نیاز دارد تستها را اجرا کند و یک کامنت بگذارد، هیچ دلیلی برای داشتن دسترسی write به بخش Deployments یا Packages ندارد.
برای اجرای اصل «حداقل دسترسی»، ورکفلوها باید نیازهای خود را صریحاً تعریف کنند. بهجای پیشفرض خطرناک، باید از این موارد استفاده کنند:
contents: read(برای checkout)checks: write(برای گزارش تستها)pull-requests: write(برای ارسال کامنت)
افزودن permissions: read-all به ابتدای هر فایل ورکفلو، بهطور پیشفرض همه چیز را رد میکند و توسعهدهنده را مجبور میکند دسترسیها را بهصورت صریح برای هر Job اعطا کند.
الگوی پنهان
بررسی ۱۰۰۰ مخزن نشان داد که ریشه این مشکلات بیکفایتی نیست. بلکه این است که این اشتباهات تا زمانی که تبدیل به حادثه نشوند، نامرئی هستند. یک فایل .env در تاریخچه، خودش را فریاد نمیزند؛ یک شاخه محافظتنشده تا زمان وقوع یک force-push مشکلی ایجاد نمیکند؛ و وابستگیهای قدیمی ساکت میمانند تا زمانی که در یک CVE ظاهر شوند.
رفع هر هفت مورد کمتر از یک روز زمان میبرد و اکثر آنها تنها ۲۰ دقیقه نیاز دارند. دلیل نبود آنها این است که به برنامهنویسان گفته نشده قبل از ظهور مشکل، به دنبال چه بگردند.
برای یک برنامهنویس متوسط، این بدان معناست که جریان کاری (Workflow) شما نیاز به یک تغییر ساختاری دارد. گذار از امنیت «انضباطمحور» به امنیت «اتوماسیونمحور» — از طریق pre-commit hookها و بلوکهای CI — تنها راه اطمینان از تکرار نشدن این هفت اشتباه است.
چکلیست هفت دقیقهای بازرسی مخزن:
- ✅ جستجوی تاریخچه گیت برای .env، اسرار و کلیدهای API:
git log --all -S "SECRET" - ✅ فعال بودن Branch Protection روی main با الزام به بازبینی
- ✅ میانگین اندازه PR زیر ۴۰۰ خط (بررسی ۱۰ مورد آخر)
- ✅ وجود پیشنیازها، مراحل نصب و بخش خطاهای رایج در README
- ✅ وجود قالبهای Issue در مسیر
.github/ISSUE_TEMPLATE/ - ✅ فعال بودن Dependabot و بازبینی PRهای وابستگی
- ✅ تعریف صریح دسترسیها در GitHub Actions با پیشفرض
read-all
گام بعدی شما
- تاریخچه گیت خود را برای یافتن اسرار جستجو کنید:
git log --all -S "SECRET" - حفاظت از شاخه main را با الزام به بازبینی فعال کنید.
- یک فایل
.github/dependabot.ymlبسازید تا وابستگیهای شما بهروز بمانند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو