Environment Variable چیست و چرا نباید Secretها را داخل کد قرار دهیم؟
تعریف Environment Variable، تفاوت .env با Secret کامیتشده، اصل ۱۲-Factor Config، و نقش Secret Scanning و Push Protection در GitHub — با مثال placeholder.
بنیانگذار و مهندس محصول

تقریباً هر نرمافزار آنلاین به چیزهایی وابسته است که در هر محیط فرق میکند: آدرس دیتابیس، کلید درگاه پرداخت، توکن سرویس ایمیل، یا حتی نام دامنهٔ canonical. اگر این مقادیر را بهصورت ثابت داخل سورس بنویسید، دو مشکل همزمان دارید: اول اینکه برای Local و Production باید کد را عوض کنید؛ دوم اینکه هر کسی با دسترسی به مخزن، کلیدهای واقعی را میبیند.
Environment Variable (متغیر محیطی) راه استاندارد جدا کردن «پیکربندی که بین Deployها فرق میکند» از «کدی که باید ثابت بماند» است. اصل Config در Twelve-Factor App دقیقاً همین را میگوید: config را در محیط نگه دارید، نه در کد. litmus test معروفشان این است که آیا میتوانید همین لحظه مخزن را open-source کنید بدون اینکه اعتبارنامه لو برود؟
این مقاله تعریف عملی Secret، تفاوت .env با Secret کامیتشده، اشتباههای رایج، و ابزارهایی مثل Secret Scanning و Push Protection در GitHub را جمع میکند. در مثالها فقط placeholder میبینید — هرگز کلید واقعی paste نکنید.

پاسخ کوتاه
Secret یعنی هر چیزی که اگر افشا شود دسترسی غیرمجاز میدهد: API key، رمز دیتابیس، کلید خصوصی، توکن CI. اینها را داخل سورس، Issue، لاگ عمومی یا پرامپت عمومی نگذارید. بهجای آن، مقدار را از Environment Variable بخوانید و مقدار واقعی را فقط در محیط اجرا (Local امن، Staging، Production، یا Secret store پلتفرم) تزریق کنید.
فایل .env برای راحتی توسعهٔ محلی مفید است، اما اگر به Git برود همان Secret کامیتشده است. Twelve-Factor میگوید env varها را granular و مستقل نگه دارید، نه اینکه یک بستهٔ عظیم «environment=production» را داخل کد شاخه کنید. در GitHub، secret scanning نشت را در تاریخچه و Issue و PR پیدا میکند؛ push protection سعی میکند قبل از ورود Secret به مخزن، push را متوقف کند.
اگر کدتان را فردا عمومی کنید و هنوز نگران کلیدها هستید، پیکربندی را از کد جدا نکردهاید.
Environment Variable دقیقاً چیست؟
سیستمعامل و بیشتر پلتفرمهای اجرا، مجموعهای از جفتهای نام=مقدار به فرایند میدهند. برنامه موقع اجرا میتواند مثلاً DATABASE_URL یا STRIPE_SECRET_KEY را بخواند. همین نام در Local شما به یک دیتابیس تست اشاره میکند و در Production به دیتابیس واقعی — بدون تغییر خط کد.
Twelve-Factor «config» را اینگونه تعریف میکند: هر چیزی که احتمال دارد بین Deployها فرق کند — handle منابع backing service مثل دیتابیس و Memcached، اعتبارنامهٔ سرویسهای خارجی، و مقادیر per-deploy مثل hostname. چیزهایی که بین Deployها ثابتاند (مثلاً ساختار route داخل فریمورک) config به این معنا نیستند و میتوانند در کد بمانند.
مزیت env var نسبت به فایل config پراکنده این است که استاندارد زبان/سیستمعامل است، بین Deployها بدون rebuild عوض میشود، و نسبت به ثابت داخل سورس شانس کمتری برای کامیت تصادفی دارد — هرچند .env اشتباهکامیتشده هنوز یکی از پرتکرارترین رخدادهاست.
نمونهٔ خواندن (با placeholder)
python
# مثال مفهومی — مقادیر ساختگیاند import os database_url = os.environ["DATABASE_URL"] # مثال مقدار: postgres://app_user:REDACTED@db.example.internal:5432/app api_key = os.environ.get("PAYMENT_API_KEY") # مثال مقدار: pk_test_REPLACE_ME
اگر کلید را ننویسید و برنامه با KeyError یا پیام واضح «متغیر لازم تنظیم نشده» متوقف شود، بهتر از آن است که با یک مقدار پیشفرض خطرناک (مثل رمز خالی یا حساب demo) بالا بیاید.
Secret چیست و چه چیزهایی Secret نیستند؟
مرز ساده: اگر افشا شدن مقدار، به مهاجم امکان عمل بهجای شما یا خواندن دادهٔ حساس بدهد، Secret است. اگر فقط رفتار عمومی یا نام محصول را توصیف کند، معمولاً Secret نیست.
- Secret: رمز دیتابیس، private key، توکن دسترسی ابری، webhook signing secret، session signing key.
- معمولاً غیر-Secret: نام محیط (staging)، feature flag عمومی، timeout به میلیثانیه، آدرس مستندات عمومی.
- خاکستری: آدرس داخلی سرویسها — گاهی حساس عملیاتی است حتی اگر «رمز» نباشد؛ بهتر است در env بماند و در لاگ عمومی چاپ نشود.
اشتباه رایج: فکر کردن که «مخزن private است پس hard-code اشکالی ندارد.» دسترسی به مخزن گسترش مییابد (پیمانکار، لپتاپ دزدیدهشده، اشتباه در تغییر visibility). اصل جداسازی config از کد برای private هم صدق میکند.
.env در برابر Secret کامیتشده
فایل .env معمولاً لیستی از KEY=value برای بارگذاری محلی است. ابزارهایی مثل dotenv آن را میخوانند تا مجبور نباشید هر بار export دستی بزنید. این الگو برای توسعه خوب است، بهشرطی که:
- .env در .gitignore باشد و هرگز stage نشود.
- یک .env.example فقط با نام کلیدها و مقدارهای جعلی/خالی در مخزن باشد تا همتیمی بداند چه متغیرهایی لازم است.
- مقادیر Production داخل .env لپتاپ توسعهدهنده کپی نشوند مگر با کنترل سخت و عمر کوتاه.
Secret کامیتشده یعنی همان مقادیر حساس داخل تاریخچهٔ Git (در سورس، در فایل config، در اسکریپت، حتی در تست). حذف از آخرین commit کافی نیست؛ تاریخچه هنوز دارد. GitHub در مستندات secret scanning تأکید میکند وقتی credential بهصورت hard-coded در تاریخچه برود، هدف سوءاستفاده میشود؛ و توصیه میکند اول credential را rotate کنید — پاک کردن تاریخچه زمانبر است و اگر کلید هنوز زنده باشد کافی نیست.
| روش | مزیت | ریسک اصلی |
|---|---|---|
| ثابت داخل کد | سریع برای دمو | نشت با هر clone؛ litmus test ۱۲-Factor رد میشود |
| .env محلی + gitignore | راحتی توسعه | کامیت تصادفی؛ کپی Production روی لپتاپ |
| Env روی پلتفرم Host/CI | جدا از سورس؛ per-environment | دسترسی بیش از حد به پنل؛ نشت در لاگ |
| Secret manager اختصاصی | چرخش، audit، دسترسی نقشمحور | پیچیدگی عملیاتی برای تیم خیلی کوچک |
اصل ۱۲-Factor Config به زبان تصمیم
صفحهٔ Store config in the environment در 12factor.net چند نکتهٔ تصمیمگیری دارد که برای صاحب محصول و مدیر فنی مهمتر از syntax است:
- کد بین Staging و Production یکی است؛ آنچه عوض میشود config است.
- گروهبندی شکننده مثل یک فایل عظیم «همهٔ تنظیمات production» با رشد محیطها (qa، staging جواد، …) شکننده میشود؛ env varهای مستقل مقیاسپذیرترند.
- فایل config خارج از VCS بهتر از ثابت داخل کد است، اما هنوز اشتباهکامیت و پراکندگی فرمت دارد؛ env استاندارد مشترک است.
ترجمهٔ عملی برای تیم کوچک ایرانی که روی یک VPS یا PaaS کار میکند: سه مجموعهٔ جدا برای Local، Staging، Production؛ نام کلیدها یکسان؛ مقدارها متفاوت؛ و هیچ مقدار Production داخل مخزن یا چت تیمی.
Secret Scanning و Push Protection در GitHub
طبق مستندات GitHub، secret scanning تاریخچهٔ تمام شاخهها را برای credentialهای hard-coded میگردد و برای جلوگیری از secret sprawl طراحی شده است. علاوه بر کد، Issue، PR، Discussion، Wiki و gistهای secret را هم پوشش میدهد. در مخازن عمومی بهصورت پیشفرض فعال است؛ برای بسیاری از مخازن خصوصی سازمانی به قابلیت Secret Protection وابسته است.
وقتی نشتی پیدا شود، هشدار در تب Security ساخته میشود. اقدام اول رسمی: فوراً credential را rotate کنید تا دسترسی غیرمجاز قطع شود. شریکهای GitHub ممکن است برای بعضی انواع Secret مستقیماً به ارائهدهنده اطلاع دهند تا کلید باطل شود.
Push protection لایهٔ پیشگیرانه است: بهجای هشدار بعد از واقعه، pushای که Secret شناختهشده دارد را قبل از ورود به مخزن مسدود میکند — از CLI، UI، آپلود فایل، برخی مسیرهای API و در مخازن عمومی حتی تعامل با GitHub MCP. پیام مسدودی به توسعهدهنده میگوید چه چیزی مشکوک است تا حذف کند و دوباره تلاش کند.
دو شکل رایج: push protection سطح مخزن/سازمان (قابل پیکربندی، معمولاً پیشفرض خاموش تا ادمین روشن کند) و push protection سطح کاربر روی GitHub.com که بهصورت پیشفرض کمک میکند Secret به مخازن عمومی push نشود. امکان bypass با دلیل وجود دارد؛ برای کنترل سختتر، delegated bypass تعریف میشود.
اشتباههای عملی که هنوز تکرار میشوند
- گذاشتن کلید واقعی در README «برای راحتی onboard».
- چاپ کردن کل env در لاگ خطا هنگام دیباگ.
- فرستادن .env در Slack/تلگرام بهجای Secret store.
- استفاده از یک Secret مشترک بین Staging و Production.
- دادن Secret به ابزار AI در پرامپت یا فایل باز پروژه.
- فکر کردن که بعد از amend تاریخچه برای همه پاک شده است.
برای AI و چت: اگر فایل .env در workspace ابزار باز باشد، مدل ممکن است آن را در پیشنهاد کد تکرار کند. قانون ساده: Secret را در پرامپت نگذارید؛ از placeholder استفاده کنید؛ و قبل از commit، diff را برای رشتههای شبیه کلید اسکن چشمی کنید.
چکلیست حداقلی قبل از اولین Production
- فهرست نام متغیرهای لازم را در .env.example بدون مقدار واقعی بنویسید.
- .env و فایلهای مشابه را در gitignore قفل کنید.
- در پنل Host/CI، Secretهای Production را جدا از Staging تنظیم کنید.
- Secret scanning (و در صورت امکان push protection) را برای مخزن بررسی کنید.
- یک بار عمداً یک کلید جعلی شبیه الگوی واقعی را در branch تست کنید تا ببینید ابزار چه واکنشی دارد — نه کلید واقعی.
- اگر قبلاً کلید واقعی رفته، همان لحظه rotate کنید؛ بعد سراغ پاکسازی تاریخچه.
این چکلیست جایگزین سیاست سازمانی کامل نیست، اما فاصلهٔ «دمو کار میکند» تا «کلید در Git نیست» را کوتاه میکند. جزئیات محیطها و استقرار را در مقالات Local/Staging/Production و Deployment بخوانید.
چگونه Secret را در تیم کوچک جابهجا کنیم؟
اولین بار که همتیمی جدید میآید، وسوسه این است که فایل .env را در پیامرسان بفرستید. این کار Secret sprawl میسازد: کپی در تاریخچهٔ چت، روی چند گوشی، و گاهی در جستجوی کلاینت پیامرسان. مسیر بهتر:
- نام کلیدها را از .env.example بخواند.
- مقادیر Non-production را از Password manager تیمی یا پنل Host با دسترسی نقشدار بگیرد.
- مقادیر Production را فقط کسانی ببینند که Deploy یا on-call دارند.
- هر بار که نفر از تیم خارج شد، کلیدهای مشترک را rotate کنید — نه فقط دسترسی Git را قطع کنید.
اگر هنوز Password manager ندارید، حداقل یک کانال رمزنگاریشدهٔ موقت با انقضای پیام بهتر از paste دائمی در گروه عمومی است. هدف این نیست که تشریفات سازمانی بیاورید؛ هدف این است که Secret عمر و محل مشخص داشته باشد.
لاگ، خطا و مانیتورینگ: نشت از در پشتی
خیلی از نشتها از commit نمیآیند؛ از exception میآیند. کتابخانهای که درخواست HTTP را با header کامل لاگ میکند، یا ORMای که connection string را در stack trace میآورد، همان Secret را به سامانهٔ لاگ میفرستد. اگر لاگ به سرویس ابری یا کانال Slack متصل باشد، دامنهٔ افشا بزرگ میشود.
عادتهای ساده: قبل از لاگ کردن config، فیلدهای حساس را mask کنید؛ در حالت debug هم روی Production تمام env را dump نکنید؛ و نمونهٔ خطا را در Issue عمومی بدون پاکسازی paste نکنید. Secret scanning مخزن را میبیند، نه لزوماً همهٔ مقصدهای لاگ شما را.
تستها و CI
در تست، از مقدارهای جعلی ثابت استفاده کنید که شبیه کلید واقعی نباشند تا push protection بیجهت شلوغ نشود — و اگر الگوی تست به کلید واقعی شبیه است، آن را در اسناد bypass بهعنوان «used in tests» مدیریت کنید، نه اینکه کلید live بگذارید. در CI، Secret را از Secret store همان محیط تزریق کنید؛ چاپ کردن echo برای دیباگ workflow یکی از مسیرهای کلاسیک افشاست.
تفاوت Staging و Production را در CI جدی بگیرید: workflowای که به environment: production وصل است باید قوانین سختتری از staging داشته باشد. این موضوع در استقرار (مقالهٔ ۰۵۰) و CI/CD (۱۱۲) باز میشود؛ از زاویهٔ Secret، پیام ساده است — یک انبار کلید برای همهٔ شغلها نسازید.
چرخش (Rotate) را بخشی از عادی کار بدانید
کلید خوب کلید کوتاهعمر است. حتی بدون نشت، چرخش دورهای برای توکنهای ابری و webhookها عادت سالمی است. وقتی نشت قطعی شد، ترتیب کار معمولاً این است: قطع دسترسی (revoke/rotate)، بررسی سوءاستفاده، سپس پاکسازی تاریخچه اگر لازم بود. مقالهٔ ۰۸۲ جزئیات واکنش را میگوید؛ اینجا فقط این را ثبت میکنیم که «پاک کردن فایل از commit آخر» پایان ماجرا نیست.
برای مالک غیرتکنیکال: اگر پیمانکار یا فریلنسر به پنل ابری دسترسی داشت و پروژه تمام شد، همان روز کلیدها را عوض کنید. هزینهٔ پنج دقیقه چرخش کمتر از یک صورتحساب غافلگیرکننده است.
جمعبندی برای تصمیم
Environment Variable ابزار جدا کردن config متغیر از کد ثابت است. Secret زیرمجموعهای از آن config است که افشایش گران تمام میشود. .env راحتی Local است، نه محل نگهداری Production در Git. Twelve-Factor Config و قابلیتهای secret scanning / push protection در GitHub، هر دو یک پیام دارند: اعتبارنامه را از مسیر سورس و تاریخچه دور نگه دارید و وقتی نشت شد اول rotate کنید.
اگر فقط یک کار این هفته انجام میدهید: مخزن را برای .env کامیتشده و رشتههای شبیه کلید بگردید، push protection را جدی بگیرید، و کلیدهای مشکوک را عوض کنید — نه اینکه فقط از آخرین commit پاکشان کنید.
منابع و مراجع
- The Twelve-Factor App — III. Config / Store config in the environment — https://12factor.net/config
- GitHub Docs — About secret scanning — https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning
- GitHub Docs — About push protection — https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection
اگر روی جداسازی محیطها یا واکنش به نشت کار میکنید، مقالات مرتبط همین سری را در کنار این صفحه بخوانید؛ اینجا پایهٔ «Secret کجا زندگی میکند» است.
نویسنده
سهیل ابراهیمپور بنیانگذار FutureForge است. روی طراحی محصول، معماری و استقرار نرمافزار سفارشی کار میکند.
یادداشتهای مرتبط
اگر در انتخاب معماری یا مسیر توسعه مطمئن نیستید، میتوانید درباره پروژه صحبت کنیم.
مسئله و محدودیت را بنویسید. اگر تطابق داشته باشیم، برای گفتگو هماهنگ میکنیم.




